File indexing completed on 2024-05-12 06:02:40

0001 <?php
0002 
0003 /**
0004  * Zend Framework
0005  *
0006  * LICENSE
0007  *
0008  * This source file is subject to the new BSD license that is bundled
0009  * with this package in the file LICENSE.txt.
0010  * It is also available through the world-wide-web at this URL:
0011  * http://framework.zend.com/license/new-bsd
0012  * If you did not receive a copy of the license and are unable to
0013  * obtain it through the world-wide-web, please send an email
0014  * to license@zend.com so we can send you a copy immediately.
0015  *
0016  * @category   Zend
0017  * @package    Zend_Gdata
0018  * @subpackage App
0019  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0020  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0021  * @version    $Id$
0022  */
0023 
0024 /**
0025  * Zend_Gdata_Feed
0026  */
0027 // require_once 'Zend/Gdata/App/Feed.php';
0028 
0029 /**
0030  * Zend_Gdata_Http_Client
0031  */
0032 // require_once 'Zend/Http/Client.php';
0033 
0034 /**
0035  * Zend_Version
0036  */
0037 // require_once 'Zend/Version.php';
0038 
0039 /**
0040  * Zend_Gdata_App_MediaSource
0041  */
0042 // require_once 'Zend/Gdata/App/MediaSource.php';
0043 
0044 /**
0045  * Zend_Uri/Http
0046  */
0047 // require_once 'Zend/Uri/Http.php';
0048 
0049 /** @see Zend_Xml_Security */
0050 // require_once 'Zend/Xml/Security.php';
0051 
0052 /**
0053  * Provides Atom Publishing Protocol (APP) functionality.  This class and all
0054  * other components of Zend_Gdata_App are designed to work independently from
0055  * other Zend_Gdata components in order to interact with generic APP services.
0056  *
0057  * @category   Zend
0058  * @package    Zend_Gdata
0059  * @subpackage App
0060  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0061  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0062  */
0063 class Zend_Gdata_App
0064 {
0065 
0066     /** Default major protocol version.
0067       *
0068       * @see _majorProtocolVersion
0069       */
0070     const DEFAULT_MAJOR_PROTOCOL_VERSION = 1;
0071 
0072     /** Default minor protocol version.
0073       *
0074       * @see _minorProtocolVersion
0075       */
0076     const DEFAULT_MINOR_PROTOCOL_VERSION = null;
0077 
0078     /**
0079      * Client object used to communicate
0080      *
0081      * @var Zend_Http_Client
0082      */
0083     protected $_httpClient;
0084 
0085     /**
0086      * Client object used to communicate in static context
0087      *
0088      * @var Zend_Http_Client
0089      */
0090     protected static $_staticHttpClient = null;
0091 
0092     /**
0093      * Override HTTP PUT and DELETE request methods?
0094      *
0095      * @var boolean
0096      */
0097     protected static $_httpMethodOverride = false;
0098 
0099     /**
0100      * Enable gzipped responses?
0101      *
0102      * @var boolean
0103      */
0104     protected static $_gzipEnabled = false;
0105 
0106     /**
0107      * Use verbose exception messages.  In the case of HTTP errors,
0108      * use the body of the HTTP response in the exception message.
0109      *
0110      * @var boolean
0111      */
0112     protected static $_verboseExceptionMessages = true;
0113 
0114     /**
0115      * Default URI to which to POST.
0116      *
0117      * @var string
0118      */
0119     protected $_defaultPostUri = null;
0120 
0121     /**
0122      * Packages to search for classes when using magic __call method, in order.
0123      *
0124      * @var array
0125      */
0126     protected $_registeredPackages = array(
0127             'Zend_Gdata_App_Extension',
0128             'Zend_Gdata_App');
0129 
0130     /**
0131      * Maximum number of redirects to follow during HTTP operations
0132      *
0133      * @var int
0134      */
0135     protected static $_maxRedirects = 5;
0136 
0137     /**
0138       * Indicates the major protocol version that should be used.
0139       * At present, recognized values are either 1 or 2. However, any integer
0140       * value >= 1 is considered valid.
0141       *
0142       * Under most circumtances, this will be automatically set by
0143       * Zend_Gdata_App subclasses.
0144       *
0145       * @see setMajorProtocolVersion()
0146       * @see getMajorProtocolVersion()
0147       */
0148     protected $_majorProtocolVersion;
0149 
0150     /**
0151       * Indicates the minor protocol version that should be used. Can be set
0152       * to either an integer >= 0, or NULL if no minor version should be sent
0153       * to the server.
0154       *
0155       * At present, this field is not used by any Google services, but may be
0156       * used in the future.
0157       *
0158       * Under most circumtances, this will be automatically set by
0159       * Zend_Gdata_App subclasses.
0160       *
0161       * @see setMinorProtocolVersion()
0162       * @see getMinorProtocolVersion()
0163       */
0164     protected $_minorProtocolVersion;
0165 
0166     /**
0167      * Whether we want to use XML to object mapping when fetching data.
0168      *
0169      * @var boolean
0170      */
0171     protected $_useObjectMapping = true;
0172 
0173     /**
0174      * Create Gdata object
0175      *
0176      * @param Zend_Http_Client $client
0177      * @param string $applicationId
0178      */
0179     public function __construct($client = null, $applicationId = 'MyCompany-MyApp-1.0')
0180     {
0181         $this->setHttpClient($client, $applicationId);
0182         // Set default protocol version. Subclasses should override this as
0183         // needed once a given service supports a new version.
0184         $this->setMajorProtocolVersion(self::DEFAULT_MAJOR_PROTOCOL_VERSION);
0185         $this->setMinorProtocolVersion(self::DEFAULT_MINOR_PROTOCOL_VERSION);
0186     }
0187 
0188     /**
0189      * Adds a Zend Framework package to the $_registeredPackages array.
0190      * This array is searched when using the magic __call method below
0191      * to instantiante new objects.
0192      *
0193      * @param string $name The name of the package (eg Zend_Gdata_App)
0194      * @return void
0195      */
0196     public function registerPackage($name)
0197     {
0198         array_unshift($this->_registeredPackages, $name);
0199     }
0200 
0201     /**
0202      * Retrieve feed as string or object
0203      *
0204      * @param string $uri The uri from which to retrieve the feed
0205      * @param string $className The class which is used as the return type
0206      * @return string|Zend_Gdata_App_Feed Returns string only if the object
0207      *                                    mapping has been disabled explicitly
0208      *                                    by passing false to the
0209      *                                    useObjectMapping() function.
0210      */
0211     public function getFeed($uri, $className='Zend_Gdata_App_Feed')
0212     {
0213         return $this->importUrl($uri, $className, null);
0214     }
0215 
0216     /**
0217      * Retrieve entry as string or object
0218      *
0219      * @param string $uri
0220      * @param string $className The class which is used as the return type
0221      * @return string|Zend_Gdata_App_Entry Returns string only if the object
0222      *                                     mapping has been disabled explicitly
0223      *                                     by passing false to the
0224      *                                     useObjectMapping() function.
0225      */
0226     public function getEntry($uri, $className='Zend_Gdata_App_Entry')
0227     {
0228         return $this->importUrl($uri, $className, null);
0229     }
0230 
0231     /**
0232      * Get the Zend_Http_Client object used for communication
0233      *
0234      * @return Zend_Http_Client
0235      */
0236     public function getHttpClient()
0237     {
0238         return $this->_httpClient;
0239     }
0240 
0241     /**
0242      * Set the Zend_Http_Client object used for communication
0243      *
0244      * @param Zend_Http_Client $client The client to use for communication
0245      * @throws Zend_Gdata_App_HttpException
0246      * @return Zend_Gdata_App Provides a fluent interface
0247      */
0248     public function setHttpClient($client,
0249         $applicationId = 'MyCompany-MyApp-1.0')
0250     {
0251         if ($client === null) {
0252             $client = new Zend_Http_Client();
0253         }
0254         if (!$client instanceof Zend_Http_Client) {
0255             // require_once 'Zend/Gdata/App/HttpException.php';
0256             throw new Zend_Gdata_App_HttpException(
0257                 'Argument is not an instance of Zend_Http_Client.');
0258         }
0259         $userAgent = $applicationId . ' Zend_Framework_Gdata/' .
0260             Zend_Version::VERSION;
0261         $client->setHeaders('User-Agent', $userAgent);
0262         $client->setConfig(array(
0263             'strictredirects' => true
0264             )
0265         );
0266         $this->_httpClient = $client;
0267         self::setStaticHttpClient($client);
0268         return $this;
0269     }
0270 
0271     /**
0272      * Set the static HTTP client instance
0273      *
0274      * Sets the static HTTP client object to use for retrieving the feed.
0275      *
0276      * @param  Zend_Http_Client $httpClient
0277      * @return void
0278      */
0279     public static function setStaticHttpClient(Zend_Http_Client $httpClient)
0280     {
0281         self::$_staticHttpClient = $httpClient;
0282     }
0283 
0284 
0285     /**
0286      * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used.
0287      *
0288      * @return Zend_Http_Client
0289      */
0290     public static function getStaticHttpClient()
0291     {
0292         if (!self::$_staticHttpClient instanceof Zend_Http_Client) {
0293             $client = new Zend_Http_Client();
0294             $userAgent = 'Zend_Framework_Gdata/' . Zend_Version::VERSION;
0295             $client->setHeaders('User-Agent', $userAgent);
0296             $client->setConfig(array(
0297                 'strictredirects' => true
0298                 )
0299             );
0300             self::$_staticHttpClient = $client;
0301         }
0302         return self::$_staticHttpClient;
0303     }
0304 
0305     /**
0306      * Toggle using POST instead of PUT and DELETE HTTP methods
0307      *
0308      * Some feed implementations do not accept PUT and DELETE HTTP
0309      * methods, or they can't be used because of proxies or other
0310      * measures. This allows turning on using POST where PUT and
0311      * DELETE would normally be used; in addition, an
0312      * X-Method-Override header will be sent with a value of PUT or
0313      * DELETE as appropriate.
0314      *
0315      * @param  boolean $override Whether to override PUT and DELETE with POST.
0316      * @return void
0317      */
0318     public static function setHttpMethodOverride($override = true)
0319     {
0320         self::$_httpMethodOverride = $override;
0321     }
0322 
0323     /**
0324      * Get the HTTP override state
0325      *
0326      * @return boolean
0327      */
0328     public static function getHttpMethodOverride()
0329     {
0330         return self::$_httpMethodOverride;
0331     }
0332 
0333     /**
0334      * Toggle requesting gzip encoded responses
0335      *
0336      * @param  boolean $enabled Whether or not to enable gzipped responses
0337      * @return void
0338      */
0339     public static function setGzipEnabled($enabled = false)
0340     {
0341         if ($enabled && !function_exists('gzinflate')) {
0342             // require_once 'Zend/Gdata/App/InvalidArgumentException.php';
0343             throw new Zend_Gdata_App_InvalidArgumentException(
0344                     'You cannot enable gzipped responses if the zlib module ' .
0345                     'is not enabled in your PHP installation.');
0346 
0347         }
0348         self::$_gzipEnabled = $enabled;
0349     }
0350 
0351     /**
0352      * Get the HTTP override state
0353      *
0354      * @return boolean
0355      */
0356     public static function getGzipEnabled()
0357     {
0358         return self::$_gzipEnabled;
0359     }
0360 
0361     /**
0362      * Get whether to use verbose exception messages
0363      *
0364      * In the case of HTTP errors,  use the body of the HTTP response
0365      * in the exception message.
0366      *
0367      * @return boolean
0368      */
0369     public static function getVerboseExceptionMessages()
0370     {
0371         return self::$_verboseExceptionMessages;
0372     }
0373 
0374     /**
0375      * Set whether to use verbose exception messages
0376      *
0377      * In the case of HTTP errors, use the body of the HTTP response
0378      * in the exception message.
0379      *
0380      * @param boolean $verbose Whether to use verbose exception messages
0381      */
0382     public static function setVerboseExceptionMessages($verbose)
0383     {
0384         self::$_verboseExceptionMessages = $verbose;
0385     }
0386 
0387     /**
0388      * Set the maximum number of redirects to follow during HTTP operations
0389      *
0390      * @param int $maxRedirects Maximum number of redirects to follow
0391      * @return void
0392      */
0393     public static function setMaxRedirects($maxRedirects)
0394     {
0395         self::$_maxRedirects = $maxRedirects;
0396     }
0397 
0398     /**
0399      * Get the maximum number of redirects to follow during HTTP operations
0400      *
0401      * @return int Maximum number of redirects to follow
0402      */
0403     public static function getMaxRedirects()
0404     {
0405         return self::$_maxRedirects;
0406     }
0407 
0408     /**
0409      * Set the major protocol version that should be used. Values < 1 will
0410      * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
0411      *
0412      * @see _majorProtocolVersion
0413      * @param int $value The major protocol version to use.
0414      * @throws Zend_Gdata_App_InvalidArgumentException
0415      */
0416     public function setMajorProtocolVersion($value)
0417     {
0418         if (!($value >= 1)) {
0419             // require_once('Zend/Gdata/App/InvalidArgumentException.php');
0420             throw new Zend_Gdata_App_InvalidArgumentException(
0421                     'Major protocol version must be >= 1');
0422         }
0423         $this->_majorProtocolVersion = $value;
0424     }
0425 
0426     /**
0427      * Get the major protocol version that is in use.
0428      *
0429      * @see _majorProtocolVersion
0430      * @return int The major protocol version in use.
0431      */
0432     public function getMajorProtocolVersion()
0433     {
0434         return $this->_majorProtocolVersion;
0435     }
0436 
0437     /**
0438      * Set the minor protocol version that should be used. If set to NULL, no
0439      * minor protocol version will be sent to the server. Values < 0 will
0440      * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
0441      *
0442      * @see _minorProtocolVersion
0443      * @param (int|NULL) $value The minor protocol version to use.
0444      * @throws Zend_Gdata_App_InvalidArgumentException
0445      */
0446     public function setMinorProtocolVersion($value)
0447     {
0448         if (!($value >= 0)) {
0449             // require_once('Zend/Gdata/App/InvalidArgumentException.php');
0450             throw new Zend_Gdata_App_InvalidArgumentException(
0451                     'Minor protocol version must be >= 0');
0452         }
0453         $this->_minorProtocolVersion = $value;
0454     }
0455 
0456     /**
0457      * Get the minor protocol version that is in use.
0458      *
0459      * @see _minorProtocolVersion
0460      * @return (int|NULL) The major protocol version in use, or NULL if no
0461      *         minor version is specified.
0462      */
0463     public function getMinorProtocolVersion()
0464     {
0465         return $this->_minorProtocolVersion;
0466     }
0467 
0468     /**
0469      * Provides pre-processing for HTTP requests to APP services.
0470      *
0471      * 1. Checks the $data element and, if it's an entry, extracts the XML,
0472      *    multipart data, edit link (PUT,DELETE), etc.
0473      * 2. If $data is a string, sets the default content-type  header as
0474      *    'application/atom+xml' if it's not already been set.
0475      * 3. Adds a x-http-method override header and changes the HTTP method
0476      *    to 'POST' if necessary as per getHttpMethodOverride()
0477      *
0478      * @param string $method The HTTP method for the request - 'GET', 'POST',
0479      *                       'PUT', 'DELETE'
0480      * @param string $url The URL to which this request is being performed,
0481      *                    or null if found in $data
0482      * @param array $headers An associative array of HTTP headers for this
0483      *                       request
0484      * @param mixed $data The Zend_Gdata_App_Entry or XML for the
0485      *                    body of the request
0486      * @param string $contentTypeOverride The override value for the
0487      *                                    content type of the request body
0488      * @return array An associative array containing the determined
0489      *               'method', 'url', 'data', 'headers', 'contentType'
0490      */
0491     public function prepareRequest($method,
0492                                    $url = null,
0493                                    $headers = array(),
0494                                    $data = null,
0495                                    $contentTypeOverride = null)
0496     {
0497         // As a convenience, if $headers is null, we'll convert it back to
0498         // an empty array.
0499         if ($headers === null) {
0500             $headers = array();
0501         }
0502 
0503         $rawData = null;
0504         $finalContentType = null;
0505         if ($url == null) {
0506             $url = $this->_defaultPostUri;
0507         }
0508 
0509         if (is_string($data)) {
0510             $rawData = $data;
0511             if ($contentTypeOverride === null) {
0512                 $finalContentType = 'application/atom+xml';
0513             }
0514         } elseif ($data instanceof Zend_Gdata_App_MediaEntry) {
0515             $rawData = $data->encode();
0516             if ($data->getMediaSource() !== null) {
0517                 $finalContentType = $rawData->getContentType();
0518                 $headers['MIME-version'] = '1.0';
0519                 $headers['Slug'] = $data->getMediaSource()->getSlug();
0520             } else {
0521                 $finalContentType = 'application/atom+xml';
0522             }
0523             if ($method == 'PUT' || $method == 'DELETE') {
0524                 $editLink = $data->getEditLink();
0525                 if ($editLink != null && $url == null) {
0526                     $url = $editLink->getHref();
0527                 }
0528             }
0529         } elseif ($data instanceof Zend_Gdata_App_Entry) {
0530             $rawData = $data->saveXML();
0531             $finalContentType = 'application/atom+xml';
0532             if ($method == 'PUT' || $method == 'DELETE') {
0533                 $editLink = $data->getEditLink();
0534                 if ($editLink != null) {
0535                     $url = $editLink->getHref();
0536                 }
0537             }
0538         } elseif ($data instanceof Zend_Gdata_App_MediaSource) {
0539             $rawData = $data->encode();
0540             if ($data->getSlug() !== null) {
0541                 $headers['Slug'] = $data->getSlug();
0542             }
0543             $finalContentType = $data->getContentType();
0544         }
0545 
0546         if ($method == 'DELETE') {
0547             $rawData = null;
0548         }
0549 
0550         // Set an If-Match header if:
0551         //   - This isn't a DELETE
0552         //   - If this isn't a GET, the Etag isn't weak
0553         //   - A similar header (If-Match/If-None-Match) hasn't already been
0554         //     set.
0555         if ($method != 'DELETE' && (
0556                 !array_key_exists('If-Match', $headers) &&
0557                 !array_key_exists('If-None-Match', $headers)
0558                 ) ) {
0559             $allowWeak = $method == 'GET';
0560             if ($ifMatchHeader = $this->generateIfMatchHeaderData(
0561                     $data, $allowWeak)) {
0562                 $headers['If-Match'] = $ifMatchHeader;
0563             }
0564         }
0565 
0566         if ($method != 'POST' && $method != 'GET' && Zend_Gdata_App::getHttpMethodOverride()) {
0567             $headers['x-http-method-override'] = $method;
0568             $method = 'POST';
0569         } else {
0570             $headers['x-http-method-override'] = null;
0571         }
0572 
0573         if ($contentTypeOverride != null) {
0574             $finalContentType = $contentTypeOverride;
0575         }
0576 
0577         return array('method' => $method, 'url' => $url,
0578             'data' => $rawData, 'headers' => $headers,
0579             'contentType' => $finalContentType);
0580     }
0581 
0582     /**
0583      * Performs a HTTP request using the specified method
0584      *
0585      * @param string $method The HTTP method for the request - 'GET', 'POST',
0586      *                       'PUT', 'DELETE'
0587      * @param string $url The URL to which this request is being performed
0588      * @param array $headers An associative array of HTTP headers
0589      *                       for this request
0590      * @param string $body The body of the HTTP request
0591      * @param string $contentType The value for the content type
0592      *                                of the request body
0593      * @param int $remainingRedirects Number of redirects to follow if request
0594      *                              s results in one
0595      * @return Zend_Http_Response The response object
0596      */
0597     public function performHttpRequest($method, $url, $headers = null,
0598         $body = null, $contentType = null, $remainingRedirects = null)
0599     {
0600         // require_once 'Zend/Http/Client/Exception.php';
0601         if ($remainingRedirects === null) {
0602             $remainingRedirects = self::getMaxRedirects();
0603         }
0604         if ($headers === null) {
0605             $headers = array();
0606         }
0607         // Append a Gdata version header if protocol v2 or higher is in use.
0608         // (Protocol v1 does not use this header.)
0609         $major = $this->getMajorProtocolVersion();
0610         $minor = $this->getMinorProtocolVersion();
0611         if ($major >= 2) {
0612             $headers['GData-Version'] = $major +
0613                     (($minor === null) ? '.' + $minor : '');
0614         }
0615 
0616         // check the overridden method
0617         if (($method == 'POST' || $method == 'PUT') && $body === null &&
0618             $headers['x-http-method-override'] != 'DELETE') {
0619                 // require_once 'Zend/Gdata/App/InvalidArgumentException.php';
0620                 throw new Zend_Gdata_App_InvalidArgumentException(
0621                         'You must specify the data to post as either a ' .
0622                         'string or a child of Zend_Gdata_App_Entry');
0623         }
0624         if ($url === null) {
0625             // require_once 'Zend/Gdata/App/InvalidArgumentException.php';
0626             throw new Zend_Gdata_App_InvalidArgumentException(
0627                 'You must specify an URI to which to post.');
0628         }
0629         $headers['Content-Type'] = $contentType;
0630         if (Zend_Gdata_App::getGzipEnabled()) {
0631             // some services require the word 'gzip' to be in the user-agent
0632             // header in addition to the accept-encoding header
0633             if (strpos($this->_httpClient->getHeader('User-Agent'),
0634                 'gzip') === false) {
0635                 $headers['User-Agent'] =
0636                     $this->_httpClient->getHeader('User-Agent') . ' (gzip)';
0637             }
0638             $headers['Accept-encoding'] = 'gzip, deflate';
0639         } else {
0640             $headers['Accept-encoding'] = 'identity';
0641         }
0642 
0643         // Make sure the HTTP client object is 'clean' before making a request
0644         // In addition to standard headers to reset via resetParameters(),
0645         // also reset the Slug and If-Match headers
0646         $this->_httpClient->resetParameters();
0647         $this->_httpClient->setHeaders(array('Slug', 'If-Match'));
0648 
0649         // Set the params for the new request to be performed
0650         $this->_httpClient->setHeaders($headers);
0651         // require_once 'Zend/Uri/Http.php';
0652         $uri = Zend_Uri_Http::fromString($url);
0653         preg_match("/^(.*?)(\?.*)?$/", $url, $matches);
0654         $this->_httpClient->setUri($matches[1]);
0655         $queryArray = $uri->getQueryAsArray();
0656         foreach ($queryArray as $name => $value) {
0657             $this->_httpClient->setParameterGet($name, $value);
0658         }
0659 
0660 
0661         $this->_httpClient->setConfig(array('maxredirects' => 0));
0662 
0663         // Set the proper adapter if we are handling a streaming upload
0664         $usingMimeStream = false;
0665         $oldHttpAdapter = null;
0666 
0667         if ($body instanceof Zend_Gdata_MediaMimeStream) {
0668             $usingMimeStream = true;
0669             $this->_httpClient->setRawDataStream($body, $contentType);
0670             $oldHttpAdapter = $this->_httpClient->getAdapter();
0671 
0672             if ($oldHttpAdapter instanceof Zend_Http_Client_Adapter_Proxy) {
0673                 // require_once 'Zend/Gdata/HttpAdapterStreamingProxy.php';
0674                 $newAdapter = new Zend_Gdata_HttpAdapterStreamingProxy();
0675             } else {
0676                 // require_once 'Zend/Gdata/HttpAdapterStreamingSocket.php';
0677                 $newAdapter = new Zend_Gdata_HttpAdapterStreamingSocket();
0678             }
0679             $this->_httpClient->setAdapter($newAdapter);
0680         } else {
0681             $this->_httpClient->setRawData($body, $contentType);
0682         }
0683 
0684         try {
0685             $response = $this->_httpClient->request($method);
0686             // reset adapter
0687             if ($usingMimeStream) {
0688                 $this->_httpClient->setAdapter($oldHttpAdapter);
0689             }
0690         } catch (Zend_Http_Client_Exception $e) {
0691             // reset adapter
0692             if ($usingMimeStream) {
0693                 $this->_httpClient->setAdapter($oldHttpAdapter);
0694             }
0695             // require_once 'Zend/Gdata/App/HttpException.php';
0696             throw new Zend_Gdata_App_HttpException($e->getMessage(), $e);
0697         }
0698         if ($response->isRedirect() && $response->getStatus() != '304') {
0699             if ($remainingRedirects > 0) {
0700                 $newUrl = $response->getHeader('Location');
0701                 $response = $this->performHttpRequest(
0702                     $method, $newUrl, $headers, $body,
0703                     $contentType, $remainingRedirects);
0704             } else {
0705                 // require_once 'Zend/Gdata/App/HttpException.php';
0706                 throw new Zend_Gdata_App_HttpException(
0707                         'Number of redirects exceeds maximum', null, $response);
0708             }
0709         }
0710         if (!$response->isSuccessful()) {
0711             // require_once 'Zend/Gdata/App/HttpException.php';
0712             $exceptionMessage = 'Expected response code 200, got ' .
0713                 $response->getStatus();
0714             if (self::getVerboseExceptionMessages()) {
0715                 $exceptionMessage .= "\n" . $response->getBody();
0716             }
0717             $exception = new Zend_Gdata_App_HttpException($exceptionMessage);
0718             $exception->setResponse($response);
0719             throw $exception;
0720         }
0721         return $response;
0722     }
0723 
0724     /**
0725      * Imports a feed located at $uri.
0726      *
0727      * @param  string $uri
0728      * @param  Zend_Http_Client $client The client used for communication
0729      * @param  string $className The class which is used as the return type
0730      * @param  bool $useObjectMapping Enable/disable the use of XML to object mapping.
0731      * @throws Zend_Gdata_App_Exception
0732      * @return string|Zend_Gdata_App_Feed Returns string only if the fourth
0733      *                                    parameter ($useObjectMapping) is set
0734      *                                    to false.
0735      */
0736     public static function import($uri, $client = null,
0737         $className='Zend_Gdata_App_Feed', $useObjectMapping = true)
0738     {
0739         $app = new Zend_Gdata_App($client);
0740         $requestData = $app->prepareRequest('GET', $uri);
0741         $response = $app->performHttpRequest(
0742             $requestData['method'], $requestData['url']);
0743 
0744         $feedContent = $response->getBody();
0745         if (false === $useObjectMapping) {
0746             return $feedContent;
0747         }
0748         $feed = self::importString($feedContent, $className);
0749         if ($client != null) {
0750             $feed->setHttpClient($client);
0751         }
0752         return $feed;
0753     }
0754 
0755     /**
0756      * Imports the specified URL (non-statically).
0757      *
0758      * @param  string $url The URL to import
0759      * @param  string $className The class which is used as the return type
0760      * @param array $extraHeaders Extra headers to add to the request, as an
0761      *        array of string-based key/value pairs.
0762      * @throws Zend_Gdata_App_Exception
0763      * @return string|Zend_Gdata_App_Feed Returns string only if the object
0764      *                                    mapping has been disabled explicitly
0765      *                                    by passing false to the
0766      *                                    useObjectMapping() function.
0767      */
0768     public function importUrl($url, $className='Zend_Gdata_App_Feed',
0769         $extraHeaders = array())
0770     {
0771         $response = $this->get($url, $extraHeaders);
0772 
0773         $feedContent = $response->getBody();
0774         if (!$this->_useObjectMapping) {
0775             return $feedContent;
0776         }
0777 
0778         $protocolVersionStr = $response->getHeader('GData-Version');
0779         $majorProtocolVersion = null;
0780         $minorProtocolVersion = null;
0781         if ($protocolVersionStr !== null) {
0782             // Extract protocol major and minor version from header
0783             $delimiterPos = strpos($protocolVersionStr, '.');
0784             $length = strlen($protocolVersionStr);
0785             $major = substr($protocolVersionStr, 0, $delimiterPos);
0786             $minor = substr($protocolVersionStr, $delimiterPos + 1, $length);
0787             $majorProtocolVersion = $major;
0788             $minorProtocolVersion = $minor;
0789         }
0790 
0791         $feed = self::importString($feedContent, $className,
0792             $majorProtocolVersion, $minorProtocolVersion);
0793         if ($this->getHttpClient() != null) {
0794             $feed->setHttpClient($this->getHttpClient());
0795         }
0796         $etag = $response->getHeader('ETag');
0797         if ($etag !== null) {
0798             $feed->setEtag($etag);
0799         }
0800         return $feed;
0801     }
0802 
0803 
0804     /**
0805      * Imports a feed represented by $string.
0806      *
0807      * @param string $string
0808      * @param string $className The class which is used as the return type
0809      * @param integer $majorProcolVersion (optional) The major protocol version
0810      *        of the data model object that is to be created.
0811      * @param integer $minorProcolVersion (optional) The minor protocol version
0812      *        of the data model object that is to be created.
0813      * @throws Zend_Gdata_App_Exception
0814      * @return Zend_Gdata_App_Feed
0815      */
0816     public static function importString($string,
0817         $className='Zend_Gdata_App_Feed', $majorProtocolVersion = null,
0818         $minorProtocolVersion = null)
0819     {
0820         if (!class_exists($className, false)) {
0821           // require_once 'Zend/Loader.php';
0822           @Zend_Loader::loadClass($className);
0823         }
0824 
0825         // Load the feed as an XML DOMDocument object
0826         @ini_set('track_errors', 1);
0827         $doc = new DOMDocument();
0828         $doc = @Zend_Xml_Security::scan($string, $doc);
0829         @ini_restore('track_errors');
0830 
0831         if (!$doc) {
0832             // require_once 'Zend/Gdata/App/Exception.php';
0833             throw new Zend_Gdata_App_Exception(
0834                 "DOMDocument cannot parse XML: $php_errormsg");
0835         }
0836 
0837         $feed = new $className();
0838         $feed->setMajorProtocolVersion($majorProtocolVersion);
0839         $feed->setMinorProtocolVersion($minorProtocolVersion);
0840         $feed->transferFromXML($string);
0841         $feed->setHttpClient(self::getstaticHttpClient());
0842         return $feed;
0843     }
0844 
0845 
0846     /**
0847      * Imports a feed from a file located at $filename.
0848      *
0849      * @param  string $filename
0850      * @param  string $className The class which is used as the return type
0851      * @param  string $useIncludePath Whether the include_path should be searched
0852      * @throws Zend_Gdata_App_Exception
0853      * @return Zend_Gdata_App_Feed
0854      */
0855     public static function importFile($filename,
0856             $className='Zend_Gdata_App_Feed', $useIncludePath = false)
0857     {
0858         @ini_set('track_errors', 1);
0859         $feed = @file_get_contents($filename, $useIncludePath);
0860         @ini_restore('track_errors');
0861         if ($feed === false) {
0862             // require_once 'Zend/Gdata/App/Exception.php';
0863             throw new Zend_Gdata_App_Exception(
0864                 "File could not be loaded: $php_errormsg");
0865         }
0866         return self::importString($feed, $className);
0867     }
0868 
0869     /**
0870      * GET a URI using client object.
0871      *
0872      * @param string $uri GET URI
0873      * @param array $extraHeaders Extra headers to add to the request, as an
0874      *        array of string-based key/value pairs.
0875      * @throws Zend_Gdata_App_HttpException
0876      * @return Zend_Http_Response
0877      */
0878     public function get($uri, $extraHeaders = array())
0879     {
0880         $requestData = $this->prepareRequest('GET', $uri, $extraHeaders);
0881         return $this->performHttpRequest(
0882             $requestData['method'], $requestData['url'],
0883             $requestData['headers']);
0884     }
0885 
0886     /**
0887      * POST data with client object
0888      *
0889      * @param mixed $data The Zend_Gdata_App_Entry or XML to post
0890      * @param string $uri POST URI
0891      * @param array $headers Additional HTTP headers to insert.
0892      * @param string $contentType Content-type of the data
0893      * @param array $extraHeaders Extra headers to add to the request, as an
0894      *        array of string-based key/value pairs.
0895      * @return Zend_Http_Response
0896      * @throws Zend_Gdata_App_Exception
0897      * @throws Zend_Gdata_App_HttpException
0898      * @throws Zend_Gdata_App_InvalidArgumentException
0899      */
0900     public function post($data, $uri = null, $remainingRedirects = null,
0901             $contentType = null, $extraHeaders = null)
0902     {
0903         $requestData = $this->prepareRequest(
0904             'POST', $uri, $extraHeaders, $data, $contentType);
0905         return $this->performHttpRequest(
0906                 $requestData['method'], $requestData['url'],
0907                 $requestData['headers'], $requestData['data'],
0908                 $requestData['contentType']);
0909     }
0910 
0911     /**
0912      * PUT data with client object
0913      *
0914      * @param mixed $data The Zend_Gdata_App_Entry or XML to post
0915      * @param string $uri PUT URI
0916      * @param array $headers Additional HTTP headers to insert.
0917      * @param string $contentType Content-type of the data
0918      * @param array $extraHeaders Extra headers to add to the request, as an
0919      *        array of string-based key/value pairs.
0920      * @return Zend_Http_Response
0921      * @throws Zend_Gdata_App_Exception
0922      * @throws Zend_Gdata_App_HttpException
0923      * @throws Zend_Gdata_App_InvalidArgumentException
0924      */
0925     public function put($data, $uri = null, $remainingRedirects = null,
0926             $contentType = null, $extraHeaders = null)
0927     {
0928         $requestData = $this->prepareRequest(
0929             'PUT', $uri, $extraHeaders, $data, $contentType);
0930         return $this->performHttpRequest(
0931                 $requestData['method'], $requestData['url'],
0932                 $requestData['headers'], $requestData['data'],
0933                 $requestData['contentType']);
0934     }
0935 
0936     /**
0937      * DELETE entry with client object
0938      *
0939      * @param mixed $data The Zend_Gdata_App_Entry or URL to delete
0940      * @return void
0941      * @throws Zend_Gdata_App_Exception
0942      * @throws Zend_Gdata_App_HttpException
0943      * @throws Zend_Gdata_App_InvalidArgumentException
0944      */
0945     public function delete($data, $remainingRedirects = null)
0946     {
0947         if (is_string($data)) {
0948             $requestData = $this->prepareRequest('DELETE', $data);
0949         } else {
0950             $headers = array();
0951 
0952             $requestData = $this->prepareRequest(
0953                 'DELETE', null, $headers, $data);
0954         }
0955         return $this->performHttpRequest($requestData['method'],
0956                                          $requestData['url'],
0957                                          $requestData['headers'],
0958                                          '',
0959                                          $requestData['contentType'],
0960                                          $remainingRedirects);
0961     }
0962 
0963     /**
0964      * Inserts an entry to a given URI and returns the response as a
0965      * fully formed Entry.
0966      *
0967      * @param mixed  $data The Zend_Gdata_App_Entry or XML to post
0968      * @param string $uri POST URI
0969      * @param string $className The class of entry to be returned.
0970      * @param array $extraHeaders Extra headers to add to the request, as an
0971      *        array of string-based key/value pairs.
0972      * @return Zend_Gdata_App_Entry The entry returned by the service after
0973      *         insertion.
0974      */
0975     public function insertEntry($data, $uri, $className='Zend_Gdata_App_Entry',
0976         $extraHeaders = array())
0977     {
0978         if (!class_exists($className, false)) {
0979           // require_once 'Zend/Loader.php';
0980           @Zend_Loader::loadClass($className);
0981         }
0982 
0983         $response = $this->post($data, $uri, null, null, $extraHeaders);
0984 
0985         $returnEntry = new $className($response->getBody());
0986         $returnEntry->setHttpClient(self::getstaticHttpClient());
0987 
0988         $etag = $response->getHeader('ETag');
0989         if ($etag !== null) {
0990             $returnEntry->setEtag($etag);
0991         }
0992 
0993         return $returnEntry;
0994     }
0995 
0996     /**
0997      * Update an entry
0998      *
0999      * @param mixed $data Zend_Gdata_App_Entry or XML (w/ID and link rel='edit')
1000      * @param string|null The URI to send requests to, or null if $data
1001      *        contains the URI.
1002      * @param string|null The name of the class that should be deserialized
1003      *        from the server response. If null, then 'Zend_Gdata_App_Entry'
1004      *        will be used.
1005      * @param array $extraHeaders Extra headers to add to the request, as an
1006      *        array of string-based key/value pairs.
1007      * @return Zend_Gdata_App_Entry The entry returned from the server
1008      * @throws Zend_Gdata_App_Exception
1009      */
1010     public function updateEntry($data, $uri = null, $className = null,
1011         $extraHeaders = array())
1012     {
1013         if ($className === null && $data instanceof Zend_Gdata_App_Entry) {
1014             $className = get_class($data);
1015         } elseif ($className === null) {
1016             $className = 'Zend_Gdata_App_Entry';
1017         }
1018 
1019         if (!class_exists($className, false)) {
1020           // require_once 'Zend/Loader.php';
1021           @Zend_Loader::loadClass($className);
1022         }
1023 
1024         $response = $this->put($data, $uri, null, null, $extraHeaders);
1025         $returnEntry = new $className($response->getBody());
1026         $returnEntry->setHttpClient(self::getstaticHttpClient());
1027 
1028         $etag = $response->getHeader('ETag');
1029         if ($etag !== null) {
1030             $returnEntry->setEtag($etag);
1031         }
1032 
1033         return $returnEntry;
1034     }
1035 
1036     /**
1037      * Provides a magic factory method to instantiate new objects with
1038      * shorter syntax than would otherwise be required by the Zend Framework
1039      * naming conventions.  For instance, to construct a new
1040      * Zend_Gdata_Calendar_Extension_Color, a developer simply needs to do
1041      * $gCal->newColor().  For this magic constructor, packages are searched
1042      * in the same order as which they appear in the $_registeredPackages
1043      * array
1044      *
1045      * @param string $method The method name being called
1046      * @param array $args The arguments passed to the call
1047      * @throws Zend_Gdata_App_Exception
1048      */
1049     public function __call($method, $args)
1050     {
1051         if (preg_match('/^new(\w+)/', $method, $matches)) {
1052             $class = $matches[1];
1053             $foundClassName = null;
1054             foreach ($this->_registeredPackages as $name) {
1055                  try {
1056                      // Autoloading disabled on next line for compatibility
1057                      // with magic factories. See ZF-6660.
1058                      if (!class_exists($name . '_' . $class, false)) {
1059                         // require_once 'Zend/Loader.php';
1060                         @Zend_Loader::loadClass($name . '_' . $class);
1061                      }
1062                      $foundClassName = $name . '_' . $class;
1063                      break;
1064                  } catch (Zend_Exception $e) {
1065                      // package wasn't here- continue searching
1066                  } catch (ErrorException $e) {
1067                      // package wasn't here- continue searching
1068                      // @see ZF-7013 and ZF-11959
1069                  }
1070             }
1071             if ($foundClassName != null) {
1072                 $reflectionObj = new ReflectionClass($foundClassName);
1073                 $instance = $reflectionObj->newInstanceArgs($args);
1074                 if ($instance instanceof Zend_Gdata_App_FeedEntryParent) {
1075                     $instance->setHttpClient($this->_httpClient);
1076 
1077                     // Propogate version data
1078                     $instance->setMajorProtocolVersion(
1079                             $this->_majorProtocolVersion);
1080                     $instance->setMinorProtocolVersion(
1081                             $this->_minorProtocolVersion);
1082                 }
1083                 return $instance;
1084             } else {
1085                 // require_once 'Zend/Gdata/App/Exception.php';
1086                 throw new Zend_Gdata_App_Exception(
1087                         "Unable to find '${class}' in registered packages");
1088             }
1089         } else {
1090             // require_once 'Zend/Gdata/App/Exception.php';
1091             throw new Zend_Gdata_App_Exception("No such method ${method}");
1092         }
1093     }
1094 
1095     /**
1096      * Retrieve all entries for a feed, iterating through pages as necessary.
1097      * Be aware that calling this function on a large dataset will take a
1098      * significant amount of time to complete. In some cases this may cause
1099      * execution to timeout without proper precautions in place.
1100      *
1101      * @param object $feed The feed to iterate through.
1102      * @return mixed A new feed of the same type as the one originally
1103      *          passed in, containing all relevent entries.
1104      */
1105     public function retrieveAllEntriesForFeed($feed) {
1106         $feedClass = get_class($feed);
1107         $reflectionObj = new ReflectionClass($feedClass);
1108         $result = $reflectionObj->newInstance();
1109         do {
1110             foreach ($feed as $entry) {
1111                 $result->addEntry($entry);
1112             }
1113 
1114             $next = $feed->getLink('next');
1115             if ($next !== null) {
1116                 $feed = $this->getFeed($next->href, $feedClass);
1117             } else {
1118                 $feed = null;
1119             }
1120         }
1121         while ($feed != null);
1122         return $result;
1123     }
1124 
1125     /**
1126      * This method enables logging of requests by changing the
1127      * Zend_Http_Client_Adapter used for performing the requests.
1128      * NOTE: This will not work if you have customized the adapter
1129      * already to use a proxy server or other interface.
1130      *
1131      * @param string $logfile The logfile to use when logging the requests
1132      */
1133     public function enableRequestDebugLogging($logfile)
1134     {
1135         $this->_httpClient->setConfig(array(
1136             'adapter' => 'Zend_Gdata_App_LoggingHttpClientAdapterSocket',
1137             'logfile' => $logfile
1138             ));
1139     }
1140 
1141     /**
1142      * Retrieve next set of results based on a given feed.
1143      *
1144      * @param Zend_Gdata_App_Feed $feed The feed from which to
1145      *          retreive the next set of results.
1146      * @param string $className (optional) The class of feed to be returned.
1147      *          If null, the next feed (if found) will be the same class as
1148      *          the feed that was given as the first argument.
1149      * @return Zend_Gdata_App_Feed|null Returns a
1150      *          Zend_Gdata_App_Feed or null if no next set of results
1151      *          exists.
1152      */
1153     public function getNextFeed($feed, $className = null)
1154     {
1155         $nextLink = $feed->getNextLink();
1156         if (!$nextLink) {
1157             return null;
1158         }
1159         $nextLinkHref = $nextLink->getHref();
1160 
1161         if ($className === null) {
1162             $className = get_class($feed);
1163         }
1164 
1165         return $this->getFeed($nextLinkHref, $className);
1166     }
1167 
1168     /**
1169      * Retrieve previous set of results based on a given feed.
1170      *
1171      * @param Zend_Gdata_App_Feed $feed The feed from which to
1172      *          retreive the previous set of results.
1173      * @param string $className (optional) The class of feed to be returned.
1174      *          If null, the previous feed (if found) will be the same class as
1175      *          the feed that was given as the first argument.
1176      * @return Zend_Gdata_App_Feed|null Returns a
1177      *          Zend_Gdata_App_Feed or null if no previous set of results
1178      *          exists.
1179      */
1180     public function getPreviousFeed($feed, $className = null)
1181     {
1182         $previousLink = $feed->getPreviousLink();
1183         if (!$previousLink) {
1184             return null;
1185         }
1186         $previousLinkHref = $previousLink->getHref();
1187 
1188         if ($className === null) {
1189             $className = get_class($feed);
1190         }
1191 
1192         return $this->getFeed($previousLinkHref, $className);
1193     }
1194 
1195     /**
1196      * Returns the data for an If-Match header based on the current Etag
1197      * property. If Etags are not supported by the server or cannot be
1198      * extracted from the data, then null will be returned.
1199      *
1200      * @param boolean $allowWeak If false, then if a weak Etag is detected,
1201      *        then return null rather than the Etag.
1202      * @return string|null $data
1203      */
1204     public function generateIfMatchHeaderData($data, $allowWeek)
1205     {
1206         $result = '';
1207         // Set an If-Match header if an ETag has been set (version >= 2 only)
1208         if ($this->_majorProtocolVersion >= 2 &&
1209                 $data instanceof Zend_Gdata_App_Entry) {
1210             $etag = $data->getEtag();
1211             if (($etag !== null) &&
1212                     ($allowWeek || substr($etag, 0, 2) != 'W/')) {
1213                 $result = $data->getEtag();
1214             }
1215         }
1216         return $result;
1217     }
1218 
1219     /**
1220      * Determine whether service object is using XML to object mapping.
1221      *
1222      * @return boolean True if service object is using XML to object mapping,
1223      *                 false otherwise.
1224      */
1225     public function usingObjectMapping()
1226     {
1227         return $this->_useObjectMapping;
1228     }
1229 
1230     /**
1231      * Enable/disable the use of XML to object mapping.
1232      *
1233      * @param boolean $value Pass in true to use the XML to object mapping.
1234      *                       Pass in false or null to disable it.
1235      * @return void
1236      */
1237     public function useObjectMapping($value)
1238     {
1239         if ($value === True) {
1240             $this->_useObjectMapping = true;
1241         } else {
1242             $this->_useObjectMapping = false;
1243         }
1244     }
1245 
1246 }