diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/Gdata/App.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/Gdata/App.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,1235 @@ += 1 is considered valid. + * + * Under most circumtances, this will be automatically set by + * Zend_Gdata_App subclasses. + * + * @see setMajorProtocolVersion() + * @see getMajorProtocolVersion() + */ + protected $_majorProtocolVersion; + + /** + * Indicates the minor protocol version that should be used. Can be set + * to either an integer >= 0, or NULL if no minor version should be sent + * to the server. + * + * At present, this field is not used by any Google services, but may be + * used in the future. + * + * Under most circumtances, this will be automatically set by + * Zend_Gdata_App subclasses. + * + * @see setMinorProtocolVersion() + * @see getMinorProtocolVersion() + */ + protected $_minorProtocolVersion; + + /** + * Whether we want to use XML to object mapping when fetching data. + * + * @var boolean + */ + protected $_useObjectMapping = true; + + /** + * Create Gdata object + * + * @param Zend_Http_Client $client + * @param string $applicationId + */ + public function __construct($client = null, $applicationId = 'MyCompany-MyApp-1.0') + { + $this->setHttpClient($client, $applicationId); + // Set default protocol version. Subclasses should override this as + // needed once a given service supports a new version. + $this->setMajorProtocolVersion(self::DEFAULT_MAJOR_PROTOCOL_VERSION); + $this->setMinorProtocolVersion(self::DEFAULT_MINOR_PROTOCOL_VERSION); + } + + /** + * Adds a Zend Framework package to the $_registeredPackages array. + * This array is searched when using the magic __call method below + * to instantiante new objects. + * + * @param string $name The name of the package (eg Zend_Gdata_App) + * @return void + */ + public function registerPackage($name) + { + array_unshift($this->_registeredPackages, $name); + } + + /** + * Retrieve feed as string or object + * + * @param string $uri The uri from which to retrieve the feed + * @param string $className The class which is used as the return type + * @return string|Zend_Gdata_App_Feed Returns string only if the object + * mapping has been disabled explicitly + * by passing false to the + * useObjectMapping() function. + */ + public function getFeed($uri, $className='Zend_Gdata_App_Feed') + { + return $this->importUrl($uri, $className, null); + } + + /** + * Retrieve entry as string or object + * + * @param string $uri + * @param string $className The class which is used as the return type + * @return string|Zend_Gdata_App_Entry Returns string only if the object + * mapping has been disabled explicitly + * by passing false to the + * useObjectMapping() function. + */ + public function getEntry($uri, $className='Zend_Gdata_App_Entry') + { + return $this->importUrl($uri, $className, null); + } + + /** + * Get the Zend_Http_Client object used for communication + * + * @return Zend_Http_Client + */ + public function getHttpClient() + { + return $this->_httpClient; + } + + /** + * Set the Zend_Http_Client object used for communication + * + * @param Zend_Http_Client $client The client to use for communication + * @throws Zend_Gdata_App_HttpException + * @return Zend_Gdata_App Provides a fluent interface + */ + public function setHttpClient($client, + $applicationId = 'MyCompany-MyApp-1.0') + { + if ($client === null) { + $client = new Zend_Http_Client(); + } + if (!$client instanceof Zend_Http_Client) { + require_once 'Zend/Gdata/App/HttpException.php'; + throw new Zend_Gdata_App_HttpException( + 'Argument is not an instance of Zend_Http_Client.'); + } + $userAgent = $applicationId . ' Zend_Framework_Gdata/' . + Zend_Version::VERSION; + $client->setHeaders('User-Agent', $userAgent); + $client->setConfig(array( + 'strictredirects' => true + ) + ); + $this->_httpClient = $client; + self::setStaticHttpClient($client); + return $this; + } + + /** + * Set the static HTTP client instance + * + * Sets the static HTTP client object to use for retrieving the feed. + * + * @param Zend_Http_Client $httpClient + * @return void + */ + public static function setStaticHttpClient(Zend_Http_Client $httpClient) + { + self::$_staticHttpClient = $httpClient; + } + + + /** + * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used. + * + * @return Zend_Http_Client + */ + public static function getStaticHttpClient() + { + if (!self::$_staticHttpClient instanceof Zend_Http_Client) { + $client = new Zend_Http_Client(); + $userAgent = 'Zend_Framework_Gdata/' . Zend_Version::VERSION; + $client->setHeaders('User-Agent', $userAgent); + $client->setConfig(array( + 'strictredirects' => true + ) + ); + self::$_staticHttpClient = $client; + } + return self::$_staticHttpClient; + } + + /** + * Toggle using POST instead of PUT and DELETE HTTP methods + * + * Some feed implementations do not accept PUT and DELETE HTTP + * methods, or they can't be used because of proxies or other + * measures. This allows turning on using POST where PUT and + * DELETE would normally be used; in addition, an + * X-Method-Override header will be sent with a value of PUT or + * DELETE as appropriate. + * + * @param boolean $override Whether to override PUT and DELETE with POST. + * @return void + */ + public static function setHttpMethodOverride($override = true) + { + self::$_httpMethodOverride = $override; + } + + /** + * Get the HTTP override state + * + * @return boolean + */ + public static function getHttpMethodOverride() + { + return self::$_httpMethodOverride; + } + + /** + * Toggle requesting gzip encoded responses + * + * @param boolean $enabled Whether or not to enable gzipped responses + * @return void + */ + public static function setGzipEnabled($enabled = false) + { + if ($enabled && !function_exists('gzinflate')) { + require_once 'Zend/Gdata/App/InvalidArgumentException.php'; + throw new Zend_Gdata_App_InvalidArgumentException( + 'You cannot enable gzipped responses if the zlib module ' . + 'is not enabled in your PHP installation.'); + + } + self::$_gzipEnabled = $enabled; + } + + /** + * Get the HTTP override state + * + * @return boolean + */ + public static function getGzipEnabled() + { + return self::$_gzipEnabled; + } + + /** + * Get whether to use verbose exception messages + * + * In the case of HTTP errors, use the body of the HTTP response + * in the exception message. + * + * @return boolean + */ + public static function getVerboseExceptionMessages() + { + return self::$_verboseExceptionMessages; + } + + /** + * Set whether to use verbose exception messages + * + * In the case of HTTP errors, use the body of the HTTP response + * in the exception message. + * + * @param boolean $verbose Whether to use verbose exception messages + */ + public static function setVerboseExceptionMessages($verbose) + { + self::$_verboseExceptionMessages = $verbose; + } + + /** + * Set the maximum number of redirects to follow during HTTP operations + * + * @param int $maxRedirects Maximum number of redirects to follow + * @return void + */ + public static function setMaxRedirects($maxRedirects) + { + self::$_maxRedirects = $maxRedirects; + } + + /** + * Get the maximum number of redirects to follow during HTTP operations + * + * @return int Maximum number of redirects to follow + */ + public static function getMaxRedirects() + { + return self::$_maxRedirects; + } + + /** + * Set the major protocol version that should be used. Values < 1 will + * cause a Zend_Gdata_App_InvalidArgumentException to be thrown. + * + * @see _majorProtocolVersion + * @param int $value The major protocol version to use. + * @throws Zend_Gdata_App_InvalidArgumentException + */ + public function setMajorProtocolVersion($value) + { + if (!($value >= 1)) { + require_once('Zend/Gdata/App/InvalidArgumentException.php'); + throw new Zend_Gdata_App_InvalidArgumentException( + 'Major protocol version must be >= 1'); + } + $this->_majorProtocolVersion = $value; + } + + /** + * Get the major protocol version that is in use. + * + * @see _majorProtocolVersion + * @return int The major protocol version in use. + */ + public function getMajorProtocolVersion() + { + return $this->_majorProtocolVersion; + } + + /** + * Set the minor protocol version that should be used. If set to NULL, no + * minor protocol version will be sent to the server. Values < 0 will + * cause a Zend_Gdata_App_InvalidArgumentException to be thrown. + * + * @see _minorProtocolVersion + * @param (int|NULL) $value The minor protocol version to use. + * @throws Zend_Gdata_App_InvalidArgumentException + */ + public function setMinorProtocolVersion($value) + { + if (!($value >= 0)) { + require_once('Zend/Gdata/App/InvalidArgumentException.php'); + throw new Zend_Gdata_App_InvalidArgumentException( + 'Minor protocol version must be >= 0'); + } + $this->_minorProtocolVersion = $value; + } + + /** + * Get the minor protocol version that is in use. + * + * @see _minorProtocolVersion + * @return (int|NULL) The major protocol version in use, or NULL if no + * minor version is specified. + */ + public function getMinorProtocolVersion() + { + return $this->_minorProtocolVersion; + } + + /** + * Provides pre-processing for HTTP requests to APP services. + * + * 1. Checks the $data element and, if it's an entry, extracts the XML, + * multipart data, edit link (PUT,DELETE), etc. + * 2. If $data is a string, sets the default content-type header as + * 'application/atom+xml' if it's not already been set. + * 3. Adds a x-http-method override header and changes the HTTP method + * to 'POST' if necessary as per getHttpMethodOverride() + * + * @param string $method The HTTP method for the request - 'GET', 'POST', + * 'PUT', 'DELETE' + * @param string $url The URL to which this request is being performed, + * or null if found in $data + * @param array $headers An associative array of HTTP headers for this + * request + * @param mixed $data The Zend_Gdata_App_Entry or XML for the + * body of the request + * @param string $contentTypeOverride The override value for the + * content type of the request body + * @return array An associative array containing the determined + * 'method', 'url', 'data', 'headers', 'contentType' + */ + public function prepareRequest($method, + $url = null, + $headers = array(), + $data = null, + $contentTypeOverride = null) + { + // As a convenience, if $headers is null, we'll convert it back to + // an empty array. + if ($headers === null) { + $headers = array(); + } + + $rawData = null; + $finalContentType = null; + if ($url == null) { + $url = $this->_defaultPostUri; + } + + if (is_string($data)) { + $rawData = $data; + if ($contentTypeOverride === null) { + $finalContentType = 'application/atom+xml'; + } + } elseif ($data instanceof Zend_Gdata_App_MediaEntry) { + $rawData = $data->encode(); + if ($data->getMediaSource() !== null) { + $finalContentType = $rawData->getContentType(); + $headers['MIME-version'] = '1.0'; + $headers['Slug'] = $data->getMediaSource()->getSlug(); + } else { + $finalContentType = 'application/atom+xml'; + } + if ($method == 'PUT' || $method == 'DELETE') { + $editLink = $data->getEditLink(); + if ($editLink != null && $url == null) { + $url = $editLink->getHref(); + } + } + } elseif ($data instanceof Zend_Gdata_App_Entry) { + $rawData = $data->saveXML(); + $finalContentType = 'application/atom+xml'; + if ($method == 'PUT' || $method == 'DELETE') { + $editLink = $data->getEditLink(); + if ($editLink != null) { + $url = $editLink->getHref(); + } + } + } elseif ($data instanceof Zend_Gdata_App_MediaSource) { + $rawData = $data->encode(); + if ($data->getSlug() !== null) { + $headers['Slug'] = $data->getSlug(); + } + $finalContentType = $data->getContentType(); + } + + if ($method == 'DELETE') { + $rawData = null; + } + + // Set an If-Match header if: + // - This isn't a DELETE + // - If this isn't a GET, the Etag isn't weak + // - A similar header (If-Match/If-None-Match) hasn't already been + // set. + if ($method != 'DELETE' && ( + !array_key_exists('If-Match', $headers) && + !array_key_exists('If-None-Match', $headers) + ) ) { + $allowWeak = $method == 'GET'; + if ($ifMatchHeader = $this->generateIfMatchHeaderData( + $data, $allowWeak)) { + $headers['If-Match'] = $ifMatchHeader; + } + } + + if ($method != 'POST' && $method != 'GET' && Zend_Gdata_App::getHttpMethodOverride()) { + $headers['x-http-method-override'] = $method; + $method = 'POST'; + } else { + $headers['x-http-method-override'] = null; + } + + if ($contentTypeOverride != null) { + $finalContentType = $contentTypeOverride; + } + + return array('method' => $method, 'url' => $url, + 'data' => $rawData, 'headers' => $headers, + 'contentType' => $finalContentType); + } + + /** + * Performs a HTTP request using the specified method + * + * @param string $method The HTTP method for the request - 'GET', 'POST', + * 'PUT', 'DELETE' + * @param string $url The URL to which this request is being performed + * @param array $headers An associative array of HTTP headers + * for this request + * @param string $body The body of the HTTP request + * @param string $contentType The value for the content type + * of the request body + * @param int $remainingRedirects Number of redirects to follow if request + * s results in one + * @return Zend_Http_Response The response object + */ + public function performHttpRequest($method, $url, $headers = null, + $body = null, $contentType = null, $remainingRedirects = null) + { + require_once 'Zend/Http/Client/Exception.php'; + if ($remainingRedirects === null) { + $remainingRedirects = self::getMaxRedirects(); + } + if ($headers === null) { + $headers = array(); + } + // Append a Gdata version header if protocol v2 or higher is in use. + // (Protocol v1 does not use this header.) + $major = $this->getMajorProtocolVersion(); + $minor = $this->getMinorProtocolVersion(); + if ($major >= 2) { + $headers['GData-Version'] = $major + + (($minor === null) ? '.' + $minor : ''); + } + + // check the overridden method + if (($method == 'POST' || $method == 'PUT') && $body === null && + $headers['x-http-method-override'] != 'DELETE') { + require_once 'Zend/Gdata/App/InvalidArgumentException.php'; + throw new Zend_Gdata_App_InvalidArgumentException( + 'You must specify the data to post as either a ' . + 'string or a child of Zend_Gdata_App_Entry'); + } + if ($url === null) { + require_once 'Zend/Gdata/App/InvalidArgumentException.php'; + throw new Zend_Gdata_App_InvalidArgumentException( + 'You must specify an URI to which to post.'); + } + $headers['Content-Type'] = $contentType; + if (Zend_Gdata_App::getGzipEnabled()) { + // some services require the word 'gzip' to be in the user-agent + // header in addition to the accept-encoding header + if (strpos($this->_httpClient->getHeader('User-Agent'), + 'gzip') === false) { + $headers['User-Agent'] = + $this->_httpClient->getHeader('User-Agent') . ' (gzip)'; + } + $headers['Accept-encoding'] = 'gzip, deflate'; + } else { + $headers['Accept-encoding'] = 'identity'; + } + + // Make sure the HTTP client object is 'clean' before making a request + // In addition to standard headers to reset via resetParameters(), + // also reset the Slug and If-Match headers + $this->_httpClient->resetParameters(); + $this->_httpClient->setHeaders(array('Slug', 'If-Match')); + + // Set the params for the new request to be performed + $this->_httpClient->setHeaders($headers); + require_once 'Zend/Uri/Http.php'; + $uri = Zend_Uri_Http::fromString($url); + preg_match("/^(.*?)(\?.*)?$/", $url, $matches); + $this->_httpClient->setUri($matches[1]); + $queryArray = $uri->getQueryAsArray(); + foreach ($queryArray as $name => $value) { + $this->_httpClient->setParameterGet($name, $value); + } + + + $this->_httpClient->setConfig(array('maxredirects' => 0)); + + // Set the proper adapter if we are handling a streaming upload + $usingMimeStream = false; + $oldHttpAdapter = null; + + if ($body instanceof Zend_Gdata_MediaMimeStream) { + $usingMimeStream = true; + $this->_httpClient->setRawDataStream($body, $contentType); + $oldHttpAdapter = $this->_httpClient->getAdapter(); + + if ($oldHttpAdapter instanceof Zend_Http_Client_Adapter_Proxy) { + require_once 'Zend/Gdata/HttpAdapterStreamingProxy.php'; + $newAdapter = new Zend_Gdata_HttpAdapterStreamingProxy(); + } else { + require_once 'Zend/Gdata/HttpAdapterStreamingSocket.php'; + $newAdapter = new Zend_Gdata_HttpAdapterStreamingSocket(); + } + $this->_httpClient->setAdapter($newAdapter); + } else { + $this->_httpClient->setRawData($body, $contentType); + } + + try { + $response = $this->_httpClient->request($method); + // reset adapter + if ($usingMimeStream) { + $this->_httpClient->setAdapter($oldHttpAdapter); + } + } catch (Zend_Http_Client_Exception $e) { + // reset adapter + if ($usingMimeStream) { + $this->_httpClient->setAdapter($oldHttpAdapter); + } + require_once 'Zend/Gdata/App/HttpException.php'; + throw new Zend_Gdata_App_HttpException($e->getMessage(), $e); + } + if ($response->isRedirect() && $response->getStatus() != '304') { + if ($remainingRedirects > 0) { + $newUrl = $response->getHeader('Location'); + $response = $this->performHttpRequest( + $method, $newUrl, $headers, $body, + $contentType, $remainingRedirects); + } else { + require_once 'Zend/Gdata/App/HttpException.php'; + throw new Zend_Gdata_App_HttpException( + 'Number of redirects exceeds maximum', null, $response); + } + } + if (!$response->isSuccessful()) { + require_once 'Zend/Gdata/App/HttpException.php'; + $exceptionMessage = 'Expected response code 200, got ' . + $response->getStatus(); + if (self::getVerboseExceptionMessages()) { + $exceptionMessage .= "\n" . $response->getBody(); + } + $exception = new Zend_Gdata_App_HttpException($exceptionMessage); + $exception->setResponse($response); + throw $exception; + } + return $response; + } + + /** + * Imports a feed located at $uri. + * + * @param string $uri + * @param Zend_Http_Client $client The client used for communication + * @param string $className The class which is used as the return type + * @throws Zend_Gdata_App_Exception + * @return string|Zend_Gdata_App_Feed Returns string only if the object + * mapping has been disabled explicitly + * by passing false to the + * useObjectMapping() function. + */ + public static function import($uri, $client = null, + $className='Zend_Gdata_App_Feed') + { + $app = new Zend_Gdata_App($client); + $requestData = $app->prepareRequest('GET', $uri); + $response = $app->performHttpRequest( + $requestData['method'], $requestData['url']); + + $feedContent = $response->getBody(); + if (!$this->_useObjectMapping) { + return $feedContent; + } + $feed = self::importString($feedContent, $className); + if ($client != null) { + $feed->setHttpClient($client); + } + return $feed; + } + + /** + * Imports the specified URL (non-statically). + * + * @param string $url The URL to import + * @param string $className The class which is used as the return type + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @throws Zend_Gdata_App_Exception + * @return string|Zend_Gdata_App_Feed Returns string only if the object + * mapping has been disabled explicitly + * by passing false to the + * useObjectMapping() function. + */ + public function importUrl($url, $className='Zend_Gdata_App_Feed', + $extraHeaders = array()) + { + $response = $this->get($url, $extraHeaders); + + $feedContent = $response->getBody(); + if (!$this->_useObjectMapping) { + return $feedContent; + } + + $protocolVersionStr = $response->getHeader('GData-Version'); + $majorProtocolVersion = null; + $minorProtocolVersion = null; + if ($protocolVersionStr !== null) { + // Extract protocol major and minor version from header + $delimiterPos = strpos($protocolVersionStr, '.'); + $length = strlen($protocolVersionStr); + $major = substr($protocolVersionStr, 0, $delimiterPos); + $minor = substr($protocolVersionStr, $delimiterPos + 1, $length); + $majorProtocolVersion = $major; + $minorProtocolVersion = $minor; + } + + $feed = self::importString($feedContent, $className, + $majorProtocolVersion, $minorProtocolVersion); + if ($this->getHttpClient() != null) { + $feed->setHttpClient($this->getHttpClient()); + } + $etag = $response->getHeader('ETag'); + if ($etag !== null) { + $feed->setEtag($etag); + } + return $feed; + } + + + /** + * Imports a feed represented by $string. + * + * @param string $string + * @param string $className The class which is used as the return type + * @param integer $majorProcolVersion (optional) The major protocol version + * of the data model object that is to be created. + * @param integer $minorProcolVersion (optional) The minor protocol version + * of the data model object that is to be created. + * @throws Zend_Gdata_App_Exception + * @return Zend_Gdata_App_Feed + */ + public static function importString($string, + $className='Zend_Gdata_App_Feed', $majorProtocolVersion = null, + $minorProtocolVersion = null) + { + if (!class_exists($className, false)) { + require_once 'Zend/Loader.php'; + @Zend_Loader::loadClass($className); + } + + // Load the feed as an XML DOMDocument object + @ini_set('track_errors', 1); + $doc = new DOMDocument(); + $success = @$doc->loadXML($string); + @ini_restore('track_errors'); + + if (!$success) { + require_once 'Zend/Gdata/App/Exception.php'; + throw new Zend_Gdata_App_Exception( + "DOMDocument cannot parse XML: $php_errormsg"); + } + + $feed = new $className(); + $feed->setMajorProtocolVersion($majorProtocolVersion); + $feed->setMinorProtocolVersion($minorProtocolVersion); + $feed->transferFromXML($string); + $feed->setHttpClient(self::getstaticHttpClient()); + return $feed; + } + + + /** + * Imports a feed from a file located at $filename. + * + * @param string $filename + * @param string $className The class which is used as the return type + * @param string $useIncludePath Whether the include_path should be searched + * @throws Zend_Gdata_App_Exception + * @return Zend_Gdata_App_Feed + */ + public static function importFile($filename, + $className='Zend_Gdata_App_Feed', $useIncludePath = false) + { + @ini_set('track_errors', 1); + $feed = @file_get_contents($filename, $useIncludePath); + @ini_restore('track_errors'); + if ($feed === false) { + require_once 'Zend/Gdata/App/Exception.php'; + throw new Zend_Gdata_App_Exception( + "File could not be loaded: $php_errormsg"); + } + return self::importString($feed, $className); + } + + /** + * GET a URI using client object. + * + * @param string $uri GET URI + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @throws Zend_Gdata_App_HttpException + * @return Zend_Http_Response + */ + public function get($uri, $extraHeaders = array()) + { + $requestData = $this->prepareRequest('GET', $uri, $extraHeaders); + return $this->performHttpRequest( + $requestData['method'], $requestData['url'], + $requestData['headers']); + } + + /** + * POST data with client object + * + * @param mixed $data The Zend_Gdata_App_Entry or XML to post + * @param string $uri POST URI + * @param array $headers Additional HTTP headers to insert. + * @param string $contentType Content-type of the data + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @return Zend_Http_Response + * @throws Zend_Gdata_App_Exception + * @throws Zend_Gdata_App_HttpException + * @throws Zend_Gdata_App_InvalidArgumentException + */ + public function post($data, $uri = null, $remainingRedirects = null, + $contentType = null, $extraHeaders = null) + { + $requestData = $this->prepareRequest( + 'POST', $uri, $extraHeaders, $data, $contentType); + return $this->performHttpRequest( + $requestData['method'], $requestData['url'], + $requestData['headers'], $requestData['data'], + $requestData['contentType']); + } + + /** + * PUT data with client object + * + * @param mixed $data The Zend_Gdata_App_Entry or XML to post + * @param string $uri PUT URI + * @param array $headers Additional HTTP headers to insert. + * @param string $contentType Content-type of the data + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @return Zend_Http_Response + * @throws Zend_Gdata_App_Exception + * @throws Zend_Gdata_App_HttpException + * @throws Zend_Gdata_App_InvalidArgumentException + */ + public function put($data, $uri = null, $remainingRedirects = null, + $contentType = null, $extraHeaders = null) + { + $requestData = $this->prepareRequest( + 'PUT', $uri, $extraHeaders, $data, $contentType); + return $this->performHttpRequest( + $requestData['method'], $requestData['url'], + $requestData['headers'], $requestData['data'], + $requestData['contentType']); + } + + /** + * DELETE entry with client object + * + * @param mixed $data The Zend_Gdata_App_Entry or URL to delete + * @return void + * @throws Zend_Gdata_App_Exception + * @throws Zend_Gdata_App_HttpException + * @throws Zend_Gdata_App_InvalidArgumentException + */ + public function delete($data, $remainingRedirects = null) + { + if (is_string($data)) { + $requestData = $this->prepareRequest('DELETE', $data); + } else { + $headers = array(); + + $requestData = $this->prepareRequest( + 'DELETE', null, $headers, $data); + } + return $this->performHttpRequest($requestData['method'], + $requestData['url'], + $requestData['headers'], + '', + $requestData['contentType'], + $remainingRedirects); + } + + /** + * Inserts an entry to a given URI and returns the response as a + * fully formed Entry. + * + * @param mixed $data The Zend_Gdata_App_Entry or XML to post + * @param string $uri POST URI + * @param string $className The class of entry to be returned. + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @return Zend_Gdata_App_Entry The entry returned by the service after + * insertion. + */ + public function insertEntry($data, $uri, $className='Zend_Gdata_App_Entry', + $extraHeaders = array()) + { + if (!class_exists($className, false)) { + require_once 'Zend/Loader.php'; + @Zend_Loader::loadClass($className); + } + + $response = $this->post($data, $uri, null, null, $extraHeaders); + + $returnEntry = new $className($response->getBody()); + $returnEntry->setHttpClient(self::getstaticHttpClient()); + + $etag = $response->getHeader('ETag'); + if ($etag !== null) { + $returnEntry->setEtag($etag); + } + + return $returnEntry; + } + + /** + * Update an entry + * + * @param mixed $data Zend_Gdata_App_Entry or XML (w/ID and link rel='edit') + * @param string|null The URI to send requests to, or null if $data + * contains the URI. + * @param string|null The name of the class that should be deserialized + * from the server response. If null, then 'Zend_Gdata_App_Entry' + * will be used. + * @param array $extraHeaders Extra headers to add to the request, as an + * array of string-based key/value pairs. + * @return Zend_Gdata_App_Entry The entry returned from the server + * @throws Zend_Gdata_App_Exception + */ + public function updateEntry($data, $uri = null, $className = null, + $extraHeaders = array()) + { + if ($className === null && $data instanceof Zend_Gdata_App_Entry) { + $className = get_class($data); + } elseif ($className === null) { + $className = 'Zend_Gdata_App_Entry'; + } + + if (!class_exists($className, false)) { + require_once 'Zend/Loader.php'; + @Zend_Loader::loadClass($className); + } + + $response = $this->put($data, $uri, null, null, $extraHeaders); + $returnEntry = new $className($response->getBody()); + $returnEntry->setHttpClient(self::getstaticHttpClient()); + + $etag = $response->getHeader('ETag'); + if ($etag !== null) { + $returnEntry->setEtag($etag); + } + + return $returnEntry; + } + + /** + * Provides a magic factory method to instantiate new objects with + * shorter syntax than would otherwise be required by the Zend Framework + * naming conventions. For instance, to construct a new + * Zend_Gdata_Calendar_Extension_Color, a developer simply needs to do + * $gCal->newColor(). For this magic constructor, packages are searched + * in the same order as which they appear in the $_registeredPackages + * array + * + * @param string $method The method name being called + * @param array $args The arguments passed to the call + * @throws Zend_Gdata_App_Exception + */ + public function __call($method, $args) + { + if (preg_match('/^new(\w+)/', $method, $matches)) { + $class = $matches[1]; + $foundClassName = null; + foreach ($this->_registeredPackages as $name) { + try { + // Autoloading disabled on next line for compatibility + // with magic factories. See ZF-6660. + if (!class_exists($name . '_' . $class, false)) { + require_once 'Zend/Loader.php'; + @Zend_Loader::loadClass($name . '_' . $class); + } + $foundClassName = $name . '_' . $class; + break; + } catch (Zend_Exception $e) { + // package wasn't here- continue searching + } + } + if ($foundClassName != null) { + $reflectionObj = new ReflectionClass($foundClassName); + $instance = $reflectionObj->newInstanceArgs($args); + if ($instance instanceof Zend_Gdata_App_FeedEntryParent) { + $instance->setHttpClient($this->_httpClient); + + // Propogate version data + $instance->setMajorProtocolVersion( + $this->_majorProtocolVersion); + $instance->setMinorProtocolVersion( + $this->_minorProtocolVersion); + } + return $instance; + } else { + require_once 'Zend/Gdata/App/Exception.php'; + throw new Zend_Gdata_App_Exception( + "Unable to find '${class}' in registered packages"); + } + } else { + require_once 'Zend/Gdata/App/Exception.php'; + throw new Zend_Gdata_App_Exception("No such method ${method}"); + } + } + + /** + * Retrieve all entries for a feed, iterating through pages as necessary. + * Be aware that calling this function on a large dataset will take a + * significant amount of time to complete. In some cases this may cause + * execution to timeout without proper precautions in place. + * + * @param $feed The feed to iterate through. + * @return mixed A new feed of the same type as the one originally + * passed in, containing all relevent entries. + */ + public function retrieveAllEntriesForFeed($feed) { + $feedClass = get_class($feed); + $reflectionObj = new ReflectionClass($feedClass); + $result = $reflectionObj->newInstance(); + do { + foreach ($feed as $entry) { + $result->addEntry($entry); + } + + $next = $feed->getLink('next'); + if ($next !== null) { + $feed = $this->getFeed($next->href, $feedClass); + } else { + $feed = null; + } + } + while ($feed != null); + return $result; + } + + /** + * This method enables logging of requests by changing the + * Zend_Http_Client_Adapter used for performing the requests. + * NOTE: This will not work if you have customized the adapter + * already to use a proxy server or other interface. + * + * @param $logfile The logfile to use when logging the requests + */ + public function enableRequestDebugLogging($logfile) + { + $this->_httpClient->setConfig(array( + 'adapter' => 'Zend_Gdata_App_LoggingHttpClientAdapterSocket', + 'logfile' => $logfile + )); + } + + /** + * Retrieve next set of results based on a given feed. + * + * @param Zend_Gdata_App_Feed $feed The feed from which to + * retreive the next set of results. + * @param string $className (optional) The class of feed to be returned. + * If null, the next feed (if found) will be the same class as + * the feed that was given as the first argument. + * @return Zend_Gdata_App_Feed|null Returns a + * Zend_Gdata_App_Feed or null if no next set of results + * exists. + */ + public function getNextFeed($feed, $className = null) + { + $nextLink = $feed->getNextLink(); + if (!$nextLink) { + return null; + } + $nextLinkHref = $nextLink->getHref(); + + if ($className === null) { + $className = get_class($feed); + } + + return $this->getFeed($nextLinkHref, $className); + } + + /** + * Retrieve previous set of results based on a given feed. + * + * @param Zend_Gdata_App_Feed $feed The feed from which to + * retreive the previous set of results. + * @param string $className (optional) The class of feed to be returned. + * If null, the previous feed (if found) will be the same class as + * the feed that was given as the first argument. + * @return Zend_Gdata_App_Feed|null Returns a + * Zend_Gdata_App_Feed or null if no previous set of results + * exists. + */ + public function getPreviousFeed($feed, $className = null) + { + $previousLink = $feed->getPreviousLink(); + if (!$previousLink) { + return null; + } + $previousLinkHref = $previousLink->getHref(); + + if ($className === null) { + $className = get_class($feed); + } + + return $this->getFeed($previousLinkHref, $className); + } + + /** + * Returns the data for an If-Match header based on the current Etag + * property. If Etags are not supported by the server or cannot be + * extracted from the data, then null will be returned. + * + * @param boolean $allowWeak If false, then if a weak Etag is detected, + * then return null rather than the Etag. + * @return string|null $data + */ + public function generateIfMatchHeaderData($data, $allowWeek) + { + $result = ''; + // Set an If-Match header if an ETag has been set (version >= 2 only) + if ($this->_majorProtocolVersion >= 2 && + $data instanceof Zend_Gdata_App_Entry) { + $etag = $data->getEtag(); + if (($etag !== null) && + ($allowWeek || substr($etag, 0, 2) != 'W/')) { + $result = $data->getEtag(); + } + } + return $result; + } + + /** + * Determine whether service object is using XML to object mapping. + * + * @return boolean True if service object is using XML to object mapping, + * false otherwise. + */ + public function usingObjectMapping() + { + return $this->_useObjectMapping; + } + + /** + * Enable/disable the use of XML to object mapping. + * + * @param boolean $value Pass in true to use the XML to object mapping. + * Pass in false or null to disable it. + * @return void + */ + public function useObjectMapping($value) + { + if ($value === True) { + $this->_useObjectMapping = true; + } else { + $this->_useObjectMapping = false; + } + } + +}