PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 158
Lasso Lite – Affiliate Link Manager & Product Displays v158
158 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 All 57 releases
← All changes | classes/class-amazon-api.php +574 -49 115158 View file →
@@ -6,8 +6,9 @@
6 6 */
7 7
8 8 namespace LassoLite\Classes;
9 9
10 +use LassoLite\Admin\Constant;
10 11 use LassoLite\Classes\Affiliate_Link;
11 12 use LassoLite\Classes\Cache_Per_Process;
12 13 use LassoLite\Classes\Helper;
13 14 use LassoLite\Classes\Setting;
@@ -13,8 +14,10 @@
13 14 use LassoLite\Classes\Setting;
14 15
15 16 use LassoLite\Libs\Amazon_Api_V5\AwsV5;
16 17
18 +use LassoLite\Models\Amazon_Products;
19 +
17 20 /**
18 21 * Config
19 22 */
20 23 class Amazon_Api {
@@ -397,9 +400,9 @@
397 400 * Check whether a url is amazon link or not
398 401 *
399 402 * @param string $url Url.
400 403 */
401 - public static function is_amazon_shortented_url( $url ) {
404 + public static function is_amazon_shortened_url( $url ) {
402 405 $is_amazon_url = self::is_amazon_url( $url );
403 406 $is_shortented_url = strpos( $url, 'amzn.to' ) || strpos( $url, 'amzn.com' );
404 407
405 408 return $is_amazon_url && $is_shortented_url;
@@ -454,18 +457,38 @@
454 457 * @param string $product_id Amazon product id.
455 458 * @param bool $store_product Store product into DB or not. Default to false.
456 459 * @param bool|string $updated_at Set date time or not. Default to false.
457 460 * @param string $amz_link Amazon link. Default to empty.
461 + * @param bool $url_version_param Is add version param to request api url to ignore cache. Default to false.
462 + * @param bool $force_bls Force fetch product from BLS or not. Default to false.
463 + * @param bool $refresh_image Bypass cache and fetch fresh product image/metadata. Default to false.
458 464 * @return mixed
459 465 */
460 - public function fetch_product_info( $product_id, $store_product = false, $updated_at = false, $amz_link = '' ) {
466 + public function fetch_product_info( $product_id, $store_product = false, $updated_at = false, $amz_link = '', $url_version_param = false, $force_bls = false, $refresh_image = false ) {
461 467 $lasso_db = new Lasso_DB();
462 468
463 469 $lasso_settings = Setting::get_settings();
464 470 $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;
471 +
472 + if (
473 + self::is_amazon_creators_verified( $lasso_settings )
474 + && $this->is_same_domain( $amz_link )
475 + ) {
476 + $creators_result = $this->fetch_product_info_from_creators_api(
477 + $product_id,
478 + $store_product,
479 + $updated_at,
480 + $amz_link,
481 + $lasso_settings
482 + );
483 + if ( null !== $creators_result ) {
484 + return $creators_result;
485 + }
486 + }
487 +
488 + $result = ! $force_bls && $is_amazon_configured && $this->is_same_domain( $amz_link ) ? $this->get_product_by_id_v5( $product_id ) : false;
466 489 // phpcs:ignore
467 - if ( isset( $result->Errors[0] ) && (
490 + if ( is_object( $result ) && isset( $result->Errors[0] ) && (
468 491 "The ItemId $product_id provided in the request is invalid." === $result->Errors[0]->Message // phpcs:ignore
469 492 || "The value [$product_id] provided in the request for ItemIds is invalid." === $result->Errors[0]->Message // phpcs:ignore
470 493 )
471 494 ) {
@@ -475,9 +498,9 @@
475 498 'full_item' => array(),
476 499 'status' => 'failed',
477 500 'error_code' => 'NotFound',
478 501 );
479 - } elseif ( isset( $result->ItemsResult->Items[0] ) ) { // phpcs:ignore
502 + } elseif ( is_object( $result ) && isset( $result->ItemsResult->Items[0] ) ) { // phpcs:ignore
480 503 $item = $result->ItemsResult->Items[0]; // phpcs:ignore
481 504
482 505 $product = $this->extract_search_result_v5( $item, true );
483 506 $product_url = $amz_link ? $amz_link : $product['url'];
@@ -492,23 +515,10 @@
492 515 $product = $variation_product;
493 516 }
494 517 }
495 518
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;
519 + $product = $this->enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link );
504 520
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 521 return array(
512 522 'product' => $product,
513 523 'api' => 'yes',
514 524 'full_item' => $item,
@@ -514,20 +524,152 @@
514 524 'full_item' => $item,
515 525 'status' => 'success',
516 526 'error_code' => '',
517 527 );
528 + } elseif ( '' !== $lasso_settings['license_serial'] || $force_bls ) {
529 + list( $product, $status ) = $this->fetch_product_from_bls( $product_id, $store_product, $updated_at, $amz_link, $url_version_param, $force_bls, $refresh_image );
530 +
531 + return array(
532 + 'product' => $product,
533 + 'api' => 'no',
534 + 'full_item' => array(),
535 + 'status' => 200 === $status ? 'success' : 'fail',
536 + 'error_code' => 404 === $status ? 'NotFound' : '',
537 + );
538 + }
539 +
540 + return array(
541 + 'product' => array(),
542 + 'api' => 'no',
543 + 'full_item' => array(),
544 + 'status' => 'failed',
545 + 'error_code' => 'NotFound',
546 + );
547 + }
548 +
549 + /**
550 + * Fetch amazon product from BLS (Lambda)
551 + *
552 + * @param string $product_id Amazon product id.
553 + * @param bool $store_product Store product into DB or not. Default to false.
554 + * @param bool|string $updated_at Set date time or not. Default to false.
555 + * @param string $amz_link Amazon link. Default to empty.
556 + * @param bool $url_version_param Is add version param to request api url to ignore cache. Default to false.
557 + * @param bool $force_bls Force fetch product from BLS or not. Default to false.
558 + * @param bool $refresh_image Bypass cache and fetch fresh product image/metadata. Default to false.
559 + */
560 + public function fetch_product_from_bls( $product_id, $store_product = false, $updated_at = false, $amz_link = '', $url_version_param = false, $force_bls = false, $refresh_image = false ) {
561 + $url = strpos( $amz_link, 'amazon.' ) !== false ? $amz_link : $this->get_amazon_link_by_product_id( $product_id, $amz_link );
562 + $m_link = self::get_amazon_product_url( $url );
563 + $url = self::get_amazon_product_url( $url, false );
564 +
565 + $amazon_product = array(
566 + 'title' => '',
567 + 'image' => '',
568 + 'url' => $m_link,
569 + 'price' => '',
570 + 'currency' => '',
571 + 'savings_amount' => 0,
572 + 'savings_percent' => 0,
573 + 'savings_basis' => 0,
574 + );
575 +
576 + try {
577 + $res = Helper::get_url_status_code_by_broken_link_service( $url, true, false, $url_version_param, $force_bls, $refresh_image );
578 + } catch ( \Throwable $e ) {
579 + $res = array(
580 + 'status_code' => 500,
581 + 'response' => null,
582 + );
583 + }
584 +
585 + $bls_response = ( isset( $res['response'] ) && is_object( $res['response'] ) ) ? $res['response'] : null;
586 + $bls_status = ( null !== $bls_response && isset( $bls_response->status ) ) ? intval( $bls_response->status ) : 0;
587 +
588 + if ( 200 === $res['status_code'] && 200 === $bls_status ) {
589 + $img_url = $bls_response->imgUrl ?? '';
590 + $img_url = '' !== $img_url ? $img_url : '';
591 + $product_name = $bls_response->productName ?? '';
592 + $product_name = '' === $product_name ? ( $bls_response->pageTitle ?? '' ) : $product_name;
593 + $quantity = $bls_response->quantity ?? 200;
594 + $price = $bls_response->price ?? '';
595 + $product_id = self::get_product_id_by_url( $url );
596 +
597 + $temp_url = $bls_response->finalUrl ?? $url;
598 + $url = '' !== $temp_url ? $temp_url : $url;
599 + $m_link = self::get_amazon_product_url( $amz_link ? $amz_link : $url );
600 +
601 + if ( strpos( $product_name, 'Amazon.com:' ) === 0 ) {
602 + $product_name = str_replace( 'Amazon.com:', '', $product_name );
603 + $product_name = trim( $product_name );
604 + }
605 +
606 + if ( $product_id && $store_product ) {
607 + $store_data = array(
608 + 'product_id' => $product_id,
609 + 'title' => $product_name,
610 + 'price' => $price,
611 + 'default_url' => $url,
612 + 'url' => $m_link,
613 + 'image' => trim( $img_url ),
614 + 'quantity' => intval( $quantity ), // Manual checks won't show out of stock for now. TODO: Add BLS to out of stock checks.
615 + 'is_manual' => 1,
616 + );
617 +
618 + // ? Set additional data for amazon product
619 + if ( isset( $bls_response->additionalData ) && ! empty( $bls_response->additionalData ) ) {
620 + $basis_price = $bls_response->additionalData->basis_price ?? '';
621 + $basis_price = Helper::get_price_value_from_price_text( $basis_price );
622 + $savings_amount = $bls_response->additionalData->saving_amount ?? '';
623 + $savings_amount = Helper::get_price_value_from_price_text( $savings_amount );
624 +
625 + $store_data['currency'] = $bls_response->additionalData->currency_name ?? '';
626 + $store_data['savings_basis'] = $basis_price;
627 + $store_data['savings_amount'] = $savings_amount;
628 + $store_data['savings_percent'] = $bls_response->additionalData->saving_amount_percent ?? '';
629 +
630 + $amazon_product['currency'] = $store_data['currency'];
631 + $amazon_product['savings_basis'] = $store_data['savings_basis'];
632 + $amazon_product['savings_amount'] = $store_data['savings_amount'];
633 + $amazon_product['savings_percent'] = $store_data['savings_percent'];
634 + }
635 +
636 + $this->update_amazon_product_in_db( $store_data, $updated_at, true ); // allow_partial: BLS may omit imgUrl.
637 + }
638 +
639 + $amazon_product['title'] = $product_name;
640 + $amazon_product['image'] = $img_url;
641 + $amazon_product['url'] = $m_link;
642 + $amazon_product['price'] = $price;
643 + $amazon_product['quantity'] = $quantity;
644 + $amazon_product['status_code'] = $bls_status;
645 + }
646 + if ( 404 === $res['status_code'] || ( 200 === $res['status_code'] && 404 === $bls_status ) ) {
647 + $amz_model = new Amazon_Products();
648 + $last_updated = gmdate( 'Y-m-d H:i:s', time() );
649 + $amz_model->update_amazon_field( $product_id, 'last_updated', $last_updated );
650 + $amz_model->update_amazon_field( $product_id, 'out_of_stock', 0 );
651 + }
652 +
653 + if ( $bls_status ) {
654 + $status = $bls_status;
655 + } elseif ( 200 === intval( $res['status_code'] ?? 0 ) ) {
656 + $status = 500;
518 657 } else {
519 - return array();
658 + $status = intval( $res['status_code'] ?? 500 );
520 659 }
660 +
661 + return array( $amazon_product, intval( $status ) );
521 662 }
522 663
523 664 /**
524 665 * Insert or Update Amazon Product Data
525 666 *
526 - * @param array $product Amazon product.
527 - * @param bool|string $updated_at Set update date time. Default to false.
667 + * @param array $product Amazon product.
668 + * @param bool|string $updated_at Set update date time. Default to false.
669 + * @param bool $allow_partial When true, allow empty image (BLS may omit imgUrl). Default false.
528 670 */
529 - public function update_amazon_product_in_db( $product, $updated_at = false ) {
671 + public function update_amazon_product_in_db( $product, $updated_at = false, $allow_partial = false ) {
530 672 global $wpdb;
531 673
532 674 $lasso_db = new Lasso_DB();
533 675
@@ -549,9 +691,11 @@
549 691 $is_manual = $product['is_manual'] ?? 0;
550 692 $quantity = intval( $product['quantity'] ?? 200 );
551 693 $out_of_stock = 0 === $quantity ? 1 : 0;
552 694
553 - if ( '' === $amazon_id || '' === $default_product_name || '' === $default_image
695 + $image_required = ! $allow_partial;
696 + if ( '' === $amazon_id || '' === $default_product_name
697 + || ( $image_required && '' === $default_image )
554 698 || ( '' !== $default_image && Helper::validate_url( $default_image ) === false && strpos( $default_image, 'data:image' ) !== 0 )
555 699 ) {
556 700 return false;
557 701 }
@@ -880,8 +1024,44 @@
880 1024 return isset( $matches[1] ) && ! empty( $matches[1] ) ? $matches[1] : false;
881 1025 }
882 1026
883 1027 /**
1028 + * Keep URL arguments for Amazon URL
1029 + *
1030 + * @param string $product_url Amazon product url.
1031 + *
1032 + * @return array $results
1033 + */
1034 + private static function keep_args( $product_url ) {
1035 + // ? amazon link but it is not product url
1036 + if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortened_url( $product_url ) ) {
1037 + return array();
1038 + }
1039 +
1040 + $results = array();
1041 + $url_params = array(
1042 + 'maas',
1043 + 'ref_',
1044 + 's',
1045 + 'aa_campaignid',
1046 + 'aa_creativeid',
1047 + 'aa_adgroupid',
1048 + 'campaignId',
1049 + 'linkCode',
1050 + 'linkId',
1051 + );
1052 +
1053 + foreach ( $url_params as $param_name ) {
1054 + $param_value = Helper::get_argument_from_url( $product_url, $param_name );
1055 + if ( $param_value ) {
1056 + $results[] = $param_name . '=' . $param_value;
1057 + }
1058 + }
1059 +
1060 + return $results;
1061 + }
1062 +
1063 + /**
884 1064 * Get amazon tracking id by url
885 1065 * This function will be deprecated in the future. Please use Helper::get_argument_from_url() instead
886 1066 *
887 1067 * @param string $link Amazon link.
@@ -895,8 +1075,18 @@
895 1075 return $matches[2] ?? '';
896 1076 }
897 1077
898 1078 /**
1079 + * Clean Amazon URL that contains maas parameter
1080 + *
1081 + * @param string $product_url Amazon product URL.
1082 + * @return string
1083 + */
1084 + public static function clean_maas_url( $product_url ) {
1085 + return $product_url;
1086 + }
1087 +
1088 + /**
899 1089 * Make product url to shorten url if this setting is enabled
900 1090 *
901 1091 * @param string $product_url Amazon product url.
902 1092 * @param bool $monetize Monetize link or not. Default to true.
@@ -904,62 +1094,67 @@
904 1094 * @param string $custom_tracking_id Allow to use custom tracking id.
905 1095 */
906 1096 public static function get_amazon_product_url( $product_url, $monetize = true, $check_lasso_post = true, $custom_tracking_id = '' ) {
907 1097 // ? amazon link but it is not product url
908 - if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortented_url( $product_url ) ) {
1098 + if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortened_url( $product_url ) ) {
909 1099 return $product_url;
910 1100 }
911 1101
1102 + // If MAAS param exists and the original tag is exactly "maas", leave the URL untouched
1103 + $__maas_arg = Helper::get_argument_from_url( $product_url, 'maas' );
1104 + $__orig_tag = Helper::get_argument_from_url( $product_url, 'tag' );
1105 + if ( $__maas_arg && strtolower( $__orig_tag ) === 'maas' ) {
1106 + return $product_url;
1107 + }
1108 +
912 1109 $product_id = self::get_product_id_by_url( $product_url );
913 1110
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 1111 // ? remove all url queries, just keep needed args
924 1112 $url_without_params = explode( '?', $product_url )[0];
925 1113 if ( $product_id && ! $monetize ) {
926 - $args = Helper::build_url_parameter_string( array( $maas_args, $ref__args, $s_args ) );
1114 + $keep_args = self::keep_args( $product_url );
1115 + $args = Helper::build_url_parameter_string( $keep_args );
927 1116
928 - return $args ? $url_without_params . '?' . $args : $url_without_params;
1117 + $product_url = $args ? $url_without_params . '?' . $args : $url_without_params;
1118 + $product_url = self::clean_maas_url( $product_url );
1119 +
1120 + return $product_url;
929 1121 }
930 1122
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'] ?? '' );
1123 + $lasso_settings = Setting::get_settings();
1124 + $amazon_tracking_id = trim( $lasso_settings['amazon_tracking_id'] ?? '' );
1125 + $amazon_add_tracking_id_to_attribution = $lasso_settings['amazon_add_tracking_id_to_attribution'] ?? true;
935 1126
936 - if ( ! $amazon_multiple_tracking_id ) {
937 - $amazon_tracking_id_whitelist = array();
938 - }
939 -
940 1127 $lasso_db = new Lasso_DB();
941 1128 $amz_cache_key = self::OBJECT_KEY . '_' . self::FUNCTION_NAME_GET_LASSO_ID_BY_PRODUCT_ID_AND_TYPE . '_' . $product_id . '_' . self::PRODUCT_TYPE;
942 1129 $lasso_id = Cache_Per_Process::get_instance()->get_cache( $amz_cache_key, null );
943 1130 if ( null === $lasso_id ) {
944 - $lasso_id = $lasso_db->get_lasso_id_by_product_id_and_type( $product_id );
1131 + $lasso_id = $lasso_db->get_lasso_id_by_product_id_and_type( $product_id, self::PRODUCT_TYPE );
945 1132 Cache_Per_Process::get_instance()->set_cache( $amz_cache_key, $lasso_id );
946 1133 }
947 1134
948 1135 if ( $check_lasso_post && ! $lasso_id ) {
1136 + $product_url = self::clean_maas_url( $product_url );
949 1137 return $product_url;
950 1138 }
951 1139
1140 + $tag = Helper::get_argument_from_url( $product_url, 'tag' );
952 1141 $tag = $custom_tracking_id ? $custom_tracking_id : $tag;
953 1142 $tag = $tag ? $tag : '';
954 - $tag = ! empty( $amazon_tracking_id ) && ! in_array( $tag, $amazon_tracking_id_whitelist, true )
955 - ? $amazon_tracking_id : $tag;
1143 + $tag = ! empty( $tag ) ? $tag : $amazon_tracking_id;
956 1144
957 1145 // ? Return the remove all url queries for product url
958 1146 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 ) );
1147 + $tag_args = $tag ? 'tag=' . $tag : '';
1148 + $keep_args = self::keep_args( $product_url );
1149 + $maas = Helper::get_argument_from_url( $product_url, 'maas' );
1150 + $add_tag_to_url = ( $maas && $amazon_add_tracking_id_to_attribution ) || ! $maas;
961 1151
1152 + if ( $add_tag_to_url ) {
1153 + array_unshift( $keep_args, $tag_args );
1154 + }
1155 +
1156 + $args = Helper::build_url_parameter_string( $keep_args );
962 1157 $product_url = $args ? $url_without_params . '?' . $args : $url_without_params;
963 1158 } else {
964 1159 $product_url = str_replace( '&', '&', $product_url );
965 1160 $parse = wp_parse_url( $product_url );
@@ -981,8 +1176,10 @@
981 1176 $product_url = trim( $product_url );
982 1177 $product_url = trim( $product_url, '?' );
983 1178 }
984 1179
1180 + $product_url = self::clean_maas_url( $product_url );
1181 +
985 1182 return $product_url;
986 1183 }
987 1184
988 1185 /**
@@ -1273,6 +1470,334 @@
1273 1470
1274 1471 $json_response = $this->query_amazon_v5( $parameters );
1275 1472
1276 1473 return $json_response;
1474 + }
1475 +
1476 + /**
1477 + * Build discount pricing html.
1478 + *
1479 + * @param string $latest_price Latest price.
1480 + * @param mixed $basis_price Basis price value.
1481 + * @param string $currency Currency ISO.
1482 + */
1483 + public static function build_discount_pricing_html( $latest_price, $basis_price, $currency ) {
1484 + $result = '';
1485 +
1486 + try {
1487 + $latest_price_value = Helper::get_price_value_from_price_text( $latest_price );
1488 + $basis_price_value = Helper::get_price_value_from_price_text( $basis_price );
1489 +
1490 + if ( $basis_price_value && ( round( $latest_price_value, 2 ) < round( $basis_price_value, 2 ) ) ) {
1491 + $currency_symbol = Helper::get_currency_symbol_from_iso_code( $currency );
1492 + $currency_position = preg_match( '/[€]|R\$|TL|kr|zł/', $latest_price ) ? 'end' : 'begin';
1493 + $basis_price_format = preg_match( '/[€]|R\$|TL|kr|zł/', $latest_price ) ? number_format( $basis_price_value, 2, ',', '.' ) : number_format( $basis_price_value, 2, '.', ',' );
1494 + $format_price = 'begin' === $currency_position ? $currency_symbol . $basis_price_format : $basis_price_format . ' ' . $currency_symbol;
1495 +
1496 + $result = "<strike>$format_price</strike>";
1497 + }
1498 + } catch ( \Exception $e ) {
1499 + $result = '';
1500 + }
1501 +
1502 + return $result;
1503 + }
1504 +
1505 + /**
1506 + * Check amazon setting is configured or not
1507 + *
1508 + * @return boolean
1509 + */
1510 + public static function is_amazon_setting_configured() {
1511 + $lasso_settings = Setting::get_settings();
1512 + $is_amazon_configured = $lasso_settings['amazon_access_key_id'] && $lasso_settings['amazon_secret_key'] && $lasso_settings['amazon_tracking_id'];
1513 +
1514 + return $is_amazon_configured || self::is_amazon_creators_verified( $lasso_settings );
1515 + }
1516 +
1517 + /**
1518 + * Whether Amazon Creators API credentials are complete in settings.
1519 + *
1520 + * @param array|false $lasso_settings Settings array. Default false loads current settings.
1521 + * @return bool
1522 + */
1523 + public static function is_amazon_creators_configured( $lasso_settings = false ) {
1524 + if ( ! is_array( $lasso_settings ) ) {
1525 + $lasso_settings = Setting::get_settings();
1526 + }
1527 +
1528 + $fields = array(
1529 + 'amazon_creators_credential_id',
1530 + 'amazon_creators_secret',
1531 + 'amazon_creators_version',
1532 + 'amazon_creators_partner_tag',
1533 + );
1534 +
1535 + foreach ( $fields as $field ) {
1536 + if ( '' === trim( (string) ( $lasso_settings[ $field ] ?? '' ) ) ) {
1537 + return false;
1538 + }
1539 + }
1540 +
1541 + return true;
1542 + }
1543 +
1544 + /**
1545 + * Whether Creators credentials were verified successfully (signature matches stored settings).
1546 + *
1547 + * @param array|false $lasso_settings Settings array. Default false loads current settings.
1548 + * @return bool
1549 + */
1550 + public static function is_amazon_creators_verified( $lasso_settings = false ) {
1551 + if ( ! self::is_amazon_creators_configured( $lasso_settings ) ) {
1552 + return false;
1553 + }
1554 +
1555 + if ( ! is_array( $lasso_settings ) ) {
1556 + $lasso_settings = Setting::get_settings();
1557 + }
1558 +
1559 + $current_signature = self::get_amazon_creators_signature( $lasso_settings );
1560 + $saved_signature = (string) Helper::get_option( Constant::LASSO_OPTION_AMAZON_CREATORS_VERIFIED_SIGNATURE, '' );
1561 +
1562 + return '' !== $current_signature && '' !== $saved_signature && hash_equals( $saved_signature, $current_signature );
1563 + }
1564 +
1565 + /**
1566 + * Build a stable signature for the Creators payload.
1567 + *
1568 + * @param array $settings Settings values.
1569 + * @return string Empty when Creators credentials are incomplete.
1570 + */
1571 + public static function get_amazon_creators_signature( $settings ) {
1572 + if ( ! self::is_amazon_creators_configured( $settings ) ) {
1573 + return '';
1574 + }
1575 +
1576 + return hash(
1577 + 'sha256',
1578 + wp_json_encode(
1579 + array(
1580 + 'credential_id' => (string) ( $settings['amazon_creators_credential_id'] ?? '' ),
1581 + 'secret' => (string) ( $settings['amazon_creators_secret'] ?? '' ),
1582 + 'credential_version' => (string) ( $settings['amazon_creators_version'] ?? '' ),
1583 + 'partner_tag' => (string) ( $settings['amazon_creators_partner_tag'] ?? '' ),
1584 + 'country' => (string) ( $settings['amazon_default_tracking_country'] ?? '' ),
1585 + )
1586 + )
1587 + );
1588 + }
1589 +
1590 + /**
1591 + * Resolve Creators API country from an Amazon URL or default tracking country.
1592 + *
1593 + * @param string $amazon_url Amazon product URL (optional).
1594 + * @param array|false $lasso_settings Settings array. Default false loads current settings.
1595 + * @return string
1596 + */
1597 + public static function get_country_for_creators_api( $amazon_url, $lasso_settings = false ) {
1598 + if ( ! is_array( $lasso_settings ) ) {
1599 + $lasso_settings = Setting::get_settings();
1600 + }
1601 +
1602 + if ( $amazon_url && self::is_amazon_url( $amazon_url ) ) {
1603 + $base_domain = strtolower( Helper::get_base_domain( $amazon_url ) );
1604 + if ( '' !== $base_domain ) {
1605 + foreach ( self::get_amazon_api_countries() as $country_code => $country_data ) {
1606 + $amazon_domain = strtolower( Helper::get_base_domain( (string) ( $country_data['amazon_domain'] ?? '' ) ) );
1607 + if ( $base_domain === $amazon_domain ) {
1608 + return (string) $country_code;
1609 + }
1610 + }
1611 + }
1612 + }
1613 +
1614 + return (string) ( $lasso_settings['amazon_default_tracking_country'] ?? '' );
1615 + }
1616 +
1617 + /**
1618 + * Whether the Lite install is linked to a Lasso account email.
1619 + *
1620 + * @return bool
1621 + */
1622 + public static function is_lite_account_connected() {
1623 + $email = Helper::get_option( Constant::LASSO_ACCOUNT_EMAIL, '' );
1624 +
1625 + return is_string( $email ) && '' !== trim( $email );
1626 + }
1627 +
1628 + /**
1629 + * Fetch product data via direct Amazon Creators API (replaces deprecated PA-API GetItems).
1630 + *
1631 + * @param string $product_id Amazon ASIN.
1632 + * @param bool $store_product Store in local DB.
1633 + * @param bool|string $updated_at Optional updated timestamp.
1634 + * @param string $amz_link Amazon URL.
1635 + * @param array $lasso_settings Plugin settings.
1636 + * @return array|null Same shape as fetch_product_info success/failure, or null to fall back.
1637 + */
1638 + private function fetch_product_info_from_creators_api( $product_id, $store_product, $updated_at, $amz_link, $lasso_settings ) {
1639 + if ( ! self::is_amazon_creators_configured( $lasso_settings ) ) {
1640 + return null;
1641 + }
1642 +
1643 + $api_result = Amazon_Creators_Api::fetch_product(
1644 + $product_id,
1645 + self::get_country_for_creators_api( $amz_link, $lasso_settings ),
1646 + array(
1647 + 'product_only' => false,
1648 + )
1649 + );
1650 +
1651 + if ( 'fail' === ( $api_result['status'] ?? '' ) ) {
1652 + $error_code = (string) ( $api_result['error_code'] ?? '' );
1653 +
1654 + if ( 'NotFound' === $error_code ) {
1655 + return $this->build_fetch_product_info_response( array(), 'yes', array(), 'failed', 'NotFound' );
1656 + }
1657 +
1658 + return null;
1659 + }
1660 +
1661 + $product_data = $api_result['product'] ?? array();
1662 + if ( ! is_array( $product_data ) || empty( $product_data ) ) {
1663 + return null;
1664 + }
1665 +
1666 + $full_item = $api_result['item'] ?? array();
1667 + $product = $this->finalize_fetch_product_info_product(
1668 + $product_data,
1669 + $product_id,
1670 + $store_product,
1671 + $updated_at,
1672 + $amz_link
1673 + );
1674 +
1675 + return $this->build_fetch_product_info_response( $product, 'yes', $full_item, 'success', '' );
1676 + }
1677 +
1678 + /**
1679 + * Build fetch_product_info() return array (same contract as get_product_by_id_v5 path).
1680 + *
1681 + * @param array $product Product fields.
1682 + * @param string $api API source flag.
1683 + * @param array|object $full_item Raw item payload.
1684 + * @param string $status success|failed.
1685 + * @param string $error_code Error code.
1686 + * @return array
1687 + */
1688 + private function build_fetch_product_info_response( $product, $api, $full_item, $status, $error_code ) {
1689 + return array(
1690 + 'product' => $product,
1691 + 'api' => $api,
1692 + 'full_item' => $full_item,
1693 + 'status' => $status,
1694 + 'error_code' => $error_code,
1695 + );
1696 + }
1697 +
1698 + /**
1699 + * Apply the same post-processing as the PA-API branch in fetch_product_info().
1700 + *
1701 + * @param array $product Product fields from API.
1702 + * @param string $product_id Amazon ASIN.
1703 + * @param bool $store_product Store in local DB.
1704 + * @param bool|string $updated_at Optional updated timestamp.
1705 + * @param string $amz_link Amazon URL.
1706 + * @return array
1707 + */
1708 + private function finalize_fetch_product_info_product( $product, $product_id, $store_product, $updated_at, $amz_link ) {
1709 + if ( empty( $product['product_id'] ) ) {
1710 + $product['product_id'] = $product_id;
1711 + }
1712 +
1713 + return $this->enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link );
1714 + }
1715 +
1716 + /**
1717 + * Shared post-fetch enrichment for PA-API and Creators product payloads.
1718 + *
1719 + * @param array $product Product fields.
1720 + * @param bool $store_product Store in local DB.
1721 + * @param bool|string $updated_at Optional updated timestamp.
1722 + * @param string $amz_link Amazon URL.
1723 + * @return array
1724 + */
1725 + private function enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link ) {
1726 + global $wpdb;
1727 +
1728 + $lasso_db = new Lasso_DB();
1729 + $product_url = $amz_link ? $amz_link : ( $product['url'] ?? '' );
1730 + $product['status_code'] = 200;
1731 + $product['url'] = self::get_amazon_product_url( $product_url );
1732 +
1733 + $amazon_product_id = (string) ( $product['product_id'] ?? '' );
1734 + $query = $wpdb->prepare(
1735 + 'SELECT post_id FROM ' . $lasso_db->postmeta . " WHERE meta_key = 'amazon_product_id' AND meta_value = %s",
1736 + $amazon_product_id
1737 + );
1738 + $lasso_id = $lasso_db->get_var( $query );
1739 + $product['lasso_id'] = ( isset( $lasso_id ) ) ? $lasso_id : 0;
1740 +
1741 + if ( $store_product ) {
1742 + $amazon_tracking_id = Setting::get_setting( 'amazon_tracking_id', '' );
1743 + $product['default_url'] = '' === $amazon_tracking_id ? $amz_link : ( $product['default_url'] ?? '' );
1744 + $this->update_amazon_product_in_db( $product, $updated_at );
1745 + }
1746 +
1747 + return $product;
1748 + }
1749 +
1750 + /**
1751 + * Get final url of the amazon shortlink from cache
1752 + *
1753 + * @param string $shortlink Amazon shortlink.
1754 + * @return string|null
1755 + */
1756 + public static function get_shortlink_final_url_cached( $shortlink ) {
1757 + if ( ! self::is_amazon_shortened_url( $shortlink ) ) {
1758 + return null;
1759 + }
1760 + $final_url_cache = get_option( self::build_shortlink_cache_key( $shortlink ) );
1761 +
1762 + if ( $final_url_cache === $shortlink ) {
1763 + return null;
1764 + }
1765 +
1766 + return $final_url_cache ? $final_url_cache : null;
1767 + }
1768 +
1769 + /**
1770 + * Build amazon shortlink cache key
1771 + *
1772 + * @param string $shortlink Amazon shortlink.
1773 + * @return string|null
1774 + */
1775 + public static function build_shortlink_cache_key( $shortlink ) {
1776 + if ( ! self::is_amazon_shortened_url( $shortlink ) ) {
1777 + return null;
1778 + }
1779 +
1780 + $parse = wp_parse_url( $shortlink );
1781 + $host = str_replace( '.', '_', $parse['host'] );
1782 + $id = trim( $parse['path'] ?? '', '/' );
1783 +
1784 + return $host . '_' . $id;
1785 + }
1786 +
1787 + /**
1788 + * Format Amazon URLs
1789 + *
1790 + * @param string $url Amazon product url.
1791 + * @return string URL.
1792 + */
1793 + public static function format_amazon_url( $url ) {
1794 + $is_amazon_link = self::is_amazon_url( $url );
1795 + $product_id = self::get_product_id_by_url( $url );
1796 +
1797 + if ( $is_amazon_link && $product_id && strpos( $url, 'smile.amazon.' ) !== false ) {
1798 + $url = str_replace( 'smile.amazon.', 'amazon.', $url );
1799 + }
1800 +
1801 + return $url;
1277 1802 }
1278 1803 }