PluginProbe
Datafeedr API / 1.4.3
Datafeedr API v1.4.3
1.4.3 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 All 182 releases
← All changes | libraries/datafeedr.php +1969 -829 1.0.15 → 1.4.3 View file →
@@ -1,968 +1,2108 @@
1 1 <?php
2 2
3 3 /**
4 - * Datafeedr Api Client Library.
4 + * Datafeedr API PHP Library
5 5 *
6 - * @version 0.1b.6693
7 - * @copyright Datafeedr 2007 ~ 2013 - All Rights Reserved
6 + * @version 3.0.0
8 7 *
9 - * @mainpage
8 + * Copyright (c) 2007 ~ 2017, Datafeedr - All Rights Reserved
10 9 *
11 - * Example of use:
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.
12 13 *
13 - * @code
14 - * $api = new DatafeedrApi('<access id>', '<secret key>');
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
15 25 *
16 - * foreach($api->getMerchants() as $merchant)
17 - * print $merchant['name'];
18 - *
19 - * $search = $api->searchRequest();
20 - * $search->addFilter("name LIKE shoe");
21 - * $search->addFilter("price < 100");
22 - * $search->addSort("price", DatafeedrApi::SORT_ASCENDING);
23 - * $search->setLimit(25);
24 - *
25 - * $products = $search->execute();
26 - *
27 - * foreach($products as $product)
28 - * print $product['name'];
29 - *
30 - * @endcode
31 - *
32 - *
33 - *
34 -**/
26 + * This is the core Datafeedr API class.
27 + */
28 +class DatafeedrApi {
35 29
36 -if(!class_exists('DatafeedrApi', false)) {
30 + protected $_accessId;
31 + protected $_secretKey;
37 32
38 -/**
39 - * Datafeedr API core class.
40 - **/
41 -class DatafeedrApi
42 -{
43 - protected $_accessId;
44 - protected $_secretKey;
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;
45 44
46 - protected $_transport;
47 - protected $_timeout;
48 - protected $_url;
49 - protected $_returnObjects;
45 + protected $_status;
46 + protected $_errors;
50 47
51 - protected $_status;
48 + const SORT_DESCENDING = - 1;
49 + const SORT_ASCENDING = + 1;
52 50
53 - const SORT_DESCENDING = -1;
54 - const SORT_ASCENDING = +1;
51 + const DEFAULT_HOST = 'api.datafeedr.com';
55 52
56 - const DEFAULT_URL = 'http://api.datafeedr.com';
57 - const DEFAULT_TIMEOUT = 30;
53 + const REQUEST_COMPRESSION_THRESHOLD = 1024;
58 54
59 - const VERSION = '0.1b.6693';
55 + const VERSION = '3.0.0';
60 56
61 - /**
62 - * Constructor.
63 - *
64 - * @param string $accessId Access ID.
65 - * @param object $secretKey Secret key.
66 - * @param string|callable $transport (optional) HTTP transport function.
67 - * @param int $timeout (optional) HTTP connection timeout, in seconds.
68 - * @param bool $returnObjects (optional) if TRUE, responses are objects, otherwise associative arrays.
69 - *
70 - * The optional $transport parameter tells how HTTP requests should be made.
71 - * It can be either a string that describes one of built-in transports ("curl", "file" or "socket"),
72 - * or a callable object that should accept an url, an array of headers and a string of post data and
73 - * should return an array [int http response status, string response body].
74 - *
75 - **/
76 - public function __construct($accessId, $secretKey, $transport = 'curl', $timeout = 0, $returnObjects = FALSE) {
77 - $this->_accessId = $accessId;
78 - $this->_secretKey = $secretKey;
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 ) {
79 86
80 - $this->_errors = array(
81 - 1 => 'DatafeedrBadRequestError',
82 - 2 => 'DatafeedrAuthenticationError',
83 - 3 => 'DatafeedrLimitExceededError',
84 - 4 => 'DatafeedrQueryError',
85 - 7 => 'DatafeedrExternalError',
86 - 9 => 'DatafeedrError',
87 - );
87 + $this->_accessId = $accessId;
88 + $this->_secretKey = $secretKey;
88 89
89 - $this->_url = self::DEFAULT_URL;
90 - $this->_timeout = $timeout ? $timeout : self::DEFAULT_TIMEOUT;
91 - $this->_returnObjects = $returnObjects;
90 + $this->_errors = array(
91 + 1 => 'DatafeedrBadRequestError',
92 + 2 => 'DatafeedrAuthenticationError',
93 + 3 => 'DatafeedrLimitExceededError',
94 + 4 => 'DatafeedrQueryError',
95 + 7 => 'DatafeedrExternalError',
96 + 9 => 'DatafeedrServerError',
97 + );
92 98
93 - switch($transport) {
94 - case 'curl':
95 - $this->_transport = array($this, '_transportCurl');
96 - break;
97 - case 'file':
98 - $this->_transport = array($this, '_transportFile');
99 - break;
100 - case 'socket':
101 - $this->_transport = array($this, '_transportSocket');
102 - break;
103 - default:
104 - if(!is_callable($transport)) {
105 - throw new DatafeedrError("Transport must be a function");
106 - }
107 - $this->_transport = $transport;
108 - }
109 - }
99 + $this->_parseOptions( $options );
100 + }
110 101
111 - /**
112 - * Return status information.
113 - *
114 - * @return array
115 - *
116 - **/
117 - public function getStatus() {
118 - $this->apiCall('status');
119 - return $this->_status;
120 - }
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' );
121 111
122 - /**
123 - * Return status information from the last request.
124 - *
125 - * If no Api request have been made, return NULL
126 - *
127 - * @return array|null
128 - *
129 - **/
130 - public function lastStatus() {
131 - return $this->_status;
132 - }
112 + return $this->_status;
113 + }
133 114
134 - /**
135 - * Return the list of networks.
136 - *
137 - * @param int|array $networkId (optional) Network id or an array of network ids
138 - * @param bool $includeEmpty (optional) If FALSE, omit networks with 0 products
139 - * @param array $fields (optional) list of fields to retrieve
140 - * @return array
141 - *
142 - **/
143 - public function getNetworks($networkId = NULL, $includeEmpty = FALSE, $fields = NULL) {
144 - $request = array();
145 - if($networkId) {
146 - $request['_ids'] = $this->_intarray($networkId);
147 - }
148 - $request['skip_empty'] = intval(!$includeEmpty);
149 - if($fields) {
150 - $request['fields'] = $fields;
151 - }
152 - $response = $this->apiCall('networks', $request);
153 - return $this->_get($response, 'networks');
154 - }
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 + }
155 127
156 - /**
157 - * Return the list of merchants.
158 - *
159 - * @param int|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) list of fields to retrieve
162 - * @return array
163 - *
164 - **/
165 - public function getMerchants($networkId = NULL, $includeEmpty = FALSE, $fields = NULL) {
166 - $request = array();
167 - if($networkId) {
168 - $request['source_ids'] = $this->_intarray($networkId);
169 - }
170 - $request['skip_empty'] = intval(!$includeEmpty);
171 - if($fields) {
172 - $request['fields'] = $fields;
173 - }
174 - $response = $this->apiCall('merchants', $request);
175 - return $this->_get($response, 'merchants');
176 - }
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 ) {
177 141
178 - /**
179 - * Return the list of merchants by their ids.
180 - *
181 - * @param int|array $merchantId Merchant id or array of network ids
182 - * @param bool $includeEmpty (optional) If FALSE, omit merchants with 0 products
183 - * @param array $fields (optional) list of fields to retrieve
184 - * @return array
185 - *
186 - **/
187 - public function getMerchantsById($merchantId, $includeEmpty = FALSE, $fields = NULL) {
188 - $request = array();
189 - $request['_ids'] = $this->_intarray($merchantId);
190 - $request['skip_empty'] = intval(!$includeEmpty);
191 - if($fields) {
192 - $request['fields'] = $fields;
193 - }
194 - $response = $this->apiCall('merchants', $request);
195 - return $this->_get($response, 'merchants');
196 - }
142 + $request = array();
197 143
198 - /**
199 - * Return the list of searchable fields.
200 - *
201 - * @param int|array $networkId (optional) Network id or array of network ids
202 - * @return array
203 - *
204 - **/
205 - public function getFields($networkId = NULL) {
206 - $request = array();
207 - if($networkId) {
208 - $request['source_ids'] = $this->_intarray($networkId);
209 - }
210 - $response = $this->apiCall('fields', $request);
211 - return $this->_get($response, 'fields');
212 - }
144 + if ( $networkId ) {
145 + $request['_ids'] = $this->_intarray( $networkId );
146 + }
213 147
214 - /**
215 - * Return the list of products by their ids.
216 - *
217 - * @param int|array $productId Product id or an array of products ids.
218 - * @param array $fields (optional) list of fields to retrieve.
219 - * @return array
220 - *
221 - **/
222 - public function getProducts($productId, $fields = NULL) {
223 - $request = array();
224 - $request['_ids'] = $this->_intarray($productId);
225 - $request['string_ids'] = 1;
226 - if($fields) {
227 - $request['fields'] = $fields;
228 - }
229 - $response = $this->apiCall('get', $request);
230 - return $this->_get($response, 'products');
231 - }
148 + $request['skip_empty'] = intval( ! $includeEmpty );
232 149
233 - /**
234 - * Return the list of Zanox merchant ids ("zmids").
235 - *
236 - * @param int|array $merchantId Merchant id or an array of merchant ids.
237 - * @param int $adspaceId Zanox adspace Id.
238 - * @param string $connectId Zanox connection Id.
239 - * @return array
240 - *
241 - **/
242 - public function getZanoxMerchantIds($merchantId, $adspaceId, $connectId) {
243 - $request = array();
244 - $request['merchant_ids'] = $this->_intarray($merchantId);
245 - $request['adspace_id'] = $adspaceId;
246 - $request['connect_id'] = $connectId;
247 - $response = $this->apiCall('zanox_merchant_ids', $request);
248 - return $this->_get($response, 'zanox_merchant_ids');
249 - }
150 + if ( $fields ) {
151 + $request['fields'] = $fields;
152 + }
250 153
251 - /**
252 - * Create a new DatafeedrSearchRequest object.
253 - *
254 - * @return DatafeedrSearchRequest
255 - *
256 - **/
257 - public function searchRequest() {
258 - return new DatafeedrSearchRequest($this);
259 - }
154 + $response = $this->apiCall( 'networks', $request );
260 155
261 - /**
262 - * Create a new DatafeedrAmazonSearchRequest object.
263 - *
264 - * @return DatafeedrAmazonSearchRequest
265 - *
266 - **/
267 - public function amazonSearchRequest($awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale="US") {
268 - return new DatafeedrAmazonSearchRequest($this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale);
269 - }
156 + return $this->_get( $response, 'networks' );
157 + }
270 158
271 - /**
272 - * Create a new DatafeedrAmazonLookupRequest object.
273 - *
274 - * @return DatafeedrAmazonLookupRequest
275 - *
276 - **/
277 - public function amazonLookupRequest($awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale="US") {
278 - return new DatafeedrAmazonLookupRequest($this, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale);
279 - }
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 ) {
280 172
281 - /**
282 - * Perform the raw Api call.
283 - *
284 - * @param string $action Api action.
285 - * @param array $request (optional) Request data.
286 - * @return array
287 - *
288 - **/
289 - public function apiCall($action, $request = NULL) {
290 - if(!$request) {
291 - $request = array();
292 - }
173 + $request = array();
293 174
294 - $request['aid'] = $this->_accessId;
295 - $request['timestamp'] = gmdate('Y-m-d H:i:s');
175 + if ( $networkId ) {
176 + $request['source_ids'] = $this->_intarray( $networkId );
177 + }
296 178
297 - $message = $request['aid'] .$action . $request['timestamp'];
298 - $request['signature'] = hash_hmac('sha256', $message, $this->_secretKey, FALSE);
179 + $request['skip_empty'] = intval( ! $includeEmpty );
299 180
300 - $postdata = json_encode($request);
301 - $url = $this->_url . '/' . $action;
302 - $headers = array(
303 - 'Content-Type: application/json',
304 - 'Accept: application/json',
305 - 'Content-Length: '. strlen($postdata),
306 - 'Connection: close',
307 - 'User-Agent: datafeedr.php.' . self::VERSION
308 - );
309 - list($status, $response) = call_user_func($this->_transport, $url, $headers, $postdata);
310 - if(strlen($response)) {
311 - $response = json_decode($response, !$this->_returnObjects);
312 - }
181 + if ( $fields ) {
182 + $request['fields'] = $fields;
183 + }
313 184
314 - $error = $this->_get($response, 'error');
315 - if($error) {
316 - $type = $this->_get($response, 'type');
317 - $cls = isset($this->_errors[$type]) ? $this->_errors[$type] : 'DatafeedrError';
318 - throw new $cls($this->_get($response, 'message'), $error);
319 - }
185 + $response = $this->apiCall( 'merchants', $request );
320 186
321 - if($status != 200) {
322 - throw new DatafeedrHTTPError("Status $status");
323 - }
187 + return $this->_get( $response, 'merchants' );
188 + }
324 189
325 - $this->_status = $this->_get($response, 'status');
326 - return $response;
327 - }
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 ) {
328 203
329 - protected function _intarray($id_or_ids) {
330 - if(is_numeric($id_or_ids)) {
331 - return array($id_or_ids);
332 - }
333 - if(is_array($id_or_ids)) {
334 - return $id_or_ids;
335 - }
336 - return array();
337 - }
204 + $request = array();
205 + $request['_ids'] = $this->_intarray( $merchantId );
206 + $request['skip_empty'] = intval( ! $includeEmpty );
338 207
339 - protected function _get($obj, $prop, $default=NULL) {
340 - if(is_array($obj) && isset($obj[$prop])) {
341 - return $obj[$prop];
342 - }
343 - if(is_object($obj) && isset($obj->$prop)) {
344 - return $obj->$prop;
345 - }
346 - return $default;
347 - }
208 + if ( $fields ) {
209 + $request['fields'] = $fields;
210 + }
348 211
212 + $response = $this->apiCall( 'merchants', $request );
349 213
350 - /**
351 - * Perform a HTTP post request by the means of the curl library.
352 - *
353 - * @param string $url Request url.
354 - * @param array $headers Array of headers.
355 - * @param string $postdata Post data.
356 - * @return array (int http status, string response body)
357 - *
358 - **/
359 - protected function _transportCurl($url, $headers, $postdata) {
360 - $ch = curl_init();
361 - curl_setopt($ch, CURLOPT_URL, $url);
362 - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout);
363 - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
364 - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
365 - curl_setopt($ch, CURLOPT_POST, 1);
366 - curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
214 + return $this->_get( $response, 'merchants' );
215 + }
367 216
368 - $response = curl_exec($ch);
369 - $status = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
370 - $errno = curl_errno($ch);
371 - $errmsg = curl_error($ch);
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 ) {
372 229
373 - curl_close($ch);
230 + $request = array();
374 231
375 - if($errno) {
376 - throw new DatafeedrHTTPError($errmsg, $errno);
377 - }
232 + if ( $networkId ) {
233 + $request['source_ids'] = $this->_intarray( $networkId );
234 + }
378 235
379 - return array($status, $response);
380 - }
236 + $response = $this->apiCall( 'fields', $request );
381 237
382 - /**
383 - * Perform a HTTP post request using file functions.
384 - *
385 - * @param string $url Request url.
386 - * @param array $headers Array of headers.
387 - * @param string $postdata Post data.
388 - * @return array (int http status, string response body)
389 - *
390 - **/
391 - protected function _transportFile($url, $headers, $postdata) {
392 - $options = array('http' => array(
393 - 'method' => 'POST',
394 - 'content' => $postdata,
395 - 'header' => implode("\r\n", $headers),
396 - 'ignore_errors' => TRUE,
397 - 'timeout' => $this->_timeout,
398 - ));
399 - $context = stream_context_create($options);
400 - $response = file_get_contents($url, false, $context);
238 + return $this->_get( $response, 'fields' );
239 + }
401 240
402 - $status = 200;
403 - if(isset($http_response_header) && isset($http_response_header[0])) {
404 - if(preg_match('/HTTP.+?(\d\d\d)/', $http_response_header[0], $match)) {
405 - $status = intval($match[1]);
406 - }
407 - } else if($response === false) {
408 - throw new DatafeedrHTTPError("HTTP error: invalid response");
409 - }
410 - return array($status, $response);
411 - }
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 ) {
412 253
413 - /**
414 - * Perform a HTTP post request using sockets.
415 - *
416 - * @param string $url Request url.
417 - * @param array $headers Array of headers.
418 - * @param string $postdata Post data.
419 - * @return array (int http status, string response body)
420 - *
421 - **/
422 - protected function _transportSocket($url, $headers, $postdata) {
423 - $parts = parse_url($url);
424 - $errno = 0;
425 - $errmsg = '';
254 + $request = array();
255 + $request['_ids'] = $this->_intarray( $productId );
256 + $request['string_ids'] = 1;
426 257
427 - $fp = fsockopen($parts['host'], 80, $errno, $errmsg, $this->_timeout);
428 - if(!$fp) {
429 - throw new DatafeedrHTTPError($errmsg, $errno);
430 - }
258 + if ( $fields ) {
259 + $request['fields'] = $fields;
260 + }
431 261
432 - fwrite($fp, "POST " . $parts['path'] . " HTTP/1.1\r\n");
433 - fwrite($fp, implode("\r\n", $headers) . "\r\n\r\n");
434 - fwrite($fp, $postdata);
262 + $response = $this->apiCall( 'get', $request );
435 263
436 - $buf = '';
437 - while(!feof($fp)) {
438 - $buf .= fgets($fp, 1024);
439 - }
440 - fclose($fp);
264 + return $this->_get( $response, 'products' );
265 + }
441 266
442 - $buf = explode("\r\n\r\n", $buf, 2);
443 - if(count($buf) != 2) {
444 - throw new DatafeedrHTTPError("Invalid response");
445 - }
446 - if(preg_match('/HTTP.+?(\d\d\d)/', $buf[0], $match)) {
447 - $status = intval($match[1]);
448 - } else {
449 - throw new DatafeedrHTTPError("Invalid status");
450 - }
451 - return array($status, $buf[1]);
452 - }
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 + }
453 780 }
454 -}
455 781
456 -if(!class_exists('DatafeedrSearchRequestBase', false)) {
457 782 /**
458 783 * Generic Datafeedr API search request.
459 - **/
460 -class DatafeedrSearchRequestBase
461 -{
462 - protected $_api;
463 - protected $_lastResponse;
784 + */
785 +class DatafeedrSearchRequestBase {
464 786
465 - /**
466 - * Constructor.
467 - *
468 - * @param object $api DatafeedrApi object.
469 - *
470 - **/
471 - public function __construct($api) {
472 - $this->_api = $api;
473 - }
787 + /**
788 + * The DatafeedrAPI object.
789 + *
790 + * @since 1.0.0
791 + * @var DatafeedrAPI $_api
792 + */
793 + protected $_api;
474 794
475 - /**
476 - * Get the number of found products.
477 - *
478 - * @return int
479 - *
480 - **/
481 - public function getFoundCount() {
482 - return $this->_responseItem('found_count', 0);
483 - }
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;
484 802
485 - /**
486 - * Get the number of products that can be retrieved from the server.
487 - *
488 - * @return int
489 - *
490 - **/
491 - public function getResultCount() {
492 - return $this->_responseItem('result_count', 0);
493 - }
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 + }
494 814
495 - /**
496 - * Return the response from the last search.
497 - *
498 - * @return array
499 - *
500 - **/
501 - public function getResponse() {
502 - return $this->_lastResponse;
503 - }
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 + }
504 825
505 - protected function _responseItem($prop, $default) {
506 - if(is_null($this->_lastResponse)) {
507 - throw new DatafeedrError("Reading from an empty request");
508 - }
509 - if(is_object($this->_lastResponse) && isset($this->_lastResponse->$prop)) {
510 - return $this->_lastResponse->$prop;
511 - }
512 - if(is_array($this->_lastResponse) && isset($this->_lastResponse[$prop])) {
513 - return $this->_lastResponse[$prop];
514 - }
515 - return $default;
516 - }
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 + }
517 836
518 - function _apiCall($action, $request = NULL) {
519 - $this->_lastResponse = $this->_api->apiCall($action, $request);
520 - }
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 + }
521 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 + }
522 916 }
523 -}
524 917
525 -if(!class_exists('DatafeedrSearchRequest', false)) {
526 918 /**
919 + * Class DatafeedrSearchRequest
920 + *
527 921 * Search request for Datafeedr API.
528 -**/
529 -class DatafeedrSearchRequest extends DatafeedrSearchRequestBase
530 -{
531 - /**
532 - * Constructor.
533 - *
534 - * @param object $api DatafeedrApi object.
535 - *
536 - **/
537 - public function __construct($api) {
538 - parent::__construct($api);
922 + */
923 +class DatafeedrSearchRequest extends DatafeedrSearchRequestBase {
539 924
540 - $this->_query = array();
541 - $this->_sort = array();
542 - $this->_fields = array();
543 - $this->_limit = 0;
544 - $this->_offset = 0;
545 - $this->_priceGroups = 0;
546 - $this->_excludeDuplicates = "";
547 - }
925 + protected $_query;
926 + protected $_sort;
927 + protected $_fields;
928 + protected $_limit;
929 + protected $_offset;
930 + protected $_priceGroups;
931 + protected $_excludeDuplicates;
932 + protected $_merchantLimit;
548 933
549 - /**
550 - * Add a query filter.
551 - *
552 - * @param string $filter Query filter.
553 - * @return $this
554 - *
555 - **/
556 - public function addFilter($filter) {
557 - $this->_query []= $filter;
558 - return $this;
559 - }
934 + /**
935 + * DatafeedrSearchRequest constructor.
936 + *
937 + * @since 1.0.0
938 + *
939 + * @param DatafeedrApi $api
940 + *
941 + */
942 + public function __construct( $api ) {
560 943
561 - /**
562 - * Add a sort field.
563 - *
564 - * @param string $field Field name.
565 - * @param int $order One of DatafeedrApi::SORT_ASCENDING or DatafeedrApi::SORT_DESCENDING
566 - * @return $this
567 - *
568 - **/
569 - public function addSort($field, $order = DatafeedrApi::SORT_ASCENDING) {
570 - if(strlen($field) && ($field[0] == '+' || $field[0] == '-')) {
571 - $this->_sort []= $field;
572 - } else if($order == DatafeedrApi::SORT_ASCENDING) {
573 - $this->_sort []= '+' . $field;
574 - } else if($order == DatafeedrApi::SORT_DESCENDING) {
575 - $this->_sort []= '-' . $field;
576 - } else {
577 - throw new DatafeedrError("Invalid sort order");
578 - }
579 - return $this;
580 - }
944 + parent::__construct( $api );
581 945
582 - /**
583 - * Set which fields to retrieve.
584 - *
585 - * @param array $fields List of field names.
586 - * @return $this
587 - *
588 - **/
589 - public function setFields($fields) {
590 - $this->_fields = $fields;
591 - return $this;
592 - }
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 + }
593 955
594 - /**
595 - * Exclude duplicate results.
596 - *
597 - * @param string $filter Equality filter in form "field1 field2 | field3".
598 - * @return $this
599 - *
600 - **/
601 - public function excludeDuplicates($filter) {
602 - if(is_array($filter)) {
603 - $filter = implode(' ', $filter);
604 - }
605 - $this->_excludeDuplicates = $filter;
606 - return $this;
607 - }
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;
608 967
609 - /**
610 - * Set a limit.
611 - *
612 - * @param int $limit The limit.
613 - * @return $this
614 - *
615 - **/
616 - public function setLimit($limit) {
617 - $this->_limit = $limit;
618 - return $this;
619 - }
968 + return $this;
969 + }
620 970
621 - /**
622 - * Set an offset.
623 - *
624 - * @param int $offset The offset.
625 - * @return $this
626 - *
627 - **/
628 - public function setOffset($offset) {
629 - $this->_offset = $offset;
630 - return $this;
631 - }
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 ) {
632 985
633 - /**
634 - * Set a price group count.
635 - *
636 - * @param int $groups Group count.
637 - * @return $this
638 - *
639 - **/
640 - public function setPriceGroups($groups) {
641 - $this->_priceGroups = $groups;
642 - return $this;
643 - }
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 + }
644 995
996 + return $this;
997 + }
645 998
646 - /**
647 - * Get found networks.
648 - *
649 - * @return array
650 - *
651 - **/
652 - public function getNetworks() {
653 - return $this->_responseItem('networks', array());
654 - }
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;
655 1019
656 - /**
657 - * Get found merchants.
658 - *
659 - * @return array
660 - *
661 - **/
662 - public function getMerchants() {
663 - return $this->_responseItem('merchants', array());
664 - }
1020 + return $this;
1021 + }
665 1022
666 - /**
667 - * Get found price groups.
668 - *
669 - * @return array
670 - *
671 - **/
672 - public function getPriceGroups() {
673 - return $this->_responseItem('price_groups', array());
674 - }
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 ) {
675 1033
676 - /**
677 - * Create a request object to use with the API.
678 - *
679 - * @return array
680 - *
681 - **/
682 - public function getParams() {
683 - $request = array();
684 - if($this->_query) {
685 - $request['query'] = $this->_query;
686 - }
687 - if($this->_sort) {
688 - $request['sort'] = $this->_sort;
689 - }
690 - if($this->_fields) {
691 - $request['fields'] = $this->_fields;
692 - }
693 - if($this->_limit) {
694 - $request['limit'] = $this->_limit;
695 - }
696 - if($this->_offset) {
697 - $request['offset'] = $this->_offset;
698 - }
699 - if($this->_priceGroups) {
700 - $request['price_groups'] = $this->_priceGroups;
701 - }
702 - if($this->_excludeDuplicates) {
703 - $request['exclude_duplicates'] = $this->_excludeDuplicates;
704 - }
705 - $request['string_ids'] = 1;
706 - return $request;
707 - }
1034 + if ( is_array( $filter ) ) {
1035 + $filter = implode( ' ', $filter );
1036 + }
708 1037
709 - /**
710 - * Run search and return a list of products.
711 - *
712 - * @return array
713 - *
714 - **/
715 - public function execute() {
716 - $params = $this->getParams();
717 - if(!isset($params['query'])) {
718 - throw new DatafeedrError("Query can't be empty");
719 - }
720 - $this->_apiCall('search', $params);
721 - return $this->_responseItem('products', array());
722 - }
1038 + $this->_excludeDuplicates = $filter;
723 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 + }
724 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 + }
725 1446 }
726 1447
727 -if(!class_exists('DatafeedrAmazonRequest', false)) {
728 1448 /**
729 1449 * Generic Amazon request.
730 - **/
731 -class DatafeedrAmazonRequest extends DatafeedrSearchRequestBase
732 -{
733 - protected $_found = -1;
1450 + */
1451 +class DatafeedrAmazonRequest extends DatafeedrSearchRequestBase {
734 1452
735 - const AWS_VERSION = "2011-08-01";
1453 + protected $_found = - 1;
736 1454
737 - /**
738 - * Constructor.
739 - *
740 - * @param object $api DatafeedrApi object.
741 - *
742 - **/
743 - public function __construct($api, $awsAccessKeyId, $awsSecretKey, $awsAssociateTag, $locale="US") {
744 - parent::__construct($api);
745 - $this->_hosts = array(
746 - "CA" => "webservices.amazon.ca",
747 - "CN" => "webservices.amazon.cn",
748 - "DE" => "webservices.amazon.de",
749 - "ES" => "webservices.amazon.es",
750 - "FR" => "webservices.amazon.fr",
751 - "IT" => "webservices.amazon.it",
752 - "JP" => "webservices.amazon.co.jp",
753 - "UK" => "webservices.amazon.co.uk",
754 - "US" => "webservices.amazon.com",
755 - );
1455 + protected $_hosts;
1456 + protected $_params;
1457 + protected $_locale;
1458 + protected $_awsAccessKeyId;
1459 + protected $_awsSecretKey;
1460 + protected $_awsAssociateTag;
756 1461
757 - $this->_params = array();
758 - $this->_locale = strtoupper($locale);
1462 + const AWS_VERSION = "2011-08-01";
759 1463
760 - if(!isset($this->_hosts[$this->_locale])) {
761 - throw new DatafeedrError("Invalid Amazon locale");
762 - }
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' ) {
763 1479
764 - $this->_awsAccessKeyId = $awsAccessKeyId;
765 - $this->_awsSecretKey = $awsSecretKey;
766 - $this->_awsAssociateTag = $awsAssociateTag;
767 - }
1480 + parent::__construct( $api );
768 1481
769 - /**
770 - * Returns all parameters.
771 - *
772 - * @return array
773 - *
774 - **/
775 - public function getParams() {
776 - return $this->_params;
777 - }
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 + );
778 1502
779 - protected function _amazonUrl($operation, $params, $defaults=NULL) {
780 - $params = array_filter($params);
1503 + $this->_params = array();
1504 + $this->_locale = strtoupper( $locale );
781 1505
782 - if(!is_null($defaults)) {
783 - foreach($defaults as $k => $v) {
784 - if(!isset($params[$k])) {
785 - $params[$k] = $v;
786 - }
787 - }
788 - }
1506 + if ( ! isset( $this->_hosts[ $this->_locale ] ) ) {
1507 + throw new DatafeedrError( 'Invalid Amazon locale' );
1508 + }
789 1509
790 - $params["Operation"] = $operation;
791 - $params["Service"] = "AWSECommerceService";
792 - $params["AWSAccessKeyId"] = $this->_awsAccessKeyId;
793 - $params["AssociateTag"] = $this->_awsAssociateTag;
794 - $params["Version"] = self::AWS_VERSION;
795 - $params["Timestamp"] = gmdate("Y-m-d\\TH:i:s\\Z");
1510 + $this->_awsAccessKeyId = $awsAccessKeyId;
1511 + $this->_awsSecretKey = $awsSecretKey;
1512 + $this->_awsAssociateTag = $awsAssociateTag;
1513 + }
796 1514
797 - ksort($params);
798 - $query = array();
799 - foreach($params as $k => $v) {
800 - if(is_array($v)) {
801 - $v = implode(',', $v);
802 - }
803 - $query []= $k . '=' . rawurlencode($v);
804 - }
805 - $query = implode('&', $query);
806 - $host = $this->_hosts[$this->_locale];
807 - $path = "/onca/xml";
808 - $subj = sprintf("GET\n%s\n%s\n%s", $host, $path, $query);
809 - $sign = rawurlencode(base64_encode(hash_hmac("sha256", $subj, $this->_awsSecretKey, TRUE)));
810 - return "http://{$host}{$path}?{$query}&Signature={$sign}";
811 - }
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 + }
812 1547 }
813 -}
814 1548
815 -if(!class_exists('DatafeedrAmazonSearchRequest', false)) {
816 1549 /**
817 - * Amazon search request.
818 - **/
819 -class DatafeedrAmazonSearchRequest extends DatafeedrAmazonRequest
820 -{
821 - /**
822 - * Add a parameter.
823 - *
824 - * @param string $name Parameter name.
825 - * @param string $value Parameter value.
826 - * @return $this
827 - *
828 - * @see http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemSearch.html
829 - *
830 - **/
831 - public function addParam($name, $value) {
832 - $this->_params[$name] = $value;
833 - return $this;
834 - }
1550 + * Class DatafeedrAmazonSearchRequest
1551 + *
1552 + * Amazon search request class.
1553 + */
1554 +class DatafeedrAmazonSearchRequest extends DatafeedrAmazonRequest {
835 1555
836 - /**
837 - * Run search and return a list of products.
838 - *
839 - * @return array
840 - *
841 - **/
842 - public function execute() {
843 - $defaults = array(
844 - 'ResponseGroup' => 'ItemAttributes,Images,OfferFull,BrowseNodes,EditorialReview,VariationSummary',
845 - 'SearchIndex' => 'All',
846 - );
847 - $url = $this->_amazonUrl('ItemSearch', $this->_params, $defaults);
848 - $this->_apiCall('amazon_search', array('url' => $url));
849 - return $this->_responseItem('products', array());
850 - }
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 + }
851 1594 }
852 -}
853 1595
854 -if(!class_exists('DatafeedrAmazonLookupRequest', false)) {
855 1596 /**
1597 + * Class DatafeedrAmazonLookupRequest
1598 + *
856 1599 * Amazon lookup request.
857 - **/
858 -class DatafeedrAmazonLookupRequest extends DatafeedrAmazonRequest
859 -{
860 - /**
861 - * Add a parameter.
862 - *
863 - * @param string $name Parameter name - one of 'ASIN', 'SKU', 'UPC', 'EAN', 'ISBN'
864 - * @param string|array $value Parameter value or an array of values (up to 10).
865 - * @return $this
866 - *
867 - * @see http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html
868 - *
869 - **/
870 - public function addParam($name, $value) {
871 - $this->_params[$name] = $value;
872 - return $this;
873 - }
1600 + */
1601 +class DatafeedrAmazonLookupRequest extends DatafeedrAmazonRequest {
874 1602
875 - /**
876 - * Run search and return a list of products.
877 - *
878 - * @return array
879 - *
880 - **/
881 - public function execute() {
882 - $params = array_filter($this->_params);
883 - $types = array('ASIN', 'SKU', 'UPC', 'EAN', 'ISBN', 'asin', 'sku', 'upc', 'ean', 'isbn');
884 - foreach($types as $type) {
885 - if(isset($params[$type])) {
886 - $params['IdType'] = strtoupper($type);
887 - $params['ItemId'] = $params[$type];
888 - unset($params[$type]);
889 - }
890 - }
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;
891 1618
892 - $defaults = array(
893 - 'ResponseGroup' => 'ItemAttributes,Images,OfferFull,BrowseNodes,EditorialReview,VariationSummary',
894 - );
895 - if(isset($params['IdType']) && $params['IdType'] != 'ASIN') {
896 - $defaults['SearchIndex'] = 'All';
897 - }
898 - $url = $this->_amazonUrl('ItemLookup', $params, $defaults);
899 - $this->_apiCall('amazon_search', array('url' => $url));
900 - return $this->_responseItem('products', array());
901 - }
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 + }
902 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 +// }
903 1725 }
904 1726
905 -if(!class_exists('DatafeedrError', false)) {
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 +
906 2038 /**
2039 + * Class DatafeedrError.
2040 + *
907 2041 * Generic Api error.
908 -**/
909 -class DatafeedrError extends Exception
910 -{
2042 + */
2043 +class DatafeedrError extends Exception {
911 2044 }
912 -}
913 2045
914 -if(!class_exists('DatafeedrBadRequestError', false)) {
915 2046 /**
2047 + * Class DatafeedrBadRequestError.
2048 + *
916 2049 * API error: Invalid Request.
917 -**/
918 -class DatafeedrBadRequestError extends DatafeedrError
919 -{
2050 + */
2051 +class DatafeedrBadRequestError extends DatafeedrError {
920 2052 }
921 -}
922 2053
923 -if(!class_exists('DatafeedrAuthenticationError', false)) {
924 2054 /**
2055 + * Class DatafeedrAuthenticationError.
2056 + *
925 2057 * API error: Authentication failed.
926 -**/
927 -class DatafeedrAuthenticationError extends DatafeedrError
928 -{
2058 + */
2059 +class DatafeedrAuthenticationError extends DatafeedrError {
929 2060 }
930 -}
931 2061
932 -if(!class_exists('DatafeedrLimitExceededError', false)) {
933 2062 /**
2063 + * Class DatafeedrLimitExceededError.
2064 + *
934 2065 * API error: Query limit exceeded.
935 -**/
936 -class DatafeedrLimitExceededError extends DatafeedrError
937 -{
2066 + */
2067 +class DatafeedrLimitExceededError extends DatafeedrError {
938 2068 }
939 -}
940 2069
941 -if(!class_exists('DatafeedrHTTPError', false)) {
942 2070 /**
2071 + * Class DatafeedrHTTPError.
2072 + *
943 2073 * API error: Unspecified HTTP error.
944 - **/
945 -class DatafeedrHTTPError extends DatafeedrError
946 -{
2074 + */
2075 +class DatafeedrHTTPError extends DatafeedrError {
947 2076 }
2077 +
2078 +/**
2079 + * Class DatafeedrConnectionError.
2080 + *
2081 + * API error: Connection error.
2082 + */
2083 +class DatafeedrConnectionError extends DatafeedrError {
948 2084 }
949 2085
950 -if(!class_exists('DatafeedrQueryError', false)) {
951 2086 /**
2087 + * Class DatafeedrQueryError.
2088 + *
952 2089 * API error: Error in the search query.
953 -**/
954 -class DatafeedrQueryError extends DatafeedrError
955 -{
2090 + */
2091 +class DatafeedrQueryError extends DatafeedrError {
956 2092 }
957 -}
958 2093
959 -if(!class_exists('DatafeedrExternalError', false)) {
960 2094 /**
2095 + * Class DatafeedrExternalError.
2096 + *
961 2097 * API error: External service error.
962 -**/
963 -class DatafeedrExternalError extends DatafeedrError
964 -{
2098 + */
2099 +class DatafeedrExternalError extends DatafeedrError {
965 2100 }
2101 +
2102 +/**
2103 + * Class DatafeedrServerError.
2104 + *
2105 + * API error: Internal server error.
2106 + */
2107 +class DatafeedrServerError extends DatafeedrError {
966 2108 }
967 -
968 -?>