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