PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 116
Lasso Lite – Affiliate Link Manager & Product Displays v116
157 155 156 154 153 152 151 150 149 148 trunk 0.9.9 104 105 106 107 108 109 110 111 112 113 114 115 116 All 56 releases
simple-urls / classes / class-amazon-api.php

class-amazon-api.php in Lasso Lite – Affiliate Link Manager & Product Displays 116, at classes/class-amazon-api.php

1,279 lines 39.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Declare class Config
4 *
5 * @package Config
6 */
7
8 namespace LassoLite\Classes;
9
10 use LassoLite\Classes\Affiliate_Link;
11 use LassoLite\Classes\Cache_Per_Process;
12 use LassoLite\Classes\Helper;
13 use LassoLite\Classes\Setting;
14
15 use LassoLite\Libs\Amazon_Api_V5\AwsV5;
16
17 /**
18 * Config
19 */
20 class Amazon_Api {
21
22 const OBJECT_KEY = 'lasso_amazon_api';
23 const FUNCTION_NAME_GET_LASSO_ID_BY_PRODUCT_ID_AND_TYPE = 'get_lasso_id_by_product_id_and_type';
24 const PRODUCT_TYPE = 'amazon';
25 const SHORT_LINK_DOMAINS = array( 'amzn.com', 'amzn.to' );
26 const FILTER_AMAZON_PRODUCT = 'filter_amazon_product';
27 const TRACKING_ID_REGEX = '^[a-zA-Z0-9-]+-\d{2,3}$';
28 const CURRENCY_ISO = array( 'USD', 'AUD', 'CAD', 'EUR', 'MXN', 'CNY', 'JPY', 'INR', 'SEK', 'BRL', 'TRY', 'GBP', 'PLN', 'EGP', 'SGD', 'AED' );
29 const VARIATION_PAGE_LIMIT = 2;
30
31 /**
32 * Get amazon API countries
33 */
34 public static function get_amazon_api_countries() {
35 return array(
36 'us' => array(
37 'name' => 'United States',
38 'amazon_domain' => 'www.amazon.com',
39 'pa_endpoint' => 'webservices.amazon.com',
40 'region' => 'us-east-1',
41 ),
42 'usa' => array(
43 'name' => 'United States',
44 'amazon_domain' => 'www.amazon.com',
45 'pa_endpoint' => 'webservices.amazon.com',
46 'region' => 'us-east-1',
47 ),
48 'au' => array(
49 'name' => 'Australia',
50 'amazon_domain' => 'www.amazon.com.au',
51 'pa_endpoint' => 'webservices.amazon.com.au',
52 'region' => 'us-west-2',
53 ),
54 'aus' => array(
55 'name' => 'Australia',
56 'amazon_domain' => 'www.amazon.com.au',
57 'pa_endpoint' => 'webservices.amazon.com.au',
58 'region' => 'us-west-2',
59 ),
60 'br' => array(
61 'name' => 'Brazil',
62 'amazon_domain' => 'www.amazon.com.br',
63 'pa_endpoint' => 'webservices.amazon.com.br',
64 'region' => 'us-east-1',
65 ),
66 'bra' => array(
67 'name' => 'Brazil',
68 'amazon_domain' => 'www.amazon.com.br',
69 'pa_endpoint' => 'webservices.amazon.com.br',
70 'region' => 'us-east-1',
71 ),
72 'ca' => array(
73 'name' => 'Canada',
74 'amazon_domain' => 'www.amazon.ca',
75 'pa_endpoint' => 'webservices.amazon.ca',
76 'region' => 'us-east-1',
77 ),
78 'can' => array(
79 'name' => 'Canada',
80 'amazon_domain' => 'www.amazon.ca',
81 'pa_endpoint' => 'webservices.amazon.ca',
82 'region' => 'us-east-1',
83 ),
84 'cn' => array(
85 'name' => 'China',
86 'amazon_domain' => 'www.amazon.cn',
87 'pa_endpoint' => 'webservices.amazon.cn',
88 'region' => 'us-east-1',
89 ),
90 'chn' => array(
91 'name' => 'China',
92 'amazon_domain' => 'www.amazon.cn',
93 'pa_endpoint' => 'webservices.amazon.cn',
94 'region' => 'us-east-1',
95 ),
96 'fr' => array(
97 'name' => 'France',
98 'amazon_domain' => 'www.amazon.fr',
99 'pa_endpoint' => 'webservices.amazon.fr',
100 'region' => 'eu-west-1',
101 ),
102 'fra' => array(
103 'name' => 'France',
104 'amazon_domain' => 'www.amazon.fr',
105 'pa_endpoint' => 'webservices.amazon.fr',
106 'region' => 'eu-west-1',
107 ),
108 'de' => array(
109 'name' => 'Germany',
110 'amazon_domain' => 'www.amazon.de',
111 'pa_endpoint' => 'webservices.amazon.de',
112 'region' => 'eu-west-1',
113 ),
114 'deu' => array(
115 'name' => 'Germany',
116 'amazon_domain' => 'www.amazon.de',
117 'pa_endpoint' => 'webservices.amazon.de',
118 'region' => 'eu-west-1',
119 ),
120 'in' => array(
121 'name' => 'India',
122 'amazon_domain' => 'www.amazon.in',
123 'pa_endpoint' => 'webservices.amazon.in',
124 'region' => 'eu-west-1',
125 ),
126 'ind' => array(
127 'name' => 'India',
128 'amazon_domain' => 'www.amazon.in',
129 'pa_endpoint' => 'webservices.amazon.in',
130 'region' => 'eu-west-1',
131 ),
132 'it' => array(
133 'name' => 'Italy',
134 'amazon_domain' => 'www.amazon.it',
135 'pa_endpoint' => 'webservices.amazon.it',
136 'region' => 'eu-west-1',
137 ),
138 'ita' => array(
139 'name' => 'Italy',
140 'amazon_domain' => 'www.amazon.it',
141 'pa_endpoint' => 'webservices.amazon.it',
142 'region' => 'eu-west-1',
143 ),
144 'jp' => array(
145 'name' => 'Japan',
146 'amazon_domain' => 'www.amazon.co.jp',
147 'pa_endpoint' => 'webservices.amazon.co.jp',
148 'region' => 'us-west-2',
149 ),
150 'jpn' => array(
151 'name' => 'Japan',
152 'amazon_domain' => 'www.amazon.co.jp',
153 'pa_endpoint' => 'webservices.amazon.co.jp',
154 'region' => 'us-west-2',
155 ),
156 'mx' => array(
157 'name' => 'Mexico',
158 'amazon_domain' => 'www.amazon.com.mx',
159 'pa_endpoint' => 'webservices.amazon.com.mx',
160 'region' => 'us-east-1',
161 ),
162 'mex' => array(
163 'name' => 'Mexico',
164 'amazon_domain' => 'www.amazon.com.mx',
165 'pa_endpoint' => 'webservices.amazon.com.mx',
166 'region' => 'us-east-1',
167 ),
168 'nl' => array(
169 'name' => 'Netherlands',
170 'amazon_domain' => 'www.amazon.nl',
171 'pa_endpoint' => 'webservices.amazon.nl',
172 'region' => 'eu-west-1',
173 ),
174 'nld' => array(
175 'name' => 'Netherlands',
176 'amazon_domain' => 'www.amazon.nl',
177 'pa_endpoint' => 'webservices.amazon.nl',
178 'region' => 'eu-west-1',
179 ),
180 'se' => array(
181 'name' => 'Sweden',
182 'amazon_domain' => 'www.amazon.se',
183 'pa_endpoint' => 'webservices.amazon.se',
184 'region' => 'us-west-1',
185 ),
186 'sek' => array(
187 'name' => 'Sweden',
188 'amazon_domain' => 'www.amazon.se',
189 'pa_endpoint' => 'webservices.amazon.se',
190 'region' => 'us-west-1',
191 ),
192 'sg' => array(
193 'name' => 'Singapore',
194 'amazon_domain' => 'www.amazon.sg',
195 'pa_endpoint' => 'webservices.amazon.sg',
196 'region' => 'us-west-2',
197 ),
198 'sgp' => array(
199 'name' => 'Singapore',
200 'amazon_domain' => 'www.amazon.sg',
201 'pa_endpoint' => 'webservices.amazon.sg',
202 'region' => 'us-west-2',
203 ),
204 'es' => array(
205 'name' => 'Spain',
206 'amazon_domain' => 'www.amazon.es',
207 'pa_endpoint' => 'webservices.amazon.es',
208 'region' => 'eu-west-1',
209 ),
210 'esp' => array(
211 'name' => 'Spain',
212 'amazon_domain' => 'www.amazon.es',
213 'pa_endpoint' => 'webservices.amazon.es',
214 'region' => 'eu-west-1',
215 ),
216 'tr' => array(
217 'name' => 'Turkey',
218 'amazon_domain' => 'www.amazon.com.tr',
219 'pa_endpoint' => 'webservices.amazon.com.tr',
220 'region' => 'eu-west-1',
221 ),
222 'tur' => array(
223 'name' => 'Turkey',
224 'amazon_domain' => 'www.amazon.com.tr',
225 'pa_endpoint' => 'webservices.amazon.com.tr',
226 'region' => 'eu-west-1',
227 ),
228 'ae' => array(
229 'name' => 'United Arab Emirates',
230 'amazon_domain' => 'www.amazon.ae',
231 'pa_endpoint' => 'webservices.amazon.ae',
232 'region' => 'eu-west-1',
233 ),
234 'are' => array(
235 'name' => 'United Arab Emirates',
236 'amazon_domain' => 'www.amazon.ae',
237 'pa_endpoint' => 'webservices.amazon.ae',
238 'region' => 'eu-west-1',
239 ),
240 'gb' => array(
241 'name' => 'United Kingdom',
242 'amazon_domain' => 'www.amazon.co.uk',
243 'pa_endpoint' => 'webservices.amazon.co.uk',
244 'region' => 'eu-west-1',
245 ),
246 'gbr' => array(
247 'name' => 'United Kingdom',
248 'amazon_domain' => 'www.amazon.co.uk',
249 'pa_endpoint' => 'webservices.amazon.co.uk',
250 'region' => 'eu-west-1',
251 ),
252 );
253 }
254
255 /**
256 * Get ignore errors list
257 */
258 public static function get_ignore_error_codes() {
259 return array(
260 'ItemNotAccessible',
261 'InvalidParameterValue',
262 'AccessDeniedException',
263 'AccessDenied',
264 'TooManyRequestsException',
265 'TooManyRequests',
266 'ThrottlingException',
267 'RequestThrottled',
268 'AWS.ThrottlingException',
269 'AWS.RequestThrottled',
270 'AWS.AccessDeniedException',
271 'UnrecognizedClient',
272 );
273 }
274
275 /**
276 * Get amazon domains
277 */
278 public static function get_domains() {
279 return array(
280 'amazon.com', // ? US
281 'amazon.ca', // ? Canada
282 'amazon.co.uk', // ? UK
283 'amazon.com.au', // ? Australia
284 'amazon.com.br', // ? Brazil
285 'amazon.com.mx', // ? Mexico
286 'amazon.fr', // ? France
287 'amazon.de', // ? Germany
288 'amazon.it', // ? Italy
289 'amazon.in', // ? India
290 'amazon.es', // ? Spain
291 'amazon.cn', // ? China
292 'amazon.co.jp', // ? Japan
293 'amazon.nl', // ? Netherlands
294 'amazon.se', // ? Sweden
295 'amazon.sg', // ? Singapore
296 'amazon.com.tr', // ? Turkey
297 'amazon.ae', // ? United Arab Emirates
298 'amzn.com', // ? Short URL
299 'amzn.to', // ? Short URL
300 'amazon-adsystem.com', // ? Amazon Embed
301 'smile.amazon.com', // ? Amazon Smile
302 );
303 }
304
305 /**
306 * Get amazon link and flag
307 */
308 public static function get_aff_link_and_flag() {
309 return array(
310 'www.amazon.com' => array(
311 'flag' => '🇺🇸',
312 'aff_link' => 'https://affiliate-program.amazon.com/',
313 ),
314 'www.amazon.ca' => array(
315 'flag' => '🇨🇦',
316 'aff_link' => 'https://associates.amazon.ca/',
317 ),
318 'www.amazon.com.br' => array(
319 'flag' => '🇧🇷',
320 'aff_link' => 'https://associados.amazon.com.br/',
321 ),
322 'www.amazon.com.mx' => array(
323 'flag' => '🇲🇽',
324 'aff_link' => 'https://afiliados.amazon.com.mx/',
325 ),
326 'www.amazon.fr' => array(
327 'flag' => '🇫🇷',
328 'aff_link' => 'https://partenaires.amazon.fr/',
329 ),
330 'www.amazon.de' => array(
331 'flag' => '🇩🇪',
332 'aff_link' => 'https://partnernet.amazon.de/',
333 ),
334 'www.amazon.it' => array(
335 'flag' => '🇮🇹',
336 'aff_link' => 'https://programma-affiliazione.amazon.it/',
337 ),
338 'www.amazon.es' => array(
339 'flag' => '🇪🇸',
340 'aff_link' => 'https://afiliados.amazon.es/',
341 ),
342 'www.amazon.co.uk' => array(
343 'flag' => '🇬🇧',
344 'aff_link' => 'https://affiliate-program.amazon.co.uk/',
345 ),
346 'www.amazon.cn' => array(
347 'flag' => '🇨🇳',
348 'aff_link' => 'https://associates.amazon.cn/',
349 ),
350 'www.amazon.co.jp' => array(
351 'flag' => '🇯🇵',
352 'aff_link' => 'https://affiliate.amazon.co.jp/',
353 ),
354 'www.amazon.in' => array(
355 'flag' => '🇮🇳',
356 'aff_link' => 'https://affiliate-program.amazon.in/',
357 ),
358 'www.amazon.com.au' => array(
359 'flag' => '🇦🇺',
360 'aff_link' => 'https://affiliate-program.amazon.com.au/',
361 ),
362 );
363 }
364
365 /**
366 * Check whether a url is amazon link or not
367 *
368 * @param string $url Url.
369 */
370 public static function is_amazon_url( $url ) {
371 if ( empty( $url ) ) {
372 return false;
373 }
374
375 $domains = self::get_domains();
376 $url = Helper::add_https( $url );
377
378 if ( ! Helper::validate_url( $url ) ) {
379 return false;
380 }
381
382 $parse_url = wp_parse_url( $url );
383 if ( ! isset( $parse_url['host'] ) ) {
384 return false;
385 }
386
387 $domain = ltrim( $parse_url['host'], 'www.' );
388
389 if ( in_array( $domain, $domains, true ) ) {
390 return true;
391 }
392
393 return false;
394 }
395
396 /**
397 * Check whether a url is amazon link or not
398 *
399 * @param string $url Url.
400 */
401 public static function is_amazon_shortented_url( $url ) {
402 $is_amazon_url = self::is_amazon_url( $url );
403 $is_shortented_url = strpos( $url, 'amzn.to' ) || strpos( $url, 'amzn.com' );
404
405 return $is_amazon_url && $is_shortented_url;
406 }
407
408 /**
409 * Search amazon product
410 *
411 * @param string $keyword Keyword: product name,...
412 */
413 public function search_product( $keyword ) {
414 $result = $this->get_product_by_keyword_v5( $keyword, 'All' );
415 if ( isset( $result->SearchResult->Items ) ) { // phpcs:ignore
416 $items = $result->SearchResult->Items; // phpcs:ignore
417 $products = array();
418
419 foreach ( $items as $item ) {
420 $product = $this->extract_search_result_v5( $item );
421 array_push( $products, $product );
422 }
423
424 return $products;
425
426 } elseif ( isset( $result->Errors ) ) { // phpcs:ignore
427 return array(
428 'error' => $result->Errors, // phpcs:ignore
429 );
430 }
431 }
432
433 /**
434 * Check whether product url has the same domain with Amazon settings
435 *
436 * @param string $product_url Amazon link.
437 */
438 public function is_same_domain( $product_url ) {
439 if ( '' === $product_url ) {
440 return false;
441 }
442
443 $amazon_default_tracking_country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' );
444 $all_countries = self::get_amazon_api_countries();
445 $domain = $all_countries[ $amazon_default_tracking_country ]['amazon_domain'] ?? 'www.amazon.com';
446 $domain = str_replace( 'www.', '', $domain );
447
448 return strpos( $product_url, $domain ) !== false;
449 }
450
451 /**
452 * Fetch amazon product from Amazon API v5
453 *
454 * @param string $product_id Amazon product id.
455 * @param bool $store_product Store product into DB or not. Default to false.
456 * @param bool|string $updated_at Set date time or not. Default to false.
457 * @param string $amz_link Amazon link. Default to empty.
458 * @return mixed
459 */
460 public function fetch_product_info( $product_id, $store_product = false, $updated_at = false, $amz_link = '' ) {
461 $lasso_db = new Lasso_DB();
462
463 $lasso_settings = Setting::get_settings();
464 $is_amazon_configured = $lasso_settings['amazon_access_key_id'] && $lasso_settings['amazon_secret_key'] && $lasso_settings['amazon_tracking_id'];
465 $result = $is_amazon_configured && $this->is_same_domain( $amz_link ) ? $this->get_product_by_id_v5( $product_id ) : false;
466 // phpcs:ignore
467 if ( isset( $result->Errors[0] ) && (
468 "The ItemId $product_id provided in the request is invalid." === $result->Errors[0]->Message // phpcs:ignore
469 || "The value [$product_id] provided in the request for ItemIds is invalid." === $result->Errors[0]->Message // phpcs:ignore
470 )
471 ) {
472 return array(
473 'product' => array(),
474 'api' => 'yes',
475 'full_item' => array(),
476 'status' => 'failed',
477 'error_code' => 'NotFound',
478 );
479 } elseif ( isset( $result->ItemsResult->Items[0] ) ) { // phpcs:ignore
480 $item = $result->ItemsResult->Items[0]; // phpcs:ignore
481
482 $product = $this->extract_search_result_v5( $item, true );
483 $product_url = $amz_link ? $amz_link : $product['url'];
484 $product['status_code'] = 200;
485 $product['url'] = self::get_amazon_product_url( $product_url );
486
487 // ? If $item->Offers is missing, we try to get the quantity from variation data
488 if ( ! isset( $item->Offers ) ) { // phpcs:ignore
489 sleep( 1 ); // ? Delay for a while before call the next request
490 $variation_product = $this->get_product_variation( $product_id, $product_url );
491 if ( $variation_product && isset( $variation_product['quantity'] ) && $variation_product['quantity'] ) {
492 $product = $variation_product;
493 }
494 }
495
496 // ? Get Lasso ID
497 $query = '
498 SELECT post_id
499 FROM ' . $lasso_db->postmeta . "
500 WHERE meta_key = 'amazon_product_id' AND meta_value = '" . $product['product_id'] . "'
501 ";
502 $lasso_id = $lasso_db->get_var( $query );
503 $product['lasso_id'] = ( isset( $lasso_id ) ) ? $lasso_id : 0;
504
505 if ( $store_product ) {
506 $amazon_tracking_id = Setting::get_setting( 'amazon_tracking_id', '' );
507 $product['default_url'] = '' === $amazon_tracking_id ? $amz_link : $product['default_url'];
508 $this->update_amazon_product_in_db( $product, $updated_at );
509 }
510
511 return array(
512 'product' => $product,
513 'api' => 'yes',
514 'full_item' => $item,
515 'status' => 'success',
516 'error_code' => '',
517 );
518 } else {
519 return array();
520 }
521 }
522
523 /**
524 * Insert or Update Amazon Product Data
525 *
526 * @param array $product Amazon product.
527 * @param bool|string $updated_at Set update date time. Default to false.
528 */
529 public function update_amazon_product_in_db( $product, $updated_at = false ) {
530 global $wpdb;
531
532 $lasso_db = new Lasso_DB();
533
534 $amazon_id = $product['product_id'] ?? '';
535 $default_product_name = $product['title'] ?? '';
536 $latest_price = $product['price'] ?? '';
537 $latest_price = '0' === $latest_price || ( is_int( $latest_price ) && 0 === $latest_price ) ? '' : $latest_price;
538 $base_url = self::get_amazon_product_url( $product['default_url'] ?? '', false, false );
539 $monetized_url = self::get_amazon_product_url( $product['url'] ?? '', true, false );
540 $default_image = trim( $product['image'] ?? '' );
541 $last_updated = gmdate( 'Y-m-d H:i:s', time() );
542 $last_updated = $updated_at ? $updated_at : $last_updated;
543 $is_prime = $product['is_prime'] ?? '';
544 $currency = $product['currency'] ?? '';
545 $features = wp_json_encode( $product['features'] ?? array() );
546 $savings_amount = $product['savings_amount'] ?? '';
547 $savings_percent = $product['savings_percent'] ?? '';
548 $savings_basis = $product['savings_basis'] ?? '';
549 $is_manual = $product['is_manual'] ?? 0;
550 $quantity = intval( $product['quantity'] ?? 200 );
551 $out_of_stock = 0 === $quantity ? 1 : 0;
552
553 if ( '' === $amazon_id || '' === $default_product_name || '' === $default_image
554 || ( '' !== $default_image && Helper::validate_url( $default_image ) === false && strpos( $default_image, 'data:image' ) !== 0 )
555 ) {
556 return false;
557 }
558
559 $base_url = trim( $base_url );
560 $monetized_url = trim( $monetized_url );
561
562 $query = '
563 INSERT INTO ' . $lasso_db->amazon_products . "
564 (
565 amazon_id, default_product_name, latest_price, base_url,
566 monetized_url, default_image, last_updated, is_prime,
567 currency, features, savings_amount, savings_percent,
568 savings_basis, is_manual, out_of_stock
569 )
570 VALUES
571 (
572 %s, %s, %s, %s,
573 %s, %s, %s, %d,
574 %s, %s, %s, %d,
575 %s, %d, %d
576 )
577 ON DUPLICATE KEY UPDATE
578 amazon_id = %s,
579 default_product_name = %s,
580 latest_price = %s,
581 base_url = %s,
582 monetized_url = %s,
583 default_image = (CASE WHEN %s='' or %s IS NULL THEN `default_image` ELSE %s END),
584 last_updated = %s,
585 is_prime = %d,
586 currency = %s,
587 features = %s,
588 savings_amount = %s,
589 savings_percent = %d,
590 savings_basis = %s,
591 is_manual = %d,
592 out_of_stock = %d
593 ;
594 ";
595 $prepare = $wpdb->prepare(
596 // phpcs:ignore
597 $query,
598 // ? First for insert
599 $amazon_id,
600 $default_product_name,
601 $latest_price,
602 $base_url,
603 $monetized_url,
604 $default_image,
605 $last_updated,
606 $is_prime,
607 $currency,
608 $features,
609 $savings_amount,
610 $savings_percent,
611 $savings_basis,
612 $is_manual,
613 $out_of_stock,
614 // ? Second for update
615 $amazon_id,
616 $default_product_name,
617 $latest_price,
618 $base_url,
619 $monetized_url,
620 $default_image,
621 $default_image,
622 $default_image,
623 $last_updated,
624 $is_prime,
625 $currency,
626 $features,
627 $savings_amount,
628 $savings_percent,
629 $savings_basis,
630 $is_manual,
631 $out_of_stock
632 );
633
634 $lasso_db->query( $prepare );
635
636 return true;
637 }
638
639 /**
640 * Get amazon product from DB
641 *
642 * @param string $product_id Amazon Product id.
643 */
644 public function get_amazon_product_from_db( $product_id ) {
645 if ( empty( $product_id ) ) {
646 return false;
647 }
648
649 global $wpdb;
650
651 $lasso_db = new Lasso_DB();
652
653 $sql = '
654 SELECT *
655 FROM ' . $lasso_db->amazon_products . '
656 WHERE amazon_id = %s
657 ';
658
659 $prepare = $wpdb->prepare( $sql, $product_id ); // phpcs:ignore
660 $result = $lasso_db->get_row( $prepare, ARRAY_A );
661
662 if ( $result ) {
663 $result = apply_filters( self::FILTER_AMAZON_PRODUCT, $result );
664 $result['monetized_url'] = self::get_amazon_product_url( $result['monetized_url'] );
665 $result['base_url'] = self::get_amazon_product_url( $result['base_url'], false );
666 $result['features'] = json_decode( $result['features'] );
667 }
668
669 return $result;
670 }
671
672 /**
673 * Extract result to an array
674 *
675 * @param object $response Data from Amazon.
676 * @param bool $large_image Get large image size. Default to false.
677 * @param string $product_id Product Id. Default to empty.
678 * @param string $product_url Product url. Default to empty.
679 *
680 * @return array
681 */
682 private function extract_search_result_v5( $response, $large_image = false, $product_id = '', $product_url = '' ) {
683 $image = '';
684 if ( isset( $response->Images->Primary ) ) { // phpcs:ignore
685 $image = $large_image ? $response->Images->Primary->Large->URL : $response->Images->Primary->Small->URL; // phpcs:ignore
686 }
687
688 // @codingStandardsIgnoreStart
689 $result = array(
690 'product_id' => $product_id ? $product_id : ( $response->ASIN ?? 0 ),
691 'title' => $response->ItemInfo->Title->DisplayValue ?? '',
692 'url' => $product_url ? $product_url : ( $response->DetailPageURL ?? '' ),
693 'default_url' => $response->DetailPageURL ?? '',
694 'image' => $image,
695 'quantity' => $response->Offers->Summaries[0]->OfferCount ?? 0,
696 'is_prime' => $response->Offers->Listings[0]->DeliveryInfo->IsPrimeEligible ?? false,
697 'price' => $response->Offers->Listings[0]->Price->DisplayAmount ?? 0,
698 'amount' => $response->Offers->Listings[0]->Price->Amount ?? 0,
699 'currency' => $response->Offers->Listings[0]->Price->Currency ?? '',
700 'features' => $response->ItemInfo->Features->DisplayValues ?? array(),
701 'savings_amount' => $response->Offers->Listings[0]->Price->Savings->Amount ?? 0.0,
702 'savings_percent' => $response->Offers->Listings[0]->Price->Savings->Percentage ?? 0,
703 'savings_basis' => $response->Offers->Listings[0]->SavingBasis->Amount ?? 0.0,
704 );
705 // @codingStandardsIgnoreEnd
706
707 return $result;
708 }
709
710 /**
711 * Query amazon v5
712 *
713 * @param array $parameters Amazon API params.
714 * @param boolean $lasso_settings Lasso settings. Default to false.
715 *
716 * @return array
717 */
718 public function query_amazon_v5( $parameters, $lasso_settings = false ) {
719 try {
720 if ( ! $lasso_settings ) {
721 $lasso_settings = Setting::get_settings();
722 }
723
724 $this->amazon_access_key_id = $lasso_settings['amazon_access_key_id'] ?? '';
725 $this->amazon_secret_key = $lasso_settings['amazon_secret_key'] ?? '';
726 $this->amazon_tracking_id = $lasso_settings['amazon_tracking_id'] ?? '';
727
728 $result = $this->aws_signed_request_v5( $parameters, $this->amazon_access_key_id, $this->amazon_secret_key, $this->amazon_tracking_id );
729
730 if ( isset( $result->Errors ) ) { // phpcs:ignore
731 $error = $result->Errors[0]; // phpcs:ignore
732 }
733
734 return $result;
735 } catch ( \Exception $e ) {
736 return array();
737 }
738 }
739
740 /**
741 * Get Amazon product by product id
742 *
743 * @param string $product_id Amazon product id.
744 *
745 * @return object
746 */
747 public function get_product_by_id_v5( $product_id ) {
748 $parameters = array(
749 'Operation' => 'GetItems',
750 'ItemIds' => array( $product_id ),
751 'Resources' => array(
752 'Images.Primary.Small',
753 'Images.Primary.Large',
754 'ItemInfo.Title',
755 'ItemInfo.ContentRating',
756 'ItemInfo.Features',
757 'ItemInfo.ProductInfo',
758 'ItemInfo.TechnicalInfo',
759 'Offers.Listings.Price',
760 'Offers.Listings.SavingBasis',
761 'Offers.Summaries.OfferCount',
762 'Offers.Listings.DeliveryInfo.IsPrimeEligible',
763 ),
764 );
765
766 $json_response = $this->query_amazon_v5( $parameters );
767
768 return $json_response;
769 }
770
771 /**
772 * Get product from Amazon by product name
773 *
774 * @param string $keyword Keyword.
775 * @param string $product_type Product type.
776 *
777 * @return object
778 */
779 public function get_product_by_keyword_v5( $keyword, $product_type ) {
780 $parameters = array(
781 'Operation' => 'SearchItems',
782 'Keywords' => $keyword,
783 'SearchIndex' => $product_type,
784 'Resources' => array(
785 'Images.Primary.Small',
786 'Images.Primary.Large',
787 'ItemInfo.Title',
788 'ItemInfo.ContentRating',
789 'ItemInfo.Features',
790 'ItemInfo.ProductInfo',
791 'ItemInfo.TechnicalInfo',
792 'Offers.Listings.Price',
793 'Offers.Listings.SavingBasis',
794 'Offers.Summaries.OfferCount',
795 'Offers.Listings.DeliveryInfo.IsPrimeEligible',
796 ),
797 );
798
799 $json_response = $this->query_amazon_v5( $parameters );
800
801 return $json_response;
802 }
803
804 /**
805 * Sign request v5
806 *
807 * @param array $params Amazon params.
808 * @param string $amazon_access_key_id Amazon access key.
809 * @param string $amazon_secret_key Amazon secret key.
810 * @param string $amazon_tracking_id Amazon tracking id.
811 *
812 * @return object|bool
813 */
814 private function aws_signed_request_v5( $params, $amazon_access_key_id, $amazon_secret_key, $amazon_tracking_id ) {
815 // phpcs:ignore
816 // $amazon_domain = 'www.amazon.com';
817 // $pa_endpoint = 'webservices.amazon.com';
818
819 $country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' );
820 $countries = self::get_amazon_api_countries();
821 $amazon_domain = $countries[ $country ]['amazon_domain'];
822 $pa_endpoint = $countries[ $country ]['pa_endpoint'];
823 $amazon_region = $countries[ $country ]['region'];
824
825 $params['Marketplace'] = $amazon_domain;
826 $params['PartnerType'] = 'Associates';
827 $params['PartnerTag'] = $amazon_tracking_id;
828 $post_fields = wp_json_encode( $params );
829
830 $aws_v5 = new AwsV5( $amazon_access_key_id, $amazon_secret_key );
831 $aws_v5->setHost( $pa_endpoint );
832 $aws_v5->setRegionName( $amazon_region );
833 $aws_v5->setPayload( $post_fields );
834 $aws_v5->addHeader( 'x-amz-target', 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.' . $params['Operation'] );
835 $headers = $aws_v5->getHeaders( true );
836 $url = "https://$pa_endpoint/paapi5/searchitems";
837
838 // @codingStandardsIgnoreStart
839 $ch = curl_init();
840 curl_setopt( $ch, CURLOPT_URL, $url );
841 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
842 curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields );
843 curl_setopt( $ch, CURLOPT_POST, 1 );
844
845 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
846
847 $result = curl_exec( $ch );
848 if ( curl_errno( $ch ) ) {
849 // phpcs:ignore
850 // $error = curl_error( $ch );
851 return false;
852 }
853 curl_close( $ch );
854 // @codingStandardsIgnoreEnd
855
856 return json_decode( $result );
857 }
858
859 /**
860 * Get Amazon product id by url
861 *
862 * @param string $url Amazon link.
863 * @return string|bool
864 */
865 public static function get_product_id_by_url( $url ) {
866 $url = Helper::add_https( $url );
867
868 if ( ! self::is_amazon_url( $url ) || strpos( $url, '.' ) === false ) {
869 return false;
870 }
871
872 $parse_url = wp_parse_url( $url );
873 $amazon_domain = trim( $parse_url['host'] ?? '', 'www.' );
874 $amazon_domain_regex = str_replace( '.', '\.', str_replace( 'www.', '', $amazon_domain ) );
875
876 $reg = '#(?:https?://(?:www\.){0,1}' . $amazon_domain_regex . '(?:/.*){0,1}(?:/dp/|/gp/product/|/ASIN/|/gp/video/detail/))([a-zA-Z0-9]*)(.*?)(?:/.*|$)#';
877 $matches = array();
878 preg_match( $reg, $url, $matches );
879
880 return isset( $matches[1] ) && ! empty( $matches[1] ) ? $matches[1] : false;
881 }
882
883 /**
884 * Get amazon tracking id by url
885 * This function will be deprecated in the future. Please use Helper::get_argument_from_url() instead
886 *
887 * @param string $link Amazon link.
888 * @return string
889 */
890 public static function get_amazon_tracking_id_by_url( $link ) {
891 $search = '/([?|&|&amp;|\/]{1})tag=([a-zA-Z0-9\-\_]*)/i';
892 $matches = array();
893 preg_match( $search, $link, $matches );
894
895 return $matches[2] ?? '';
896 }
897
898 /**
899 * Make product url to shorten url if this setting is enabled
900 *
901 * @param string $product_url Amazon product url.
902 * @param bool $monetize Monetize link or not. Default to true.
903 * @param bool $check_lasso_post Check Lasso post or ignore. Default to true.
904 * @param string $custom_tracking_id Allow to use custom tracking id.
905 */
906 public static function get_amazon_product_url( $product_url, $monetize = true, $check_lasso_post = true, $custom_tracking_id = '' ) {
907 // ? amazon link but it is not product url
908 if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortented_url( $product_url ) ) {
909 return $product_url;
910 }
911
912 $product_id = self::get_product_id_by_url( $product_url );
913
914 $tag = Helper::get_argument_from_url( $product_url, 'tag' );
915 $maas = Helper::get_argument_from_url( $product_url, 'maas' );
916 $ref_ = Helper::get_argument_from_url( $product_url, 'ref_' );
917 $s_args = Helper::get_argument_from_url( $product_url, 's' );
918
919 $maas_args = $maas ? 'maas=' . $maas : '';
920 $ref__args = $ref_ ? 'ref_=' . $ref_ : '';
921 $s_args = $s_args ? 's=' . $s_args : '';
922
923 // ? remove all url queries, just keep needed args
924 $url_without_params = explode( '?', $product_url )[0];
925 if ( $product_id && ! $monetize ) {
926 $args = Helper::build_url_parameter_string( array( $maas_args, $ref__args, $s_args ) );
927
928 return $args ? $url_without_params . '?' . $args : $url_without_params;
929 }
930
931 $lasso_settings = Setting::get_settings();
932 $amazon_multiple_tracking_id = $lasso_settings['amazon_multiple_tracking_id'] ?? true;
933 $amazon_tracking_id_whitelist = $lasso_settings['amazon_tracking_id_whitelist'] ?? array();
934 $amazon_tracking_id = trim( $lasso_settings['amazon_tracking_id'] ?? '' );
935
936 if ( ! $amazon_multiple_tracking_id ) {
937 $amazon_tracking_id_whitelist = array();
938 }
939
940 $lasso_db = new Lasso_DB();
941 $amz_cache_key = self::OBJECT_KEY . '_' . self::FUNCTION_NAME_GET_LASSO_ID_BY_PRODUCT_ID_AND_TYPE . '_' . $product_id . '_' . self::PRODUCT_TYPE;
942 $lasso_id = Cache_Per_Process::get_instance()->get_cache( $amz_cache_key, null );
943 if ( null === $lasso_id ) {
944 $lasso_id = $lasso_db->get_lasso_id_by_product_id_and_type( $product_id );
945 Cache_Per_Process::get_instance()->set_cache( $amz_cache_key, $lasso_id );
946 }
947
948 if ( $check_lasso_post && ! $lasso_id ) {
949 return $product_url;
950 }
951
952 $tag = $custom_tracking_id ? $custom_tracking_id : $tag;
953 $tag = $tag ? $tag : '';
954 $tag = ! empty( $amazon_tracking_id ) && ! in_array( $tag, $amazon_tracking_id_whitelist, true )
955 ? $amazon_tracking_id : $tag;
956
957 // ? Return the remove all url queries for product url
958 if ( $product_id ) {
959 $tag_args = $tag ? 'tag=' . $tag : '';
960 $args = Helper::build_url_parameter_string( array( $tag_args, $maas_args, $ref__args, $s_args ) );
961
962 $product_url = $args ? $url_without_params . '?' . $args : $url_without_params;
963 } else {
964 $product_url = str_replace( '&amp;', '&', $product_url );
965 $parse = wp_parse_url( $product_url );
966 parse_str( $parse['query'] ?? '', $query );
967
968 // ? set tag id (tracking id) at the end of the url
969 if ( $tag ) {
970 $query['tag'] = $tag;
971 } elseif ( ! empty( $amazon_tracking_id ) ) {
972 $query['tag'] = $amazon_tracking_id;
973 }
974
975 if ( ! $monetize ) {
976 unset( $query['tag'] );
977 }
978
979 $parse['query'] = Helper::get_query_from_array( $query );
980 $product_url = Helper::get_url_from_parse( $parse );
981 $product_url = trim( $product_url );
982 $product_url = trim( $product_url, '?' );
983 }
984
985 return $product_url;
986 }
987
988 /**
989 * Get Amazon product info from url accept url or post_id
990 *
991 * @param string|int $url_or_post_id URL or post id.
992 */
993 public function get_amazon_product( $url_or_post_id ) {
994
995 if ( is_numeric( $url_or_post_id ) ) {
996 // ? get amazon product using post_id
997 $post_id = $url_or_post_id;
998 $amazon_id = Affiliate_Link::get_amazon_id( $post_id );
999 $product = $this->get_amazon_product_from_db( $amazon_id );
1000 return $product;
1001 } else {
1002 $url = $url_or_post_id;
1003 }
1004
1005 // ? get amazon prodcut using url
1006 if ( empty( $url ) ) {
1007 return '';
1008 }
1009
1010 $url = trim( $url, '/' );
1011 $product_id = self::get_product_id_by_url( $url );
1012 $product = $this->fetch_product_info( $product_id, true ); // ? Let's save all Amazon details as well
1013 $amazon_product = '';
1014
1015 if ( 'success' === $product['status'] ) {
1016 $product = $product['product'];
1017 $shorten_url = self::get_amazon_product_url( $product['url'] );
1018 $amazon_product = array(
1019 'id' => $product['product_id'],
1020 'name' => $product['title'],
1021 'price' => $product['price'],
1022 'url' => $shorten_url,
1023 'image' => $product['image'],
1024 'description' => '',
1025 );
1026 }
1027
1028 return $amazon_product;
1029 }
1030
1031 /**
1032 * Check whether a URL is amazon search page
1033 *
1034 * @param string $url URL.
1035 *
1036 * @return bool|string
1037 */
1038 public static function is_amazon_search_page( $url ) {
1039 $amazon_id = self::get_product_id_by_url( $url );
1040 $parse = wp_parse_url( $url );
1041 $path = $parse['path'] ?? '';
1042 $path = rtrim( $path, '/' );
1043 $keywords = Helper::get_argument_from_url( $url, 'keywords' );
1044 $field_keywords = Helper::get_argument_from_url( $url, 'field-keywords' );
1045 $k = Helper::get_argument_from_url( $url, 'k' );
1046 $k = $k ? $k : $field_keywords;
1047 $k = $k ? $k : $keywords;
1048
1049 if ( ! $amazon_id && ( '/s' === substr( $path, -2 ) || strpos( $path, '/s/' ) !== false ) && $k ) {
1050 return $k;
1051 }
1052
1053 return false;
1054 }
1055
1056 /**
1057 * Check whether a URL is amazon search page
1058 *
1059 * @param string $url URL.
1060 *
1061 * @return bool|string
1062 */
1063 public static function get_search_page_title( $url ) {
1064 $new_title = 'Amazon';
1065 $k = self::is_amazon_search_page( $url );
1066 if ( $k ) {
1067 $base_domain = Helper::get_base_domain( $url );
1068 $title_prefix = ucfirst( $base_domain );
1069 $new_title = $title_prefix . ' : ' . $k;
1070 }
1071
1072 return $new_title;
1073 }
1074
1075 /**
1076 * Validate Amazon tracking id
1077 *
1078 * @param string $tracking_id Amazon tracking id.
1079 * @return boolean
1080 */
1081 public static function validate_tracking_id( $tracking_id ) {
1082 return (bool) preg_match( '/' . self::TRACKING_ID_REGEX . '/i', $tracking_id );
1083 }
1084
1085 /**
1086 * Get Amazon link by product id
1087 *
1088 * @param string $product_id Amazon product id.
1089 * @param string $amz_link Amazon link. Default to empty.
1090 */
1091 public function get_amazon_link_by_product_id( $product_id, $amz_link = '' ) {
1092 if ( ! $product_id ) {
1093 return $amz_link;
1094 }
1095
1096 if ( '' !== $amz_link ) {
1097 $parse = wp_parse_url( $amz_link );
1098 $host = $parse['host'] ?? '';
1099 if ( '' !== $host ) {
1100 return 'https://' . $host . '/dp/' . $product_id;
1101 }
1102 }
1103
1104 $country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' );
1105 $countries = self::get_amazon_api_countries();
1106 $amazon_domain = $countries[ $country ]['amazon_domain'];
1107
1108 return 'https://' . $amazon_domain . '/dp/' . $product_id;
1109 }
1110
1111 /**
1112 * Check whether a URL is Amazon redirect page
1113 *
1114 * @param string $url Amazon URL.
1115 */
1116 public static function is_amazon_redirect_page( $url ) {
1117 if ( ! self::is_amazon_url( $url ) || strpos( $url, '/gp/slredirect/' ) === false ) {
1118 return false;
1119 }
1120
1121 return true;
1122 }
1123
1124 /**
1125 * Get redirect url
1126 *
1127 * @param string $url Amazon URL.
1128 */
1129 public static function get_redirect_url( $url ) {
1130 if ( self::is_amazon_redirect_page( $url ) ) {
1131 $url_param = Helper::get_argument_from_url( $url, 'url' );
1132 $amazon_domain = Helper::get_base_domain( $url );
1133 $amazon_domain = Helper::add_https( $amazon_domain );
1134 $new_url = $amazon_domain . $url_param;
1135 $product_id = self::get_product_id_by_url( $new_url );
1136
1137 if ( $product_id ) {
1138 $url = $new_url;
1139 }
1140 }
1141
1142 return $url;
1143 }
1144
1145 /**
1146 * Format price
1147 * Ex: convert 19.89USD to $19.89
1148 *
1149 * @param string $price Price.
1150 * @param string $currency_iso Currency ISO.
1151 * @return string
1152 */
1153 public static function format_price( $price, $currency_iso = null ) {
1154 $currency_iso = $currency_iso ? $currency_iso : self::get_currency_iso_from_price_text( $price );
1155
1156 if ( $price && $currency_iso ) {
1157 return self::build_price_with_currency_iso( $price, $currency_iso );
1158 }
1159
1160 return $price;
1161 }
1162
1163 /**
1164 * Get Currency ISO from price text
1165 *
1166 * @param string $price Price text.
1167 * @return mixed|string
1168 */
1169 public static function get_currency_iso_from_price_text( $price ) {
1170 $result = '';
1171
1172 foreach ( self::CURRENCY_ISO as $currency_iso ) {
1173 if ( strpos( $price, $currency_iso ) !== false ) {
1174 return $currency_iso;
1175 }
1176 }
1177
1178 return $result;
1179 }
1180
1181 /**
1182 * Build price final format base on the currency ISO
1183 *
1184 * @param string $price_value Price value.
1185 * @param string $currency_iso Currency ISO.
1186 * @return string
1187 */
1188 public static function build_price_with_currency_iso( $price_value, $currency_iso ) {
1189 $currency_symbol = Helper::get_currency_symbol_from_iso_code( $currency_iso );
1190 $price_without_iso = str_replace( $currency_iso, '', $price_value );
1191 $price_value = Helper::get_price_value_from_price_text( $price_without_iso, $currency_symbol );
1192 $currency_position = preg_match( '/[]|R\$|TL|kr|zł/', $currency_symbol ) ? 'end' : 'begin';
1193 $price_format = preg_match( '/[]|R\$|TL|kr|zł/', $currency_symbol ) ? number_format( $price_value, 2, ',', '.' ) : number_format( $price_value, 2, '.', ',' );
1194
1195 return 'begin' === $currency_position ? $currency_symbol . $price_format : $price_format . $currency_symbol;
1196 }
1197
1198 /**
1199 * Get the variation item in stock
1200 *
1201 * @param string $product_id Product id.
1202 * @param string $product_url Product url.
1203 * @param int $variation_page Variation page.
1204 * @return array
1205 */
1206 public function get_product_variation( $product_id, $product_url, $variation_page = 1 ) {
1207 $result = $this->get_product_variations_by_id_v5( $product_id, $variation_page );
1208 $items = $result->VariationsResult->Items ?? array(); // phpcs:ignore
1209
1210 if ( ! empty( $items ) ) {
1211 $items = $result->VariationsResult->Items; // phpcs:ignore
1212 $product_variations = array();
1213
1214 // ? Get product variation list
1215 foreach ( $items as $item ) {
1216 $product_variations[] = $this->extract_search_result_v5( $item, true, $product_id, $product_url );
1217 }
1218
1219 // ? Sort price from lowest to highest
1220 usort(
1221 $product_variations,
1222 function( $a, $b ) {
1223 return strcmp( $a['amount'], $b['amount'] );
1224 }
1225 );
1226
1227 // ? Get the in-stock product
1228 foreach ( $product_variations as $product_variation ) {
1229 if ( $product_variation['quantity'] && $product_variation['price'] ) {
1230 return $product_variation;
1231 }
1232 }
1233
1234 // ? If all variation products in this page unavailable, we request to the next page
1235 $page_count = $result->VariationsResult->VariationSummary->PageCount ?? 1; // phpcs:ignore
1236 if ( $variation_page < self::VARIATION_PAGE_LIMIT && $variation_page < $page_count ) {
1237 sleep( 1 ); // ? Delay for a while before call the next request
1238 return $this->get_product_variation( $product_id, $product_url, $variation_page + 1 );
1239 }
1240 }
1241
1242 return array();
1243 }
1244
1245 /**
1246 * Get Amazon product variations by product id
1247 *
1248 * @param string $product_id Amazon product id.
1249 * @param int $variation_page Variation page.
1250 *
1251 * @return object
1252 */
1253 public function get_product_variations_by_id_v5( $product_id, $variation_page = 1 ) {
1254 $parameters = array(
1255 'Operation' => 'GetVariations',
1256 'ASIN' => $product_id,
1257 'Condition' => 'New',
1258 'VariationPage' => $variation_page,
1259 'Resources' => array(
1260 'Images.Primary.Small',
1261 'Images.Primary.Large',
1262 'ItemInfo.Title',
1263 'ItemInfo.ContentRating',
1264 'ItemInfo.Features',
1265 'ItemInfo.ProductInfo',
1266 'ItemInfo.TechnicalInfo',
1267 'Offers.Listings.Price',
1268 'Offers.Listings.SavingBasis',
1269 'Offers.Summaries.OfferCount',
1270 'Offers.Listings.DeliveryInfo.IsPrimeEligible',
1271 ),
1272 );
1273
1274 $json_response = $this->query_amazon_v5( $parameters );
1275
1276 return $json_response;
1277 }
1278 }
1279