File indexing completed on 2024-12-22 05:37:02

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Service_Amazon
0017  * @subpackage SimpleDb
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  */
0021 
0022 /**
0023  * @see Zend_Service_Amazon_Abstract
0024  */
0025 // require_once 'Zend/Service/Amazon/Abstract.php';
0026 
0027 /**
0028  * @see Zend_Service_Amazon_SimpleDb_Response
0029  */
0030 // require_once 'Zend/Service/Amazon/SimpleDb/Response.php';
0031 
0032 /**
0033  * @see Zend_Service_Amazon_SimpleDb_Page
0034  */
0035 // require_once 'Zend/Service/Amazon/SimpleDb/Page.php';
0036 
0037 /**
0038  * @see Zend_Service_Amazon_SimpleDb_Attribute
0039  */
0040 // require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php';
0041 
0042 /**
0043  * @see Zend_Service_Amazon_SimpleDb_Exception
0044  */
0045 // require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
0046 
0047 /**
0048  * @see Zend_Crypt_Hmac
0049  */
0050 // require_once 'Zend/Crypt/Hmac.php';
0051 
0052 /**
0053  * @category   Zend
0054  * @package    Zend_Service_Amazon
0055  * @subpackage SimpleDb
0056  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0057  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0058  */
0059 class Zend_Service_Amazon_SimpleDb extends Zend_Service_Amazon_Abstract
0060 {
0061     /* Notes */
0062     // TODO SSL is required
0063 
0064     /**
0065      * The HTTP query server
0066      */
0067     protected $_sdbEndpoint = 'sdb.amazonaws.com/';
0068 
0069     /**
0070      * Period after which HTTP request will timeout in seconds
0071      */
0072     protected $_httpTimeout = 10;
0073 
0074     /**
0075      * The API version to use
0076      */
0077     protected $_sdbApiVersion = '2009-04-15';
0078 
0079     /**
0080      * Signature Version
0081      */
0082     protected $_signatureVersion = '2';
0083 
0084     /**
0085      * Signature Encoding Method
0086      */
0087     protected $_signatureMethod = 'HmacSHA256';
0088 
0089     /**
0090      * Create Amazon SimpleDB client.
0091      *
0092      * @param string $accessKey       Override the default Access Key
0093      * @param string $secretKey       Override the default Secret Key
0094      */
0095     public function __construct($accessKey, $secretKey)
0096     {
0097         parent::__construct($accessKey, $secretKey);
0098         $this->setEndpoint("https://" . $this->_sdbEndpoint);
0099     }
0100 
0101     /**
0102      * Set SimpleDB endpoint to use
0103      *
0104      * @param string|Zend_Uri_Http $endpoint
0105      * @throws Zend_Service_Amazon_SimpleDb_Exception
0106      * @throws Zend_Uri_Exception
0107      * @return Zend_Service_Amazon_SimpleDb
0108      */
0109     public function setEndpoint($endpoint)
0110     {
0111         if(!($endpoint instanceof Zend_Uri_Http)) {
0112             $endpoint = Zend_Uri::factory($endpoint);
0113         }
0114         if(!$endpoint->valid()) {
0115             // require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
0116             throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid endpoint supplied");
0117         }
0118         $this->_endpoint = $endpoint;
0119         return $this;
0120     }
0121 
0122     /**
0123      * Get SimpleDB endpoint
0124      *
0125      * @return Zend_Uri_Http
0126      */
0127     public function getEndpoint()
0128     {
0129         return $this->_endpoint;
0130     }
0131 
0132     /**
0133      * Get attributes API method
0134      *
0135      * @param string      $domainName Domain name within database
0136      * @param string      $itemName
0137      * @param string|null $attributeName
0138      * @throws Zend_Service_Amazon_SimpleDb_Exception
0139      * @return array
0140      */
0141     public function getAttributes(
0142         $domainName, $itemName, $attributeName = null
0143     ) {
0144         $params               = array();
0145         $params['Action']     = 'GetAttributes';
0146         $params['DomainName'] = $domainName;
0147         $params['ItemName']   = $itemName;
0148 
0149         if (isset($attributeName)) {
0150             $params['AttributeName'] = $attributeName;
0151         }
0152 
0153         $response = $this->_sendRequest($params);
0154         $document = $response->getSimpleXMLDocument();
0155 
0156         $attributeNodes = $document->GetAttributesResult->Attribute;
0157 
0158         // Return an array of arrays
0159         $attributes = array();
0160         foreach($attributeNodes as $attributeNode) {
0161             $name       = (string)$attributeNode->Name;
0162             $valueNodes = $attributeNode->Value;
0163             $data       = null;
0164             if (is_array($valueNodes) && !empty($valueNodes)) {
0165                 $data = array();
0166                 foreach($valueNodes as $valueNode) {
0167                     $data[] = (string)$valueNode;
0168                 }
0169             } elseif (isset($valueNodes)) {
0170                 $data = (string)$valueNodes;
0171             }
0172             if (isset($attributes[$name])) {
0173                 $attributes[$name]->addValue($data);
0174             } else {
0175                 $attributes[$name] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $data);
0176             }
0177         }
0178         return $attributes;
0179     }
0180 
0181     /**
0182      * Push attributes
0183      *
0184      * @param  string $domainName
0185      * @param  string $itemName
0186      * @param  array|Traversable $attributes
0187      * @param  array $replace
0188      * @return void
0189      */
0190     public function putAttributes(
0191         $domainName, $itemName, $attributes, $replace = array()
0192     ) {
0193         $params               = array();
0194         $params['Action']     = 'PutAttributes';
0195         $params['DomainName'] = $domainName;
0196         $params['ItemName']   = $itemName;
0197 
0198         $index = 0;
0199         foreach ($attributes as $attribute) {
0200             $attributeName = $attribute->getName();
0201             foreach ($attribute->getValues() as $value) {
0202                 $params['Attribute.' . $index . '.Name']  = $attributeName;
0203                 $params['Attribute.' . $index . '.Value'] = $value;
0204 
0205                 // Check if it should be replaced
0206                 if(array_key_exists($attributeName, $replace) && $replace[$attributeName]) {
0207                     $params['Attribute.' . $index . '.Replace'] = 'true';
0208                 }
0209                 $index++;
0210             }
0211         }
0212 
0213         // Exception should get thrown if there's an error
0214         $response = $this->_sendRequest($params);
0215     }
0216 
0217     /**
0218      * Add many attributes at once
0219      *
0220      * @param  array $items
0221      * @param  string $domainName
0222      * @param  array $replace
0223      * @return void
0224      */
0225     public function batchPutAttributes($items, $domainName, array $replace = array())
0226     {
0227 
0228         $params               = array();
0229         $params['Action']     = 'BatchPutAttributes';
0230         $params['DomainName'] = $domainName;
0231 
0232         $itemIndex = 0;
0233         foreach ($items as $name => $attributes) {
0234             $params['Item.' . $itemIndex . '.ItemName'] = $name;
0235             $attributeIndex = 0;
0236             foreach ($attributes as $attribute) {
0237                 // attribute value cannot be array, so when several items are passed
0238                 // they are treated as separate values with the same attribute name
0239                 foreach($attribute->getValues() as $value) {
0240                     $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
0241                     $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value;
0242                     if (isset($replace[$name])
0243                         && isset($replace[$name][$attribute->getName()])
0244                         && $replace[$name][$attribute->getName()]
0245                     ) {
0246                         $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true';
0247                     }
0248                     $attributeIndex++;
0249                 }
0250             }
0251             $itemIndex++;
0252         }
0253 
0254         $response = $this->_sendRequest($params);
0255     }
0256 
0257     /**
0258      * Delete attributes
0259      *
0260      * @param  string $domainName
0261      * @param  string $itemName
0262      * @param  array $attributes
0263      * @return void
0264      */
0265     public function deleteAttributes($domainName, $itemName, array $attributes = array())
0266     {
0267         $params               = array();
0268         $params['Action']     = 'DeleteAttributes';
0269         $params['DomainName'] = $domainName;
0270         $params['ItemName']   = $itemName;
0271 
0272         $attributeIndex = 0;
0273         foreach ($attributes as $attribute) {
0274             foreach ($attribute->getValues() as $value) {
0275                 $params['Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
0276                 $params['Attribute.' . $attributeIndex . '.Value'] = $value;
0277                 $attributeIndex++;
0278             }
0279         }
0280 
0281         $response = $this->_sendRequest($params);
0282 
0283         return true;
0284     }
0285 
0286     /**
0287      * List domains
0288      *
0289      * @param int $maxNumberOfDomains
0290      * @param int $nextToken
0291      * @return array              0 or more domain names
0292      */
0293     public function listDomains($maxNumberOfDomains = 100, $nextToken = null)
0294     {
0295         $params                       = array();
0296         $params['Action']             = 'ListDomains';
0297         $params['MaxNumberOfDomains'] = $maxNumberOfDomains;
0298 
0299         if (null !== $nextToken) {
0300             $params['NextToken'] = $nextToken;
0301         }
0302         $response = $this->_sendRequest($params);
0303 
0304         $domainNodes = $response->getSimpleXMLDocument()->ListDomainsResult->DomainName;
0305 
0306         $data = array();
0307         foreach ($domainNodes as $domain) {
0308             $data[] = (string)$domain;
0309         }
0310 
0311         $nextTokenNode = $response->getSimpleXMLDocument()->ListDomainsResult->NextToken;
0312         $nextToken     = (string)$nextTokenNode;
0313 
0314         return new Zend_Service_Amazon_SimpleDb_Page($data, $nextToken);
0315     }
0316 
0317     /**
0318      * Retrieve domain metadata
0319      *
0320      * @param string $domainName Name of the domain for which metadata will be requested
0321      * @return array Key/value array of metadatum names and values.
0322      */
0323     public function domainMetadata($domainName)
0324     {
0325         $params               = array();
0326         $params['Action']     = 'DomainMetadata';
0327         $params['DomainName'] = $domainName;
0328         $response             = $this->_sendRequest($params);
0329 
0330         $document = $response->getSimpleXMLDocument();
0331 
0332         $metadataNodes = $document->DomainMetadataResult->children();
0333         $metadata      = array();
0334         foreach ($metadataNodes as $metadataNode) {
0335             $name            = $metadataNode->getName();
0336             $metadata[$name] = (string)$metadataNode;
0337         }
0338 
0339         return $metadata;
0340     }
0341 
0342     /**
0343      * Create a new domain
0344      *
0345      * @param string $domainName Valid domain name of the domain to create
0346      * @return boolean True if successful, false if not
0347      */
0348     public function createDomain($domainName)
0349     {
0350         $params               = array();
0351         $params['Action']     = 'CreateDomain';
0352         $params['DomainName'] = $domainName;
0353         $response             = $this->_sendRequest($params);
0354         return $response->getHttpResponse()->isSuccessful();
0355     }
0356 
0357     /**
0358      * Delete a domain
0359      *
0360      * @param string $domainName Valid domain name of the domain to delete
0361      * @return boolean True if successful, false if not
0362      */
0363     public function deleteDomain($domainName)
0364     {
0365         $params               = array();
0366         $params['Action']     = 'DeleteDomain';
0367         $params['DomainName'] = $domainName;
0368         $response             = $this->_sendRequest($params);
0369         return $response->getHttpResponse()->isSuccessful();
0370     }
0371 
0372     /**
0373      * Select items from the database
0374      *
0375      * @param  string $selectExpression
0376      * @param  null|string $nextToken
0377      * @return Zend_Service_Amazon_SimpleDb_Page
0378      */
0379     public function select($selectExpression, $nextToken = null)
0380     {
0381         $params                     = array();
0382         $params['Action']           = 'Select';
0383         $params['SelectExpression'] = $selectExpression;
0384 
0385         if (null !== $nextToken) {
0386             $params['NextToken'] = $nextToken;
0387         }
0388 
0389         $response = $this->_sendRequest($params);
0390         $xml      = $response->getSimpleXMLDocument();
0391 
0392         $attributes = array();
0393         foreach ($xml->SelectResult->Item as $item) {
0394             $itemName = (string)$item->Name;
0395 
0396             foreach ($item->Attribute as $attribute) {
0397                 $attributeName = (string)$attribute->Name;
0398 
0399                 $values = array();
0400                 foreach ($attribute->Value as $value) {
0401                     $values[] = (string)$value;
0402                 }
0403                 $attributes[$itemName][$attributeName] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $attributeName, $values);
0404             }
0405         }
0406 
0407         $nextToken = (string)$xml->NextToken;
0408 
0409         return new Zend_Service_Amazon_SimpleDb_Page($attributes, $nextToken);
0410     }
0411 
0412     /**
0413      * Quote SDB value
0414      *
0415      * Wraps it in ''
0416      *
0417      * @param string $value
0418      * @return string
0419      */
0420     public function quote($value)
0421     {
0422         // wrap in single quotes and convert each ' inside to ''
0423         return "'" . str_replace("'", "''", $value) . "'";
0424     }
0425 
0426     /**
0427      * Quote SDB column or table name
0428      *
0429      * Wraps it in ``
0430      *
0431      * @param  string $name
0432      * @throws Zend_Service_Amazon_SimpleDb_Exception
0433      * @return string
0434      */
0435     public function quoteName($name)
0436     {
0437         if (preg_match('/^[a-z_$][a-z0-9_$-]*$/i', $name) == false) {
0438             throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid name: can contain only alphanumeric characters, \$ and _");
0439         }
0440         return "`$name`";
0441     }
0442 
0443    /**
0444      * Sends a HTTP request to the SimpleDB service using Zend_Http_Client
0445      *
0446      * @param array $params         List of parameters to send with the request
0447      * @return Zend_Service_Amazon_SimpleDb_Response
0448      * @throws Zend_Service_Amazon_SimpleDb_Exception
0449      */
0450     protected function _sendRequest(array $params = array())
0451     {
0452         // UTF-8 encode all parameters and replace '+' characters
0453         foreach ($params as $name => $value) {
0454             unset($params[$name]);
0455             $params[utf8_encode($name)] = $value;
0456         }
0457 
0458         $params = $this->_addRequiredParameters($params);
0459 
0460         try {
0461             /* @var $request Zend_Http_Client */
0462             $request = self::getHttpClient();
0463             $request->resetParameters();
0464 
0465             $request->setConfig(array(
0466                 'timeout' => $this->_httpTimeout
0467             ));
0468 
0469 
0470             $request->setUri($this->getEndpoint());
0471             $request->setMethod(Zend_Http_Client::POST);
0472             foreach ($params as $key => $value) {
0473                 $params_out[] = rawurlencode($key)."=".rawurlencode($value);
0474             }
0475             $request->setRawData(join('&', $params_out), Zend_Http_Client::ENC_URLENCODED);
0476             $httpResponse = $request->request();
0477         } catch (Zend_Http_Client_Exception $zhce) {
0478             $message = 'Error in request to AWS service: ' . $zhce->getMessage();
0479             throw new Zend_Service_Amazon_SimpleDb_Exception($message, $zhce->getCode());
0480         }
0481         $response = new Zend_Service_Amazon_SimpleDb_Response($httpResponse);
0482         $this->_checkForErrors($response);
0483         return $response;
0484     }
0485 
0486     /**
0487      * Adds required authentication and version parameters to an array of
0488      * parameters
0489      *
0490      * The required parameters are:
0491      * - AWSAccessKey
0492      * - SignatureVersion
0493      * - Timestamp
0494      * - Version and
0495      * - Signature
0496      *
0497      * If a required parameter is already set in the <tt>$parameters</tt> array,
0498      * it is overwritten.
0499      *
0500      * @param array $parameters the array to which to add the required
0501      *                          parameters.
0502      *
0503      * @return array
0504      */
0505     protected function _addRequiredParameters(array $parameters)
0506     {
0507         $parameters['AWSAccessKeyId']   = $this->_getAccessKey();
0508         $parameters['SignatureVersion'] = $this->_signatureVersion;
0509         $parameters['Timestamp']        = gmdate('c');
0510         $parameters['Version']          = $this->_sdbApiVersion;
0511         $parameters['SignatureMethod']  = $this->_signatureMethod;
0512         $parameters['Signature']        = $this->_signParameters($parameters);
0513 
0514         return $parameters;
0515     }
0516 
0517     /**
0518      * Computes the RFC 2104-compliant HMAC signature for request parameters
0519      *
0520      * This implements the Amazon Web Services signature, as per the following
0521      * specification:
0522      *
0523      * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
0524      *    excluding <tt>Signature</tt>, the value of which is being created),
0525      *    ignoring case.
0526      *
0527      * 2. Iterate over the sorted list and append the parameter name (in its
0528      *    original case) and then its value. Do not URL-encode the parameter
0529      *    values before constructing this string. Do not use any separator
0530      *    characters when appending strings.
0531      *
0532      * @param array $parameters the parameters for which to get the signature.
0533      * @return string the signed data.
0534      */
0535     protected function _signParameters(array $parameters)
0536     {
0537         $data  = "POST\n";
0538         $data .= $this->getEndpoint()->getHost() . "\n";
0539         $data .= "/\n";
0540 
0541         uksort($parameters, 'strcmp');
0542         unset($parameters['Signature']);
0543 
0544         $arrData = array();
0545         foreach ($parameters as $key => $value) {
0546             $value = urlencode($value);
0547             $value = str_replace("%7E", "~", $value);
0548             $value = str_replace("+", "%20", $value);
0549             $arrData[] = urlencode($key) . '=' . $value;
0550         }
0551 
0552         $data .= implode('&', $arrData);
0553 
0554         // require_once 'Zend/Crypt/Hmac.php';
0555         $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY);
0556 
0557         return base64_encode($hmac);
0558     }
0559 
0560     /**
0561      * Checks for errors responses from Amazon
0562      *
0563      * @param Zend_Service_Amazon_SimpleDb_Response $response the response object to
0564      *                                                   check.
0565      * @throws Zend_Service_Amazon_SimpleDb_Exception if one or more errors are
0566      *         returned from Amazon.
0567      */
0568     private function _checkForErrors(Zend_Service_Amazon_SimpleDb_Response $response)
0569     {
0570         $xpath = new DOMXPath($response->getDocument());
0571         $list  = $xpath->query('//Error');
0572         if ($list->length > 0) {
0573             $node    = $list->item(0);
0574             $code    = $xpath->evaluate('string(Code/text())', $node);
0575             $message = $xpath->evaluate('string(Message/text())', $node);
0576             throw new Zend_Service_Amazon_SimpleDb_Exception($message, 0, $code);
0577         }
0578     }
0579 }