PluginProbe
Datafeedr API / 1.4.2
Datafeedr API v1.4.2
1.4.2 1.0.125 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 1.0.30 1.0.31 1.0.32 1.0.33 All 181 releases
datafeedr-api / libraries / datafeedr.php

datafeedr.php in Datafeedr API 1.4.2, at libraries/datafeedr.php

2,109 lines 51.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Datafeedr API PHP Library
5 *
6 * @version 3.0.0
7 *
8 * Copyright (c) 2007 ~ 2017, Datafeedr - All Rights Reserved
9 *
10 * Permission to use, copy, modify, and/or distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 */
22
23 /**
24 * Class DatafeedrApi
25 *
26 * This is the core Datafeedr API class.
27 */
28 class DatafeedrApi {
29
30 protected $_accessId;
31 protected $_secretKey;
32
33 protected $_hasZlib;
34 protected $_host;
35 protected $_retry;
36 protected $_retryTimeout;
37 protected $_returnObjects;
38 protected $_timeout;
39 protected $_transport;
40 protected $_https;
41 protected $_url;
42 protected $_userAgent;
43 protected $_domain;
44
45 protected $_status;
46 protected $_errors;
47
48 const SORT_DESCENDING = - 1;
49 const SORT_ASCENDING = + 1;
50
51 const DEFAULT_HOST = 'api.datafeedr.com';
52
53 const REQUEST_COMPRESSION_THRESHOLD = 1024;
54
55 const VERSION = '3.0.0';
56
57 /**
58 * DatafeedrApi constructor.
59 *
60 * @since 1.0.0
61 *
62 * @param string $secretKey Datafeedr API Secret Key.
63 * @param array $options Options.
64 *
65 * Possble options:
66 *
67 * - host: API host name. Default: 'api.datafeedr.com'
68 * - https: TRUE if using https. Default: FALSE.
69 * - transport: HTTP transport name or function. Default: 'wordpress'
70 * - timeout: HTTP connection timeout, in seconds. Default: 0
71 * - returnObjects: True to return Objects. False to return associative arrays Default: false
72 * - retry: How many times to repeat a request on a temporary failure. Default: 0 (do not repeat)
73 * - retryTimeout: Timeout between retry requests, in seconds. Default: 5
74 *
75 * The `transport` option tells how HTTP requests should be made.
76 * It can be either a string that describes one of built-in transports ("curl", "file" or "wordpress"),
77 * or a callable object that should accept a URL, an array of headers and a string of post data and
78 * should return an array [int http response status, string response body].
79 *
80 *
81 * @param string $accessId Datafeedr API Access ID.
82 *
83 * @throws DatafeedrError Throws error if any option is invalid.
84 */
85 public function __construct( $accessId, $secretKey, $options = null ) {
86
87 $this->_accessId = $accessId;
88 $this->_secretKey = $secretKey;
89
90 $this->_errors = array(
91 1 => 'DatafeedrBadRequestError',
92 2 => 'DatafeedrAuthenticationError',
93 3 => 'DatafeedrLimitExceededError',
94 4 => 'DatafeedrQueryError',
95 7 => 'DatafeedrExternalError',
96 9 => 'DatafeedrServerError',
97 );
98
99 $this->_parseOptions( $options );
100 }
101
102 /**
103 * Returns API status information.
104 *
105 * @since 1.0.0
106 *
107 * @return array An array of API Status information.
108 */
109 public function getStatus() {
110 $this->apiCall( 'status' );
111
112 return $this->_status;
113 }
114
115 /**
116 * Return status information from the last request.
117 *
118 * If no API request has been made, return NULL
119 *
120 * @since 1.0.0
121 *
122 * @return array|null Status information or NULL.
123 */
124 public function lastStatus() {
125 return $this->_status;
126 }
127
128 /**
129 * Return the list of networks.
130 *
131 * @since 1.0.0
132 *
133 * @param boolean $includeEmpty Optional. If FALSE, omit networks with 0 products.
134 * @param array $fields Optional. An array of fields to retrieve.
135 *
136 * @param integer|array $networkId Optional. Network ID or an array of network IDs.
137 *
138 * @return array An array of Networks.
139 */
140 public function getNetworks( $networkId = null, $includeEmpty = false, $fields = null ) {
141
142 $request = array();
143
144 if ( $networkId ) {
145 $request['_ids'] = $this->_intarray( $networkId );
146 }
147
148 $request['skip_empty'] = intval( ! $includeEmpty );
149
150 if ( $fields ) {
151 $request['fields'] = $fields;
152 }
153
154 $response = $this->apiCall( 'networks', $request );
155
156 return $this->_get( $response, 'networks' );
157 }
158
159 /**
160 * Return the list of merchants.
161 *
162 * @since 1.0.0
163 *
164 * @param bool $includeEmpty Optional. If FALSE, omit merchants with 0 products.
165 * @param array $fields Optional. An array of fields to retrieve.
166 *
167 * @param integer|array $networkId Optional. Network ID or array of Network IDs.
168 *
169 * @return array An array of merchants.
170 */
171 public function getMerchants( $networkId = null, $includeEmpty = false, $fields = null ) {
172
173 $request = array();
174
175 if ( $networkId ) {
176 $request['source_ids'] = $this->_intarray( $networkId );
177 }
178
179 $request['skip_empty'] = intval( ! $includeEmpty );
180
181 if ( $fields ) {
182 $request['fields'] = $fields;
183 }
184
185 $response = $this->apiCall( 'merchants', $request );
186
187 return $this->_get( $response, 'merchants' );
188 }
189
190 /**
191 * Return a list of merchants by their IDs.
192 *
193 * @since 1.0.0
194 *
195 * @param boolean $includeEmpty Optional. If FALSE, omit merchants with 0 products.
196 * @param array $fields Optional. An array of fields to retrieve.
197 *
198 * @param integer|array $merchantId Merchant ID or array of Merchant IDs.
199 *
200 * @return array An array of merchants.
201 */
202 public function getMerchantsById( $merchantId, $includeEmpty = false, $fields = null ) {
203
204 $request = array();
205 $request['_ids'] = $this->_intarray( $merchantId );
206 $request['skip_empty'] = intval( ! $includeEmpty );
207
208 if ( $fields ) {
209 $request['fields'] = $fields;
210 }
211
212 $response = $this->apiCall( 'merchants', $request );
213
214 return $this->_get( $response, 'merchants' );
215 }
216
217 /**
218 * Return a list of searchable fields.
219 *
220 * @since 1.0.0
221 *
222 * @param integer|array $networkId Optional. Network ID or array of network IDs.
223 *
224 * @return array An array of searchable fields.
225 * @todo - Does this return a list of all fields for a specific network or only indexed/searchable fields? Update docs accordingly.
226 *
227 */
228 public function getFields( $networkId = null ) {
229
230 $request = array();
231
232 if ( $networkId ) {
233 $request['source_ids'] = $this->_intarray( $networkId );
234 }
235
236 $response = $this->apiCall( 'fields', $request );
237
238 return $this->_get( $response, 'fields' );
239 }
240
241 /**
242 * Return a list of products by their IDs.
243 *
244 * @since 1.0.0
245 *
246 * @param array $fields Optional. An array of fields to retrieve.
247 *
248 * @param integer|array $productId Product ID or an array of products IDs.
249 *
250 * @return array An array of Products.
251 */
252 public function getProducts( $productId, $fields = null ) {
253
254 $request = array();
255 $request['_ids'] = $this->_intarray( $productId );
256 $request['string_ids'] = 1;
257
258 if ( $fields ) {
259 $request['fields'] = $fields;
260 }
261
262 $response = $this->apiCall( 'get', $request );
263
264 return $this->_get( $response, 'products' );
265 }
266
267 /**
268 * Return a list of Zanox merchant IDs ("zmids").
269 *
270 * @since 1.0.0
271 *
272 * @param integer $adspaceId Zanox Adspace ID.
273 * @param string $connectId Zanox Connection ID.
274 *
275 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
276 *
277 * @return array An array of arrays (adspace_id, merchant_id, program_id, zmid).
278 */
279 public function getZanoxMerchantIds( $merchantId, $adspaceId, $connectId ) {
280
281 $request = array();
282
283 $request['merchant_ids'] = $this->_intarray( $merchantId );
284 $request['adspace_id'] = $adspaceId;
285 $request['connect_id'] = $connectId;
286
287 $response = $this->apiCall( 'zanox_merchant_ids', $request );
288
289 return $this->_get( $response, 'zanox_merchant_ids' );
290 }
291
292 /**
293 * Return a list of PerformanceHorizon campaign references ("camrefs").
294 *
295 * @since 2.0.0
296 *
297 * @param string $applicationKey PerformanceHorizon application_key.
298 * @param string $userApiKey PerformanceHorizon user_api_key.
299 * @param string $publisherId PerformanceHorizon publisher_id.
300 *
301 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
302 *
303 * @return array An array of arrays (campaign_id, camref, merchant_id).
304 */
305
306 public function getPerformanceHorizonCamrefs( $merchantId, $applicationKey, $userApiKey, $publisherId ) {
307
308 $request = array();
309
310 $request['merchant_ids'] = $this->_intarray( $merchantId );
311 $request['application_key'] = $applicationKey;
312 $request['user_api_key'] = $userApiKey;
313 $request['publisher_id'] = $publisherId;
314
315 $response = $this->apiCall( 'performancehorizon_camrefs', $request );
316
317 return $this->_get( $response, 'performancehorizon_camrefs' );
318 }
319
320 /**
321 * Return a list of Effiliation affiliate ids.
322 *
323 * @since 2.0.2
324 *
325 * @param string $apiKey Effiliation api_key.
326 *
327 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
328 *
329 * @return array An array of arrays (affiliate_id, merchant_id).
330 */
331
332 public function getEffiliationAffiliateIds( $merchantId, $apiKey ) {
333
334 $request = array();
335
336 $request['merchant_ids'] = $this->_intarray( $merchantId );
337 $request['api_key'] = $apiKey;
338
339 $response = $this->apiCall( 'effiliation_affiliate_ids', $request );
340
341 return $this->_get( $response, 'effiliation_affiliate_ids' );
342 }
343
344 /**
345 * Create a new DatafeedrSearchRequest object.
346 *
347 * @since 1.0.0
348 *
349 * @return DatafeedrSearchRequest
350 */
351 public function searchRequest() {
352 return new DatafeedrSearchRequest( $this );
353 }
354
355 /**
356 * Create a new DatafeedrMerchantSearchRequest object.
357 *
358 * @since 1.0.0
359 *
360 * @return DatafeedrMerchantSearchRequest
361 */
362 public function merchantSearchRequest() {
363 return new DatafeedrMerchantSearchRequest( $this );
364 }
365
366 /**
367 * Create a new DatafeedrAmazonSearchRequest object.
368 *
369 * @since 1.0.0
370 *
371 * @param string $awsSecretKey Amazon Secret Key.
372 * @param string $awsAssociateTag Amazon Associates tag.
373 * @param string $locale The country locale code.
374 *
375 * @param string $awsAccessKeyId Amazon Access Key.
376 *
377 * @return DatafeedrAmazonSearchRequest
378 */
379 public function amazonSearchRequest( $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
380 return new DatafeedrAmazonSearchRequest( $this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale );
381 }
382
383 /**
384 * Create a new DatafeedrAmazonLookupRequest object.
385 *
386 * @since 1.0.0
387 *
388 * @param string $awsSecretKey Amazon Secret Key.
389 * @param string $awsAssociateTag Amazon Associates tag.
390 * @param string $locale The country locale code.
391 *
392 * @param string $awsAccessKeyId Amazon Access Key.
393 *
394 * @return DatafeedrAmazonLookupRequest
395 */
396 public function amazonLookupRequest( $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
397 return new DatafeedrAmazonLookupRequest( $this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale );
398 }
399
400 /** CreatorAPI */
401 public function amazonCreatorApiSearchRequest( $capiMarketplace = 'US' ) {
402 return new DatafeedrAmazonCreatorApiSearchRequest( $this, $capiMarketplace = 'US' );
403 }
404
405 /** CreatorAPI */
406 // public function amazonCreatorApiLookupRequest( $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
407 // return new DatafeedrAmazonCreatorApiLookupRequest( $this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale );
408 // }
409
410 /**
411 * Perform the raw API call.
412 *
413 * @since 1.0.0
414 *
415 * @param array $request Optional. Request data.
416 *
417 * @param string $action API Action. (Examples: status, merchants, networks, search, etc...)
418 *
419 * @return array Returns $response array.
420 * @throws DatafeedrHTTPError Throws error if request status is not 200.
421 *
422 */
423 public function apiCall( $action, $request = null ) {
424
425 if ( ! $request ) {
426 $request = array();
427 }
428
429 $request['aid'] = $this->_accessId;
430 $request['timestamp'] = gmdate( 'Y-m-d H:i:s' );
431 $request['domain'] = $this->_domain;
432
433 if ( $this->_https ) {
434 $request['akey'] = $this->_secretKey;
435 } else {
436 $message = $request['aid'] . $action . $request['timestamp'];
437 $request['signature'] = hash_hmac( 'sha256', $message, $this->_secretKey, false );
438 }
439
440 $postdata = json_encode( $request );
441 $url = $this->_url . '/' . $action;
442 $headers = array(
443 'Host: ' . $this->_host,
444 'Content-Type: application/json',
445 'Accept: application/json',
446 'Connection: close',
447 'User-Agent: ' . $this->_userAgent
448 );
449
450 if ( $this->_hasZlib && strlen( $postdata ) >= self::REQUEST_COMPRESSION_THRESHOLD ) {
451 $postdata = gzcompress( $postdata );
452 $headers [] = 'Content-Encoding: deflate';
453 }
454
455 $headers [] = 'Content-Length: ' . strlen( $postdata );
456
457 list( $status, $response ) = $this->_performRequest( $url, $headers, $postdata );
458
459 if ( strlen( $response ) ) {
460 $response = json_decode( $response, ! $this->_returnObjects );
461 }
462
463 $error = $this->_get( $response, 'error' );
464 if ( $error ) {
465 $type = $this->_get( $response, 'type' );
466 $cls = isset( $this->_errors[ $type ] ) ? $this->_errors[ $type ] : 'DatafeedrError';
467 throw new $cls( $this->_get( $response, 'message' ), $error );
468 }
469
470 if ( 200 != $status ) {
471 throw new DatafeedrHTTPError( "Status $status" );
472 }
473
474 $this->_status = $this->_get( $response, 'status' );
475
476 return $response;
477 }
478
479 /**
480 * Returns the default set of options.
481 *
482 * @since 2.0.0
483 *
484 * @returns array $options.
485 *
486 */
487 protected function _defaultOptions() {
488 return array(
489 'host' => self::DEFAULT_HOST,
490 'https' => false,
491 'transport' => 'wordpress_or_curl',
492 'timeout' => 30,
493 'returnObjects' => false,
494 'retry' => 0,
495 'retryTimeout' => 5,
496 'domain' => '',
497 );
498 }
499
500 /**
501 * Parse constructor options.
502 *
503 * @since 2.0.0
504 *
505 * @param array $options .
506 *
507 * @throws DatafeedrError Throws error if any option is invalid.
508 */
509 protected function _parseOptions( $options ) {
510 $opts = $this->_defaultOptions();
511
512 if ( ! is_null( $options ) ) {
513 if ( ! is_array( $options ) ) {
514 throw new DatafeedrError( "Options must be an array" );
515 }
516
517 foreach ( $options as $key => $value ) {
518 if ( isset( $opts[ $key ] ) ) {
519 $opts[ $key ] = $value;
520 }
521 }
522 }
523
524 $pt = $opts['https'] ? 'https' : 'http';
525 $tr = $opts['transport'];
526
527 $this->_url = $pt . '://' . $opts['host'];
528 $this->_https = $opts['https'];
529 $this->_host = $opts['host'];
530 $this->_timeout = (int) $opts['timeout'];
531 $this->_returnObjects = (int) $opts['returnObjects'];
532 $this->_retry = (int) $opts['retry'];
533 $this->_retryTimeout = (int) $opts['retryTimeout'];
534 $this->_domain = (string) $opts['domain'];
535
536 switch ( $tr ) {
537 case 'curl':
538 $this->_transport = array( $this, '_transportCurl' );
539 break;
540 case 'file':
541 $this->_transport = array( $this, '_transportFile' );
542 break;
543 case 'wordpress':
544 if ( ! function_exists( 'wp_remote_post' ) ) {
545 throw new DatafeedrError( "Wordpress transport requires wp_remote_post" );
546 }
547 $this->_transport = array( $this, '_transportWordpress' );
548 break;
549 case 'wordpress_or_curl':
550 if ( ! function_exists( 'wp_remote_post' ) ) {
551 $this->_transport = array( $this, '_transportCurl' );
552 $tr = 'curl';
553 } else {
554 $this->_transport = array( $this, '_transportWordpress' );
555 $tr = 'wordpress';
556 }
557 break;
558
559 default:
560 if ( ! is_callable( $tr ) ) {
561 throw new DatafeedrError( "Transport must be a function" );
562 }
563 $this->_transport = $tr;
564 $tr = 'custom';
565 }
566
567 $this->_hasZlib = function_exists( 'gzcompress' );
568 $this->_userAgent = sprintf( 'datafeedr.php.%s/%s/zlib=%s', self::VERSION, $tr,
569 $this->_hasZlib ? 'yes' : 'no' );
570 }
571
572 /**
573 * Perform an HTTP request.
574 *
575 * @since 2.0.0
576 *
577 * @param array $headers
578 * @param string $postdata
579 *
580 * @param string $url
581 *
582 * @return array An array of (status, responseBody)
583 * @throws DatafeedrConnectionError
584 *
585 */
586 protected function _performRequest( $url, $headers, $postdata ) {
587 $retry = $this->_retry;
588
589 while ( true ) {
590 try {
591 return call_user_func( $this->_transport, $url, $headers, $postdata );
592 } catch ( DatafeedrConnectionError $err ) {
593 if ( $retry <= 0 ) {
594 throw $err;
595 }
596 sleep( $this->_retryTimeout );
597 $retry --;
598 }
599 }
600
601 return array();
602 }
603
604 /**
605 * Convert an ID or an array of IDs to a simple array of IDs.
606 *
607 * @since 1.0.0
608 *
609 * @param integer|string|array $id_or_ids An ID or an array of IDs.
610 *
611 * @return array An array of IDs.
612 */
613 protected function _intarray( $id_or_ids ) {
614
615 if ( is_numeric( $id_or_ids ) ) {
616 return array( $id_or_ids );
617 }
618
619 if ( is_array( $id_or_ids ) ) {
620 return array_values( $id_or_ids );
621 }
622
623 return array();
624 }
625
626 /**
627 * Returns a specific value from an array or object for a given key or property. If key or
628 * property does not exist, returns $default.
629 *
630 * @since 1.0.0
631 *
632 * @param string $prop The array key or object property to get the value for.
633 * @param null $default Optional. The value to return if $obj or $prop does not exist.
634 *
635 * @param array|object $obj An array or object to extract value from.
636 *
637 * @return mixed|null The returned value.
638 */
639 protected function _get( $obj, $prop, $default = null ) {
640
641 if ( is_array( $obj ) && isset( $obj[ $prop ] ) ) {
642 return $obj[ $prop ];
643 }
644
645 if ( is_object( $obj ) && isset( $obj->$prop ) ) {
646 return $obj->$prop;
647 }
648
649 return $default;
650 }
651
652 /**
653 * Perform an HTTP POST request using the CURL library.
654 *
655 * @since 1.0.0
656 *
657 * @param array $headers Array of headers.
658 * @param string $postdata Post data.
659 *
660 * @param string $url Request url.
661 *
662 * @return array (int http status, string response body)
663 * @throws DatafeedrConnectionError Throws error if curl_errno() returns an error.
664 *
665 */
666 protected function _transportCurl( $url, $headers, $postdata ) {
667
668 $ch = curl_init();
669 curl_setopt( $ch, CURLOPT_URL, $url );
670 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout );
671 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
672 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
673 curl_setopt( $ch, CURLOPT_POST, 1 );
674 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
675 curl_setopt( $ch, CURLOPT_ENCODING, '' );
676 $response = curl_exec( $ch );
677 $status = intval( curl_getinfo( $ch, CURLINFO_HTTP_CODE ) );
678 $errno = curl_errno( $ch );
679 $errmsg = curl_error( $ch );
680
681 curl_close( $ch );
682
683 if ( $errno ) {
684 throw new DatafeedrConnectionError( $errmsg, $errno );
685 }
686
687 return array( $status, $response );
688 }
689
690 /**
691 * Perform a HTTP post request using file functions.
692 *
693 * @since 1.0.0
694 *
695 * @param array $headers Array of headers.
696 * @param string $postdata Post data.
697 *
698 * @param string $url Request url.
699 *
700 * @return array (int http status, string response body)
701 * @throws DatafeedrConnectionError Throws error if $response is false.
702 *
703 */
704 protected function _transportFile( $url, $headers, $postdata ) {
705
706 $options = array(
707 'http' => array(
708 'method' => 'POST',
709 'content' => $postdata,
710 'header' => implode( "\r\n", $headers ),
711 'ignore_errors' => true,
712 'timeout' => $this->_timeout,
713 )
714 );
715 $context = stream_context_create( $options );
716 $response = file_get_contents( $url, false, $context );
717
718 $status = 200;
719 if ( isset( $http_response_header ) && isset( $http_response_header[0] ) ) {
720 if ( preg_match( '/HTTP.+?(\d\d\d)/', $http_response_header[0], $match ) ) {
721 $status = intval( $match[1] );
722 }
723 } else if ( $response === false ) {
724 throw new DatafeedrConnectionError( "Invalid response" );
725 }
726
727 return array( $status, $response );
728 }
729
730 /**
731 * Perform a HTTP post request using Wordpress functions.
732 *
733 * @since 1.0.0
734 *
735 * @param array $headers Array of headers.
736 * @param string $postdata Post data.
737 *
738 * @param string $url Request url.
739 *
740 * @return array (int http status, string response body)
741 * @throws DatafeedrConnectionError Throws error if $response is WP_Error and the error code is 'http_request_failed'.
742 * @throws DatafeedrHTTPError Throws error if $response is WP_Error.
743 *
744 */
745 protected function _transportWordpress( $url, $headers, $postdata ) {
746
747 $ha = array();
748 foreach ( $headers as $h ) {
749 $h = explode( ':', $h, 2 );
750 $ha[ strtolower( $h[0] ) ] = $h[1];
751 }
752
753 $args = array(
754 'method' => 'POST',
755 'headers' => $ha,
756 'body' => $postdata,
757 'httpversion' => '1.1',
758 'timeout' => $this->_timeout,
759 'blocking' => true,
760 'compress' => false,
761 'decompress' => true,
762 'user-agent' => $ha['user-agent']
763 );
764
765 $res = wp_remote_post( $url, $args );
766
767 if ( is_wp_error( $res ) ) {
768 $code = $res->get_error_code();
769 $message = $res->get_error_message();
770
771 if ( $code === 'http_request_failed' ) {
772 throw new DatafeedrConnectionError( $message );
773 } else {
774 throw new DatafeedrHTTPError( $message );
775 }
776 }
777
778 return array( $res['response']['code'], $res['body'] );
779 }
780 }
781
782 /**
783 * Generic Datafeedr API search request.
784 */
785 class DatafeedrSearchRequestBase {
786
787 /**
788 * The DatafeedrAPI object.
789 *
790 * @since 1.0.0
791 * @var DatafeedrAPI $_api
792 */
793 protected $_api;
794
795 /**
796 * An array containing the full response of the last API request.
797 *
798 * @since 1.0.0
799 * @var array $_lastResponse Response array.
800 */
801 protected $_lastResponse;
802
803 /**
804 * DatafeedrSearchRequestBase constructor.
805 *
806 * @since 1.0.0
807 *
808 * @param DatafeedrApi $api
809 *
810 */
811 public function __construct( $api ) {
812 $this->_api = $api;
813 }
814
815 /**
816 * Get the number of found products.
817 *
818 * @since 1.0.0
819 *
820 * @return integer
821 */
822 public function getFoundCount() {
823 return $this->_responseItem( 'found_count', 0 );
824 }
825
826 /**
827 * Get the number of products that can be retrieved from the server.
828 *
829 * @since 1.0.0
830 *
831 * @return integer
832 */
833 public function getResultCount() {
834 return $this->_responseItem( 'result_count', 0 );
835 }
836
837 /**
838 * Get the complexity score of the current query.
839 *
840 * @since 1.2.12
841 * @return int
842 * @throws DatafeedrError
843 */
844 public function getQueryScore() {
845 return abs( (int) $this->_responseItem( 'score', 0 ) );
846 }
847
848 /**
849 * Returns the full response from the last search.
850 *
851 * Possible items in array include the following:
852 *
853 * Array (
854 * 'found_count' => integer,
855 * 'length' => integer,
856 * 'merchants' => array,
857 * 'networks' => array,
858 * 'price_groups' => array,
859 * 'products' => array,
860 * 'result_count' => integer,
861 * 'status' => array,
862 * 'time' => integer,
863 * 'version' => string,
864 * )
865 *
866 * @since 1.0.0
867 *
868 * @return array An array of the full response.
869 */
870 public function getResponse() {
871 return $this->_lastResponse;
872 }
873
874 /**
875 * Returns a specific item or property from the response data.
876 *
877 * @since 1.0.0
878 *
879 * @param mixed $default Return if $prop is not found in the array or object.
880 *
881 * @param string $prop The item or property to get from the response array or object.
882 *
883 * @return mixed Specific item or property from response data.
884 * @throws DatafeedrError Throws error if $this->_lastResponse is NULL.
885 *
886 */
887 protected function _responseItem( $prop, $default ) {
888
889 if ( is_null( $this->_lastResponse ) ) {
890 throw new DatafeedrError( "Reading from an empty request" );
891 }
892
893 if ( is_object( $this->_lastResponse ) && isset( $this->_lastResponse->$prop ) ) {
894 return $this->_lastResponse->$prop;
895 }
896
897 if ( is_array( $this->_lastResponse ) && isset( $this->_lastResponse[ $prop ] ) ) {
898 return $this->_lastResponse[ $prop ];
899 }
900
901 return $default;
902 }
903
904 /**
905 * Sets the $_lastResponse property to the the full response from the last API request.
906 *
907 * @since 1.0.0
908 *
909 * @param array $request The current API request.
910 *
911 * @param string $action The request action (ex. status, search, merchants, networks, etc...)
912 */
913 function _apiCall( $action, $request = null ) {
914 $this->_lastResponse = $this->_api->apiCall( $action, $request );
915 }
916 }
917
918 /**
919 * Class DatafeedrSearchRequest
920 *
921 * Search request for Datafeedr API.
922 */
923 class DatafeedrSearchRequest extends DatafeedrSearchRequestBase {
924
925 protected $_query;
926 protected $_sort;
927 protected $_fields;
928 protected $_limit;
929 protected $_offset;
930 protected $_priceGroups;
931 protected $_excludeDuplicates;
932 protected $_merchantLimit;
933
934 /**
935 * DatafeedrSearchRequest constructor.
936 *
937 * @since 1.0.0
938 *
939 * @param DatafeedrApi $api
940 *
941 */
942 public function __construct( $api ) {
943
944 parent::__construct( $api );
945
946 $this->_query = array();
947 $this->_sort = array();
948 $this->_fields = array();
949 $this->_limit = 0;
950 $this->_offset = 0;
951 $this->_priceGroups = 0;
952 $this->_excludeDuplicates = '';
953 $this->_merchantLimit = 0;
954 }
955
956 /**
957 * Add a query filter.
958 *
959 * @since 1.0.0
960 *
961 * @param string $filter Query filter.
962 *
963 * @return DatafeedrSearchRequest Returns $this.
964 */
965 public function addFilter( $filter ) {
966 $this->_query[] = $filter;
967
968 return $this;
969 }
970
971 /**
972 * Adds a sort field.
973 *
974 * @since 1.0.0
975 *
976 * @param integer $order One of DatafeedrApi::SORT_ASCENDING or DatafeedrApi::SORT_DESCENDING
977 *
978 * @param string $field Field name.
979 *
980 * @return DatafeedrSearchRequest Returns $this.
981 * @throws DatafeedrError Throws error if sort order is invalid.
982 *
983 */
984 public function addSort( $field, $order = DatafeedrApi::SORT_ASCENDING ) {
985
986 if ( strlen( $field ) && ( $field[0] == '+' || $field[0] == '-' ) ) {
987 $this->_sort [] = $field;
988 } else if ( $order == DatafeedrApi::SORT_ASCENDING ) {
989 $this->_sort [] = '+' . $field;
990 } else if ( $order == DatafeedrApi::SORT_DESCENDING ) {
991 $this->_sort [] = '-' . $field;
992 } else {
993 throw new DatafeedrError( "Invalid sort order" );
994 }
995
996 return $this;
997 }
998
999 /**
1000 * Set which fields to retrieve.
1001 *
1002 * @since 1.0.0
1003 *
1004 * @param array $fields An array of fields to return for each requested item.
1005 *
1006 * Example:
1007 *
1008 * Array (
1009 * 'name',
1010 * 'price',
1011 * 'description',
1012 * 'url',
1013 * )
1014 *
1015 * @return DatafeedrSearchRequest Returns $this.
1016 */
1017 public function setFields( $fields ) {
1018 $this->_fields = $fields;
1019
1020 return $this;
1021 }
1022
1023 /**
1024 * Exclude duplicate filter.
1025 *
1026 * @since 1.0.0
1027 *
1028 * @param string $filter Equality filter in form "field1 field2 | field3".
1029 *
1030 * @return DatafeedrSearchRequest Returns $this.
1031 */
1032 public function excludeDuplicates( $filter ) {
1033
1034 if ( is_array( $filter ) ) {
1035 $filter = implode( ' ', $filter );
1036 }
1037
1038 $this->_excludeDuplicates = $filter;
1039
1040 return $this;
1041 }
1042
1043 /**
1044 * Set a limit of number of records to return.
1045 *
1046 * @since 1.0.0
1047 *
1048 * @param integer $limit The maximum number of records to return.
1049 *
1050 * @return DatafeedrSearchRequest Returns $this.
1051 */
1052 public function setLimit( $limit ) {
1053 $this->_limit = $limit;
1054
1055 return $this;
1056 }
1057
1058 /**
1059 * Set an offset for the search.
1060 *
1061 * @since 1.0.0
1062 *
1063 * @param integer $offset The offset.
1064 *
1065 * @return DatafeedrSearchRequest Returns $this.
1066 */
1067 public function setOffset( $offset ) {
1068 $this->_offset = $offset;
1069
1070 return $this;
1071 }
1072
1073 /**
1074 * Set a limit of results by merchant.
1075 *
1076 * @since 2.0.3
1077 *
1078 * @param integer $limit The limit.
1079 *
1080 * @return DatafeedrSearchRequest Returns $this.
1081 */
1082 public function setMerchantLimit( $limit ) {
1083 $this->_merchantLimit = $limit;
1084
1085 return $this;
1086 }
1087
1088 /**
1089 * Set a price group count.
1090 *
1091 * This should be used in conjunction with the $this->getPriceGroups() method.
1092 *
1093 * Setting $groups to 3 will organize the products into 3 price groups like this:
1094 *
1095 * Array (
1096 * [0] => Array (
1097 * [product_count] => 321
1098 * [max] => 9633332
1099 * [min] => 0
1100 * )
1101 * [1] => Array (
1102 * [product_count] => 116
1103 * [max] => 19266665
1104 * [min] => 9633333
1105 * )
1106 * [2] => Array (
1107 * [product_count] => 43
1108 * [max] => 28900000
1109 * [min] => 19266666
1110 * )
1111 * )
1112 *
1113 * @since 1.0.0
1114 *
1115 * @param integer $groups Number of price groups to create.
1116 *
1117 * @return DatafeedrSearchRequest Returns $this.
1118 */
1119 public function setPriceGroups( $groups ) {
1120 $this->_priceGroups = $groups;
1121
1122 return $this;
1123 }
1124
1125 /**
1126 * Get networks found in this request.
1127 *
1128 * @since 1.0.0
1129 *
1130 * @return array
1131 */
1132 public function getNetworks() {
1133 return $this->_responseItem( 'networks', array() );
1134 }
1135
1136 /**
1137 * Get merchants found in this request.
1138 *
1139 * @since 1.0.0
1140 *
1141 * @return array
1142 */
1143 public function getMerchants() {
1144 return $this->_responseItem( 'merchants', array() );
1145 }
1146
1147 /**
1148 * Get price groups found in this request.
1149 *
1150 * This must be used in conjunction with $this->setPriceGroups();
1151 *
1152 * Returns an array like this:
1153 *
1154 * Array (
1155 * [0] => Array (
1156 * [product_count] => 321
1157 * [max] => 9633332
1158 * [min] => 0
1159 * )
1160 * [1] => Array (
1161 * [product_count] => 116
1162 * [max] => 19266665
1163 * [min] => 9633333
1164 * )
1165 * [2] => Array (
1166 * [product_count] => 43
1167 * [max] => 28900000
1168 * [min] => 19266666
1169 * )
1170 * )
1171 *
1172 * @since 1.0.0
1173 *
1174 * @return array An array of price groups and the product count, min and max prices in each group.
1175 */
1176 public function getPriceGroups() {
1177 return $this->_responseItem( 'price_groups', array() );
1178 }
1179
1180 /**
1181 * Create a request array to use for querying the API.
1182 *
1183 * @since 1.0.0
1184 *
1185 * @return array The $request array().
1186 */
1187 public function getParams() {
1188
1189 $request = array();
1190
1191 if ( $this->_query ) {
1192 $request['query'] = $this->_query;
1193 }
1194
1195 if ( $this->_sort ) {
1196 $request['sort'] = $this->_sort;
1197 }
1198
1199 if ( $this->_fields ) {
1200 $request['fields'] = $this->_fields;
1201 }
1202
1203 if ( $this->_limit ) {
1204 $request['limit'] = $this->_limit;
1205 }
1206
1207 if ( $this->_offset ) {
1208 $request['offset'] = $this->_offset;
1209 }
1210
1211 if ( $this->_priceGroups ) {
1212 $request['price_groups'] = $this->_priceGroups;
1213 }
1214
1215 if ( $this->_merchantLimit ) {
1216 $request['merchant_limit'] = $this->_merchantLimit;
1217 }
1218
1219 if ( $this->_excludeDuplicates ) {
1220 $request['exclude_duplicates'] = $this->_excludeDuplicates;
1221 }
1222
1223 $request['string_ids'] = 1;
1224
1225 return $request;
1226 }
1227
1228 /**
1229 * Run search and return a list of products.
1230 *
1231 * @since 1.0.0
1232 *
1233 * @return array An array of products.
1234 * @throws DatafeedrError Throw error if query is empty.
1235 *
1236 */
1237 public function execute() {
1238
1239 $params = $this->getParams();
1240
1241 if ( ! isset( $params['query'] ) ) {
1242 throw new DatafeedrError( "Query can't be empty" );
1243 }
1244
1245 $this->_apiCall( 'search', $params );
1246
1247 return $this->_responseItem( 'products', array() );
1248 }
1249 }
1250
1251 /**
1252 * Search request for Datafeedr Merchants.
1253 */
1254 class DatafeedrMerchantSearchRequest extends DatafeedrSearchRequestBase {
1255
1256 protected $_query;
1257 protected $_sort;
1258 protected $_fields;
1259 protected $_limit;
1260 protected $_offset;
1261
1262 /**
1263 * DatafeedrMerchantSearchRequest constructor.
1264 *
1265 * @since 1.0.0
1266 *
1267 * @param DatafeedrApi $api
1268 *
1269 */
1270 public function __construct( $api ) {
1271
1272 parent::__construct( $api );
1273
1274 $this->_query = array();
1275 $this->_sort = array();
1276 $this->_fields = array();
1277 $this->_limit = 0;
1278 $this->_offset = 0;
1279 }
1280
1281 /**
1282 * Add a query filter.
1283 *
1284 * @since 1.0.0
1285 *
1286 * @param string $filter Query filter.
1287 *
1288 * @return DatafeedrMerchantSearchRequest Returns $this.
1289 */
1290 public function addFilter( $filter ) {
1291 $this->_query [] = $filter;
1292
1293 return $this;
1294 }
1295
1296 /**
1297 * Add a sort field.
1298 *
1299 * @since 1.0.0
1300 *
1301 * @param integer $order One of DatafeedrApi::SORT_ASCENDING or DatafeedrApi::SORT_DESCENDING
1302 *
1303 * @param string $field Field name.
1304 *
1305 * @return DatafeedrMerchantSearchRequest Returns $this.
1306 * @throws DatafeedrError Throw error if invalid sort order.
1307 *
1308 */
1309 public function addSort( $field, $order = DatafeedrApi::SORT_ASCENDING ) {
1310
1311 if ( strlen( $field ) && ( $field[0] == '+' || $field[0] == '-' ) ) {
1312 $this->_sort [] = $field;
1313 } else if ( $order == DatafeedrApi::SORT_ASCENDING ) {
1314 $this->_sort [] = '+' . $field;
1315 } else if ( $order == DatafeedrApi::SORT_DESCENDING ) {
1316 $this->_sort [] = '-' . $field;
1317 } else {
1318 throw new DatafeedrError( "Invalid sort order" );
1319 }
1320
1321 return $this;
1322 }
1323
1324 /**
1325 * Set which fields to retrieve.
1326 *
1327 * @since 1.0.0
1328 *
1329 * @param array $fields An array of field names.
1330 *
1331 * @return DatafeedrMerchantSearchRequest Returns $this.
1332 */
1333 public function setFields( $fields ) {
1334 $this->_fields = $fields;
1335
1336 return $this;
1337 }
1338
1339 /**
1340 * Set a limit.
1341 *
1342 * @since 1.0.0
1343 *
1344 * @param integer $limit Number of items to return.
1345 *
1346 * @return DatafeedrMerchantSearchRequest Returns $this.
1347 */
1348 public function setLimit( $limit ) {
1349 $this->_limit = $limit;
1350
1351 return $this;
1352 }
1353
1354 /**
1355 * Set an offset.
1356 *
1357 * @since 1.0.0
1358 *
1359 * @param integer $offset The offset.
1360 *
1361 * @return DatafeedrMerchantSearchRequest Returns $this.
1362 */
1363 public function setOffset( $offset ) {
1364 $this->_offset = $offset;
1365
1366 return $this;
1367 }
1368
1369 /**
1370 * Get networks found in this request.
1371 *
1372 * @since 1.0.0
1373 *
1374 * @return array
1375 */
1376 public function getNetworks() {
1377 return $this->_responseItem( 'networks', array() );
1378 }
1379
1380 /**
1381 * Get merchants found in this request.
1382 *
1383 * @since 1.0.0
1384 *
1385 * @return array
1386 */
1387 public function getMerchants() {
1388 return $this->_responseItem( 'merchants', array() );
1389 }
1390
1391 /**
1392 * Run search and return an array of merchants.
1393 *
1394 * @since 1.0.0
1395 *
1396 * @return array Array of merchants
1397 * @throws DatafeedrError Throws error if query is empty.
1398 *
1399 */
1400 public function execute() {
1401
1402 $params = $this->getParams();
1403
1404 if ( ! isset( $params['query'] ) ) {
1405 throw new DatafeedrError( "Query can't be empty" );
1406 }
1407
1408 $this->_apiCall( 'merchant_search', $params );
1409
1410 return $this->_responseItem( 'merchants', array() );
1411 }
1412
1413 /**
1414 * Create a request array to use for querying the API.
1415 *
1416 * @since 1.0.0
1417 *
1418 * @return array The $request array().
1419 */
1420 public function getParams() {
1421
1422 $request = array();
1423
1424 if ( $this->_query ) {
1425 $request['query'] = $this->_query;
1426 }
1427
1428 if ( $this->_sort ) {
1429 $request['sort'] = $this->_sort;
1430 }
1431
1432 if ( $this->_fields ) {
1433 $request['fields'] = $this->_fields;
1434 }
1435
1436 if ( $this->_limit ) {
1437 $request['limit'] = $this->_limit;
1438 }
1439
1440 if ( $this->_offset ) {
1441 $request['offset'] = $this->_offset;
1442 }
1443
1444 return $request;
1445 }
1446 }
1447
1448 /**
1449 * Generic Amazon request.
1450 */
1451 class DatafeedrAmazonRequest extends DatafeedrSearchRequestBase {
1452
1453 protected $_found = - 1;
1454
1455 protected $_hosts;
1456 protected $_params;
1457 protected $_locale;
1458 protected $_awsAccessKeyId;
1459 protected $_awsSecretKey;
1460 protected $_awsAssociateTag;
1461
1462 const AWS_VERSION = "2011-08-01";
1463
1464 /**
1465 * DatafeedrAmazonRequest constructor
1466 *
1467 * @since 1.0.0
1468 *
1469 * @param string $awsAccessKeyId Amazon Access Key.
1470 * @param string $awsSecretKey Amazon Secret Key.
1471 * @param string $awsAssociateTag Amazon Associate tag.
1472 * @param string $locale Optional. Amazon locale (two-letter code).
1473 *
1474 * @param DatafeedrApi $api
1475 *
1476 * @throws DatafeedrError Throws error if Amazon Locale is invalid.
1477 */
1478 public function __construct( $api, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
1479
1480 parent::__construct( $api );
1481
1482 $this->_hosts = array(
1483 'AU' => 'webservices.amazon.com.au',
1484 'BR' => 'webservices.amazon.com.br',
1485 'CA' => 'webservices.amazon.ca',
1486 'FR' => 'webservices.amazon.fr',
1487 'DE' => 'webservices.amazon.de',
1488 'IN' => 'webservices.amazon.in',
1489 'IT' => 'webservices.amazon.it',
1490 'JP' => 'webservices.amazon.co.jp',
1491 'MX' => 'webservices.amazon.com.mx',
1492 'NL' => 'webservices.amazon.nl',
1493 'SG' => 'webservices.amazon.sg',
1494 'SA' => 'webservices.amazon.sa',
1495 'ES' => 'webservices.amazon.es',
1496 'SE' => 'webservices.amazon.se',
1497 'TR' => 'webservices.amazon.com.tr',
1498 'AE' => 'webservices.amazon.ae',
1499 'UK' => 'webservices.amazon.co.uk',
1500 'US' => 'webservices.amazon.com',
1501 );
1502
1503 $this->_params = array();
1504 $this->_locale = strtoupper( $locale );
1505
1506 if ( ! isset( $this->_hosts[ $this->_locale ] ) ) {
1507 throw new DatafeedrError( 'Invalid Amazon locale' );
1508 }
1509
1510 $this->_awsAccessKeyId = $awsAccessKeyId;
1511 $this->_awsSecretKey = $awsSecretKey;
1512 $this->_awsAssociateTag = $awsAssociateTag;
1513 }
1514
1515 /**
1516 * Returns all parameters.
1517 *
1518 * @since 1.0.0
1519 *
1520 * @return array
1521 */
1522 public function getParams() {
1523 return $this->_params;
1524 }
1525
1526 /**
1527 * Prepare am API request for Amazon.
1528 *
1529 * @since 3.0.0
1530 *
1531 * @param array $params
1532 *
1533 * @param string $operation
1534 *
1535 * @return array
1536 */
1537 protected function _amazonRequest( $operation, $params ) {
1538 return array(
1539 'amz_access' => $this->_awsAccessKeyId,
1540 'amz_key' => $this->_awsSecretKey,
1541 'amz_tag' => $this->_awsAssociateTag,
1542 'locale' => $this->_locale,
1543 'operation' => $operation,
1544 'params' => $params,
1545 );
1546 }
1547 }
1548
1549 /**
1550 * Class DatafeedrAmazonSearchRequest
1551 *
1552 * Amazon search request class.
1553 */
1554 class DatafeedrAmazonSearchRequest extends DatafeedrAmazonRequest {
1555
1556 /**
1557 * Add a parameter.
1558 *
1559 * @since 1.0.0
1560 *
1561 * @param string $value Parameter value.
1562 *
1563 * @param string $name Parameter name.
1564 *
1565 * @return DatafeedrAmazonSearchRequest Returns $this.
1566 *
1567 * @see https://webservices.amazon.com/paapi5/documentation/search-items.html#ItemLookup-rp
1568 */
1569 public function addParam( $name, $value ) {
1570 $this->_params[ $name ] = $value;
1571
1572 return $this;
1573 }
1574
1575 /**
1576 * Run search and return an array of products.
1577 *
1578 * IMPORTANT - The Amazon API returns a MAXIMUM of 10 products per API request and a maximum of
1579 * 50 products per search query.
1580 *
1581 * @since 1.0.0
1582 *
1583 * @return array An array of products.
1584 * @throws DatafeedrError
1585 */
1586 public function execute() {
1587
1588 $params = array_filter( $this->_params );
1589 $req = $this->_amazonRequest( 'SearchItems', $params );
1590 $this->_apiCall( 'amazon_find', $req );
1591
1592 return $this->_responseItem( 'products', array() );
1593 }
1594 }
1595
1596 /**
1597 * Class DatafeedrAmazonLookupRequest
1598 *
1599 * Amazon lookup request.
1600 */
1601 class DatafeedrAmazonLookupRequest extends DatafeedrAmazonRequest {
1602
1603 /**
1604 * Add a parameter.
1605 *
1606 * @since 1.0.0
1607 *
1608 * @param string|array $value Parameter value or an array of values (up to 10).
1609 *
1610 * @param string $name Parameter name - one of 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN'
1611 *
1612 * @return DatafeedrAmazonLookupRequest Returns $this.
1613 *
1614 * @see https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp
1615 */
1616 public function addParam( $name, $value ) {
1617 $this->_params[ $name ] = $value;
1618
1619 return $this;
1620 }
1621
1622 /**
1623 * Run search and return an array of products.
1624 *
1625 * @since 1.0.0
1626 *
1627 * @return array An array of products.
1628 */
1629 public function execute() {
1630
1631 $params = array_filter( $this->_params );
1632 $types = array( 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN', 'asin', 'sku', 'upc', 'ean', 'isbn' );
1633
1634 foreach ( $types as $type ) {
1635 if ( isset( $params[ $type ] ) ) {
1636 $params['ItemIdType'] = strtoupper( $type );
1637 $params['ItemIds'] = $params[ $type ];
1638 if ( is_string( $params['ItemIds'] ) ) {
1639 $params['ItemIds'] = explode( ',', $params['ItemIds'] );
1640 }
1641 unset( $params[ $type ] );
1642 }
1643 }
1644
1645 $req = $this->_amazonRequest( 'GetItems', $params );
1646 $this->_apiCall( 'amazon_find', $req );
1647
1648 return $this->_responseItem( 'products', array() );
1649 }
1650 }
1651
1652 /** CreatorAPI */
1653 class DatafeedrAmazonCreatorApiRequest extends DatafeedrSearchRequestBase {
1654
1655 protected $_found = - 1;
1656
1657 protected $_hosts;
1658 protected $_params;
1659 // protected $_capiCredentialId;
1660 // protected $_capiCredentialSecret;
1661 // protected $_capiVersion;
1662 protected $_capiPartnerTag;
1663 protected $_capiMarketplace;
1664
1665 public const AWS_VERSION = "2026-01-05";
1666
1667 public function __construct( $api, $capiMarketplace = 'US' ) {
1668
1669 parent::__construct( $api );
1670
1671 $capiMarketplace = strtoupper( trim( $capiMarketplace ) );
1672
1673 foreach ( dfrapi_get_capi_marketplaces() as $code => $marketplace ) {
1674 $this->_hosts[ $code ] = $marketplace['domain'];
1675 }
1676
1677 if ( ! isset( $this->_hosts[ $capiMarketplace ] ) ) {
1678 throw new DatafeedrError( 'Invalid Amazon CAPI locale' );
1679 }
1680
1681 $this->_params = array();
1682
1683 // $capiVersion = dfrapi_get_capi_marketplace( $capiMarketplace )['region']['version'];
1684
1685 // $this->_capiCredentialId = $capiCredentialId;
1686 // $this->_capiCredentialSecret = $capiCredentialSecret;
1687 // $this->_capiVersion = $capiVersion;
1688 // $this->_capiPartnerTag = $capiPartnerTag;
1689 $this->_capiMarketplace = $capiMarketplace;
1690 }
1691
1692 /**
1693 * Returns all parameters.
1694 *
1695 * @since 1.0.0
1696 *
1697 * @return array
1698 */
1699 public function getParams() {
1700 return $this->_params;
1701 }
1702
1703 /**
1704 * Prepare am API request for Amazon.
1705 *
1706 * @since 3.0.0
1707 *
1708 * @param array $params
1709 *
1710 * @param string $operation
1711 *
1712 * @return array
1713 */
1714 // protected function _amazonRequest( $operation, $params ) {
1715 // return array(
1716 // 'capi_credential_id' => $this->_capiCredentialId,
1717 // 'capi_credential_secret' => $this->_capiCredentialSecret,
1718 // 'capi_version' => $this->_capiVersion,
1719 // 'capi_partner_tag' => $this->_capiPartnerTag,
1720 // 'capi_marketplace' => $this->_capiMarketplace,
1721 // 'operation' => $operation,
1722 // 'params' => $params,
1723 // );
1724 // }
1725 }
1726
1727 /** CreatorAPI */
1728 class DatafeedrAmazonCreatorApiSearchRequest extends DatafeedrAmazonCreatorApiRequest {
1729
1730 /**
1731 * Add a parameter.
1732 *
1733 * @since 1.0.0
1734 *
1735 * @param string $value Parameter value.
1736 *
1737 * @param string $name Parameter name.
1738 *
1739 * @return DatafeedrAmazonCreatorApiSearchRequest Returns $this.
1740 *
1741 * @see https://webservices.amazon.com/paapi5/documentation/search-items.html#ItemLookup-rp
1742 */
1743 public function addParam( $name, $value ) {
1744 $this->_params[ $name ] = $value;
1745
1746 return $this;
1747 }
1748
1749 /**
1750 * Run search and return an array of products.
1751 *
1752 * IMPORTANT - The Amazon API returns a MAXIMUM of 10 products per API request and a maximum of
1753 * 50 products per search query.
1754 *
1755 * @since 1.0.0
1756 *
1757 * @return array An array of products.
1758 * @throws DatafeedrError
1759 */
1760 public function execute() {
1761
1762 $access_token = dfrapi_get_capi_access_token();
1763
1764 if ( is_wp_error( $access_token ) ) {
1765 throw new DatafeedrError( $access_token->get_error_message() );
1766 }
1767
1768 $body = array_merge( array_filter( $this->_params ), [] );
1769
1770 $credentials = dfrapi_get_capi_credentials();
1771 $marketplace = dfrapi_get_capi_marketplace( $this->_capiMarketplace );
1772
1773 $domain = $marketplace['domain'];
1774 $credential_version = $marketplace['region']['version'];
1775 $body['partnerTag'] = $credentials['partner_tag'];
1776
1777 // https://affiliate-program.amazon.com/creatorsapi/docs/en-us/api-reference/operations/search-items
1778 $resources = [
1779 // 'browseNodeInfo.browseNodes',
1780 // 'browseNodeInfo.browseNodes.ancestor',
1781 // 'browseNodeInfo.browseNodes.salesRank',
1782 // 'browseNodeInfo.websiteSalesRank',
1783 'customerReviews.count',
1784 'customerReviews.starRating',
1785 'images.primary.highRes',
1786 'images.primary.large',
1787 'images.primary.medium',
1788 'images.primary.small',
1789 // 'images.variants.highRes',
1790 // 'images.variants.large',
1791 // 'images.variants.medium',
1792 // 'images.variants.small',
1793 'itemInfo.byLineInfo',
1794 // 'itemInfo.classifications',
1795 // 'itemInfo.contentInfo',
1796 // 'itemInfo.contentRating',
1797 'itemInfo.externalIds',
1798 // 'itemInfo.features',
1799 // 'itemInfo.manufactureInfo',
1800 'itemInfo.productInfo',
1801 // 'itemInfo.technicalInfo',
1802 'itemInfo.title',
1803 'itemInfo.tradeInInfo',
1804 'offersV2.listings.availability',
1805 'offersV2.listings.condition',
1806 // 'offersV2.listings.dealDetails',
1807 // 'offersV2.listings.isBuyBoxWinner',
1808 // 'offersV2.listings.loyaltyPoints',
1809 // 'offersV2.listings.merchantInfo',
1810 'offersV2.listings.price',
1811 'offersV2.listings.type',
1812 'parentASIN',
1813 // 'searchRefinements',
1814 ];
1815
1816 $body['resources'] = $resources;
1817
1818 $response = wp_remote_post(
1819 'https://creatorsapi.amazon/catalog/v1/searchItems',
1820 [
1821 'headers' => [
1822 'Authorization' => sprintf(
1823 'Bearer %s, Version %s',
1824 $access_token,
1825 $credential_version
1826 ),
1827 'Content-Type' => 'application/json',
1828 'x-marketplace' => $domain,
1829 ],
1830 'body' => wp_json_encode( $body ),
1831 'timeout' => 20,
1832 'httpversion' => '1.1', // important for Amazon APIs
1833 ]
1834 );
1835
1836 if ( is_wp_error( $response ) ) {
1837 error_log( $response->get_error_message() );
1838
1839 throw new DatafeedrConnectionError( $response->get_error_message() );
1840 }
1841
1842 $status = wp_remote_retrieve_response_code( $response );
1843
1844 if ( 404 === $status ) {
1845 $body = wp_remote_retrieve_body( $response );
1846 $data = json_decode( $body, true );
1847 if ( isset( $data['type'] ) && 'ResourceNotFoundException' === $data['type'] ) {
1848 return [];
1849 }
1850 }
1851
1852 if ( 200 !== $status ) {
1853 $error_body = wp_remote_retrieve_body( $response );
1854 $error_data = json_decode( $error_body, true );
1855 $status_message = wp_remote_retrieve_response_message( $response );
1856
1857 if ( ! empty( $error_data['message'] ) ) {
1858 $message = $error_data['message'];
1859 } elseif ( ! empty( $error_data['reason'] ) ) {
1860 $message = $error_data['reason'];
1861 } else {
1862 $message = $status_message;
1863 }
1864
1865 error_log( sprintf( '[Datafeedr CAPI] SearchItems error (%d): %s | Response: %s', $status, $message, $error_body ) );
1866
1867 throw new DatafeedrHTTPError( $message, $status );
1868 }
1869
1870 $body = wp_remote_retrieve_body( $response );
1871
1872
1873 /*
1874
1875 [
1876 "errors" => null,
1877 "searchResult" => [
1878 "items" => [
1879 [
1880 "asin" => "0545162076",
1881 "browseNodeInfo" => null,
1882 "customerReviews" => null,
1883 "detailPageURL" => "https://www.amazon.com/dp/0545162076?tag=xyz-20&linkCode=osi&th=1&psc=1",
1884 "images" => null,
1885 "itemInfo" => [
1886 "byLineInfo" => null,
1887 "classifications" => null,
1888 "contentInfo" => null,
1889 "contentRating" => null,
1890 "externalIds" => null,
1891 "features" => null,
1892 "manufactureInfo" => null,
1893 "productInfo" => null,
1894 "technicalInfo" => null,
1895 "title" => [
1896 "displayValue" => "Harry Potter Paperback Box Set (Books 1-7)",
1897 "label" => "Title",
1898 "locale" => "en_US",
1899 ],
1900 "tradeInInfo" => null,
1901 ],
1902 "offersV2" => null,
1903 "parentASIN" => null,
1904 "score" => null,
1905 "variationAttributes" => null,
1906 ],
1907 [ ... ],
1908 [ ... ],
1909 [ ... ],
1910 ],
1911 "searchRefinements" => null,
1912 "searchURL" => "https://www.amazon.com/s?k=Harry+Potter&rh=p_n_availability%3A2661600011&tag=xyz-20&linkCode=osi",
1913 "totalResultCount" => 306,
1914 ],
1915 ]
1916
1917
1918 */
1919 /**
1920 * [
1921 * "errors" => null,
1922 * "searchResult" => [
1923 * "items" => [
1924 * [
1925 * "asin" => "0545162076",
1926 * "browseNodeInfo" => null,
1927 * "customerReviews" => null,
1928 * "detailPageURL" => "https://www.amazon.com/dp/0545162076?tag=xyz-20&linkCode=osi&th=1&psc=1",
1929 * "images" => null,
1930 * "itemInfo" => [
1931 * "byLineInfo" => null,
1932 * "classifications" => null,
1933 * "contentInfo" => null,
1934 * "contentRating" => null,
1935 * "externalIds" => null,
1936 * "features" => null,
1937 * "manufactureInfo" => null,
1938 * "productInfo" => null,
1939 * "technicalInfo" => null,
1940 * "title" => [
1941 * "displayValue" => "Harry Potter Paperback Box Set (Books 1-7)",
1942 * "label" => "Title",
1943 * "locale" => "en_US",
1944 * ],
1945 * "tradeInInfo" => null,
1946 * ],
1947 * "offersV2" => null,
1948 * "parentASIN" => null,
1949 * "score" => null,
1950 * "variationAttributes" => null,
1951 * ],
1952 * [ ... ],
1953 * [ ... ],
1954 * [ ... ],
1955 * ],
1956 * "searchRefinements" => null,
1957 * "searchURL" => "https://www.amazon.com/s?k=Harry+Potter&rh=p_n_availability%3A2661600011&tag=xyz-20&linkCode=osi",
1958 * "totalResultCount" => 306,
1959 * ],
1960 * ]
1961 */
1962 $data = json_decode( $body, true );
1963
1964 if ( ! empty( $data['errors'] ) ) {
1965 $error = $data['errors'][0];
1966 error_log( $error['message'] );
1967
1968 throw new DatafeedrExternalError( $error['message'] );
1969 }
1970
1971 $items = $data['searchResult']['items'] ?? [];
1972 $products = [];
1973
1974 foreach ( $items as $item ) {
1975 $products[] = dfrapi_transform_capi_item_into_datafeedr_product_array( $item );
1976 }
1977
1978 return $products;
1979
1980 // $data now contains the API response
1981
1982
1983 }
1984 }
1985
1986 /** CreatorAPI */
1987 //class DatafeedrAmazonCreatorApiLookupRequest extends DatafeedrAmazonCreatorApiRequest {
1988 //
1989 // /**
1990 // * Add a parameter.
1991 // *
1992 // * @since 1.0.0
1993 // *
1994 // * @param string|array $value Parameter value or an array of values (up to 10).
1995 // *
1996 // * @param string $name Parameter name - one of 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN'
1997 // *
1998 // * @return DatafeedrAmazonCreatorApiLookupRequest Returns $this.
1999 // *
2000 // * @see https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp
2001 // */
2002 // public function addParam( $name, $value ) {
2003 // $this->_params[ $name ] = $value;
2004 //
2005 // return $this;
2006 // }
2007 //
2008 // /**
2009 // * Run search and return an array of products.
2010 // *
2011 // * @since 1.0.0
2012 // *
2013 // * @return array An array of products.
2014 // */
2015 // public function execute() {
2016 //
2017 // $params = array_filter( $this->_params );
2018 // $types = array( 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN', 'asin', 'sku', 'upc', 'ean', 'isbn' );
2019 //
2020 // foreach ( $types as $type ) {
2021 // if ( isset( $params[ $type ] ) ) {
2022 // $params['itemIdType'] = strtoupper( $type );
2023 // $params['itemIds'] = $params[ $type ];
2024 // if ( is_string( $params['itemIds'] ) ) {
2025 // $params['itemIds'] = explode( ',', $params['itemIds'] );
2026 // }
2027 // unset( $params[ $type ] );
2028 // }
2029 // }
2030 //
2031 // $req = $this->_amazonRequest( 'GetItems', $params );
2032 // $this->_apiCall( 'amazon_find', $req );
2033 //
2034 // return $this->_responseItem( 'products', array() );
2035 // }
2036 //}
2037
2038 /**
2039 * Class DatafeedrError.
2040 *
2041 * Generic Api error.
2042 */
2043 class DatafeedrError extends Exception {
2044 }
2045
2046 /**
2047 * Class DatafeedrBadRequestError.
2048 *
2049 * API error: Invalid Request.
2050 */
2051 class DatafeedrBadRequestError extends DatafeedrError {
2052 }
2053
2054 /**
2055 * Class DatafeedrAuthenticationError.
2056 *
2057 * API error: Authentication failed.
2058 */
2059 class DatafeedrAuthenticationError extends DatafeedrError {
2060 }
2061
2062 /**
2063 * Class DatafeedrLimitExceededError.
2064 *
2065 * API error: Query limit exceeded.
2066 */
2067 class DatafeedrLimitExceededError extends DatafeedrError {
2068 }
2069
2070 /**
2071 * Class DatafeedrHTTPError.
2072 *
2073 * API error: Unspecified HTTP error.
2074 */
2075 class DatafeedrHTTPError extends DatafeedrError {
2076 }
2077
2078 /**
2079 * Class DatafeedrConnectionError.
2080 *
2081 * API error: Connection error.
2082 */
2083 class DatafeedrConnectionError extends DatafeedrError {
2084 }
2085
2086 /**
2087 * Class DatafeedrQueryError.
2088 *
2089 * API error: Error in the search query.
2090 */
2091 class DatafeedrQueryError extends DatafeedrError {
2092 }
2093
2094 /**
2095 * Class DatafeedrExternalError.
2096 *
2097 * API error: External service error.
2098 */
2099 class DatafeedrExternalError extends DatafeedrError {
2100 }
2101
2102 /**
2103 * Class DatafeedrServerError.
2104 *
2105 * API error: Internal server error.
2106 */
2107 class DatafeedrServerError extends DatafeedrError {
2108 }
2109