diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/Service/Amazon/SimpleDb.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/Service/Amazon/SimpleDb.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,580 @@ +setEndpoint("https://" . $this->_sdbEndpoint); + } + + /** + * Set SimpleDB endpoint to use + * + * @param string|Zend_Uri_Http $endpoint + * @return Zend_Service_Amazon_SimpleDb + */ + public function setEndpoint($endpoint) + { + if(!($endpoint instanceof Zend_Uri_Http)) { + $endpoint = Zend_Uri::factory($endpoint); + } + if(!$endpoint->valid()) { + require_once 'Zend/Service/Amazon/SimpleDb/Exception.php'; + throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid endpoint supplied"); + } + $this->_endpoint = $endpoint; + return $this; + } + + /** + * Get SimpleDB endpoint + * + * @return Zend_Uri_Http + */ + public function getEndpoint() + { + return $this->_endpoint; + } + + /** + * Get attributes API method + * + * @param string $domainName Domain name within database + * @param string + */ + public function getAttributes( + $domainName, $itemName, $attributeName = null + ) { + $params = array(); + $params['Action'] = 'GetAttributes'; + $params['DomainName'] = $domainName; + $params['ItemName'] = $itemName; + + if (isset($attributeName)) { + $params['AttributeName'] = $attributeName; + } + + $response = $this->_sendRequest($params); + $document = $response->getSimpleXMLDocument(); + + $attributeNodes = $document->GetAttributesResult->Attribute; + + // Return an array of arrays + $attributes = array(); + foreach($attributeNodes as $attributeNode) { + $name = (string)$attributeNode->Name; + $valueNodes = $attributeNode->Value; + $data = null; + if (is_array($valueNodes) && !empty($valueNodes)) { + $data = array(); + foreach($valueNodes as $valueNode) { + $data[] = (string)$valueNode; + } + } elseif (isset($valueNodes)) { + $data = (string)$valueNodes; + } + if (isset($attributes[$name])) { + $attributes[$name]->addValue($data); + } else { + $attributes[$name] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $data); + } + } + return $attributes; + } + + /** + * Push attributes + * + * @param string $domainName + * @param string $itemName + * @param array|Traverable $attributes + * @param array $replace + * @return void + */ + public function putAttributes( + $domainName, $itemName, $attributes, $replace = array() + ) { + $params = array(); + $params['Action'] = 'PutAttributes'; + $params['DomainName'] = $domainName; + $params['ItemName'] = $itemName; + + $index = 0; + foreach ($attributes as $attribute) { + $attributeName = $attribute->getName(); + foreach ($attribute->getValues() as $value) { + $params['Attribute.' . $index . '.Name'] = $attributeName; + $params['Attribute.' . $index . '.Value'] = $value; + + // Check if it should be replaced + if(array_key_exists($attributeName, $replace) && $replace[$attributeName]) { + $params['Attribute.' . $index . '.Replace'] = 'true'; + } + $index++; + } + } + + // Exception should get thrown if there's an error + $response = $this->_sendRequest($params); + } + + /** + * Add many attributes at once + * + * @param array $items + * @param string $domainName + * @param array $replace + * @return void + */ + public function batchPutAttributes($items, $domainName, array $replace = array()) + { + + $params = array(); + $params['Action'] = 'BatchPutAttributes'; + $params['DomainName'] = $domainName; + + $itemIndex = 0; + foreach ($items as $name => $attributes) { + $params['Item.' . $itemIndex . '.ItemName'] = $name; + $attributeIndex = 0; + foreach ($attributes as $attribute) { + // attribute value cannot be array, so when several items are passed + // they are treated as separate values with the same attribute name + foreach($attribute->getValues() as $value) { + $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName(); + $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value; + if (isset($replace[$name]) + && isset($replace[$name][$attribute->getName()]) + && $replace[$name][$attribute->getName()] + ) { + $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true'; + } + $attributeIndex++; + } + } + $itemIndex++; + } + + $response = $this->_sendRequest($params); + } + + /** + * Delete attributes + * + * @param string $domainName + * @param string $itemName + * @param array $attributes + * @return void + */ + public function deleteAttributes($domainName, $itemName, array $attributes = array()) + { + $params = array(); + $params['Action'] = 'DeleteAttributes'; + $params['DomainName'] = $domainName; + $params['ItemName'] = $itemName; + + $attributeIndex = 0; + foreach ($attributes as $attribute) { + foreach ($attribute->getValues() as $value) { + $params['Attribute.' . $attributeIndex . '.Name'] = $attribute->getName(); + $params['Attribute.' . $attributeIndex . '.Value'] = $value; + $attributeIndex++; + } + } + + $response = $this->_sendRequest($params); + + return true; + } + + /** + * List domains + * + * @param $maxNumberOfDomains int + * @param $nextToken int + * @return array 0 or more domain names + */ + public function listDomains($maxNumberOfDomains = 100, $nextToken = null) + { + $params = array(); + $params['Action'] = 'ListDomains'; + $params['MaxNumberOfDomains'] = $maxNumberOfDomains; + + if (null !== $nextToken) { + $params['NextToken'] = $nextToken; + } + $response = $this->_sendRequest($params); + + $domainNodes = $response->getSimpleXMLDocument()->ListDomainsResult->DomainName; + + $data = array(); + foreach ($domainNodes as $domain) { + $data[] = (string)$domain; + } + + $nextTokenNode = $response->getSimpleXMLDocument()->ListDomainsResult->NextToken; + $nextToken = (string)$nextTokenNode; + $nextToken = (trim($nextToken) === '') ? null : $nextToken; + + return new Zend_Service_Amazon_SimpleDb_Page($data, $nextToken); + } + + /** + * Retrieve domain metadata + * + * @param $domainName string Name of the domain for which metadata will be requested + * @return array Key/value array of metadatum names and values. + */ + public function domainMetadata($domainName) + { + $params = array(); + $params['Action'] = 'DomainMetadata'; + $params['DomainName'] = $domainName; + $response = $this->_sendRequest($params); + + $document = $response->getSimpleXMLDocument(); + + $metadataNodes = $document->DomainMetadataResult->children(); + $metadata = array(); + foreach ($metadataNodes as $metadataNode) { + $name = $metadataNode->getName(); + $metadata[$name] = (string)$metadataNode; + } + + return $metadata; + } + + /** + * Create a new domain + * + * @param $domainName string Valid domain name of the domain to create + * @return boolean True if successful, false if not + */ + public function createDomain($domainName) + { + $params = array(); + $params['Action'] = 'CreateDomain'; + $params['DomainName'] = $domainName; + $response = $this->_sendRequest($params); + return $response->getHttpResponse()->isSuccessful(); + } + + /** + * Delete a domain + * + * @param $domainName string Valid domain name of the domain to delete + * @return boolean True if successful, false if not + */ + public function deleteDomain($domainName) + { + $params = array(); + $params['Action'] = 'DeleteDomain'; + $params['DomainName'] = $domainName; + $response = $this->_sendRequest($params); + return $response->getHttpResponse()->isSuccessful(); + } + + /** + * Select items from the database + * + * @param string $selectExpression + * @param null|string $nextToken + * @return Zend_Service_Amazon_SimpleDb_Page + */ + public function select($selectExpression, $nextToken = null) + { + $params = array(); + $params['Action'] = 'Select'; + $params['SelectExpression'] = $selectExpression; + + if (null !== $nextToken) { + $params['NextToken'] = $nextToken; + } + + $response = $this->_sendRequest($params); + $xml = $response->getSimpleXMLDocument(); + + $attributes = array(); + foreach ($xml->SelectResult->Item as $item) { + $itemName = (string)$item->Name; + + foreach ($item->Attribute as $attribute) { + $attributeName = (string)$attribute->Name; + + $values = array(); + foreach ($attribute->Value as $value) { + $values[] = (string)$value; + } + $attributes[$itemName][$attributeName] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $attributeName, $values); + } + } + + $nextToken = (string)$xml->NextToken; + + return new Zend_Service_Amazon_SimpleDb_Page($attributes, $nextToken); + } + + /** + * Quote SDB value + * + * Wraps it in '' + * + * @param string $value + * @return string + */ + public function quote($value) + { + // wrap in single quotes and convert each ' inside to '' + return "'" . str_replace("'", "''", $value) . "'"; + } + + /** + * Quote SDB column or table name + * + * Wraps it in `` + * @param string $name + * @return string + */ + public function quoteName($name) + { + if (preg_match('/^[a-z_$][a-z0-9_$-]*$/i', $name) == false) { + throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid name: can contain only alphanumeric characters, \$ and _"); + } + return "`$name`"; + } + + /** + * Sends a HTTP request to the SimpleDB service using Zend_Http_Client + * + * @param array $params List of parameters to send with the request + * @return Zend_Service_Amazon_SimpleDb_Response + * @throws Zend_Service_Amazon_SimpleDb_Exception + */ + protected function _sendRequest(array $params = array()) + { + // UTF-8 encode all parameters and replace '+' characters + foreach ($params as $name => $value) { + unset($params[$name]); + $params[utf8_encode($name)] = $value; + } + + $params = $this->_addRequiredParameters($params); + + try { + /* @var $request Zend_Http_Client */ + $request = self::getHttpClient(); + $request->resetParameters(); + + $request->setConfig(array( + 'timeout' => $this->_httpTimeout + )); + + + $request->setUri($this->getEndpoint()); + $request->setMethod(Zend_Http_Client::POST); + foreach ($params as $key => $value) { + $params_out[] = rawurlencode($key)."=".rawurlencode($value); + } + $request->setRawData(join('&', $params_out), Zend_Http_Client::ENC_URLENCODED); + $httpResponse = $request->request(); + } catch (Zend_Http_Client_Exception $zhce) { + $message = 'Error in request to AWS service: ' . $zhce->getMessage(); + throw new Zend_Service_Amazon_SimpleDb_Exception($message, $zhce->getCode()); + } + $response = new Zend_Service_Amazon_SimpleDb_Response($httpResponse); + $this->_checkForErrors($response); + return $response; + } + + /** + * Adds required authentication and version parameters to an array of + * parameters + * + * The required parameters are: + * - AWSAccessKey + * - SignatureVersion + * - Timestamp + * - Version and + * - Signature + * + * If a required parameter is already set in the $parameters array, + * it is overwritten. + * + * @param array $parameters the array to which to add the required + * parameters. + * + * @return array + */ + protected function _addRequiredParameters(array $parameters) + { + $parameters['AWSAccessKeyId'] = $this->_getAccessKey(); + $parameters['SignatureVersion'] = $this->_signatureVersion; + $parameters['Timestamp'] = gmdate('c'); + $parameters['Version'] = $this->_sdbApiVersion; + $parameters['SignatureMethod'] = $this->_signatureMethod; + $parameters['Signature'] = $this->_signParameters($parameters); + + return $parameters; + } + + /** + * Computes the RFC 2104-compliant HMAC signature for request parameters + * + * This implements the Amazon Web Services signature, as per the following + * specification: + * + * 1. Sort all request parameters (including SignatureVersion and + * excluding Signature, the value of which is being created), + * ignoring case. + * + * 2. Iterate over the sorted list and append the parameter name (in its + * original case) and then its value. Do not URL-encode the parameter + * values before constructing this string. Do not use any separator + * characters when appending strings. + * + * @param array $parameters the parameters for which to get the signature. + * @param string $secretKey the secret key to use to sign the parameters. + * + * @return string the signed data. + */ + protected function _signParameters(array $paramaters) + { + $data = "POST\n"; + $data .= $this->getEndpoint()->getHost() . "\n"; + $data .= "/\n"; + + uksort($paramaters, 'strcmp'); + unset($paramaters['Signature']); + + $arrData = array(); + foreach ($paramaters as $key => $value) { + $value = urlencode($value); + $value = str_replace("%7E", "~", $value); + $value = str_replace("+", "%20", $value); + $arrData[] = urlencode($key) . '=' . $value; + } + + $data .= implode('&', $arrData); + + require_once 'Zend/Crypt/Hmac.php'; + $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY); + + return base64_encode($hmac); + } + + /** + * Checks for errors responses from Amazon + * + * @param Zend_Service_Amazon_SimpleDb_Response $response the response object to + * check. + * + * @return void + * + * @throws Zend_Service_Amazon_SimpleDb_Exception if one or more errors are + * returned from Amazon. + */ + private function _checkForErrors(Zend_Service_Amazon_SimpleDb_Response $response) + { + $xpath = new DOMXPath($response->getDocument()); + $list = $xpath->query('//Error'); + if ($list->length > 0) { + $node = $list->item(0); + $code = $xpath->evaluate('string(Code/text())', $node); + $message = $xpath->evaluate('string(Message/text())', $node); + throw new Zend_Service_Amazon_SimpleDb_Exception($message, 0, $code); + } + } +}