array( 'name' => 'United States', 'amazon_domain' => 'www.amazon.com', 'pa_endpoint' => 'webservices.amazon.com', 'region' => 'us-east-1', ), 'usa' => array( 'name' => 'United States', 'amazon_domain' => 'www.amazon.com', 'pa_endpoint' => 'webservices.amazon.com', 'region' => 'us-east-1', ), 'au' => array( 'name' => 'Australia', 'amazon_domain' => 'www.amazon.com.au', 'pa_endpoint' => 'webservices.amazon.com.au', 'region' => 'us-west-2', ), 'aus' => array( 'name' => 'Australia', 'amazon_domain' => 'www.amazon.com.au', 'pa_endpoint' => 'webservices.amazon.com.au', 'region' => 'us-west-2', ), 'br' => array( 'name' => 'Brazil', 'amazon_domain' => 'www.amazon.com.br', 'pa_endpoint' => 'webservices.amazon.com.br', 'region' => 'us-east-1', ), 'bra' => array( 'name' => 'Brazil', 'amazon_domain' => 'www.amazon.com.br', 'pa_endpoint' => 'webservices.amazon.com.br', 'region' => 'us-east-1', ), 'ca' => array( 'name' => 'Canada', 'amazon_domain' => 'www.amazon.ca', 'pa_endpoint' => 'webservices.amazon.ca', 'region' => 'us-east-1', ), 'can' => array( 'name' => 'Canada', 'amazon_domain' => 'www.amazon.ca', 'pa_endpoint' => 'webservices.amazon.ca', 'region' => 'us-east-1', ), 'cn' => array( 'name' => 'China', 'amazon_domain' => 'www.amazon.cn', 'pa_endpoint' => 'webservices.amazon.cn', 'region' => 'us-east-1', ), 'chn' => array( 'name' => 'China', 'amazon_domain' => 'www.amazon.cn', 'pa_endpoint' => 'webservices.amazon.cn', 'region' => 'us-east-1', ), 'fr' => array( 'name' => 'France', 'amazon_domain' => 'www.amazon.fr', 'pa_endpoint' => 'webservices.amazon.fr', 'region' => 'eu-west-1', ), 'fra' => array( 'name' => 'France', 'amazon_domain' => 'www.amazon.fr', 'pa_endpoint' => 'webservices.amazon.fr', 'region' => 'eu-west-1', ), 'de' => array( 'name' => 'Germany', 'amazon_domain' => 'www.amazon.de', 'pa_endpoint' => 'webservices.amazon.de', 'region' => 'eu-west-1', ), 'deu' => array( 'name' => 'Germany', 'amazon_domain' => 'www.amazon.de', 'pa_endpoint' => 'webservices.amazon.de', 'region' => 'eu-west-1', ), 'in' => array( 'name' => 'India', 'amazon_domain' => 'www.amazon.in', 'pa_endpoint' => 'webservices.amazon.in', 'region' => 'eu-west-1', ), 'ind' => array( 'name' => 'India', 'amazon_domain' => 'www.amazon.in', 'pa_endpoint' => 'webservices.amazon.in', 'region' => 'eu-west-1', ), 'it' => array( 'name' => 'Italy', 'amazon_domain' => 'www.amazon.it', 'pa_endpoint' => 'webservices.amazon.it', 'region' => 'eu-west-1', ), 'ita' => array( 'name' => 'Italy', 'amazon_domain' => 'www.amazon.it', 'pa_endpoint' => 'webservices.amazon.it', 'region' => 'eu-west-1', ), 'jp' => array( 'name' => 'Japan', 'amazon_domain' => 'www.amazon.co.jp', 'pa_endpoint' => 'webservices.amazon.co.jp', 'region' => 'us-west-2', ), 'jpn' => array( 'name' => 'Japan', 'amazon_domain' => 'www.amazon.co.jp', 'pa_endpoint' => 'webservices.amazon.co.jp', 'region' => 'us-west-2', ), 'mx' => array( 'name' => 'Mexico', 'amazon_domain' => 'www.amazon.com.mx', 'pa_endpoint' => 'webservices.amazon.com.mx', 'region' => 'us-east-1', ), 'mex' => array( 'name' => 'Mexico', 'amazon_domain' => 'www.amazon.com.mx', 'pa_endpoint' => 'webservices.amazon.com.mx', 'region' => 'us-east-1', ), 'nl' => array( 'name' => 'Netherlands', 'amazon_domain' => 'www.amazon.nl', 'pa_endpoint' => 'webservices.amazon.nl', 'region' => 'eu-west-1', ), 'nld' => array( 'name' => 'Netherlands', 'amazon_domain' => 'www.amazon.nl', 'pa_endpoint' => 'webservices.amazon.nl', 'region' => 'eu-west-1', ), 'se' => array( 'name' => 'Sweden', 'amazon_domain' => 'www.amazon.se', 'pa_endpoint' => 'webservices.amazon.se', 'region' => 'us-west-1', ), 'sek' => array( 'name' => 'Sweden', 'amazon_domain' => 'www.amazon.se', 'pa_endpoint' => 'webservices.amazon.se', 'region' => 'us-west-1', ), 'sg' => array( 'name' => 'Singapore', 'amazon_domain' => 'www.amazon.sg', 'pa_endpoint' => 'webservices.amazon.sg', 'region' => 'us-west-2', ), 'sgp' => array( 'name' => 'Singapore', 'amazon_domain' => 'www.amazon.sg', 'pa_endpoint' => 'webservices.amazon.sg', 'region' => 'us-west-2', ), 'es' => array( 'name' => 'Spain', 'amazon_domain' => 'www.amazon.es', 'pa_endpoint' => 'webservices.amazon.es', 'region' => 'eu-west-1', ), 'esp' => array( 'name' => 'Spain', 'amazon_domain' => 'www.amazon.es', 'pa_endpoint' => 'webservices.amazon.es', 'region' => 'eu-west-1', ), 'tr' => array( 'name' => 'Turkey', 'amazon_domain' => 'www.amazon.com.tr', 'pa_endpoint' => 'webservices.amazon.com.tr', 'region' => 'eu-west-1', ), 'tur' => array( 'name' => 'Turkey', 'amazon_domain' => 'www.amazon.com.tr', 'pa_endpoint' => 'webservices.amazon.com.tr', 'region' => 'eu-west-1', ), 'ae' => array( 'name' => 'United Arab Emirates', 'amazon_domain' => 'www.amazon.ae', 'pa_endpoint' => 'webservices.amazon.ae', 'region' => 'eu-west-1', ), 'are' => array( 'name' => 'United Arab Emirates', 'amazon_domain' => 'www.amazon.ae', 'pa_endpoint' => 'webservices.amazon.ae', 'region' => 'eu-west-1', ), 'gb' => array( 'name' => 'United Kingdom', 'amazon_domain' => 'www.amazon.co.uk', 'pa_endpoint' => 'webservices.amazon.co.uk', 'region' => 'eu-west-1', ), 'gbr' => array( 'name' => 'United Kingdom', 'amazon_domain' => 'www.amazon.co.uk', 'pa_endpoint' => 'webservices.amazon.co.uk', 'region' => 'eu-west-1', ), ); } /** * Get ignore errors list */ public static function get_ignore_error_codes() { return array( 'ItemNotAccessible', 'InvalidParameterValue', 'AccessDeniedException', 'AccessDenied', 'TooManyRequestsException', 'TooManyRequests', 'ThrottlingException', 'RequestThrottled', 'AWS.ThrottlingException', 'AWS.RequestThrottled', 'AWS.AccessDeniedException', 'UnrecognizedClient', ); } /** * Get amazon domains */ public static function get_domains() { return array( 'amazon.com', // ? US 'amazon.ca', // ? Canada 'amazon.co.uk', // ? UK 'amazon.com.au', // ? Australia 'amazon.com.br', // ? Brazil 'amazon.com.mx', // ? Mexico 'amazon.fr', // ? France 'amazon.de', // ? Germany 'amazon.it', // ? Italy 'amazon.in', // ? India 'amazon.es', // ? Spain 'amazon.cn', // ? China 'amazon.co.jp', // ? Japan 'amazon.nl', // ? Netherlands 'amazon.se', // ? Sweden 'amazon.sg', // ? Singapore 'amazon.com.tr', // ? Turkey 'amazon.ae', // ? United Arab Emirates 'amzn.com', // ? Short URL 'amzn.to', // ? Short URL 'amazon-adsystem.com', // ? Amazon Embed 'smile.amazon.com', // ? Amazon Smile ); } /** * Get amazon link and flag */ public static function get_aff_link_and_flag() { return array( 'www.amazon.com' => array( 'flag' => '🇺🇸', 'aff_link' => 'https://affiliate-program.amazon.com/', ), 'www.amazon.ca' => array( 'flag' => '🇨🇦', 'aff_link' => 'https://associates.amazon.ca/', ), 'www.amazon.com.br' => array( 'flag' => '🇧🇷', 'aff_link' => 'https://associados.amazon.com.br/', ), 'www.amazon.com.mx' => array( 'flag' => '🇲🇽', 'aff_link' => 'https://afiliados.amazon.com.mx/', ), 'www.amazon.fr' => array( 'flag' => '🇫🇷', 'aff_link' => 'https://partenaires.amazon.fr/', ), 'www.amazon.de' => array( 'flag' => '🇩🇪', 'aff_link' => 'https://partnernet.amazon.de/', ), 'www.amazon.it' => array( 'flag' => '🇮🇹', 'aff_link' => 'https://programma-affiliazione.amazon.it/', ), 'www.amazon.es' => array( 'flag' => '🇪🇸', 'aff_link' => 'https://afiliados.amazon.es/', ), 'www.amazon.co.uk' => array( 'flag' => '🇬🇧', 'aff_link' => 'https://affiliate-program.amazon.co.uk/', ), 'www.amazon.cn' => array( 'flag' => '🇨🇳', 'aff_link' => 'https://associates.amazon.cn/', ), 'www.amazon.co.jp' => array( 'flag' => '🇯🇵', 'aff_link' => 'https://affiliate.amazon.co.jp/', ), 'www.amazon.in' => array( 'flag' => '🇮🇳', 'aff_link' => 'https://affiliate-program.amazon.in/', ), 'www.amazon.com.au' => array( 'flag' => '🇦🇺', 'aff_link' => 'https://affiliate-program.amazon.com.au/', ), ); } /** * Check whether a url is amazon link or not * * @param string $url Url. */ public static function is_amazon_url( $url ) { if ( empty( $url ) ) { return false; } $domains = self::get_domains(); $url = Helper::add_https( $url ); if ( ! Helper::validate_url( $url ) ) { return false; } $parse_url = wp_parse_url( $url ); if ( ! isset( $parse_url['host'] ) ) { return false; } $domain = ltrim( $parse_url['host'], 'www.' ); if ( in_array( $domain, $domains, true ) ) { return true; } return false; } /** * Check whether a url is amazon link or not * * @param string $url Url. */ public static function is_amazon_shortened_url( $url ) { $is_amazon_url = self::is_amazon_url( $url ); $is_shortented_url = strpos( $url, 'amzn.to' ) || strpos( $url, 'amzn.com' ); return $is_amazon_url && $is_shortented_url; } /** * Search amazon product * * @param string $keyword Keyword: product name,... */ public function search_product( $keyword ) { $result = $this->get_product_by_keyword_v5( $keyword, 'All' ); if ( isset( $result->SearchResult->Items ) ) { // phpcs:ignore $items = $result->SearchResult->Items; // phpcs:ignore $products = array(); foreach ( $items as $item ) { $product = $this->extract_search_result_v5( $item ); array_push( $products, $product ); } return $products; } elseif ( isset( $result->Errors ) ) { // phpcs:ignore return array( 'error' => $result->Errors, // phpcs:ignore ); } } /** * Check whether product url has the same domain with Amazon settings * * @param string $product_url Amazon link. */ public function is_same_domain( $product_url ) { if ( '' === $product_url ) { return false; } $amazon_default_tracking_country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' ); $all_countries = self::get_amazon_api_countries(); $domain = $all_countries[ $amazon_default_tracking_country ]['amazon_domain'] ?? 'www.amazon.com'; $domain = str_replace( 'www.', '', $domain ); return strpos( $product_url, $domain ) !== false; } /** * Fetch amazon product from Amazon API v5 * * @param string $product_id Amazon product id. * @param bool $store_product Store product into DB or not. Default to false. * @param bool|string $updated_at Set date time or not. Default to false. * @param string $amz_link Amazon link. Default to empty. * @param bool $url_version_param Is add version param to request api url to ignore cache. Default to false. * @param bool $force_bls Force fetch product from BLS or not. Default to false. * @param bool $refresh_image Bypass cache and fetch fresh product image/metadata. Default to false. * @return mixed */ 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 ) { $lasso_db = new Lasso_DB(); $lasso_settings = Setting::get_settings(); $is_amazon_configured = $lasso_settings['amazon_access_key_id'] && $lasso_settings['amazon_secret_key'] && $lasso_settings['amazon_tracking_id']; if ( self::is_amazon_creators_verified( $lasso_settings ) && $this->is_same_domain( $amz_link ) ) { $creators_result = $this->fetch_product_info_from_creators_api( $product_id, $store_product, $updated_at, $amz_link, $lasso_settings ); if ( null !== $creators_result ) { return $creators_result; } } $result = ! $force_bls && $is_amazon_configured && $this->is_same_domain( $amz_link ) ? $this->get_product_by_id_v5( $product_id ) : false; // phpcs:ignore if ( is_object( $result ) && isset( $result->Errors[0] ) && ( "The ItemId $product_id provided in the request is invalid." === $result->Errors[0]->Message // phpcs:ignore || "The value [$product_id] provided in the request for ItemIds is invalid." === $result->Errors[0]->Message // phpcs:ignore ) ) { return array( 'product' => array(), 'api' => 'yes', 'full_item' => array(), 'status' => 'failed', 'error_code' => 'NotFound', ); } elseif ( is_object( $result ) && isset( $result->ItemsResult->Items[0] ) ) { // phpcs:ignore $item = $result->ItemsResult->Items[0]; // phpcs:ignore $product = $this->extract_search_result_v5( $item, true ); $product_url = $amz_link ? $amz_link : $product['url']; $product['status_code'] = 200; $product['url'] = self::get_amazon_product_url( $product_url ); // ? If $item->Offers is missing, we try to get the quantity from variation data if ( ! isset( $item->Offers ) ) { // phpcs:ignore sleep( 1 ); // ? Delay for a while before call the next request $variation_product = $this->get_product_variation( $product_id, $product_url ); if ( $variation_product && isset( $variation_product['quantity'] ) && $variation_product['quantity'] ) { $product = $variation_product; } } $product = $this->enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link ); return array( 'product' => $product, 'api' => 'yes', 'full_item' => $item, 'status' => 'success', 'error_code' => '', ); } elseif ( '' !== $lasso_settings['license_serial'] || $force_bls ) { list( $product, $status ) = $this->fetch_product_from_bls( $product_id, $store_product, $updated_at, $amz_link, $url_version_param, $force_bls, $refresh_image ); return array( 'product' => $product, 'api' => 'no', 'full_item' => array(), 'status' => 200 === $status ? 'success' : 'fail', 'error_code' => 404 === $status ? 'NotFound' : '', ); } return array( 'product' => array(), 'api' => 'no', 'full_item' => array(), 'status' => 'failed', 'error_code' => 'NotFound', ); } /** * Fetch amazon product from BLS (Lambda) * * @param string $product_id Amazon product id. * @param bool $store_product Store product into DB or not. Default to false. * @param bool|string $updated_at Set date time or not. Default to false. * @param string $amz_link Amazon link. Default to empty. * @param bool $url_version_param Is add version param to request api url to ignore cache. Default to false. * @param bool $force_bls Force fetch product from BLS or not. Default to false. * @param bool $refresh_image Bypass cache and fetch fresh product image/metadata. Default to false. */ 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 ) { $url = strpos( $amz_link, 'amazon.' ) !== false ? $amz_link : $this->get_amazon_link_by_product_id( $product_id, $amz_link ); $m_link = self::get_amazon_product_url( $url ); $url = self::get_amazon_product_url( $url, false ); $amazon_product = array( 'title' => '', 'image' => '', 'url' => $m_link, 'price' => '', 'currency' => '', 'savings_amount' => 0, 'savings_percent' => 0, 'savings_basis' => 0, ); try { $res = Helper::get_url_status_code_by_broken_link_service( $url, true, false, $url_version_param, $force_bls, $refresh_image ); } catch ( \Throwable $e ) { $res = array( 'status_code' => 500, 'response' => null, ); } $bls_response = ( isset( $res['response'] ) && is_object( $res['response'] ) ) ? $res['response'] : null; $bls_status = ( null !== $bls_response && isset( $bls_response->status ) ) ? intval( $bls_response->status ) : 0; if ( 200 === $res['status_code'] && 200 === $bls_status ) { $img_url = $bls_response->imgUrl ?? ''; $img_url = '' !== $img_url ? $img_url : ''; $product_name = $bls_response->productName ?? ''; $product_name = '' === $product_name ? ( $bls_response->pageTitle ?? '' ) : $product_name; $quantity = $bls_response->quantity ?? 200; $price = $bls_response->price ?? ''; $product_id = self::get_product_id_by_url( $url ); $temp_url = $bls_response->finalUrl ?? $url; $url = '' !== $temp_url ? $temp_url : $url; $m_link = self::get_amazon_product_url( $amz_link ? $amz_link : $url ); if ( strpos( $product_name, 'Amazon.com:' ) === 0 ) { $product_name = str_replace( 'Amazon.com:', '', $product_name ); $product_name = trim( $product_name ); } if ( $product_id && $store_product ) { $store_data = array( 'product_id' => $product_id, 'title' => $product_name, 'price' => $price, 'default_url' => $url, 'url' => $m_link, 'image' => trim( $img_url ), 'quantity' => intval( $quantity ), // Manual checks won't show out of stock for now. TODO: Add BLS to out of stock checks. 'is_manual' => 1, ); // ? Set additional data for amazon product if ( isset( $bls_response->additionalData ) && ! empty( $bls_response->additionalData ) ) { $basis_price = $bls_response->additionalData->basis_price ?? ''; $basis_price = Helper::get_price_value_from_price_text( $basis_price ); $savings_amount = $bls_response->additionalData->saving_amount ?? ''; $savings_amount = Helper::get_price_value_from_price_text( $savings_amount ); $store_data['currency'] = $bls_response->additionalData->currency_name ?? ''; $store_data['savings_basis'] = $basis_price; $store_data['savings_amount'] = $savings_amount; $store_data['savings_percent'] = $bls_response->additionalData->saving_amount_percent ?? ''; $amazon_product['currency'] = $store_data['currency']; $amazon_product['savings_basis'] = $store_data['savings_basis']; $amazon_product['savings_amount'] = $store_data['savings_amount']; $amazon_product['savings_percent'] = $store_data['savings_percent']; } $this->update_amazon_product_in_db( $store_data, $updated_at, true ); // allow_partial: BLS may omit imgUrl. } $amazon_product['title'] = $product_name; $amazon_product['image'] = $img_url; $amazon_product['url'] = $m_link; $amazon_product['price'] = $price; $amazon_product['quantity'] = $quantity; $amazon_product['status_code'] = $bls_status; } if ( 404 === $res['status_code'] || ( 200 === $res['status_code'] && 404 === $bls_status ) ) { $amz_model = new Amazon_Products(); $last_updated = gmdate( 'Y-m-d H:i:s', time() ); $amz_model->update_amazon_field( $product_id, 'last_updated', $last_updated ); $amz_model->update_amazon_field( $product_id, 'out_of_stock', 0 ); } if ( $bls_status ) { $status = $bls_status; } elseif ( 200 === intval( $res['status_code'] ?? 0 ) ) { $status = 500; } else { $status = intval( $res['status_code'] ?? 500 ); } return array( $amazon_product, intval( $status ) ); } /** * Insert or Update Amazon Product Data * * @param array $product Amazon product. * @param bool|string $updated_at Set update date time. Default to false. * @param bool $allow_partial When true, allow empty image (BLS may omit imgUrl). Default false. */ public function update_amazon_product_in_db( $product, $updated_at = false, $allow_partial = false ) { global $wpdb; $lasso_db = new Lasso_DB(); $amazon_id = $product['product_id'] ?? ''; $default_product_name = $product['title'] ?? ''; $latest_price = $product['price'] ?? ''; $latest_price = '0' === $latest_price || ( is_int( $latest_price ) && 0 === $latest_price ) ? '' : $latest_price; $base_url = self::get_amazon_product_url( $product['default_url'] ?? '', false, false ); $monetized_url = self::get_amazon_product_url( $product['url'] ?? '', true, false ); $default_image = trim( $product['image'] ?? '' ); $last_updated = gmdate( 'Y-m-d H:i:s', time() ); $last_updated = $updated_at ? $updated_at : $last_updated; $is_prime = $product['is_prime'] ?? ''; $currency = $product['currency'] ?? ''; $features = wp_json_encode( $product['features'] ?? array() ); $savings_amount = $product['savings_amount'] ?? ''; $savings_percent = $product['savings_percent'] ?? ''; $savings_basis = $product['savings_basis'] ?? ''; $is_manual = $product['is_manual'] ?? 0; $quantity = intval( $product['quantity'] ?? 200 ); $out_of_stock = 0 === $quantity ? 1 : 0; $image_required = ! $allow_partial; if ( '' === $amazon_id || '' === $default_product_name || ( $image_required && '' === $default_image ) || ( '' !== $default_image && Helper::validate_url( $default_image ) === false && strpos( $default_image, 'data:image' ) !== 0 ) ) { return false; } $base_url = trim( $base_url ); $monetized_url = trim( $monetized_url ); $query = ' INSERT INTO ' . $lasso_db->amazon_products . " ( amazon_id, default_product_name, latest_price, base_url, monetized_url, default_image, last_updated, is_prime, currency, features, savings_amount, savings_percent, savings_basis, is_manual, out_of_stock ) VALUES ( %s, %s, %s, %s, %s, %s, %s, %d, %s, %s, %s, %d, %s, %d, %d ) ON DUPLICATE KEY UPDATE amazon_id = %s, default_product_name = %s, latest_price = %s, base_url = %s, monetized_url = %s, default_image = (CASE WHEN %s='' or %s IS NULL THEN `default_image` ELSE %s END), last_updated = %s, is_prime = %d, currency = %s, features = %s, savings_amount = %s, savings_percent = %d, savings_basis = %s, is_manual = %d, out_of_stock = %d ; "; $prepare = $wpdb->prepare( // phpcs:ignore $query, // ? First for insert $amazon_id, $default_product_name, $latest_price, $base_url, $monetized_url, $default_image, $last_updated, $is_prime, $currency, $features, $savings_amount, $savings_percent, $savings_basis, $is_manual, $out_of_stock, // ? Second for update $amazon_id, $default_product_name, $latest_price, $base_url, $monetized_url, $default_image, $default_image, $default_image, $last_updated, $is_prime, $currency, $features, $savings_amount, $savings_percent, $savings_basis, $is_manual, $out_of_stock ); $lasso_db->query( $prepare ); return true; } /** * Get amazon product from DB * * @param string $product_id Amazon Product id. */ public function get_amazon_product_from_db( $product_id ) { if ( empty( $product_id ) ) { return false; } global $wpdb; $lasso_db = new Lasso_DB(); $sql = ' SELECT * FROM ' . $lasso_db->amazon_products . ' WHERE amazon_id = %s '; $prepare = $wpdb->prepare( $sql, $product_id ); // phpcs:ignore $result = $lasso_db->get_row( $prepare, ARRAY_A ); if ( $result ) { $result = apply_filters( self::FILTER_AMAZON_PRODUCT, $result ); $result['monetized_url'] = self::get_amazon_product_url( $result['monetized_url'] ); $result['base_url'] = self::get_amazon_product_url( $result['base_url'], false ); $result['features'] = json_decode( $result['features'] ); } return $result; } /** * Extract result to an array * * @param object $response Data from Amazon. * @param bool $large_image Get large image size. Default to false. * @param string $product_id Product Id. Default to empty. * @param string $product_url Product url. Default to empty. * * @return array */ private function extract_search_result_v5( $response, $large_image = false, $product_id = '', $product_url = '' ) { $image = ''; if ( isset( $response->Images->Primary ) ) { // phpcs:ignore $image = $large_image ? $response->Images->Primary->Large->URL : $response->Images->Primary->Small->URL; // phpcs:ignore } // @codingStandardsIgnoreStart $result = array( 'product_id' => $product_id ? $product_id : ( $response->ASIN ?? 0 ), 'title' => $response->ItemInfo->Title->DisplayValue ?? '', 'url' => $product_url ? $product_url : ( $response->DetailPageURL ?? '' ), 'default_url' => $response->DetailPageURL ?? '', 'image' => $image, 'quantity' => $response->Offers->Summaries[0]->OfferCount ?? 0, 'is_prime' => $response->Offers->Listings[0]->DeliveryInfo->IsPrimeEligible ?? false, 'price' => $response->Offers->Listings[0]->Price->DisplayAmount ?? 0, 'amount' => $response->Offers->Listings[0]->Price->Amount ?? 0, 'currency' => $response->Offers->Listings[0]->Price->Currency ?? '', 'features' => $response->ItemInfo->Features->DisplayValues ?? array(), 'savings_amount' => $response->Offers->Listings[0]->Price->Savings->Amount ?? 0.0, 'savings_percent' => $response->Offers->Listings[0]->Price->Savings->Percentage ?? 0, 'savings_basis' => $response->Offers->Listings[0]->SavingBasis->Amount ?? 0.0, ); // @codingStandardsIgnoreEnd return $result; } /** * Query amazon v5 * * @param array $parameters Amazon API params. * @param boolean $lasso_settings Lasso settings. Default to false. * * @return array */ public function query_amazon_v5( $parameters, $lasso_settings = false ) { try { if ( ! $lasso_settings ) { $lasso_settings = Setting::get_settings(); } $this->amazon_access_key_id = $lasso_settings['amazon_access_key_id'] ?? ''; $this->amazon_secret_key = $lasso_settings['amazon_secret_key'] ?? ''; $this->amazon_tracking_id = $lasso_settings['amazon_tracking_id'] ?? ''; $result = $this->aws_signed_request_v5( $parameters, $this->amazon_access_key_id, $this->amazon_secret_key, $this->amazon_tracking_id ); if ( isset( $result->Errors ) ) { // phpcs:ignore $error = $result->Errors[0]; // phpcs:ignore } return $result; } catch ( \Exception $e ) { return array(); } } /** * Get Amazon product by product id * * @param string $product_id Amazon product id. * * @return object */ public function get_product_by_id_v5( $product_id ) { $parameters = array( 'Operation' => 'GetItems', 'ItemIds' => array( $product_id ), 'Resources' => array( 'Images.Primary.Small', 'Images.Primary.Large', 'ItemInfo.Title', 'ItemInfo.ContentRating', 'ItemInfo.Features', 'ItemInfo.ProductInfo', 'ItemInfo.TechnicalInfo', 'Offers.Listings.Price', 'Offers.Listings.SavingBasis', 'Offers.Summaries.OfferCount', 'Offers.Listings.DeliveryInfo.IsPrimeEligible', ), ); $json_response = $this->query_amazon_v5( $parameters ); return $json_response; } /** * Get product from Amazon by product name * * @param string $keyword Keyword. * @param string $product_type Product type. * * @return object */ public function get_product_by_keyword_v5( $keyword, $product_type ) { $parameters = array( 'Operation' => 'SearchItems', 'Keywords' => $keyword, 'SearchIndex' => $product_type, 'Resources' => array( 'Images.Primary.Small', 'Images.Primary.Large', 'ItemInfo.Title', 'ItemInfo.ContentRating', 'ItemInfo.Features', 'ItemInfo.ProductInfo', 'ItemInfo.TechnicalInfo', 'Offers.Listings.Price', 'Offers.Listings.SavingBasis', 'Offers.Summaries.OfferCount', 'Offers.Listings.DeliveryInfo.IsPrimeEligible', ), ); $json_response = $this->query_amazon_v5( $parameters ); return $json_response; } /** * Sign request v5 * * @param array $params Amazon params. * @param string $amazon_access_key_id Amazon access key. * @param string $amazon_secret_key Amazon secret key. * @param string $amazon_tracking_id Amazon tracking id. * * @return object|bool */ private function aws_signed_request_v5( $params, $amazon_access_key_id, $amazon_secret_key, $amazon_tracking_id ) { // phpcs:ignore // $amazon_domain = 'www.amazon.com'; // $pa_endpoint = 'webservices.amazon.com'; $country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' ); $countries = self::get_amazon_api_countries(); $amazon_domain = $countries[ $country ]['amazon_domain']; $pa_endpoint = $countries[ $country ]['pa_endpoint']; $amazon_region = $countries[ $country ]['region']; $params['Marketplace'] = $amazon_domain; $params['PartnerType'] = 'Associates'; $params['PartnerTag'] = $amazon_tracking_id; $post_fields = wp_json_encode( $params ); $aws_v5 = new AwsV5( $amazon_access_key_id, $amazon_secret_key ); $aws_v5->setHost( $pa_endpoint ); $aws_v5->setRegionName( $amazon_region ); $aws_v5->setPayload( $post_fields ); $aws_v5->addHeader( 'x-amz-target', 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.' . $params['Operation'] ); $headers = $aws_v5->getHeaders( true ); $url = "https://$pa_endpoint/paapi5/searchitems"; // @codingStandardsIgnoreStart $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields ); curl_setopt( $ch, CURLOPT_POST, 1 ); curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); $result = curl_exec( $ch ); if ( curl_errno( $ch ) ) { // phpcs:ignore // $error = curl_error( $ch ); return false; } curl_close( $ch ); // @codingStandardsIgnoreEnd return json_decode( $result ); } /** * Get Amazon product id by url * * @param string $url Amazon link. * @return string|bool */ public static function get_product_id_by_url( $url ) { $url = Helper::add_https( $url ); if ( ! self::is_amazon_url( $url ) || strpos( $url, '.' ) === false ) { return false; } $parse_url = wp_parse_url( $url ); $amazon_domain = trim( $parse_url['host'] ?? '', 'www.' ); $amazon_domain_regex = str_replace( '.', '\.', str_replace( 'www.', '', $amazon_domain ) ); $reg = '#(?:https?://(?:www\.){0,1}' . $amazon_domain_regex . '(?:/.*){0,1}(?:/dp/|/gp/product/|/ASIN/|/gp/video/detail/))([a-zA-Z0-9]*)(.*?)(?:/.*|$)#'; $matches = array(); preg_match( $reg, $url, $matches ); return isset( $matches[1] ) && ! empty( $matches[1] ) ? $matches[1] : false; } /** * Keep URL arguments for Amazon URL * * @param string $product_url Amazon product url. * * @return array $results */ private static function keep_args( $product_url ) { // ? amazon link but it is not product url if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortened_url( $product_url ) ) { return array(); } $results = array(); $url_params = array( 'maas', 'ref_', 's', 'aa_campaignid', 'aa_creativeid', 'aa_adgroupid', 'campaignId', 'linkCode', 'linkId', ); foreach ( $url_params as $param_name ) { $param_value = Helper::get_argument_from_url( $product_url, $param_name ); if ( $param_value ) { $results[] = $param_name . '=' . $param_value; } } return $results; } /** * Get amazon tracking id by url * This function will be deprecated in the future. Please use Helper::get_argument_from_url() instead * * @param string $link Amazon link. * @return string */ public static function get_amazon_tracking_id_by_url( $link ) { $search = '/([?|&|&|\/]{1})tag=([a-zA-Z0-9\-\_]*)/i'; $matches = array(); preg_match( $search, $link, $matches ); return $matches[2] ?? ''; } /** * Clean Amazon URL that contains maas parameter * * @param string $product_url Amazon product URL. * @return string */ public static function clean_maas_url( $product_url ) { return $product_url; } /** * Make product url to shorten url if this setting is enabled * * @param string $product_url Amazon product url. * @param bool $monetize Monetize link or not. Default to true. * @param bool $check_lasso_post Check Lasso post or ignore. Default to true. * @param string $custom_tracking_id Allow to use custom tracking id. */ public static function get_amazon_product_url( $product_url, $monetize = true, $check_lasso_post = true, $custom_tracking_id = '' ) { // ? amazon link but it is not product url if ( ! self::is_amazon_url( $product_url ) || self::is_amazon_shortened_url( $product_url ) ) { return $product_url; } // If MAAS param exists and the original tag is exactly "maas", leave the URL untouched $__maas_arg = Helper::get_argument_from_url( $product_url, 'maas' ); $__orig_tag = Helper::get_argument_from_url( $product_url, 'tag' ); if ( $__maas_arg && strtolower( $__orig_tag ) === 'maas' ) { return $product_url; } $product_id = self::get_product_id_by_url( $product_url ); // ? remove all url queries, just keep needed args $url_without_params = explode( '?', $product_url )[0]; if ( $product_id && ! $monetize ) { $keep_args = self::keep_args( $product_url ); $args = Helper::build_url_parameter_string( $keep_args ); $product_url = $args ? $url_without_params . '?' . $args : $url_without_params; $product_url = self::clean_maas_url( $product_url ); return $product_url; } $lasso_settings = Setting::get_settings(); $amazon_tracking_id = trim( $lasso_settings['amazon_tracking_id'] ?? '' ); $amazon_add_tracking_id_to_attribution = $lasso_settings['amazon_add_tracking_id_to_attribution'] ?? true; $lasso_db = new Lasso_DB(); $amz_cache_key = self::OBJECT_KEY . '_' . self::FUNCTION_NAME_GET_LASSO_ID_BY_PRODUCT_ID_AND_TYPE . '_' . $product_id . '_' . self::PRODUCT_TYPE; $lasso_id = Cache_Per_Process::get_instance()->get_cache( $amz_cache_key, null ); if ( null === $lasso_id ) { $lasso_id = $lasso_db->get_lasso_id_by_product_id_and_type( $product_id, self::PRODUCT_TYPE ); Cache_Per_Process::get_instance()->set_cache( $amz_cache_key, $lasso_id ); } if ( $check_lasso_post && ! $lasso_id ) { $product_url = self::clean_maas_url( $product_url ); return $product_url; } $tag = Helper::get_argument_from_url( $product_url, 'tag' ); $tag = $custom_tracking_id ? $custom_tracking_id : $tag; $tag = $tag ? $tag : ''; $tag = ! empty( $tag ) ? $tag : $amazon_tracking_id; // ? Return the remove all url queries for product url if ( $product_id ) { $tag_args = $tag ? 'tag=' . $tag : ''; $keep_args = self::keep_args( $product_url ); $maas = Helper::get_argument_from_url( $product_url, 'maas' ); $add_tag_to_url = ( $maas && $amazon_add_tracking_id_to_attribution ) || ! $maas; if ( $add_tag_to_url ) { array_unshift( $keep_args, $tag_args ); } $args = Helper::build_url_parameter_string( $keep_args ); $product_url = $args ? $url_without_params . '?' . $args : $url_without_params; } else { $product_url = str_replace( '&', '&', $product_url ); $parse = wp_parse_url( $product_url ); parse_str( $parse['query'] ?? '', $query ); // ? set tag id (tracking id) at the end of the url if ( $tag ) { $query['tag'] = $tag; } elseif ( ! empty( $amazon_tracking_id ) ) { $query['tag'] = $amazon_tracking_id; } if ( ! $monetize ) { unset( $query['tag'] ); } $parse['query'] = Helper::get_query_from_array( $query ); $product_url = Helper::get_url_from_parse( $parse ); $product_url = trim( $product_url ); $product_url = trim( $product_url, '?' ); } $product_url = self::clean_maas_url( $product_url ); return $product_url; } /** * Get Amazon product info from url accept url or post_id * * @param string|int $url_or_post_id URL or post id. */ public function get_amazon_product( $url_or_post_id ) { if ( is_numeric( $url_or_post_id ) ) { // ? get amazon product using post_id $post_id = $url_or_post_id; $amazon_id = Affiliate_Link::get_amazon_id( $post_id ); $product = $this->get_amazon_product_from_db( $amazon_id ); return $product; } else { $url = $url_or_post_id; } // ? get amazon prodcut using url if ( empty( $url ) ) { return ''; } $url = trim( $url, '/' ); $product_id = self::get_product_id_by_url( $url ); $product = $this->fetch_product_info( $product_id, true ); // ? Let's save all Amazon details as well $amazon_product = ''; if ( 'success' === $product['status'] ) { $product = $product['product']; $shorten_url = self::get_amazon_product_url( $product['url'] ); $amazon_product = array( 'id' => $product['product_id'], 'name' => $product['title'], 'price' => $product['price'], 'url' => $shorten_url, 'image' => $product['image'], 'description' => '', ); } return $amazon_product; } /** * Check whether a URL is amazon search page * * @param string $url URL. * * @return bool|string */ public static function is_amazon_search_page( $url ) { $amazon_id = self::get_product_id_by_url( $url ); $parse = wp_parse_url( $url ); $path = $parse['path'] ?? ''; $path = rtrim( $path, '/' ); $keywords = Helper::get_argument_from_url( $url, 'keywords' ); $field_keywords = Helper::get_argument_from_url( $url, 'field-keywords' ); $k = Helper::get_argument_from_url( $url, 'k' ); $k = $k ? $k : $field_keywords; $k = $k ? $k : $keywords; if ( ! $amazon_id && ( '/s' === substr( $path, -2 ) || strpos( $path, '/s/' ) !== false ) && $k ) { return $k; } return false; } /** * Check whether a URL is amazon search page * * @param string $url URL. * * @return bool|string */ public static function get_search_page_title( $url ) { $new_title = 'Amazon'; $k = self::is_amazon_search_page( $url ); if ( $k ) { $base_domain = Helper::get_base_domain( $url ); $title_prefix = ucfirst( $base_domain ); $new_title = $title_prefix . ' : ' . $k; } return $new_title; } /** * Validate Amazon tracking id * * @param string $tracking_id Amazon tracking id. * @return boolean */ public static function validate_tracking_id( $tracking_id ) { return (bool) preg_match( '/' . self::TRACKING_ID_REGEX . '/i', $tracking_id ); } /** * Get Amazon link by product id * * @param string $product_id Amazon product id. * @param string $amz_link Amazon link. Default to empty. */ public function get_amazon_link_by_product_id( $product_id, $amz_link = '' ) { if ( ! $product_id ) { return $amz_link; } if ( '' !== $amz_link ) { $parse = wp_parse_url( $amz_link ); $host = $parse['host'] ?? ''; if ( '' !== $host ) { return 'https://' . $host . '/dp/' . $product_id; } } $country = Setting::get_setting( 'amazon_default_tracking_country', 'usa' ); $countries = self::get_amazon_api_countries(); $amazon_domain = $countries[ $country ]['amazon_domain']; return 'https://' . $amazon_domain . '/dp/' . $product_id; } /** * Check whether a URL is Amazon redirect page * * @param string $url Amazon URL. */ public static function is_amazon_redirect_page( $url ) { if ( ! self::is_amazon_url( $url ) || strpos( $url, '/gp/slredirect/' ) === false ) { return false; } return true; } /** * Get redirect url * * @param string $url Amazon URL. */ public static function get_redirect_url( $url ) { if ( self::is_amazon_redirect_page( $url ) ) { $url_param = Helper::get_argument_from_url( $url, 'url' ); $amazon_domain = Helper::get_base_domain( $url ); $amazon_domain = Helper::add_https( $amazon_domain ); $new_url = $amazon_domain . $url_param; $product_id = self::get_product_id_by_url( $new_url ); if ( $product_id ) { $url = $new_url; } } return $url; } /** * Format price * Ex: convert 19.89USD to $19.89 * * @param string $price Price. * @param string $currency_iso Currency ISO. * @return string */ public static function format_price( $price, $currency_iso = null ) { $currency_iso = $currency_iso ? $currency_iso : self::get_currency_iso_from_price_text( $price ); if ( $price && $currency_iso ) { return self::build_price_with_currency_iso( $price, $currency_iso ); } return $price; } /** * Get Currency ISO from price text * * @param string $price Price text. * @return mixed|string */ public static function get_currency_iso_from_price_text( $price ) { $result = ''; foreach ( self::CURRENCY_ISO as $currency_iso ) { if ( strpos( $price, $currency_iso ) !== false ) { return $currency_iso; } } return $result; } /** * Build price final format base on the currency ISO * * @param string $price_value Price value. * @param string $currency_iso Currency ISO. * @return string */ public static function build_price_with_currency_iso( $price_value, $currency_iso ) { $currency_symbol = Helper::get_currency_symbol_from_iso_code( $currency_iso ); $price_without_iso = str_replace( $currency_iso, '', $price_value ); $price_value = Helper::get_price_value_from_price_text( $price_without_iso, $currency_symbol ); $currency_position = preg_match( '/[€]|R\$|TL|kr|zł/', $currency_symbol ) ? 'end' : 'begin'; $price_format = preg_match( '/[€]|R\$|TL|kr|zł/', $currency_symbol ) ? number_format( $price_value, 2, ',', '.' ) : number_format( $price_value, 2, '.', ',' ); return 'begin' === $currency_position ? $currency_symbol . $price_format : $price_format . $currency_symbol; } /** * Get the variation item in stock * * @param string $product_id Product id. * @param string $product_url Product url. * @param int $variation_page Variation page. * @return array */ public function get_product_variation( $product_id, $product_url, $variation_page = 1 ) { $result = $this->get_product_variations_by_id_v5( $product_id, $variation_page ); $items = $result->VariationsResult->Items ?? array(); // phpcs:ignore if ( ! empty( $items ) ) { $items = $result->VariationsResult->Items; // phpcs:ignore $product_variations = array(); // ? Get product variation list foreach ( $items as $item ) { $product_variations[] = $this->extract_search_result_v5( $item, true, $product_id, $product_url ); } // ? Sort price from lowest to highest usort( $product_variations, function( $a, $b ) { return strcmp( $a['amount'], $b['amount'] ); } ); // ? Get the in-stock product foreach ( $product_variations as $product_variation ) { if ( $product_variation['quantity'] && $product_variation['price'] ) { return $product_variation; } } // ? If all variation products in this page unavailable, we request to the next page $page_count = $result->VariationsResult->VariationSummary->PageCount ?? 1; // phpcs:ignore if ( $variation_page < self::VARIATION_PAGE_LIMIT && $variation_page < $page_count ) { sleep( 1 ); // ? Delay for a while before call the next request return $this->get_product_variation( $product_id, $product_url, $variation_page + 1 ); } } return array(); } /** * Get Amazon product variations by product id * * @param string $product_id Amazon product id. * @param int $variation_page Variation page. * * @return object */ public function get_product_variations_by_id_v5( $product_id, $variation_page = 1 ) { $parameters = array( 'Operation' => 'GetVariations', 'ASIN' => $product_id, 'Condition' => 'New', 'VariationPage' => $variation_page, 'Resources' => array( 'Images.Primary.Small', 'Images.Primary.Large', 'ItemInfo.Title', 'ItemInfo.ContentRating', 'ItemInfo.Features', 'ItemInfo.ProductInfo', 'ItemInfo.TechnicalInfo', 'Offers.Listings.Price', 'Offers.Listings.SavingBasis', 'Offers.Summaries.OfferCount', 'Offers.Listings.DeliveryInfo.IsPrimeEligible', ), ); $json_response = $this->query_amazon_v5( $parameters ); return $json_response; } /** * Build discount pricing html. * * @param string $latest_price Latest price. * @param mixed $basis_price Basis price value. * @param string $currency Currency ISO. */ public static function build_discount_pricing_html( $latest_price, $basis_price, $currency ) { $result = ''; try { $latest_price_value = Helper::get_price_value_from_price_text( $latest_price ); $basis_price_value = Helper::get_price_value_from_price_text( $basis_price ); if ( $basis_price_value && ( round( $latest_price_value, 2 ) < round( $basis_price_value, 2 ) ) ) { $currency_symbol = Helper::get_currency_symbol_from_iso_code( $currency ); $currency_position = preg_match( '/[€]|R\$|TL|kr|zł/', $latest_price ) ? 'end' : 'begin'; $basis_price_format = preg_match( '/[€]|R\$|TL|kr|zł/', $latest_price ) ? number_format( $basis_price_value, 2, ',', '.' ) : number_format( $basis_price_value, 2, '.', ',' ); $format_price = 'begin' === $currency_position ? $currency_symbol . $basis_price_format : $basis_price_format . ' ' . $currency_symbol; $result = "$format_price"; } } catch ( \Exception $e ) { $result = ''; } return $result; } /** * Check amazon setting is configured or not * * @return boolean */ public static function is_amazon_setting_configured() { $lasso_settings = Setting::get_settings(); $is_amazon_configured = $lasso_settings['amazon_access_key_id'] && $lasso_settings['amazon_secret_key'] && $lasso_settings['amazon_tracking_id']; return $is_amazon_configured || self::is_amazon_creators_verified( $lasso_settings ); } /** * Whether Amazon Creators API credentials are complete in settings. * * @param array|false $lasso_settings Settings array. Default false loads current settings. * @return bool */ public static function is_amazon_creators_configured( $lasso_settings = false ) { if ( ! is_array( $lasso_settings ) ) { $lasso_settings = Setting::get_settings(); } $fields = array( 'amazon_creators_credential_id', 'amazon_creators_secret', 'amazon_creators_version', 'amazon_creators_partner_tag', ); foreach ( $fields as $field ) { if ( '' === trim( (string) ( $lasso_settings[ $field ] ?? '' ) ) ) { return false; } } return true; } /** * Whether Creators credentials were verified successfully (signature matches stored settings). * * @param array|false $lasso_settings Settings array. Default false loads current settings. * @return bool */ public static function is_amazon_creators_verified( $lasso_settings = false ) { if ( ! self::is_amazon_creators_configured( $lasso_settings ) ) { return false; } if ( ! is_array( $lasso_settings ) ) { $lasso_settings = Setting::get_settings(); } $current_signature = self::get_amazon_creators_signature( $lasso_settings ); $saved_signature = (string) Helper::get_option( Constant::LASSO_OPTION_AMAZON_CREATORS_VERIFIED_SIGNATURE, '' ); return '' !== $current_signature && '' !== $saved_signature && hash_equals( $saved_signature, $current_signature ); } /** * Build a stable signature for the Creators payload. * * @param array $settings Settings values. * @return string Empty when Creators credentials are incomplete. */ public static function get_amazon_creators_signature( $settings ) { if ( ! self::is_amazon_creators_configured( $settings ) ) { return ''; } return hash( 'sha256', wp_json_encode( array( 'credential_id' => (string) ( $settings['amazon_creators_credential_id'] ?? '' ), 'secret' => (string) ( $settings['amazon_creators_secret'] ?? '' ), 'credential_version' => (string) ( $settings['amazon_creators_version'] ?? '' ), 'partner_tag' => (string) ( $settings['amazon_creators_partner_tag'] ?? '' ), 'country' => (string) ( $settings['amazon_default_tracking_country'] ?? '' ), ) ) ); } /** * Resolve Creators API country from an Amazon URL or default tracking country. * * @param string $amazon_url Amazon product URL (optional). * @param array|false $lasso_settings Settings array. Default false loads current settings. * @return string */ public static function get_country_for_creators_api( $amazon_url, $lasso_settings = false ) { if ( ! is_array( $lasso_settings ) ) { $lasso_settings = Setting::get_settings(); } if ( $amazon_url && self::is_amazon_url( $amazon_url ) ) { $base_domain = strtolower( Helper::get_base_domain( $amazon_url ) ); if ( '' !== $base_domain ) { foreach ( self::get_amazon_api_countries() as $country_code => $country_data ) { $amazon_domain = strtolower( Helper::get_base_domain( (string) ( $country_data['amazon_domain'] ?? '' ) ) ); if ( $base_domain === $amazon_domain ) { return (string) $country_code; } } } } return (string) ( $lasso_settings['amazon_default_tracking_country'] ?? '' ); } /** * Whether the Lite install is linked to a Lasso account email. * * @return bool */ public static function is_lite_account_connected() { $email = Helper::get_option( Constant::LASSO_ACCOUNT_EMAIL, '' ); return is_string( $email ) && '' !== trim( $email ); } /** * Fetch product data via direct Amazon Creators API (replaces deprecated PA-API GetItems). * * @param string $product_id Amazon ASIN. * @param bool $store_product Store in local DB. * @param bool|string $updated_at Optional updated timestamp. * @param string $amz_link Amazon URL. * @param array $lasso_settings Plugin settings. * @return array|null Same shape as fetch_product_info success/failure, or null to fall back. */ private function fetch_product_info_from_creators_api( $product_id, $store_product, $updated_at, $amz_link, $lasso_settings ) { if ( ! self::is_amazon_creators_configured( $lasso_settings ) ) { return null; } $api_result = Amazon_Creators_Api::fetch_product( $product_id, self::get_country_for_creators_api( $amz_link, $lasso_settings ), array( 'product_only' => false, ) ); if ( 'fail' === ( $api_result['status'] ?? '' ) ) { $error_code = (string) ( $api_result['error_code'] ?? '' ); if ( 'NotFound' === $error_code ) { return $this->build_fetch_product_info_response( array(), 'yes', array(), 'failed', 'NotFound' ); } return null; } $product_data = $api_result['product'] ?? array(); if ( ! is_array( $product_data ) || empty( $product_data ) ) { return null; } $full_item = $api_result['item'] ?? array(); $product = $this->finalize_fetch_product_info_product( $product_data, $product_id, $store_product, $updated_at, $amz_link ); return $this->build_fetch_product_info_response( $product, 'yes', $full_item, 'success', '' ); } /** * Build fetch_product_info() return array (same contract as get_product_by_id_v5 path). * * @param array $product Product fields. * @param string $api API source flag. * @param array|object $full_item Raw item payload. * @param string $status success|failed. * @param string $error_code Error code. * @return array */ private function build_fetch_product_info_response( $product, $api, $full_item, $status, $error_code ) { return array( 'product' => $product, 'api' => $api, 'full_item' => $full_item, 'status' => $status, 'error_code' => $error_code, ); } /** * Apply the same post-processing as the PA-API branch in fetch_product_info(). * * @param array $product Product fields from API. * @param string $product_id Amazon ASIN. * @param bool $store_product Store in local DB. * @param bool|string $updated_at Optional updated timestamp. * @param string $amz_link Amazon URL. * @return array */ private function finalize_fetch_product_info_product( $product, $product_id, $store_product, $updated_at, $amz_link ) { if ( empty( $product['product_id'] ) ) { $product['product_id'] = $product_id; } return $this->enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link ); } /** * Shared post-fetch enrichment for PA-API and Creators product payloads. * * @param array $product Product fields. * @param bool $store_product Store in local DB. * @param bool|string $updated_at Optional updated timestamp. * @param string $amz_link Amazon URL. * @return array */ private function enrich_fetched_amazon_product( $product, $store_product, $updated_at, $amz_link ) { global $wpdb; $lasso_db = new Lasso_DB(); $product_url = $amz_link ? $amz_link : ( $product['url'] ?? '' ); $product['status_code'] = 200; $product['url'] = self::get_amazon_product_url( $product_url ); $amazon_product_id = (string) ( $product['product_id'] ?? '' ); $query = $wpdb->prepare( 'SELECT post_id FROM ' . $lasso_db->postmeta . " WHERE meta_key = 'amazon_product_id' AND meta_value = %s", $amazon_product_id ); $lasso_id = $lasso_db->get_var( $query ); $product['lasso_id'] = ( isset( $lasso_id ) ) ? $lasso_id : 0; if ( $store_product ) { $amazon_tracking_id = Setting::get_setting( 'amazon_tracking_id', '' ); $product['default_url'] = '' === $amazon_tracking_id ? $amz_link : ( $product['default_url'] ?? '' ); $this->update_amazon_product_in_db( $product, $updated_at ); } return $product; } /** * Get final url of the amazon shortlink from cache * * @param string $shortlink Amazon shortlink. * @return string|null */ public static function get_shortlink_final_url_cached( $shortlink ) { if ( ! self::is_amazon_shortened_url( $shortlink ) ) { return null; } $final_url_cache = get_option( self::build_shortlink_cache_key( $shortlink ) ); if ( $final_url_cache === $shortlink ) { return null; } return $final_url_cache ? $final_url_cache : null; } /** * Build amazon shortlink cache key * * @param string $shortlink Amazon shortlink. * @return string|null */ public static function build_shortlink_cache_key( $shortlink ) { if ( ! self::is_amazon_shortened_url( $shortlink ) ) { return null; } $parse = wp_parse_url( $shortlink ); $host = str_replace( '.', '_', $parse['host'] ); $id = trim( $parse['path'] ?? '', '/' ); return $host . '_' . $id; } /** * Format Amazon URLs * * @param string $url Amazon product url. * @return string URL. */ public static function format_amazon_url( $url ) { $is_amazon_link = self::is_amazon_url( $url ); $product_id = self::get_product_id_by_url( $url ); if ( $is_amazon_link && $product_id && strpos( $url, 'smile.amazon.' ) !== false ) { $url = str_replace( 'smile.amazon.', 'amazon.', $url ); } return $url; } }