1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4: */
3/**
4* Implementation of a developers backend for accessing Amazon's retail and
5* assosciate services.
6*
7* PHP versions 4 and 5
8*
9* LICENSE: Copyright 2004 John Downey. All rights reserved.
10*
11* Redistribution and use in source and binary forms, with or without
12* modification, are permitted provided that the following conditions are met:
13*
14* o Redistributions of source code must retain the above copyright notice, this
15*   list of conditions and the following disclaimer.
16* o Redistributions in binary form must reproduce the above copyright notice,
17*   this list of conditions and the following disclaimer in the documentation
18*   and/or other materials provided with the distribution.
19*
20* THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT "AS IS" AND ANY EXPRESS OR
21* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
23* EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
29* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30*
31* The views and conclusions contained in the software and documentation are
32* those of the authors and should not be interpreted as representing official
33* policies, either expressed or implied, of The PEAR Group.
34*
35* @category  Web Services
36* @package   Services_Amazon
37* @author    John Downey <jdowney@gmail.com>
38* @author    Tatsuya Tsuruoka <tatsuya.tsuruoka@gmail.com>
39* @copyright 2004 John Downey
40* @license   http://www.freebsd.org/copyright/freebsd-license.html 2 Clause BSD License
41* @version   CVS: $Id: AmazonECS4.php 280260 2009-05-10 09:40:30Z ttsuruoka $
42* @link      http://pear.php.net/package/Services_Amazon/
43* @filesource
44*/
45
46/**
47 * NOTICE:
48 * This class is for backward compatibility and should be considered obsolete.
49 * After August 15, 2009, all requests without a signature will be denied.
50 * You may as well use Services_Amazon when you create a new application.
51 */
52
53/**
54* Uses PEAR class for error management
55*/
56require_once 'PEAR.php';
57
58/**
59* Uses HTTP_Request class to send and receive data from Amazon web servers
60*/
61require_once 'HTTP/Request.php';
62
63/**
64* Uses XML_Unserializer class to parse data received from Amazon
65*/
66require_once 'XML/Unserializer.php';
67
68/**
69* A default base URL that is specific to the locale
70*
71* - Amazon.com (US)
72*   http://webservices.amazon.com/onca/xml?Service=AWSECommerceService
73* - Amazon.co.uk (UK)
74*   http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService
75* - Amazon.de (DE)
76*   http://webservices.amazon.de/onca/xml?Service=AWSECommerceService
77* - Amazon.co.jp (JP)
78*   http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService
79* - Amazon.fr (FR)
80*   http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService
81* - Amazon.ca (CA)
82*   http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService
83*/
84if (!defined('SERVICES_AMAZON_BASEURL')) {
85    define('SERVICES_AMAZON_BASEURL', 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService');
86}
87/**
88* A service version
89*
90* Use this to retrieve a particular version of the Amazon ECS.
91*/
92if (!defined('SERVICES_AMAZON_ECSVERSION')) {
93    define('SERVICES_AMAZON_ECSVERSION', '2005-10-05');
94}
95
96/**
97* Class for accessing and retrieving information from Amazon's Web Services
98*
99* @package Services_Amazon
100* @author  John Downey <jdowney@gmail.com>
101* @author  Tatsuya Tsuruoka <tatsuya.tsuruoka@gmail.com>
102* @access  public
103* @version Release: 0.9.0
104* @uses    PEAR
105* @uses    HTTP_Request
106* @uses    XML_Unserializer
107*/
108class Services_AmazonECS4
109{
110    /**
111    * An Amazon AccessKey/Subscription ID used when quering Amazon servers
112    *
113    * @access private
114    * @var    string
115    */
116    var $_keyid = null;
117
118    /**
119    * An Amazon Associate ID used in the URL's so a commision may be payed
120    *
121    * @access private
122    * @var    string
123    */
124    var $_associd = null;
125
126    /**
127    * A base URL used to build the query for the Amazon servers
128    *
129    * @access private
130    * @var    string
131    */
132    var $_baseurl = SERVICES_AMAZON_BASEURL;
133
134    /**
135    * A service version
136    *
137    * @access private
138    * @var    string
139    */
140    var $_version = SERVICES_AMAZON_ECSVERSION;
141
142    /**
143    * The time that the Amazon took to process the request
144    *
145    * @access private
146    * @var    string
147    */
148    var $_processing_time = null;
149
150    /**
151    * The last URL accessed to the Amazon (for debugging)
152    *
153    * @access private
154    * @var    string
155    */
156    var $_lasturl = null;
157
158    /**
159    * The raw result returned from the request
160    *
161    * @access private
162    * @var    string
163    */
164    var $_raw_result = null;
165
166    /**
167    * The cache object
168    *
169    * @access private
170    * @var    object
171    */
172    var $_cache = null;
173
174    /**
175    * The cache expire time
176    *
177    * Defaults to one hour.
178    *
179    * @access private
180    * @var    integer
181    */
182    var $_cache_expire = 3600;
183
184    /**
185    * Proxy server
186    *
187    * @access private
188    * @var    string
189    */
190    var $_proxy_host = null;
191
192    /**
193    * Proxy port
194    *
195    * @access private
196    * @var    integer
197    */
198    var $_proxy_port = null;
199
200    /**
201    * Proxy username
202    *
203    * @access private
204    * @var    string
205    */
206    var $_proxy_user = null;
207
208    /**
209    * Proxy password
210    *
211    * @access private
212    * @var    string
213    */
214    var $_proxy_pass = null;
215
216    /**
217    * Errors
218    *
219    * @access private
220    * @var    array
221    */
222    var $_errors = array();
223
224    /**
225    * Constructor
226    *
227    * @access public
228    * @param  string $keyid An Amazon Access Key ID used when quering Amazon servers
229    * @param  string $associd An Amazon Associate ID used in the URL's so a commision may be payed
230    * @see    setAccessKeyID
231    * @see    setAssociateID
232    * @see    setBaseUrl
233    * @see    setVersion
234    */
235    function Services_AmazonECS4($keyid, $associd = null)
236    {
237        $this->_keyid = $keyid;
238        $this->_associd = $associd;
239    }
240
241    /**
242    * Retrieves the current version of this classes API
243    *
244    * @access public
245    * @static
246    * @return string The API version
247    */
248    function getApiVersion()
249    {
250        return '0.9.0';
251    }
252
253    /**
254    * Sets an Access Key ID
255    *
256    * @access public
257    * @param  string $subid An Access Key ID
258    * @return void
259    */
260    function setAccessKeyID($keyid)
261    {
262        $this->_keyid = $keyid;
263    }
264
265    /**
266    * Sets a Subscription ID (for backward compatibility)
267    *
268    * @access public
269    * @param  string $subid A Subscription ID
270    * @return void
271    */
272    function setSubscriptionID($subid)
273    {
274        $this->_keyid = $subid;
275    }
276
277    /**
278    * Sets an Associate ID
279    *
280    * @access public
281    * @param  string $associd An Associate ID
282    * @return void
283    */
284    function setAssociateID($associd)
285    {
286        $this->_associd = $associd;
287    }
288
289    /**
290    * Sets the base URL
291    *
292    * @access public
293    * @param  string $url The base url
294    * @return void
295    */
296    function setBaseUrl($url)
297    {
298        $this->_baseurl = $url;
299    }
300
301    /**
302    * Sets the locale passed when making a query to Amazon
303    *
304    * Currently US, UK, DE, JP, FR, and CA are supported
305    *
306    * @access public
307    * @param  string $locale The new locale to use
308    * @return mixed A PEAR_Error on error, a true on success
309    */
310    function setLocale($locale)
311    {
312        $urls = array(
313            'US' => 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService',
314            'UK' => 'http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService',
315            'DE' => 'http://webservices.amazon.de/onca/xml?Service=AWSECommerceService',
316            'JP' => 'http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService',
317            'FR' => 'http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService',
318            'CA' => 'http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService',
319        );
320        $locale = strtoupper($locale);
321        if (empty($urls[$locale])) {
322            return PEAR::raiseError('Invalid locale');
323        }
324        $this->setBaseUrl($urls[$locale]);
325        return true;
326    }
327
328    /**
329    * Sets a version
330    *
331    * @access public
332    * @param  string $version A service version
333    * @return void
334    */
335    function setVersion($version)
336    {
337        $this->_version = $version;
338    }
339
340    /**
341    * Enables caching the data
342    *
343    * Requires Cache to be installed.
344    * Example:
345    * <code>
346    * <?php
347    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
348    * $amazon->setCache('file', array('cache_dir' => 'cache/'));
349    * $amazon->setCacheExpire(86400); // 86400 seconds = 24 hours
350    * $result = $amazon->BrowseNodeLookup('283155');
351    * ?>
352    * </code>
353    *
354    * @access public
355    * @param  string $container Name of container class
356    * @param  array $container_options Array with container class options
357    * @return mixed A PEAR_Error on error, a true on success
358    * @see    setCacheExpire()
359    */
360    function setCache($container = 'file', $container_options = array())
361    {
362        if(!class_exists('Cache')){
363            @include_once 'Cache.php';
364        }
365
366        @$cache = new Cache($container, $container_options);
367
368        if (is_object($cache)) {
369            $this->_cache = $cache;
370        } else {
371            $this->_cache = null;
372            return PEAR::raiseError('Cache init failed');
373        }
374
375        return true;
376    }
377
378    /**
379    * Sets cache expire time
380    *
381    * Amazon dictates that any prices that are displayed that may be over an
382    * hour old should be accompanied by some sort of timestamp. You can get
383    * around that by expiring any queries that use the time in an hour (3600
384    * seconds).
385    *
386    * @access public
387    * @param  integer $secs Expire time in seconds
388    * @return void
389    * @see    setCache()
390    */
391    function setCacheExpire($secs)
392    {
393        $this->_cache_expire = $secs;
394    }
395
396    /**
397    * Sets a proxy
398    *
399    * @access public
400    * @param string $host Proxy host
401    * @param int $port Proxy port
402    * @param string $user Proxy username
403    * @param string $pass Proxy password
404    */
405    function setProxy($host, $port = 8080, $user = null, $pass = null)
406    {
407        $this->_proxy_host = $host;
408        $this->_proxy_port = $port;
409        $this->_proxy_user = $user;
410        $this->_proxy_pass = $pass;
411    }
412
413    /**
414    * Retrieves all error codes and messages
415    *
416    * <code>
417    * if (PEAR::isError($result)) {
418    *     foreach ($amazon->getErrors() as $error) {
419    *         echo $error['Code'];
420    *         echo $error['Message'];
421    *     }
422    * }
423    * </code>
424    *
425    * @access public
426    * @return array All errors
427    */
428    function getErrors()
429    {
430        return $this->_errors;
431    }
432
433    /**
434    * Retrieves the error code and message
435    *
436    * <code>
437    * if (PEAR::isError($result)) {
438    *     $error = $amazon->getError();
439    *     echo $error['Code'];
440    *     echo $error['Message'];
441    * }
442    * </code>
443    *
444    * @access public
445    * @return array All errors
446    */
447    function getError()
448    {
449        return current($this->_errors);
450    }
451
452    /**
453    * Retrieves the processing time
454    *
455    * @access public
456    * @return string Processing time
457    */
458    function getProcessingTime()
459    {
460        return $this->_processing_time;
461    }
462
463    /**
464    * Retrieves the last URL accessed to the Amazon (for debugging)
465    *
466    * @access public
467    * @return string The Last URL
468    */
469    function getLastUrl()
470    {
471        return $this->_lasturl;
472    }
473
474    /**
475     * Retrieves the raw result
476     *
477     * @access public
478     * @return string The raw result
479     */
480    function getRawResult()
481    {
482        return $this->_raw_result;
483    }
484
485    /**
486    * Retrieves information about a browse node
487    *
488    * Example:
489    * <code>
490    * <?php
491    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
492    * $result = $amazon->BrowseNodeLookup('283155'); // 283155='Books'
493    * ?>
494    * </code>
495    *
496    * @access public
497    * @param  string $browsenode_id The browse node ID
498    * @param  array $options The optional parameters
499    * @return array The array of information returned by the query
500    */
501    function BrowseNodeLookup($browsenode_id, $options = array())
502    {
503        $params = $options;
504        $params['Operation'] = 'BrowseNodeLookup';
505        $params['BrowseNodeId'] = $browsenode_id;
506        return $this->_sendRequest($params);
507    }
508
509    /**
510    * Adds items to an existing remote shopping cart
511    *
512    * Example:
513    * <code>
514    * <?php
515    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
516    * $item = array('ASIN' => 'aaaaaaaaaa', 'Quantity' => 1);
517    * // $item = array(array('ASIN' => 'aaaaaaaaaa', 'Quantity' => 1),
518    * //               array('OfferListingId' => 'bbbbbbbbbb', 'Quantity' => 10),
519    * //               array('ASIN' => 'cccccccccc', 'Quantity' => 20));
520    * $result = $amazon->CartAdd('[Cart ID]', '[HMAC]', $item);
521    * ?>
522    * </code>
523    *
524    * @access public
525    * @param  string $cart_id A unique identifier for a cart
526    * @param  string $hmac A unique security token
527    * @param  array $item Products and the quantities
528    * @param  array $options The optional parameters
529    * @return array The array of information returned by the query
530    * @see    CartClear(), CartCreate(), CartModify()
531    */
532    function CartAdd($cart_id, $hmac, $item, $options = array())
533    {
534        $params = $options;
535        $params['Operation'] = 'CartAdd';
536        $params['CartId'] = $cart_id;
537        $params['HMAC'] = $hmac;
538        $params += $this->_assembleItemParameter($item);
539        return $this->_sendRequest($params);
540    }
541
542    /**
543    * Removes all the contents of a remote shopping cart
544    *
545    * @access public
546    * @param  string $cart_id A unique identifier for a cart
547    * @param  string $hmac A unique security token
548    * @param  array $options The optional parameters
549    * @return array The array of information returned by the query
550    * @see    CartAdd(), CartCreate(), CartGet(), CartModify()
551    */
552    function CartClear($cart_id, $hmac, $options = array())
553    {
554        $params = $options;
555        $params['Operation'] = 'CartClear';
556        $params['CartId'] = $cart_id;
557        $params['HMAC'] = $hmac;
558        return $this->_sendRequest($params);
559    }
560
561    /**
562    * Creates a new remote shopping cart
563    *
564    * Example:
565    * <code>
566    * <?php
567    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
568    * $item = array('ASIN' => 'aaaaaaaaaa', 'Quantity' => 1);
569    * // $item = array(array('ASIN' => 'aaaaaaaaaa', 'Quantity' => 1),
570    * //               array('ASIN' => 'cccccccccc', 'Quantity' => 20));
571    * $result = $amazon->CartCreate($item);
572    * ?>
573    * </code>
574    *
575    * @access public
576    * @param  array $item Products and the quantities
577    * @param  array $options The optional parameters
578    * @return array The array of information returned by the query
579    * @see    CartAdd(), CartClear(), CartGet(), CartModify()
580    */
581    function CartCreate($item, $options = array())
582    {
583        $params = $options;
584        $params['Operation'] = 'CartCreate';
585        $params += $this->_assembleItemParameter($item);
586        return $this->_sendRequest($params);
587    }
588
589    /**
590    * Retrieves the contents of a remote shopping cart
591    *
592    * @access public
593    * @param  string $cart_id A unique identifier for a cart
594    * @param  string $hmac A unique security token
595    * @param  array $options The optional parameters
596    * @return array The array of information returned by the query
597    * @see    CartAdd(), CartClear(), CartCreate(), CartModify()
598    */
599    function CartGet($cart_id, $hmac, $options = array())
600    {
601        $params = $options;
602        $params['Operation'] = 'CartGet';
603        $params['CartId'] = $cart_id;
604        $params['HMAC'] = $hmac;
605        return $this->_sendRequest($params);
606    }
607
608    /**
609    * Modifies the quantity of items in a cart and changes cart items to saved items
610    *
611    * Example:
612    * <code>
613    * <?php
614    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
615    * $item = array('CartItemId' => 'aaaaaaaaaa', 'Quantity' => 1);
616    * // $item = array('CartItemId' => 'aaaaaaaaaa', 'Action' => 'SaveForLater');
617    * // $item = array(array('CartItemId' => 'aaaaaaaaaa', 'Quantity' => 1),
618    * //               array('CartItemId' => 'cccccccccc', 'Quantity' => 20));
619    * $result = $amazon->CartModify('[Cart ID]', '[HMAC]', $item);
620    * ?>
621    * </code>
622    *
623    * @access public
624    * @param  string $cart_id A unique identifier for a cart
625    * @param  string $hmac A unique security token
626    * @param  array $item The CartItemId and the quantities or the Action
627    * @param  array $options The optional parameters
628    * @return array The array of information returned by the query
629    * @see    CartAdd(), CartClear(), CartCreate(), CartGet()
630    */
631    function CartModify($cart_id, $hmac, $item, $options = array())
632    {
633        $params = $options;
634        $params['Operation'] = 'CartModify';
635        $params['CartId'] = $cart_id;
636        $params['HMAC'] = $hmac;
637        $params += $this->_assembleItemParameter($item);
638        return $this->_sendRequest($params);
639    }
640
641    /**
642    * Retrieves publicly available content written by specific Amazon customers
643    *
644    * @access public
645    * @param  string $customer_id A customer ID
646    * @param  array $options The optional parameters
647    * @return array The array of information returned by the query
648    * @see    CustomerContentSearch()
649    */
650    function CustomerContentLookup($customer_id, $options = array())
651    {
652        $params = $options;
653        $params['Operation'] = 'CustomerContentLookup';
654        $params['CustomerId'] = $customer_id;
655        return $this->_sendRequest($params);
656    }
657
658    /**
659    * Searches for Amazon customers by name or email address
660    *
661    * @access public
662    * @param  array $customer A customer's name or its email
663    * @param  array $options The optional parameters
664    * @return array The array of information returned by the query
665    * @see    CustomerContentLookup()
666    */
667    function CustomerContentSearch($customer = null, $options = array())
668    {
669        $params = $options;
670        $params['Operation'] = 'CustomerContentSearch';
671        $params += $customer;
672        return $this->_sendRequest($params);
673    }
674
675    /**
676    * Retrieves information about operations and response groups
677    *
678    * Example:
679    * <code>
680    * <?php
681    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
682    * $result = $amazon->Help('Operation', 'ItemLookup');
683    * ?>
684    * </code>
685    *
686    * @access public
687    * @param  string $help_type The type of information
688    * @param  string $about The name of an operation or a response group
689    * @param  array $options The optional parameters
690    * @return array The array of information returned by the query
691    */
692    function Help($help_type, $about, $options = array())
693    {
694        $params = $options;
695        $params['Operation'] = 'Help';
696        $params['HelpType'] = $help_type;
697        $params['About'] = $about;
698        return $this->_sendRequest($params);
699    }
700
701    /**
702    * Retrieves information for products
703    *
704    * Example:
705    * <code>
706    * <?php
707    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
708    * $options = array();
709    * $options['ResponseGroup'] = 'Large';
710    * $result = $amazon->ItemLookup('[ASIN(s)]', $options);
711    * ?>
712    * </code>
713    *
714    * @access public
715    * @param  string $item_id Product IDs
716    * @param  array $options The optional parameters
717    * @return array The array of information returned by the query
718    * @see    ItemSearch()
719    */
720    function ItemLookup($item_id, $options = array())
721    {
722        $params = $options;
723        $params['Operation'] = 'ItemLookup';
724        if (is_array($item_id)) {
725            $item_id = implode(',', $item_id);
726        }
727        $params['ItemId'] = $item_id;
728        return $this->_sendRequest($params);
729    }
730
731    /**
732    * Searches for products
733    *
734    * Example:
735    * <code>
736    * <?php
737    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
738    * $options = array();
739    * $options['Keywords'] = 'sushi';
740    * $options['Sort'] = 'salesrank';
741    * $options['ResponseGroup'] = 'ItemIds,ItemAttributes,Images';
742    * $result = $amazon->ItemSearch('Books', $options);
743    * ?>
744    * </code>
745    *
746    * @access public
747    * @param  string $search_index A search index
748    * @param  array $options The optional parameters
749    * @return array The array of information returned by the query
750    * @see    ItemLookup()
751    */
752    function ItemSearch($search_index, $options = array())
753    {
754        $params = $options;
755        $params['Operation'] = 'ItemSearch';
756        $params['SearchIndex'] = $search_index;
757        return $this->_sendRequest($params);
758    }
759
760    /**
761    * Retrieves products in a specific list
762    *
763    * @access public
764    * @param  string $list_type The type of list
765    * @param  string $list_id A list ID
766    * @param  array $options The optional parameters
767    * @return array The array of information returned by the query
768    * @see    ListSearch()
769    */
770    function ListLookup($list_type, $list_id, $options = array())
771    {
772        $params = $options;
773        $params['Operation'] = 'ListLookup';
774        $params['ListType'] = $list_type;
775        $params['ListId'] = $list_id;
776        return $this->_sendRequest($params);
777    }
778
779    /**
780    * Searches for a wish list, baby registry, or wedding registry
781    *
782    * Example:
783    * <code>
784    * <?php
785    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
786    * $keywords = array('Name' => 'hoge');
787    * $result = $amazon->ListSearch('WishList', $keywords);
788    * ?>
789    * </code>
790    *
791    * @access public
792    * @param  string $list_type The type of list
793    * @param  array $keywords Parameters to search for
794    * @param  array $options The optional parameters
795    * @return array The array of information returned by the query
796    * @see    ListLookup()
797    */
798    function ListSearch($list_type, $keywords, $options = array())
799    {
800        $params = $options;
801        $params['Operation'] = 'ListSearch';
802        $params['ListType'] = $list_type;
803        $params += $keywords;
804        return $this->_sendRequest($params);
805    }
806
807    /**
808    * Retrieves information about Amazon zShops and Marketplace products
809    *
810    * @access public
811    * @param  string $id_type The type of ID
812    * @param  string $id The exchange ID or the listing ID
813    * @param  array $options The optional parameters
814    * @return array The array of information returned by the query
815    * @see    SellerListingSearch()
816    */
817    function SellerListingLookup($id_type, $id, $options = array())
818    {
819        $params = $options;
820        $params['Operation'] = 'SellerListingLookup';
821        $params['IdType'] = $id_type;
822        $params['Id'] = $id;
823        return $this->_sendRequest($params);
824    }
825
826    /**
827    * Searches for Amazon zShops and Marketplace products
828    *
829    * Example:
830    * <code>
831    * <?php
832    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
833    * $keywords = array('Keywords' => 'pizza');
834    * $result = $amazon->SellerListingSearch('zShops', $keywords);
835    * ?>
836    * </code>
837    *
838    * @access public
839    * @param  string $search_index The type of seller listings
840    * @param  array $options The optional parameters
841    * @return array The array of information returned by the query
842    * @see    SellerListingLookup()
843    */
844    function SellerListingSearch($search_index, $options = array())
845    {
846        $params = $options;
847        $params['Operation'] = 'SellerListingSearch';
848        $params['SearchIndex'] = $search_index;
849        return $this->_sendRequest($params);
850    }
851
852    /**
853    * Retrieves information about specific sellers
854    *
855    * @access public
856    * @param  string $seller_id IDs for Amazon sellers
857    * @param  array $options The optional parameters
858    * @return array The array of information returned by the query
859    */
860    function SellerLookup($seller_id, $options = array())
861    {
862        $params = $options;
863        $params['Operation'] = 'SellerLookup';
864        $params['SellerId'] = $seller_id;
865        return $this->_sendRequest($params);
866    }
867
868    /**
869    * Retrieves products that are similar to Amazon products
870    *
871    * @access public
872    * @param  string $item_id Product IDs
873    * @param  array $options The optional parameters
874    * @return array The array of information returned by the query
875    */
876    function SimilarityLookup($item_id, $options = array())
877    {
878        $params = $options;
879        $params['Operation'] = 'SimilarityLookup';
880        if (is_array($item_id)) {
881            $item_id = implode(',', $item_id);
882        }
883        $params['ItemId'] = $item_id;
884        return $this->_sendRequest($params);
885    }
886
887    /**
888    * Retrieves information about the status of financial transactions
889    *
890    * @access public
891    * @param  string $transaction_id Transaction IDs
892    * @param  array $options The optional parameters
893    * @return array The array of information returned by the query
894    */
895    function TransactionLookup($transaction_id, $options = array())
896    {
897        $params = $options;
898        $params['Operation'] = 'TransactionLookup';
899        $params['TransactionId'] = $transaction_id;
900        return $this->_sendRequest($params);
901    }
902
903    /**
904    * Combines requests for the same operation into a single request
905    *
906    * Example:
907    * <code>
908    * <?php
909    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
910    * $shared = array('SearchIndex' => 'Books',
911    *                 'Keywords' => 'php');
912    * $params1 = array('ItemPage' => '1');
913    * $params2 = array('ItemPage' => '2');
914    * $result = $amazon->doBatch('ItemSearch', $shared, $params1, $params2);
915    * ?>
916    * </code>
917    *
918    * @access public
919    * @param  string $operation The operation
920    * @param  array $shared Shared parameters
921    * @param  array $params1 The parameters specific to the first request
922    * @param  array $params2 The parameters specific to the second request
923    * @return array The array of information returned by the query
924    */
925    function doBatch($operation, $shared, $params1 = array(), $params2 = array())
926    {
927        $params = array();
928        $params['Operation'] = $operation;
929        foreach ($shared as $k => $v) {
930            $params[$operation . '.Shared.' . $k] = $v;
931        }
932        foreach ($params1 as $k => $v) {
933            $params[$operation . '.1.' . $k] = $v;
934        }
935        foreach ($params2 as $k => $v) {
936            $params[$operation . '.2.' . $k] = $v;
937        }
938        return $this->_sendRequest($params);
939    }
940
941    /**
942    * Combines the different operations into a single request
943    *
944    * Example:
945    * <code>
946    * <?php
947    * $amazon = new Services_AmazonECS4('[your Access Key ID here]');
948    * $params1 = array('SearchIndex' => 'Books',
949    *                  'Title' => 'sushi');
950    * $params2 = array('Keywords' => 'tempura');
951    * $result = $amazon->doMultiOperation('ItemSearch', $params1,
952    *                                     'SellerListingSearch', $params2);
953    * ?>
954    * </code>
955    *
956    * @access public
957    * @param  string $operation1 The first operation
958    * @param  array $params1 The parameters specific to the first request
959    * @param  string $operation2 The second operation
960    * @param  array $params2 The parameters specific to the second request
961    * @return array The array of information returned by the query
962    */
963    function doMultiOperation($operation1, $params1, $operation2, $params2)
964    {
965        $params = array();
966        $params['Operation'] = $operation1 . ',' . $operation2;
967        foreach ($params1 as $k => $v) {
968            $params[$operation1 . '.1.' . $k] = $v;
969        }
970        foreach ($params2 as $k => $v) {
971            $params[$operation2 . '.1.' . $k] = $v;
972        }
973        return $this->_sendRequest($params);
974    }
975
976    /**
977    * Assembles the Item parameters
978    *
979    * @access private
980    * @param  array $items The items
981    * @return array The item parameters
982    */
983    function _assembleItemParameter($items)
984    {
985        $params = array();
986        if (!is_array(current($items))) {
987            $items = array(0 => $items);
988        }
989        $i = 1;
990        foreach ($items as $item) {
991            foreach ($item as $k => $v) {
992                $params['Item.' . $i . '.' . $k] = $v;
993            }
994            $i++;
995        }
996        return $params;
997    }
998
999    /**
1000    * Ignores the caching of specific operations
1001    *
1002    * @access private
1003    * @param  string $operation The operation
1004    * @return bool Returns true if the operation isn't cached, false otherwise
1005    */
1006    function _ignoreCache($operation)
1007    {
1008        $ignore = array('CartAdd', 'CartClear', 'CartGet', 'CartModify', 'TransactionLookup');
1009        if (!strchr($operation, ',')) {
1010            return in_array($operation, $ignore);
1011        }
1012        $operations = explode(',', $operation);
1013        foreach ($operations as $v) {
1014            if (in_array($v, $ignore)) {
1015                return true;
1016            }
1017        }
1018        return false;
1019    }
1020
1021    /**
1022    * Generates ID used as cache identifier
1023    *
1024    * @access private
1025    * @param  array $params
1026    * @return string Cache ID
1027    */
1028    function _generateCacheId($params)
1029    {
1030        unset($params['AWSAccessKeyId']);
1031        unset($params['AssociateTag']);
1032        $str = '';
1033        foreach ($params as $k => $v) {
1034            $str .= $k . $v;
1035        }
1036        return md5($str);
1037    }
1038
1039    /**
1040    * Builds a URL
1041    *
1042    * @access private
1043    * @param array $params
1044    * @return string URL
1045    */
1046    function _buildUrl($params)
1047    {
1048        $params['AWSAccessKeyId'] = $this->_keyid;
1049        $params['AssociateTag'] = $this->_associd;
1050        $params['Version'] = $this->_version;
1051        $url = $this->_baseurl;
1052        foreach ($params as $k => $v) {
1053            $url .= '&' . $k . '=' . urlencode($v);
1054        }
1055        return $url;
1056    }
1057
1058    /**
1059    * Sends a request
1060    *
1061    * @access private
1062    * @param string $url
1063    * @return string The response
1064    */
1065    function _sendHttpRequest($url)
1066    {
1067        $http = &new HTTP_Request($url);
1068        $http->setHttpVer('1.0');
1069        $http->addHeader('User-Agent', 'Services_AmazonECS4/' . $this->getApiVersion());
1070        if ($this->_proxy_host) {
1071            $http->setProxy($this->_proxy_host, $this->_proxy_port, $this->_proxy_user, $this->_proxy_pass);
1072        }
1073
1074        $result = $http->sendRequest();
1075        if (PEAR::isError($result)) {
1076            return PEAR::raiseError('HTTP_Request::sendRequest failed: ' . $result->message);
1077        }
1078
1079        if ($http->getResponseCode() != 200){
1080            return PEAR::raiseError('Amazon returned invalid HTTP response code ' . $http->getResponseCode());
1081        }
1082        return $http->getResponseBody();
1083    }
1084
1085    /**
1086    * Parses raw XML result
1087    *
1088    * @access private
1089    * @param string $raw_result
1090    * @return string The contents
1091    */
1092    function _parseRawResult($raw_result)
1093    {
1094        $xml = &new XML_Unserializer();
1095        $xml->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true);
1096        $xml->setOption(XML_UNSERIALIZER_OPTION_FORCE_ENUM,
1097                        array('Item', 'Review', 'EditorialReview',
1098                              'Parameter', 'Author', 'Creator', 'ResponseGroup', 'Error'));
1099        $xml->unserialize($raw_result, false);
1100        $data = $xml->getUnserializedData();
1101        if (PEAR::isError($data)) {
1102            return $data;
1103        }
1104
1105        if (isset($data['Error'])) {
1106            $this->_errors = $data['Error'];
1107            return PEAR::raiseError(implode(':', $this->getError()));
1108        }
1109
1110        if (isset($data['OperationRequest']['RequestProcessingTime'])) {
1111            $this->_processing_time = $data['OperationRequest']['RequestProcessingTime'];
1112        }
1113
1114        if (isset($data['OperationRequest']['Errors'])) {
1115            $this->_errors = $data['OperationRequest']['Errors']['Error'];
1116            return PEAR::raiseError(implode(':', $this->getError()));
1117        }
1118
1119        // Get values of the second level content elements
1120        unset($data['xmlns']);
1121        unset($data['OperationRequest']);
1122        $contents = array();
1123        $keys = array_keys($data);
1124        foreach ($keys as $v) {
1125            if (strstr($v, 'Response')) {
1126                $data[$v] = current($data[$v]);
1127                $contents[$v] = $data[$v];
1128            } else {
1129                $contents = $data[$v];
1130            }
1131            $result = $this->_checkContentError($data[$v]);
1132            if (PEAR::isError($result)) {
1133                return $result;
1134            }
1135        }
1136        return $contents;
1137    }
1138
1139    /**
1140    * Checks error codes at the content elements
1141    *
1142    * @access private
1143    * @param  array $content Values of the content elements
1144    * @return array mixed A PEAR_Error on error, a true on success
1145    * @see    _parseRawResult
1146    */
1147    function _checkContentError($content)
1148    {
1149        if (isset($content['Request']['Errors'])) {
1150            $this->_errors = $content['Request']['Errors']['Error'];
1151            return PEAR::raiseError(implode(':', $this->getError()));
1152        } else if (isset($content[0])) {
1153            $errors = array();
1154            foreach ($content as $v) {
1155                if (isset($v['Request']['Errors']['Error'])) {
1156                    $errors = array_merge($errors, $v['Request']['Errors']['Error']);
1157                }
1158            }
1159            if (!empty($errors)) {
1160                $this->_errors = $errors;
1161                return PEAR::raiseError(implode(':', $this->getError()));
1162            }
1163        }
1164        return true;
1165    }
1166
1167    /**
1168    * Sends the request to Amazon
1169    *
1170    * @access private
1171    * @param  array $params The array of request parameters
1172    * @return array The array of information returned by the query
1173    */
1174    function _sendRequest($params)
1175    {
1176        $this->_errors = array();
1177
1178        if (is_null($this->_keyid)) {
1179            return PEAR::raiseError('Access Key ID have not been set');
1180        }
1181
1182        $url = $this->_buildUrl($params);
1183        $this->_lasturl = $url;
1184        if (PEAR::isError($url)) {
1185            return $url;
1186        }
1187
1188        // Return cached data if available
1189        $cache_id = false;
1190        if (isset($this->_cache) && !$this->_ignoreCache($params['Operation'])) {
1191            $cache_id = $this->_generateCacheId($params);
1192            $cache = $this->_cache->get($cache_id);
1193            if (!is_null($cache)) {
1194                $this->_processing_time = 0;
1195                return $cache;
1196            }
1197        }
1198
1199        $result = $this->_sendHttpRequest($url);
1200        $this->_raw_result = $result;
1201        if (PEAR::isError($result)) {
1202            return $result;
1203        }
1204
1205        $contents = $this->_parseRawResult($result);
1206        if (PEAR::isError($contents)) {
1207            return $contents;
1208        }
1209
1210        if ($cache_id) {
1211            $this->_cache->save($cache_id, $contents, $this->_cache_expire);
1212        }
1213
1214        return $contents;
1215    }
1216
1217}
1218?>
1219