PluginProbe
Datafeedr API / 1.0.124
Datafeedr API v1.0.124
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.0.124, at libraries/datafeedr.php

1,674 lines 39.3 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
44 protected $_status;
45 protected $_errors;
46
47 const SORT_DESCENDING = - 1;
48 const SORT_ASCENDING = + 1;
49
50 const DEFAULT_HOST = 'api.datafeedr.com';
51
52 const REQUEST_COMPRESSION_THRESHOLD = 1024;
53
54 const VERSION = '3.0.0';
55
56 /**
57 * DatafeedrApi constructor.
58 *
59 * @param string $accessId Datafeedr API Access ID.
60 * @param string $secretKey Datafeedr API Secret Key.
61 * @param array $options Options.
62 *
63 * Possble options:
64 *
65 * - host: API host name. Default: 'api.datafeedr.com'
66 * - https: TRUE if using https. Default: FALSE.
67 * - transport: HTTP transport name or function. Default: 'wordpress'
68 * - timeout: HTTP connection timeout, in seconds. Default: 0
69 * - returnObjects: True to return Objects. False to return associative arrays Default: false
70 * - retry: How many times to repeat a request on a temporary failure. Default: 0 (do not repeat)
71 * - retryTimeout: Timeout between retry requests, in seconds. Default: 5
72 *
73 * The `transport` option tells how HTTP requests should be made.
74 * It can be either a string that describes one of built-in transports ("curl", "file" or "wordpress"),
75 * or a callable object that should accept a URL, an array of headers and a string of post data and
76 * should return an array [int http response status, string response body].
77 *
78 *
79 * @throws DatafeedrError Throws error if any option is invalid.
80 * @since 1.0.0
81 *
82 */
83 public function __construct( $accessId, $secretKey, $options = null ) {
84
85 $this->_accessId = $accessId;
86 $this->_secretKey = $secretKey;
87
88 $this->_errors = array(
89 1 => 'DatafeedrBadRequestError',
90 2 => 'DatafeedrAuthenticationError',
91 3 => 'DatafeedrLimitExceededError',
92 4 => 'DatafeedrQueryError',
93 7 => 'DatafeedrExternalError',
94 9 => 'DatafeedrServerError',
95 );
96
97 $this->_parseOptions( $options );
98 }
99
100 /**
101 * Returns API status information.
102 *
103 * @return array An array of API Status information.
104 * @since 1.0.0
105 *
106 */
107 public function getStatus() {
108 $this->apiCall( 'status' );
109
110 return $this->_status;
111 }
112
113 /**
114 * Return status information from the last request.
115 *
116 * If no API request has been made, return NULL
117 *
118 * @return array|null Status information or NULL.
119 * @since 1.0.0
120 *
121 */
122 public function lastStatus() {
123 return $this->_status;
124 }
125
126 /**
127 * Return the list of networks.
128 *
129 * @param integer|array $networkId Optional. Network ID or an array of network IDs.
130 * @param boolean $includeEmpty Optional. If FALSE, omit networks with 0 products.
131 * @param array $fields Optional. An array of fields to retrieve.
132 *
133 * @return array An array of Networks.
134 * @since 1.0.0
135 *
136 */
137 public function getNetworks( $networkId = null, $includeEmpty = false, $fields = null ) {
138
139 $request = array();
140
141 if ( $networkId ) {
142 $request['_ids'] = $this->_intarray( $networkId );
143 }
144
145 $request['skip_empty'] = intval( ! $includeEmpty );
146
147 if ( $fields ) {
148 $request['fields'] = $fields;
149 }
150
151 $response = $this->apiCall( 'networks', $request );
152
153 return $this->_get( $response, 'networks' );
154 }
155
156 /**
157 * Return the list of merchants.
158 *
159 * @param integer|array $networkId Optional. Network ID or array of Network IDs.
160 * @param bool $includeEmpty Optional. If FALSE, omit merchants with 0 products.
161 * @param array $fields Optional. An array of fields to retrieve.
162 *
163 * @return array An array of merchants.
164 * @since 1.0.0
165 *
166 */
167 public function getMerchants( $networkId = null, $includeEmpty = false, $fields = null ) {
168
169 $request = array();
170
171 if ( $networkId ) {
172 $request['source_ids'] = $this->_intarray( $networkId );
173 }
174
175 $request['skip_empty'] = intval( ! $includeEmpty );
176
177 if ( $fields ) {
178 $request['fields'] = $fields;
179 }
180
181 $response = $this->apiCall( 'merchants', $request );
182
183 return $this->_get( $response, 'merchants' );
184 }
185
186 /**
187 * Return a list of merchants by their IDs.
188 *
189 * @param integer|array $merchantId Merchant ID or array of Merchant IDs.
190 * @param boolean $includeEmpty Optional. If FALSE, omit merchants with 0 products.
191 * @param array $fields Optional. An array of fields to retrieve.
192 *
193 * @return array An array of merchants.
194 * @since 1.0.0
195 *
196 */
197 public function getMerchantsById( $merchantId, $includeEmpty = false, $fields = null ) {
198
199 $request = array();
200 $request['_ids'] = $this->_intarray( $merchantId );
201 $request['skip_empty'] = intval( ! $includeEmpty );
202
203 if ( $fields ) {
204 $request['fields'] = $fields;
205 }
206
207 $response = $this->apiCall( 'merchants', $request );
208
209 return $this->_get( $response, 'merchants' );
210 }
211
212 /**
213 * Return a list of searchable fields.
214 *
215 * @param integer|array $networkId Optional. Network ID or array of network IDs.
216 *
217 * @return array An array of searchable fields.
218 * @todo - Does this return a list of all fields for a specific network or only indexed/searchable fields? Update docs accordingly.
219 *
220 * @since 1.0.0
221 *
222 */
223 public function getFields( $networkId = null ) {
224
225 $request = array();
226
227 if ( $networkId ) {
228 $request['source_ids'] = $this->_intarray( $networkId );
229 }
230
231 $response = $this->apiCall( 'fields', $request );
232
233 return $this->_get( $response, 'fields' );
234 }
235
236 /**
237 * Return a list of products by their IDs.
238 *
239 * @param integer|array $productId Product ID or an array of products IDs.
240 * @param array $fields Optional. An array of fields to retrieve.
241 *
242 * @return array An array of Products.
243 * @since 1.0.0
244 *
245 */
246 public function getProducts( $productId, $fields = null ) {
247
248 $request = array();
249 $request['_ids'] = $this->_intarray( $productId );
250 $request['string_ids'] = 1;
251
252 if ( $fields ) {
253 $request['fields'] = $fields;
254 }
255
256 $response = $this->apiCall( 'get', $request );
257
258 return $this->_get( $response, 'products' );
259 }
260
261 /**
262 * Return a list of Zanox merchant IDs ("zmids").
263 *
264 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
265 * @param integer $adspaceId Zanox Adspace ID.
266 * @param string $connectId Zanox Connection ID.
267 *
268 * @return array An array of arrays (adspace_id, merchant_id, program_id, zmid).
269 * @since 1.0.0
270 *
271 */
272 public function getZanoxMerchantIds( $merchantId, $adspaceId, $connectId ) {
273
274 $request = array();
275
276 $request['merchant_ids'] = $this->_intarray( $merchantId );
277 $request['adspace_id'] = $adspaceId;
278 $request['connect_id'] = $connectId;
279
280 $response = $this->apiCall( 'zanox_merchant_ids', $request );
281
282 return $this->_get( $response, 'zanox_merchant_ids' );
283 }
284
285 /**
286 * Return a list of PerformanceHorizon campaign references ("camrefs").
287 *
288 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
289 * @param string $applicationKey PerformanceHorizon application_key.
290 * @param string $userApiKey PerformanceHorizon user_api_key.
291 * @param string $publisherId PerformanceHorizon publisher_id.
292 *
293 * @return array An array of arrays (campaign_id, camref, merchant_id).
294 * @since 2.0.0
295 *
296 */
297
298 public function getPerformanceHorizonCamrefs( $merchantId, $applicationKey, $userApiKey, $publisherId ) {
299
300 $request = array();
301
302 $request['merchant_ids'] = $this->_intarray( $merchantId );
303 $request['application_key'] = $applicationKey;
304 $request['user_api_key'] = $userApiKey;
305 $request['publisher_id'] = $publisherId;
306
307 $response = $this->apiCall( 'performancehorizon_camrefs', $request );
308
309 return $this->_get( $response, 'performancehorizon_camrefs' );
310 }
311
312 /**
313 * Return a list of Effiliation affiliate ids.
314 *
315 * @param integer|array $merchantId Merchant ID or an array of merchant IDs.
316 * @param string $apiKey Effiliation api_key.
317 *
318 * @return array An array of arrays (affiliate_id, merchant_id).
319 * @since 2.0.2
320 *
321 */
322
323 public function getEffiliationAffiliateIds( $merchantId, $apiKey ) {
324
325 $request = array();
326
327 $request['merchant_ids'] = $this->_intarray( $merchantId );
328 $request['api_key'] = $apiKey;
329
330 $response = $this->apiCall( 'effiliation_affiliate_ids', $request );
331
332 return $this->_get( $response, 'effiliation_affiliate_ids' );
333 }
334
335 /**
336 * Create a new DatafeedrSearchRequest object.
337 *
338 * @return DatafeedrSearchRequest
339 * @since 1.0.0
340 *
341 */
342 public function searchRequest() {
343 return new DatafeedrSearchRequest( $this );
344 }
345
346 /**
347 * Create a new DatafeedrMerchantSearchRequest object.
348 *
349 * @return DatafeedrMerchantSearchRequest
350 * @since 1.0.0
351 *
352 */
353 public function merchantSearchRequest() {
354 return new DatafeedrMerchantSearchRequest( $this );
355 }
356
357 /**
358 * Create a new DatafeedrAmazonSearchRequest object.
359 *
360 * @param string $awsAccessKeyId Amazon Access Key.
361 * @param string $awsSecretKey Amazon Secret Key.
362 * @param string $awsAssociateTag Amazon Associates tag.
363 * @param string $locale The country locale code.
364 *
365 * @return DatafeedrAmazonSearchRequest
366 * @since 1.0.0
367 *
368 */
369 public function amazonSearchRequest( $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
370 return new DatafeedrAmazonSearchRequest( $this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale );
371 }
372
373 /**
374 * Create a new DatafeedrAmazonLookupRequest object.
375 *
376 * @param string $awsAccessKeyId Amazon Access Key.
377 * @param string $awsSecretKey Amazon Secret Key.
378 * @param string $awsAssociateTag Amazon Associates tag.
379 * @param string $locale The country locale code.
380 *
381 * @return DatafeedrAmazonLookupRequest
382 * @since 1.0.0
383 *
384 */
385 public function amazonLookupRequest( $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
386 return new DatafeedrAmazonLookupRequest( $this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale );
387 }
388
389 /**
390 * Perform the raw API call.
391 *
392 * @param string $action API Action. (Examples: status, merchants, networks, search, etc...)
393 * @param array $request Optional. Request data.
394 *
395 * @return array Returns $response array.
396 * @throws DatafeedrHTTPError Throws error if request status is not 200.
397 *
398 * @since 1.0.0
399 *
400 */
401 public function apiCall( $action, $request = null ) {
402
403 if ( ! $request ) {
404 $request = array();
405 }
406
407 $request['aid'] = $this->_accessId;
408 $request['timestamp'] = gmdate( 'Y-m-d H:i:s' );
409
410 if ( $this->_https ) {
411 $request['akey'] = $this->_secretKey;
412 } else {
413 $message = $request['aid'] . $action . $request['timestamp'];
414 $request['signature'] = hash_hmac( 'sha256', $message, $this->_secretKey, false );
415 }
416
417 $postdata = json_encode( $request );
418 $url = $this->_url . '/' . $action;
419 $headers = array(
420 'Host: ' . $this->_host,
421 'Content-Type: application/json',
422 'Accept: application/json',
423 'Connection: close',
424 'User-Agent: ' . $this->_userAgent
425 );
426
427 if ( $this->_hasZlib && strlen( $postdata ) >= self::REQUEST_COMPRESSION_THRESHOLD ) {
428 $postdata = gzcompress( $postdata );
429 $headers [] = 'Content-Encoding: deflate';
430 }
431
432 $headers [] = 'Content-Length: ' . strlen( $postdata );
433
434 list( $status, $response ) = $this->_performRequest( $url, $headers, $postdata );
435
436 if ( strlen( $response ) ) {
437 $response = json_decode( $response, ! $this->_returnObjects );
438 }
439
440 $error = $this->_get( $response, 'error' );
441 if ( $error ) {
442 $type = $this->_get( $response, 'type' );
443 $cls = isset( $this->_errors[ $type ] ) ? $this->_errors[ $type ] : 'DatafeedrError';
444 throw new $cls( $this->_get( $response, 'message' ), $error );
445 }
446
447 if ( 200 != $status ) {
448 throw new DatafeedrHTTPError( "Status $status" );
449 }
450
451 $this->_status = $this->_get( $response, 'status' );
452
453 return $response;
454 }
455
456 /**
457 * Returns the default set of options.
458 *
459 * @since 2.0.0
460 *
461 * @returns array $options.
462 *
463 */
464 protected function _defaultOptions() {
465 return array(
466 'host' => self::DEFAULT_HOST,
467 'https' => false,
468 'transport' => 'wordpress_or_curl',
469 'timeout' => 30,
470 'returnObjects' => false,
471 'retry' => 0,
472 'retryTimeout' => 5
473 );
474 }
475
476 /**
477 * Parse constructor options.
478 *
479 * @param array $options .
480 *
481 * @throws DatafeedrError Throws error if any option is invalid.
482 * @since 2.0.0
483 *
484 */
485 protected function _parseOptions( $options ) {
486 $opts = $this->_defaultOptions();
487
488 if ( ! is_null( $options ) ) {
489 if ( ! is_array( $options ) ) {
490 throw new DatafeedrError( "Options must be an array" );
491 }
492
493 foreach ( $options as $key => $value ) {
494 if ( isset( $opts[ $key ] ) ) {
495 $opts[ $key ] = $value;
496 }
497 }
498 }
499
500 $pt = $opts['https'] ? 'https' : 'http';
501 $tr = $opts['transport'];
502
503 $this->_url = $pt . '://' . $opts['host'];
504 $this->_https = $opts['https'];
505 $this->_host = $opts['host'];
506 $this->_timeout = intval( $opts['timeout'] );
507 $this->_returnObjects = intval( $opts['returnObjects'] );
508 $this->_retry = intval( $opts['retry'] );
509 $this->_retryTimeout = intval( $opts['retryTimeout'] );
510
511 switch ( $tr ) {
512 case 'curl':
513 $this->_transport = array( $this, '_transportCurl' );
514 break;
515 case 'file':
516 $this->_transport = array( $this, '_transportFile' );
517 break;
518 case 'wordpress':
519 if ( ! function_exists( 'wp_remote_post' ) ) {
520 throw new DatafeedrError( "Wordpress transport requires wp_remote_post" );
521 }
522 $this->_transport = array( $this, '_transportWordpress' );
523 break;
524 case 'wordpress_or_curl':
525 if ( ! function_exists( 'wp_remote_post' ) ) {
526 $this->_transport = array( $this, '_transportCurl' );
527 $tr = 'curl';
528 } else {
529 $this->_transport = array( $this, '_transportWordpress' );
530 $tr = 'wordpress';
531 }
532 break;
533
534 default:
535 if ( ! is_callable( $tr ) ) {
536 throw new DatafeedrError( "Transport must be a function" );
537 }
538 $this->_transport = $tr;
539 $tr = 'custom';
540 }
541
542 $this->_hasZlib = function_exists( 'gzcompress' );
543 $this->_userAgent = sprintf( 'datafeedr.php.%s/%s/zlib=%s', self::VERSION, $tr,
544 $this->_hasZlib ? 'yes' : 'no' );
545 }
546
547 /**
548 * Perform an HTTP request.
549 *
550 * @param string $url
551 * @param array $headers
552 * @param string $postdata
553 *
554 * @return array An array of (status, responseBody)
555 * @throws DatafeedrConnectionError
556 *
557 * @since 2.0.0
558 *
559 */
560 protected function _performRequest( $url, $headers, $postdata ) {
561 $retry = $this->_retry;
562
563 while ( true ) {
564 try {
565 return call_user_func( $this->_transport, $url, $headers, $postdata );
566 } catch ( DatafeedrConnectionError $err ) {
567 if ( $retry <= 0 ) {
568 throw $err;
569 }
570 sleep( $this->_retryTimeout );
571 $retry --;
572 }
573 }
574
575 return array();
576 }
577
578 /**
579 * Convert an ID or an array of IDs to a simple array of IDs.
580 *
581 * @param integer|string|array $id_or_ids An ID or an array of IDs.
582 *
583 * @return array An array of IDs.
584 * @since 1.0.0
585 *
586 */
587 protected function _intarray( $id_or_ids ) {
588
589 if ( is_numeric( $id_or_ids ) ) {
590 return array( $id_or_ids );
591 }
592
593 if ( is_array( $id_or_ids ) ) {
594 return array_values( $id_or_ids );
595 }
596
597 return array();
598 }
599
600 /**
601 * Returns a specific value from an array or object for a given key or property. If key or
602 * property does not exist, returns $default.
603 *
604 * @param array|object $obj An array or object to extract value from.
605 * @param string $prop The array key or object property to get the value for.
606 * @param null $default Optional. The value to return if $obj or $prop does not exist.
607 *
608 * @return mixed|null The returned value.
609 * @since 1.0.0
610 *
611 */
612 protected function _get( $obj, $prop, $default = null ) {
613
614 if ( is_array( $obj ) && isset( $obj[ $prop ] ) ) {
615 return $obj[ $prop ];
616 }
617
618 if ( is_object( $obj ) && isset( $obj->$prop ) ) {
619 return $obj->$prop;
620 }
621
622 return $default;
623 }
624
625 /**
626 * Perform an HTTP POST request using the CURL library.
627 *
628 * @param string $url Request url.
629 * @param array $headers Array of headers.
630 * @param string $postdata Post data.
631 *
632 * @return array (int http status, string response body)
633 * @throws DatafeedrConnectionError Throws error if curl_errno() returns an error.
634 *
635 * @since 1.0.0
636 *
637 */
638 protected function _transportCurl( $url, $headers, $postdata ) {
639
640 $ch = curl_init();
641 curl_setopt( $ch, CURLOPT_URL, $url );
642 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout );
643 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
644 curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
645 curl_setopt( $ch, CURLOPT_POST, 1 );
646 curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
647 curl_setopt( $ch, CURLOPT_ENCODING, '' );
648 $response = curl_exec( $ch );
649 $status = intval( curl_getinfo( $ch, CURLINFO_HTTP_CODE ) );
650 $errno = curl_errno( $ch );
651 $errmsg = curl_error( $ch );
652
653 curl_close( $ch );
654
655 if ( $errno ) {
656 throw new DatafeedrConnectionError( $errmsg, $errno );
657 }
658
659 return array( $status, $response );
660 }
661
662 /**
663 * Perform a HTTP post request using file functions.
664 *
665 * @param string $url Request url.
666 * @param array $headers Array of headers.
667 * @param string $postdata Post data.
668 *
669 * @return array (int http status, string response body)
670 * @throws DatafeedrConnectionError Throws error if $response is false.
671 *
672 * @since 1.0.0
673 *
674 */
675 protected function _transportFile( $url, $headers, $postdata ) {
676
677 $options = array(
678 'http' => array(
679 'method' => 'POST',
680 'content' => $postdata,
681 'header' => implode( "\r\n", $headers ),
682 'ignore_errors' => true,
683 'timeout' => $this->_timeout,
684 )
685 );
686 $context = stream_context_create( $options );
687 $response = file_get_contents( $url, false, $context );
688
689 $status = 200;
690 if ( isset( $http_response_header ) && isset( $http_response_header[0] ) ) {
691 if ( preg_match( '/HTTP.+?(\d\d\d)/', $http_response_header[0], $match ) ) {
692 $status = intval( $match[1] );
693 }
694 } else if ( $response === false ) {
695 throw new DatafeedrConnectionError( "Invalid response" );
696 }
697
698 return array( $status, $response );
699 }
700
701 /**
702 * Perform a HTTP post request using Wordpress functions.
703 *
704 * @param string $url Request url.
705 * @param array $headers Array of headers.
706 * @param string $postdata Post data.
707 *
708 * @return array (int http status, string response body)
709 * @throws DatafeedrHTTPError Throws error if $response is WP_Error.
710 *
711 * @throws DatafeedrConnectionError Throws error if $response is WP_Error and the error code is 'http_request_failed'.
712 * @since 1.0.0
713 *
714 */
715 protected function _transportWordpress( $url, $headers, $postdata ) {
716
717 $ha = array();
718 foreach ( $headers as $h ) {
719 $h = explode( ':', $h, 2 );
720 $ha[ strtolower( $h[0] ) ] = $h[1];
721 }
722
723 $args = array(
724 'method' => 'POST',
725 'headers' => $ha,
726 'body' => $postdata,
727 'httpversion' => '1.1',
728 'timeout' => $this->_timeout,
729 'blocking' => true,
730 'compress' => false,
731 'decompress' => true,
732 'user-agent' => $ha['user-agent']
733 );
734
735 $res = wp_remote_post( $url, $args );
736
737 if ( is_wp_error( $res ) ) {
738 $code = $res->get_error_code();
739 $message = $res->get_error_message();
740
741 if ( $code === 'http_request_failed' ) {
742 throw new DatafeedrConnectionError( $message );
743 } else {
744 throw new DatafeedrHTTPError( $message );
745 }
746 }
747
748 return array( $res['response']['code'], $res['body'] );
749 }
750 }
751
752 /**
753 * Generic Datafeedr API search request.
754 */
755 class DatafeedrSearchRequestBase {
756
757 /**
758 * The DatafeedrAPI object.
759 *
760 * @since 1.0.0
761 * @var DatafeedrAPI $_api
762 */
763 protected $_api;
764
765 /**
766 * An array containing the full response of the last API request.
767 *
768 * @since 1.0.0
769 * @var array $_lastResponse Response array.
770 */
771 protected $_lastResponse;
772
773 /**
774 * DatafeedrSearchRequestBase constructor.
775 *
776 * @param DatafeedrApi $api
777 *
778 * @since 1.0.0
779 *
780 */
781 public function __construct( $api ) {
782 $this->_api = $api;
783 }
784
785 /**
786 * Get the number of found products.
787 *
788 * @return integer
789 * @since 1.0.0
790 *
791 */
792 public function getFoundCount() {
793 return $this->_responseItem( 'found_count', 0 );
794 }
795
796 /**
797 * Get the number of products that can be retrieved from the server.
798 *
799 * @return integer
800 * @since 1.0.0
801 *
802 */
803 public function getResultCount() {
804 return $this->_responseItem( 'result_count', 0 );
805 }
806
807 /**
808 * Returns the full response from the last search.
809 *
810 * Possible items in array include the following:
811 *
812 * Array (
813 * 'found_count' => integer,
814 * 'length' => integer,
815 * 'merchants' => array,
816 * 'networks' => array,
817 * 'price_groups' => array,
818 * 'products' => array,
819 * 'result_count' => integer,
820 * 'status' => array,
821 * 'time' => integer,
822 * 'version' => string,
823 * )
824 *
825 * @return array An array of the full response.
826 * @since 1.0.0
827 *
828 */
829 public function getResponse() {
830 return $this->_lastResponse;
831 }
832
833 /**
834 * Returns a specific item or property from the response data.
835 *
836 * @param string $prop The item or property to get from the response array or object.
837 * @param mixed $default Return if $prop is not found in the array or object.
838 *
839 * @return mixed Specific item or property from response data.
840 * @throws DatafeedrError Throws error if $this->_lastResponse is NULL.
841 *
842 * @since 1.0.0
843 *
844 */
845 protected function _responseItem( $prop, $default ) {
846
847 if ( is_null( $this->_lastResponse ) ) {
848 throw new DatafeedrError( "Reading from an empty request" );
849 }
850
851 if ( is_object( $this->_lastResponse ) && isset( $this->_lastResponse->$prop ) ) {
852 return $this->_lastResponse->$prop;
853 }
854
855 if ( is_array( $this->_lastResponse ) && isset( $this->_lastResponse[ $prop ] ) ) {
856 return $this->_lastResponse[ $prop ];
857 }
858
859 return $default;
860 }
861
862 /**
863 * Sets the $_lastResponse property to the the full response from the last API request.
864 *
865 * @param string $action The request action (ex. status, search, merchants, networks, etc...)
866 * @param array $request The current API request.
867 *
868 * @since 1.0.0
869 *
870 */
871 function _apiCall( $action, $request = null ) {
872 $this->_lastResponse = $this->_api->apiCall( $action, $request );
873 }
874 }
875
876 /**
877 * Class DatafeedrSearchRequest
878 *
879 * Search request for Datafeedr API.
880 */
881 class DatafeedrSearchRequest extends DatafeedrSearchRequestBase {
882
883 protected $_query;
884 protected $_sort;
885 protected $_fields;
886 protected $_limit;
887 protected $_offset;
888 protected $_priceGroups;
889 protected $_excludeDuplicates;
890 protected $_merchantLimit;
891
892 /**
893 * DatafeedrSearchRequest constructor.
894 *
895 * @param DatafeedrApi $api
896 *
897 * @since 1.0.0
898 *
899 */
900 public function __construct( $api ) {
901
902 parent::__construct( $api );
903
904 $this->_query = array();
905 $this->_sort = array();
906 $this->_fields = array();
907 $this->_limit = 0;
908 $this->_offset = 0;
909 $this->_priceGroups = 0;
910 $this->_excludeDuplicates = '';
911 $this->_merchantLimit = 0;
912 }
913
914 /**
915 * Add a query filter.
916 *
917 * @param string $filter Query filter.
918 *
919 * @return DatafeedrSearchRequest Returns $this.
920 * @since 1.0.0
921 *
922 */
923 public function addFilter( $filter ) {
924 $this->_query[] = $filter;
925
926 return $this;
927 }
928
929 /**
930 * Adds a sort field.
931 *
932 * @param string $field Field name.
933 * @param integer $order One of DatafeedrApi::SORT_ASCENDING or DatafeedrApi::SORT_DESCENDING
934 *
935 * @return DatafeedrSearchRequest Returns $this.
936 * @throws DatafeedrError Throws error if sort order is invalid.
937 *
938 * @since 1.0.0
939 *
940 */
941 public function addSort( $field, $order = DatafeedrApi::SORT_ASCENDING ) {
942
943 if ( strlen( $field ) && ( $field[0] == '+' || $field[0] == '-' ) ) {
944 $this->_sort [] = $field;
945 } else if ( $order == DatafeedrApi::SORT_ASCENDING ) {
946 $this->_sort [] = '+' . $field;
947 } else if ( $order == DatafeedrApi::SORT_DESCENDING ) {
948 $this->_sort [] = '-' . $field;
949 } else {
950 throw new DatafeedrError( "Invalid sort order" );
951 }
952
953 return $this;
954 }
955
956 /**
957 * Set which fields to retrieve.
958 *
959 * @param array $fields An array of fields to return for each requested item.
960 *
961 * Example:
962 *
963 * Array (
964 * 'name',
965 * 'price',
966 * 'description',
967 * 'url',
968 * )
969 *
970 * @return DatafeedrSearchRequest Returns $this.
971 * @since 1.0.0
972 *
973 */
974 public function setFields( $fields ) {
975 $this->_fields = $fields;
976
977 return $this;
978 }
979
980 /**
981 * Exclude duplicate filter.
982 *
983 * @param string $filter Equality filter in form "field1 field2 | field3".
984 *
985 * @return DatafeedrSearchRequest Returns $this.
986 * @since 1.0.0
987 *
988 */
989 public function excludeDuplicates( $filter ) {
990
991 if ( is_array( $filter ) ) {
992 $filter = implode( ' ', $filter );
993 }
994
995 $this->_excludeDuplicates = $filter;
996
997 return $this;
998 }
999
1000 /**
1001 * Set a limit of number of records to return.
1002 *
1003 * @param integer $limit The maximum number of records to return.
1004 *
1005 * @return DatafeedrSearchRequest Returns $this.
1006 * @since 1.0.0
1007 *
1008 */
1009 public function setLimit( $limit ) {
1010 $this->_limit = $limit;
1011
1012 return $this;
1013 }
1014
1015 /**
1016 * Set an offset for the search.
1017 *
1018 * @param integer $offset The offset.
1019 *
1020 * @return DatafeedrSearchRequest Returns $this.
1021 * @since 1.0.0
1022 *
1023 */
1024 public function setOffset( $offset ) {
1025 $this->_offset = $offset;
1026
1027 return $this;
1028 }
1029
1030 /**
1031 * Set a limit of results by merchant.
1032 *
1033 * @param integer $limit The limit.
1034 *
1035 * @return DatafeedrSearchRequest Returns $this.
1036 * @since 2.0.3
1037 *
1038 */
1039 public function setMerchantLimit( $limit ) {
1040 $this->_merchantLimit = $limit;
1041
1042 return $this;
1043 }
1044
1045 /**
1046 * Set a price group count.
1047 *
1048 * This should be used in conjunction with the $this->getPriceGroups() method.
1049 *
1050 * Setting $groups to 3 will organize the products into 3 price groups like this:
1051 *
1052 * Array (
1053 * [0] => Array (
1054 * [product_count] => 321
1055 * [max] => 9633332
1056 * [min] => 0
1057 * )
1058 * [1] => Array (
1059 * [product_count] => 116
1060 * [max] => 19266665
1061 * [min] => 9633333
1062 * )
1063 * [2] => Array (
1064 * [product_count] => 43
1065 * [max] => 28900000
1066 * [min] => 19266666
1067 * )
1068 * )
1069 *
1070 * @param integer $groups Number of price groups to create.
1071 *
1072 * @return DatafeedrSearchRequest Returns $this.
1073 * @since 1.0.0
1074 *
1075 */
1076 public function setPriceGroups( $groups ) {
1077 $this->_priceGroups = $groups;
1078
1079 return $this;
1080 }
1081
1082 /**
1083 * Get networks found in this request.
1084 *
1085 * @return array
1086 * @since 1.0.0
1087 *
1088 */
1089 public function getNetworks() {
1090 return $this->_responseItem( 'networks', array() );
1091 }
1092
1093 /**
1094 * Get merchants found in this request.
1095 *
1096 * @return array
1097 * @since 1.0.0
1098 *
1099 */
1100 public function getMerchants() {
1101 return $this->_responseItem( 'merchants', array() );
1102 }
1103
1104 /**
1105 * Get price groups found in this request.
1106 *
1107 * This must be used in conjunction with $this->setPriceGroups();
1108 *
1109 * Returns an array like this:
1110 *
1111 * Array (
1112 * [0] => Array (
1113 * [product_count] => 321
1114 * [max] => 9633332
1115 * [min] => 0
1116 * )
1117 * [1] => Array (
1118 * [product_count] => 116
1119 * [max] => 19266665
1120 * [min] => 9633333
1121 * )
1122 * [2] => Array (
1123 * [product_count] => 43
1124 * [max] => 28900000
1125 * [min] => 19266666
1126 * )
1127 * )
1128 *
1129 * @return array An array of price groups and the product count, min and max prices in each group.
1130 * @since 1.0.0
1131 *
1132 */
1133 public function getPriceGroups() {
1134 return $this->_responseItem( 'price_groups', array() );
1135 }
1136
1137 /**
1138 * Create a request array to use for querying the API.
1139 *
1140 * @return array The $request array().
1141 * @since 1.0.0
1142 *
1143 */
1144 public function getParams() {
1145
1146 $request = array();
1147
1148 if ( $this->_query ) {
1149 $request['query'] = $this->_query;
1150 }
1151
1152 if ( $this->_sort ) {
1153 $request['sort'] = $this->_sort;
1154 }
1155
1156 if ( $this->_fields ) {
1157 $request['fields'] = $this->_fields;
1158 }
1159
1160 if ( $this->_limit ) {
1161 $request['limit'] = $this->_limit;
1162 }
1163
1164 if ( $this->_offset ) {
1165 $request['offset'] = $this->_offset;
1166 }
1167
1168 if ( $this->_priceGroups ) {
1169 $request['price_groups'] = $this->_priceGroups;
1170 }
1171
1172 if ( $this->_merchantLimit ) {
1173 $request['merchant_limit'] = $this->_merchantLimit;
1174 }
1175
1176 if ( $this->_excludeDuplicates ) {
1177 $request['exclude_duplicates'] = $this->_excludeDuplicates;
1178 }
1179
1180 $request['string_ids'] = 1;
1181
1182 return $request;
1183 }
1184
1185 /**
1186 * Run search and return a list of products.
1187 *
1188 * @return array An array of products.
1189 * @throws DatafeedrError Throw error if query is empty.
1190 *
1191 * @since 1.0.0
1192 *
1193 */
1194 public function execute() {
1195
1196 $params = $this->getParams();
1197
1198 if ( ! isset( $params['query'] ) ) {
1199 throw new DatafeedrError( "Query can't be empty" );
1200 }
1201
1202 $this->_apiCall( 'search', $params );
1203
1204 return $this->_responseItem( 'products', array() );
1205 }
1206 }
1207
1208 /**
1209 * Search request for Datafeedr Merchants.
1210 */
1211 class DatafeedrMerchantSearchRequest extends DatafeedrSearchRequestBase {
1212
1213 protected $_query;
1214 protected $_sort;
1215 protected $_fields;
1216 protected $_limit;
1217 protected $_offset;
1218
1219 /**
1220 * DatafeedrMerchantSearchRequest constructor.
1221 *
1222 * @param DatafeedrApi $api
1223 *
1224 * @since 1.0.0
1225 *
1226 */
1227 public function __construct( $api ) {
1228
1229 parent::__construct( $api );
1230
1231 $this->_query = array();
1232 $this->_sort = array();
1233 $this->_fields = array();
1234 $this->_limit = 0;
1235 $this->_offset = 0;
1236 }
1237
1238 /**
1239 * Add a query filter.
1240 *
1241 * @param string $filter Query filter.
1242 *
1243 * @return DatafeedrMerchantSearchRequest Returns $this.
1244 * @since 1.0.0
1245 *
1246 */
1247 public function addFilter( $filter ) {
1248 $this->_query [] = $filter;
1249
1250 return $this;
1251 }
1252
1253 /**
1254 * Add a sort field.
1255 *
1256 * @param string $field Field name.
1257 * @param integer $order One of DatafeedrApi::SORT_ASCENDING or DatafeedrApi::SORT_DESCENDING
1258 *
1259 * @return DatafeedrMerchantSearchRequest Returns $this.
1260 * @throws DatafeedrError Throw error if invalid sort order.
1261 *
1262 * @since 1.0.0
1263 *
1264 */
1265 public function addSort( $field, $order = DatafeedrApi::SORT_ASCENDING ) {
1266
1267 if ( strlen( $field ) && ( $field[0] == '+' || $field[0] == '-' ) ) {
1268 $this->_sort [] = $field;
1269 } else if ( $order == DatafeedrApi::SORT_ASCENDING ) {
1270 $this->_sort [] = '+' . $field;
1271 } else if ( $order == DatafeedrApi::SORT_DESCENDING ) {
1272 $this->_sort [] = '-' . $field;
1273 } else {
1274 throw new DatafeedrError( "Invalid sort order" );
1275 }
1276
1277 return $this;
1278 }
1279
1280 /**
1281 * Set which fields to retrieve.
1282 *
1283 * @param array $fields An array of field names.
1284 *
1285 * @return DatafeedrMerchantSearchRequest Returns $this.
1286 * @since 1.0.0
1287 *
1288 */
1289 public function setFields( $fields ) {
1290 $this->_fields = $fields;
1291
1292 return $this;
1293 }
1294
1295 /**
1296 * Set a limit.
1297 *
1298 * @param integer $limit Number of items to return.
1299 *
1300 * @return DatafeedrMerchantSearchRequest Returns $this.
1301 * @since 1.0.0
1302 *
1303 */
1304 public function setLimit( $limit ) {
1305 $this->_limit = $limit;
1306
1307 return $this;
1308 }
1309
1310 /**
1311 * Set an offset.
1312 *
1313 * @param integer $offset The offset.
1314 *
1315 * @return DatafeedrMerchantSearchRequest Returns $this.
1316 * @since 1.0.0
1317 *
1318 */
1319 public function setOffset( $offset ) {
1320 $this->_offset = $offset;
1321
1322 return $this;
1323 }
1324
1325 /**
1326 * Get networks found in this request.
1327 *
1328 * @return array
1329 * @since 1.0.0
1330 *
1331 */
1332 public function getNetworks() {
1333 return $this->_responseItem( 'networks', array() );
1334 }
1335
1336 /**
1337 * Get merchants found in this request.
1338 *
1339 * @return array
1340 * @since 1.0.0
1341 *
1342 */
1343 public function getMerchants() {
1344 return $this->_responseItem( 'merchants', array() );
1345 }
1346
1347 /**
1348 * Run search and return an array of merchants.
1349 *
1350 * @return array Array of merchants
1351 * @throws DatafeedrError Throws error if query is empty.
1352 *
1353 * @since 1.0.0
1354 *
1355 */
1356 public function execute() {
1357
1358 $params = $this->getParams();
1359
1360 if ( ! isset( $params['query'] ) ) {
1361 throw new DatafeedrError( "Query can't be empty" );
1362 }
1363
1364 $this->_apiCall( 'merchant_search', $params );
1365
1366 return $this->_responseItem( 'merchants', array() );
1367 }
1368
1369 /**
1370 * Create a request array to use for querying the API.
1371 *
1372 * @return array The $request array().
1373 * @since 1.0.0
1374 *
1375 */
1376 public function getParams() {
1377
1378 $request = array();
1379
1380 if ( $this->_query ) {
1381 $request['query'] = $this->_query;
1382 }
1383
1384 if ( $this->_sort ) {
1385 $request['sort'] = $this->_sort;
1386 }
1387
1388 if ( $this->_fields ) {
1389 $request['fields'] = $this->_fields;
1390 }
1391
1392 if ( $this->_limit ) {
1393 $request['limit'] = $this->_limit;
1394 }
1395
1396 if ( $this->_offset ) {
1397 $request['offset'] = $this->_offset;
1398 }
1399
1400 return $request;
1401 }
1402 }
1403
1404 /**
1405 * Generic Amazon request.
1406 */
1407 class DatafeedrAmazonRequest extends DatafeedrSearchRequestBase {
1408
1409 protected $_found = - 1;
1410
1411 protected $_hosts;
1412 protected $_params;
1413 protected $_locale;
1414 protected $_awsAccessKeyId;
1415 protected $_awsSecretKey;
1416 protected $_awsAssociateTag;
1417
1418 const AWS_VERSION = "2011-08-01";
1419
1420 /**
1421 * DatafeedrAmazonRequest constructor
1422 *
1423 * @param DatafeedrApi $api
1424 * @param string $awsAccessKeyId Amazon Access Key.
1425 * @param string $awsSecretKey Amazon Secret Key.
1426 * @param string $awsAssociateTag Amazon Associate tag.
1427 * @param string $locale Optional. Amazon locale (two-letter code).
1428 *
1429 * @throws DatafeedrError Throws error if Amazon Locale is invalid.
1430 * @since 1.0.0
1431 *
1432 */
1433 public function __construct( $api, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale = 'US' ) {
1434
1435 parent::__construct( $api );
1436
1437 $this->_hosts = array(
1438 'AU' => 'webservices.amazon.com.au',
1439 'BR' => 'webservices.amazon.com.br',
1440 'CA' => 'webservices.amazon.ca',
1441 'FR' => 'webservices.amazon.fr',
1442 'DE' => 'webservices.amazon.de',
1443 'IN' => 'webservices.amazon.in',
1444 'IT' => 'webservices.amazon.it',
1445 'JP' => 'webservices.amazon.co.jp',
1446 'MX' => 'webservices.amazon.com.mx',
1447 'NL' => 'webservices.amazon.nl',
1448 'SG' => 'webservices.amazon.sg',
1449 'SA' => 'webservices.amazon.sa',
1450 'ES' => 'webservices.amazon.es',
1451 'SE' => 'webservices.amazon.se',
1452 'TR' => 'webservices.amazon.com.tr',
1453 'AE' => 'webservices.amazon.ae',
1454 'UK' => 'webservices.amazon.co.uk',
1455 'US' => 'webservices.amazon.com',
1456 );
1457
1458 $this->_params = array();
1459 $this->_locale = strtoupper( $locale );
1460
1461 if ( ! isset( $this->_hosts[ $this->_locale ] ) ) {
1462 throw new DatafeedrError( 'Invalid Amazon locale' );
1463 }
1464
1465 $this->_awsAccessKeyId = $awsAccessKeyId;
1466 $this->_awsSecretKey = $awsSecretKey;
1467 $this->_awsAssociateTag = $awsAssociateTag;
1468 }
1469
1470 /**
1471 * Returns all parameters.
1472 *
1473 * @return array
1474 * @since 1.0.0
1475 *
1476 */
1477 public function getParams() {
1478 return $this->_params;
1479 }
1480
1481 /**
1482 * Prepare am API request for Amazon.
1483 *
1484 * @param string $operation
1485 * @param array $params
1486 *
1487 * @return array
1488 * @since 3.0.0
1489 *
1490 */
1491 protected function _amazonRequest( $operation, $params ) {
1492 return array(
1493 'amz_access' => $this->_awsAccessKeyId,
1494 'amz_key' => $this->_awsSecretKey,
1495 'amz_tag' => $this->_awsAssociateTag,
1496 'locale' => $this->_locale,
1497 'operation' => $operation,
1498 'params' => $params,
1499 );
1500 }
1501 }
1502
1503 /**
1504 * Class DatafeedrAmazonSearchRequest
1505 *
1506 * Amazon search request class.
1507 */
1508 class DatafeedrAmazonSearchRequest extends DatafeedrAmazonRequest {
1509
1510 /**
1511 * Add a parameter.
1512 *
1513 * @param string $name Parameter name.
1514 * @param string $value Parameter value.
1515 *
1516 * @return DatafeedrAmazonSearchRequest Returns $this.
1517 *
1518 * @since 1.0.0
1519 *
1520 * @see https://webservices.amazon.com/paapi5/documentation/search-items.html#ItemLookup-rp
1521 */
1522 public function addParam( $name, $value ) {
1523 $this->_params[ $name ] = $value;
1524
1525 return $this;
1526 }
1527
1528 /**
1529 * Run search and return an array of products.
1530 *
1531 * IMPORTANT - The Amazon API returns a MAXIMUM of 10 products per API request and a maximum of
1532 * 50 products per search query.
1533 *
1534 * @return array An array of products.
1535 * @throws DatafeedrError
1536 * @since 1.0.0
1537 *
1538 */
1539 public function execute() {
1540
1541 $params = array_filter( $this->_params );
1542 $req = $this->_amazonRequest( 'SearchItems', $params );
1543 $this->_apiCall( 'amazon_find', $req );
1544
1545 return $this->_responseItem( 'products', array() );
1546 }
1547 }
1548
1549 /**
1550 * Class DatafeedrAmazonLookupRequest
1551 *
1552 * Amazon lookup request.
1553 */
1554 class DatafeedrAmazonLookupRequest extends DatafeedrAmazonRequest {
1555
1556 /**
1557 * Add a parameter.
1558 *
1559 * @param string $name Parameter name - one of 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN'
1560 * @param string|array $value Parameter value or an array of values (up to 10).
1561 *
1562 * @return DatafeedrAmazonLookupRequest Returns $this.
1563 *
1564 * @since 1.0.0
1565 *
1566 * @see https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp
1567 */
1568 public function addParam( $name, $value ) {
1569 $this->_params[ $name ] = $value;
1570
1571 return $this;
1572 }
1573
1574 /**
1575 * Run search and return an array of products.
1576 *
1577 * @return array An array of products.
1578 * @since 1.0.0
1579 *
1580 */
1581 public function execute() {
1582
1583 $params = array_filter( $this->_params );
1584 $types = array( 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN', 'asin', 'sku', 'upc', 'ean', 'isbn' );
1585
1586 foreach ( $types as $type ) {
1587 if ( isset( $params[ $type ] ) ) {
1588 $params['ItemIdType'] = strtoupper( $type );
1589 $params['ItemIds'] = $params[ $type ];
1590 if ( is_string( $params['ItemIds'] ) ) {
1591 $params['ItemIds'] = explode( ',', $params['ItemIds'] );
1592 }
1593 unset( $params[ $type ] );
1594 }
1595 }
1596
1597 $req = $this->_amazonRequest( 'GetItems', $params );
1598 $this->_apiCall( 'amazon_find', $req );
1599
1600 return $this->_responseItem( 'products', array() );
1601 }
1602 }
1603
1604 /**
1605 * Class DatafeedrError.
1606 *
1607 * Generic Api error.
1608 */
1609 class DatafeedrError extends Exception {
1610 }
1611
1612 /**
1613 * Class DatafeedrBadRequestError.
1614 *
1615 * API error: Invalid Request.
1616 */
1617 class DatafeedrBadRequestError extends DatafeedrError {
1618 }
1619
1620 /**
1621 * Class DatafeedrAuthenticationError.
1622 *
1623 * API error: Authentication failed.
1624 */
1625 class DatafeedrAuthenticationError extends DatafeedrError {
1626 }
1627
1628 /**
1629 * Class DatafeedrLimitExceededError.
1630 *
1631 * API error: Query limit exceeded.
1632 */
1633 class DatafeedrLimitExceededError extends DatafeedrError {
1634 }
1635
1636 /**
1637 * Class DatafeedrHTTPError.
1638 *
1639 * API error: Unspecified HTTP error.
1640 */
1641 class DatafeedrHTTPError extends DatafeedrError {
1642 }
1643
1644 /**
1645 * Class DatafeedrConnectionError.
1646 *
1647 * API error: Connection error.
1648 */
1649 class DatafeedrConnectionError extends DatafeedrError {
1650 }
1651
1652 /**
1653 * Class DatafeedrQueryError.
1654 *
1655 * API error: Error in the search query.
1656 */
1657 class DatafeedrQueryError extends DatafeedrError {
1658 }
1659
1660 /**
1661 * Class DatafeedrExternalError.
1662 *
1663 * API error: External service error.
1664 */
1665 class DatafeedrExternalError extends DatafeedrError {
1666 }
1667
1668 /**
1669 * Class DatafeedrServerError.
1670 *
1671 * API error: Internal server error.
1672 */
1673 class DatafeedrServerError extends DatafeedrError {
1674 }