]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - Zend/Gdata/App.php
Release 6.5.0
[Github/sugarcrm.git] / Zend / Gdata / App.php
1 <?php
2
3 /**
4  * Zend Framework
5  *
6  * LICENSE
7  *
8  * This source file is subject to the new BSD license that is bundled
9  * with this package in the file LICENSE.txt.
10  * It is also available through the world-wide-web at this URL:
11  * http://framework.zend.com/license/new-bsd
12  * If you did not receive a copy of the license and are unable to
13  * obtain it through the world-wide-web, please send an email
14  * to license@zend.com so we can send you a copy immediately.
15  *
16  * @category   Zend
17  * @package    Zend_Gdata
18  * @subpackage App
19  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
20  * @license    http://framework.zend.com/license/new-bsd     New BSD License
21
22  */
23
24 /**
25  * Zend_Gdata_Feed
26  */
27 require_once 'Zend/Gdata/App/Feed.php';
28
29 /**
30  * Zend_Gdata_Http_Client
31  */
32 require_once 'Zend/Http/Client.php';
33
34 /**
35  * Zend_Version
36  */
37 require_once 'Zend/Version.php';
38
39 /**
40  * Zend_Gdata_App_MediaSource
41  */
42 require_once 'Zend/Gdata/App/MediaSource.php';
43
44 /**
45  * Provides Atom Publishing Protocol (APP) functionality.  This class and all
46  * other components of Zend_Gdata_App are designed to work independently from
47  * other Zend_Gdata components in order to interact with generic APP services.
48  *
49  * @category   Zend
50  * @package    Zend_Gdata
51  * @subpackage App
52  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
53  * @license    http://framework.zend.com/license/new-bsd     New BSD License
54  */
55 class Zend_Gdata_App
56 {
57
58     /** Default major protocol version.
59       *
60       * @see _majorProtocolVersion
61       */
62     const DEFAULT_MAJOR_PROTOCOL_VERSION = 1;
63
64     /** Default minor protocol version.
65       *
66       * @see _minorProtocolVersion
67       */
68     const DEFAULT_MINOR_PROTOCOL_VERSION = null;
69
70     /**
71      * Client object used to communicate
72      *
73      * @var Zend_Http_Client
74      */
75     protected $_httpClient;
76
77     /**
78      * Client object used to communicate in static context
79      *
80      * @var Zend_Http_Client
81      */
82     protected static $_staticHttpClient = null;
83
84     /**
85      * Override HTTP PUT and DELETE request methods?
86      *
87      * @var boolean
88      */
89     protected static $_httpMethodOverride = false;
90
91     /**
92      * Enable gzipped responses?
93      *
94      * @var boolean
95      */
96     protected static $_gzipEnabled = false;
97
98     /**
99      * Use verbose exception messages.  In the case of HTTP errors,
100      * use the body of the HTTP response in the exception message.
101      *
102      * @var boolean
103      */
104     protected static $_verboseExceptionMessages = true;
105
106     /**
107      * Default URI to which to POST.
108      *
109      * @var string
110      */
111     protected $_defaultPostUri = null;
112
113     /**
114      * Packages to search for classes when using magic __call method, in order.
115      *
116      * @var array
117      */
118     protected $_registeredPackages = array(
119             'Zend_Gdata_App_Extension',
120             'Zend_Gdata_App');
121
122     /**
123      * Maximum number of redirects to follow during HTTP operations
124      *
125      * @var int
126      */
127     protected static $_maxRedirects = 5;
128
129     /**
130       * Indicates the major protocol version that should be used.
131       * At present, recognized values are either 1 or 2. However, any integer
132       * value >= 1 is considered valid.
133       *
134       * Under most circumtances, this will be automatically set by
135       * Zend_Gdata_App subclasses.
136       *
137       * @see setMajorProtocolVersion()
138       * @see getMajorProtocolVersion()
139       */
140     protected $_majorProtocolVersion;
141
142     /**
143       * Indicates the minor protocol version that should be used. Can be set
144       * to either an integer >= 0, or NULL if no minor version should be sent
145       * to the server.
146       *
147       * At present, this field is not used by any Google services, but may be
148       * used in the future.
149       *
150       * Under most circumtances, this will be automatically set by
151       * Zend_Gdata_App subclasses.
152       *
153       * @see setMinorProtocolVersion()
154       * @see getMinorProtocolVersion()
155       */
156     protected $_minorProtocolVersion;
157
158     /**
159      * Whether we want to use XML to object mapping when fetching data.
160      *
161      * @var boolean
162      */
163     protected $_useObjectMapping = true;
164
165     /**
166      * Create Gdata object
167      *
168      * @param Zend_Http_Client $client
169      * @param string $applicationId
170      */
171     public function __construct($client = null, $applicationId = 'MyCompany-MyApp-1.0')
172     {
173         $this->setHttpClient($client, $applicationId);
174         // Set default protocol version. Subclasses should override this as
175         // needed once a given service supports a new version.
176         $this->setMajorProtocolVersion(self::DEFAULT_MAJOR_PROTOCOL_VERSION);
177         $this->setMinorProtocolVersion(self::DEFAULT_MINOR_PROTOCOL_VERSION);
178     }
179
180     /**
181      * Adds a Zend Framework package to the $_registeredPackages array.
182      * This array is searched when using the magic __call method below
183      * to instantiante new objects.
184      *
185      * @param string $name The name of the package (eg Zend_Gdata_App)
186      * @return void
187      */
188     public function registerPackage($name)
189     {
190         array_unshift($this->_registeredPackages, $name);
191     }
192
193     /**
194      * Retrieve feed as string or object
195      *
196      * @param string $uri The uri from which to retrieve the feed
197      * @param string $className The class which is used as the return type
198      * @return string|Zend_Gdata_App_Feed Returns string only if the object
199      *                                    mapping has been disabled explicitly
200      *                                    by passing false to the
201      *                                    useObjectMapping() function.
202      */
203     public function getFeed($uri, $className='Zend_Gdata_App_Feed')
204     {
205         return $this->importUrl($uri, $className, null);
206     }
207
208     /**
209      * Retrieve entry as string or object
210      *
211      * @param string $uri
212      * @param string $className The class which is used as the return type
213      * @return string|Zend_Gdata_App_Entry Returns string only if the object
214      *                                     mapping has been disabled explicitly
215      *                                     by passing false to the
216      *                                     useObjectMapping() function.
217      */
218     public function getEntry($uri, $className='Zend_Gdata_App_Entry')
219     {
220         return $this->importUrl($uri, $className, null);
221     }
222
223     /**
224      * Get the Zend_Http_Client object used for communication
225      *
226      * @return Zend_Http_Client
227      */
228     public function getHttpClient()
229     {
230         return $this->_httpClient;
231     }
232
233     /**
234      * Set the Zend_Http_Client object used for communication
235      *
236      * @param Zend_Http_Client $client The client to use for communication
237      * @throws Zend_Gdata_App_HttpException
238      * @return Zend_Gdata_App Provides a fluent interface
239      */
240     public function setHttpClient($client,
241         $applicationId = 'MyCompany-MyApp-1.0')
242     {
243         if ($client === null) {
244             $client = new Zend_Http_Client();
245         }
246         if (!$client instanceof Zend_Http_Client) {
247             require_once 'Zend/Gdata/App/HttpException.php';
248             throw new Zend_Gdata_App_HttpException(
249                 'Argument is not an instance of Zend_Http_Client.');
250         }
251         $userAgent = $applicationId . ' Zend_Framework_Gdata/' .
252             Zend_Version::VERSION;
253         $client->setHeaders('User-Agent', $userAgent);
254         $client->setConfig(array(
255             'strictredirects' => true
256             )
257         );
258         $this->_httpClient = $client;
259         self::setStaticHttpClient($client);
260         return $this;
261     }
262
263     /**
264      * Set the static HTTP client instance
265      *
266      * Sets the static HTTP client object to use for retrieving the feed.
267      *
268      * @param  Zend_Http_Client $httpClient
269      * @return void
270      */
271     public static function setStaticHttpClient(Zend_Http_Client $httpClient)
272     {
273         self::$_staticHttpClient = $httpClient;
274     }
275
276
277     /**
278      * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used.
279      *
280      * @return Zend_Http_Client
281      */
282     public static function getStaticHttpClient()
283     {
284         if (!self::$_staticHttpClient instanceof Zend_Http_Client) {
285             $client = new Zend_Http_Client();
286             $userAgent = 'Zend_Framework_Gdata/' . Zend_Version::VERSION;
287             $client->setHeaders('User-Agent', $userAgent);
288             $client->setConfig(array(
289                 'strictredirects' => true
290                 )
291             );
292             self::$_staticHttpClient = $client;
293         }
294         return self::$_staticHttpClient;
295     }
296
297     /**
298      * Toggle using POST instead of PUT and DELETE HTTP methods
299      *
300      * Some feed implementations do not accept PUT and DELETE HTTP
301      * methods, or they can't be used because of proxies or other
302      * measures. This allows turning on using POST where PUT and
303      * DELETE would normally be used; in addition, an
304      * X-Method-Override header will be sent with a value of PUT or
305      * DELETE as appropriate.
306      *
307      * @param  boolean $override Whether to override PUT and DELETE with POST.
308      * @return void
309      */
310     public static function setHttpMethodOverride($override = true)
311     {
312         self::$_httpMethodOverride = $override;
313     }
314
315     /**
316      * Get the HTTP override state
317      *
318      * @return boolean
319      */
320     public static function getHttpMethodOverride()
321     {
322         return self::$_httpMethodOverride;
323     }
324
325     /**
326      * Toggle requesting gzip encoded responses
327      *
328      * @param  boolean $enabled Whether or not to enable gzipped responses
329      * @return void
330      */
331     public static function setGzipEnabled($enabled = false)
332     {
333         if ($enabled && !function_exists('gzinflate')) {
334             require_once 'Zend/Gdata/App/InvalidArgumentException.php';
335             throw new Zend_Gdata_App_InvalidArgumentException(
336                     'You cannot enable gzipped responses if the zlib module ' .
337                     'is not enabled in your PHP installation.');
338
339         }
340         self::$_gzipEnabled = $enabled;
341     }
342
343     /**
344      * Get the HTTP override state
345      *
346      * @return boolean
347      */
348     public static function getGzipEnabled()
349     {
350         return self::$_gzipEnabled;
351     }
352
353     /**
354      * Get whether to use verbose exception messages
355      *
356      * In the case of HTTP errors,  use the body of the HTTP response
357      * in the exception message.
358      *
359      * @return boolean
360      */
361     public static function getVerboseExceptionMessages()
362     {
363         return self::$_verboseExceptionMessages;
364     }
365
366     /**
367      * Set whether to use verbose exception messages
368      *
369      * In the case of HTTP errors, use the body of the HTTP response
370      * in the exception message.
371      *
372      * @param boolean $verbose Whether to use verbose exception messages
373      */
374     public static function setVerboseExceptionMessages($verbose)
375     {
376         self::$_verboseExceptionMessages = $verbose;
377     }
378
379     /**
380      * Set the maximum number of redirects to follow during HTTP operations
381      *
382      * @param int $maxRedirects Maximum number of redirects to follow
383      * @return void
384      */
385     public static function setMaxRedirects($maxRedirects)
386     {
387         self::$_maxRedirects = $maxRedirects;
388     }
389
390     /**
391      * Get the maximum number of redirects to follow during HTTP operations
392      *
393      * @return int Maximum number of redirects to follow
394      */
395     public static function getMaxRedirects()
396     {
397         return self::$_maxRedirects;
398     }
399
400     /**
401      * Set the major protocol version that should be used. Values < 1 will
402      * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
403      *
404      * @see _majorProtocolVersion
405      * @param int $value The major protocol version to use.
406      * @throws Zend_Gdata_App_InvalidArgumentException
407      */
408     public function setMajorProtocolVersion($value)
409     {
410         if (!($value >= 1)) {
411             require_once('Zend/Gdata/App/InvalidArgumentException.php');
412             throw new Zend_Gdata_App_InvalidArgumentException(
413                     'Major protocol version must be >= 1');
414         }
415         $this->_majorProtocolVersion = $value;
416     }
417
418     /**
419      * Get the major protocol version that is in use.
420      *
421      * @see _majorProtocolVersion
422      * @return int The major protocol version in use.
423      */
424     public function getMajorProtocolVersion()
425     {
426         return $this->_majorProtocolVersion;
427     }
428
429     /**
430      * Set the minor protocol version that should be used. If set to NULL, no
431      * minor protocol version will be sent to the server. Values < 0 will
432      * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
433      *
434      * @see _minorProtocolVersion
435      * @param (int|NULL) $value The minor protocol version to use.
436      * @throws Zend_Gdata_App_InvalidArgumentException
437      */
438     public function setMinorProtocolVersion($value)
439     {
440         if (!($value >= 0)) {
441             require_once('Zend/Gdata/App/InvalidArgumentException.php');
442             throw new Zend_Gdata_App_InvalidArgumentException(
443                     'Minor protocol version must be >= 0');
444         }
445         $this->_minorProtocolVersion = $value;
446     }
447
448     /**
449      * Get the minor protocol version that is in use.
450      *
451      * @see _minorProtocolVersion
452      * @return (int|NULL) The major protocol version in use, or NULL if no
453      *         minor version is specified.
454      */
455     public function getMinorProtocolVersion()
456     {
457         return $this->_minorProtocolVersion;
458     }
459
460     /**
461      * Provides pre-processing for HTTP requests to APP services.
462      *
463      * 1. Checks the $data element and, if it's an entry, extracts the XML,
464      *    multipart data, edit link (PUT,DELETE), etc.
465      * 2. If $data is a string, sets the default content-type  header as
466      *    'application/atom+xml' if it's not already been set.
467      * 3. Adds a x-http-method override header and changes the HTTP method
468      *    to 'POST' if necessary as per getHttpMethodOverride()
469      *
470      * @param string $method The HTTP method for the request - 'GET', 'POST',
471      *                       'PUT', 'DELETE'
472      * @param string $url The URL to which this request is being performed,
473      *                    or null if found in $data
474      * @param array $headers An associative array of HTTP headers for this
475      *                       request
476      * @param mixed $data The Zend_Gdata_App_Entry or XML for the
477      *                    body of the request
478      * @param string $contentTypeOverride The override value for the
479      *                                    content type of the request body
480      * @return array An associative array containing the determined
481      *               'method', 'url', 'data', 'headers', 'contentType'
482      */
483     public function prepareRequest($method,
484                                    $url = null,
485                                    $headers = array(),
486                                    $data = null,
487                                    $contentTypeOverride = null)
488     {
489         // As a convenience, if $headers is null, we'll convert it back to
490         // an empty array.
491         if ($headers === null) {
492             $headers = array();
493         }
494
495         $rawData = null;
496         $finalContentType = null;
497         if ($url == null) {
498             $url = $this->_defaultPostUri;
499         }
500
501         if (is_string($data)) {
502             $rawData = $data;
503             if ($contentTypeOverride === null) {
504                 $finalContentType = 'application/atom+xml';
505             }
506         } elseif ($data instanceof Zend_Gdata_App_MediaEntry) {
507             $rawData = $data->encode();
508             if ($data->getMediaSource() !== null) {
509                 $finalContentType = $rawData->getContentType();
510                 $headers['MIME-version'] = '1.0';
511                 $headers['Slug'] = $data->getMediaSource()->getSlug();
512             } else {
513                 $finalContentType = 'application/atom+xml';
514             }
515             if ($method == 'PUT' || $method == 'DELETE') {
516                 $editLink = $data->getEditLink();
517                 if ($editLink != null && $url == null) {
518                     $url = $editLink->getHref();
519                 }
520             }
521         } elseif ($data instanceof Zend_Gdata_App_Entry) {
522             $rawData = $data->saveXML();
523             $finalContentType = 'application/atom+xml';
524             if ($method == 'PUT' || $method == 'DELETE') {
525                 $editLink = $data->getEditLink();
526                 if ($editLink != null) {
527                     $url = $editLink->getHref();
528                 }
529             }
530         } elseif ($data instanceof Zend_Gdata_App_MediaSource) {
531             $rawData = $data->encode();
532             if ($data->getSlug() !== null) {
533                 $headers['Slug'] = $data->getSlug();
534             }
535             $finalContentType = $data->getContentType();
536         }
537
538         if ($method == 'DELETE') {
539             $rawData = null;
540         }
541
542         // Set an If-Match header if:
543         //   - This isn't a DELETE
544         //   - If this isn't a GET, the Etag isn't weak
545         //   - A similar header (If-Match/If-None-Match) hasn't already been
546         //     set.
547         if ($method != 'DELETE' && (
548                 !array_key_exists('If-Match', $headers) &&
549                 !array_key_exists('If-None-Match', $headers)
550                 ) ) {
551             $allowWeak = $method == 'GET';
552             if ($ifMatchHeader = $this->generateIfMatchHeaderData(
553                     $data, $allowWeak)) {
554                 $headers['If-Match'] = $ifMatchHeader;
555             }
556         }
557
558         if ($method != 'POST' && $method != 'GET' && Zend_Gdata_App::getHttpMethodOverride()) {
559             $headers['x-http-method-override'] = $method;
560             $method = 'POST';
561         } else {
562             $headers['x-http-method-override'] = null;
563         }
564
565         if ($contentTypeOverride != null) {
566             $finalContentType = $contentTypeOverride;
567         }
568
569         return array('method' => $method, 'url' => $url,
570             'data' => $rawData, 'headers' => $headers,
571             'contentType' => $finalContentType);
572     }
573
574     /**
575      * Performs a HTTP request using the specified method
576      *
577      * @param string $method The HTTP method for the request - 'GET', 'POST',
578      *                       'PUT', 'DELETE'
579      * @param string $url The URL to which this request is being performed
580      * @param array $headers An associative array of HTTP headers
581      *                       for this request
582      * @param string $body The body of the HTTP request
583      * @param string $contentType The value for the content type
584      *                                of the request body
585      * @param int $remainingRedirects Number of redirects to follow if request
586      *                              s results in one
587      * @return Zend_Http_Response The response object
588      */
589     public function performHttpRequest($method, $url, $headers = null,
590         $body = null, $contentType = null, $remainingRedirects = null)
591     {
592         require_once 'Zend/Http/Client/Exception.php';
593         if ($remainingRedirects === null) {
594             $remainingRedirects = self::getMaxRedirects();
595         }
596         if ($headers === null) {
597             $headers = array();
598         }
599         // Append a Gdata version header if protocol v2 or higher is in use.
600         // (Protocol v1 does not use this header.)
601         $major = $this->getMajorProtocolVersion();
602         $minor = $this->getMinorProtocolVersion();
603         if ($major >= 2) {
604             $headers['GData-Version'] = $major +
605                     (($minor === null) ? '.' + $minor : '');
606         }
607
608         // check the overridden method
609         if (($method == 'POST' || $method == 'PUT') && $body === null &&
610             $headers['x-http-method-override'] != 'DELETE') {
611                 require_once 'Zend/Gdata/App/InvalidArgumentException.php';
612                 throw new Zend_Gdata_App_InvalidArgumentException(
613                         'You must specify the data to post as either a ' .
614                         'string or a child of Zend_Gdata_App_Entry');
615         }
616         if ($url === null) {
617             require_once 'Zend/Gdata/App/InvalidArgumentException.php';
618             throw new Zend_Gdata_App_InvalidArgumentException(
619                 'You must specify an URI to which to post.');
620         }
621         $headers['Content-Type'] = $contentType;
622         if (Zend_Gdata_App::getGzipEnabled()) {
623             // some services require the word 'gzip' to be in the user-agent
624             // header in addition to the accept-encoding header
625             if (strpos($this->_httpClient->getHeader('User-Agent'),
626                 'gzip') === false) {
627                 $headers['User-Agent'] =
628                     $this->_httpClient->getHeader('User-Agent') . ' (gzip)';
629             }
630             $headers['Accept-encoding'] = 'gzip, deflate';
631         } else {
632             $headers['Accept-encoding'] = 'identity';
633         }
634
635         // Make sure the HTTP client object is 'clean' before making a request
636         // In addition to standard headers to reset via resetParameters(),
637         // also reset the Slug and If-Match headers
638         $this->_httpClient->resetParameters();
639         $this->_httpClient->setHeaders(array('Slug', 'If-Match'));
640
641         // Set the params for the new request to be performed
642         $this->_httpClient->setHeaders($headers);
643         $uri = Zend_Uri_Http::fromString($url);
644         preg_match("/^(.*?)(\?.*)?$/", $url, $matches);
645         $this->_httpClient->setUri($matches[1]);
646         $queryArray = $uri->getQueryAsArray();
647         foreach ($queryArray as $name => $value) { 
648           $this->_httpClient->setParameterGet($name, $value);
649         }
650
651
652         $this->_httpClient->setConfig(array('maxredirects' => 0));
653
654         // Set the proper adapter if we are handling a streaming upload
655         $usingMimeStream = false;
656         $oldHttpAdapter = null;
657
658         if ($body instanceof Zend_Gdata_MediaMimeStream) {
659             $usingMimeStream = true;
660             $this->_httpClient->setRawDataStream($body, $contentType);
661             $oldHttpAdapter = $this->_httpClient->getAdapter();
662
663             if ($oldHttpAdapter instanceof Zend_Http_Client_Adapter_Proxy) {
664                 require_once 'Zend/Gdata/HttpAdapterStreamingProxy.php';
665                 $newAdapter = new Zend_Gdata_HttpAdapterStreamingProxy();
666             } else {
667                 require_once 'Zend/Gdata/HttpAdapterStreamingSocket.php';
668                 $newAdapter = new Zend_Gdata_HttpAdapterStreamingSocket();
669             }
670             $this->_httpClient->setAdapter($newAdapter);
671         } else {
672             $this->_httpClient->setRawData($body, $contentType);
673         }
674
675         try {
676             $response = $this->_httpClient->request($method);
677             // reset adapter
678             if ($usingMimeStream) {
679                 $this->_httpClient->setAdapter($oldHttpAdapter);
680             }
681         } catch (Zend_Http_Client_Exception $e) {
682             // reset adapter
683             if ($usingMimeStream) {
684                 $this->_httpClient->setAdapter($oldHttpAdapter);
685             }
686             require_once 'Zend/Gdata/App/HttpException.php';
687             throw new Zend_Gdata_App_HttpException($e->getMessage(), $e);
688         }
689         if ($response->isRedirect() && $response->getStatus() != '304') {
690             if ($remainingRedirects > 0) {
691                 $newUrl = $response->getHeader('Location');
692                 $response = $this->performHttpRequest(
693                     $method, $newUrl, $headers, $body,
694                     $contentType, $remainingRedirects);
695             } else {
696                 require_once 'Zend/Gdata/App/HttpException.php';
697                 throw new Zend_Gdata_App_HttpException(
698                         'Number of redirects exceeds maximum', null, $response);
699             }
700         }
701         if (!$response->isSuccessful()) {
702             require_once 'Zend/Gdata/App/HttpException.php';
703             $exceptionMessage = 'Expected response code 200, got ' .
704                 $response->getStatus();
705             if (self::getVerboseExceptionMessages()) {
706                 $exceptionMessage .= "\n" . $response->getBody();
707             }
708             $exception = new Zend_Gdata_App_HttpException($exceptionMessage);
709             $exception->setResponse($response);
710             throw $exception;
711         }
712         return $response;
713     }
714
715     /**
716      * Imports a feed located at $uri.
717      *
718      * @param  string $uri
719      * @param  Zend_Http_Client $client The client used for communication
720      * @param  string $className The class which is used as the return type
721      * @throws Zend_Gdata_App_Exception
722      * @return string|Zend_Gdata_App_Feed Returns string only if the object
723      *                                    mapping has been disabled explicitly
724      *                                    by passing false to the
725      *                                    useObjectMapping() function.
726      */
727     public static function import($uri, $client = null,
728         $className='Zend_Gdata_App_Feed')
729     {
730         $app = new Zend_Gdata_App($client);
731         $requestData = $app->prepareRequest('GET', $uri);
732         $response = $app->performHttpRequest(
733             $requestData['method'], $requestData['url']);
734
735         $feedContent = $response->getBody();
736         if (!$this->_useObjectMapping) {
737             return $feedContent;
738         }
739         $feed = self::importString($feedContent, $className);
740         if ($client != null) {
741             $feed->setHttpClient($client);
742         }
743         return $feed;
744     }
745
746     /**
747      * Imports the specified URL (non-statically).
748      *
749      * @param  string $url The URL to import
750      * @param  string $className The class which is used as the return type
751      * @param array $extraHeaders Extra headers to add to the request, as an
752      *        array of string-based key/value pairs.
753      * @throws Zend_Gdata_App_Exception
754      * @return string|Zend_Gdata_App_Feed Returns string only if the object
755      *                                    mapping has been disabled explicitly
756      *                                    by passing false to the
757      *                                    useObjectMapping() function.
758      */
759     public function importUrl($url, $className='Zend_Gdata_App_Feed',
760         $extraHeaders = array())
761     {
762         $response = $this->get($url, $extraHeaders);
763
764         $feedContent = $response->getBody();
765         if (!$this->_useObjectMapping) {
766             return $feedContent;
767         }
768
769         $protocolVersionStr = $response->getHeader('GData-Version');
770         $majorProtocolVersion = null;
771         $minorProtocolVersion = null;
772         if ($protocolVersionStr !== null) {
773             // Extract protocol major and minor version from header
774             $delimiterPos = strpos($protocolVersionStr, '.');
775             $length = strlen($protocolVersionStr);
776             $major = substr($protocolVersionStr, 0, $delimiterPos);
777             $minor = substr($protocolVersionStr, $delimiterPos + 1, $length);
778             $majorProtocolVersion = $major;
779             $minorProtocolVersion = $minor;
780         }
781
782         $feed = self::importString($feedContent, $className,
783             $majorProtocolVersion, $minorProtocolVersion);
784         if ($this->getHttpClient() != null) {
785             $feed->setHttpClient($this->getHttpClient());
786         }
787         $etag = $response->getHeader('ETag');
788         if ($etag !== null) {
789             $feed->setEtag($etag);
790         }
791         return $feed;
792     }
793
794
795     /**
796      * Imports a feed represented by $string.
797      *
798      * @param string $string
799      * @param string $className The class which is used as the return type
800      * @param integer $majorProcolVersion (optional) The major protocol version
801      *        of the data model object that is to be created.
802      * @param integer $minorProcolVersion (optional) The minor protocol version
803      *        of the data model object that is to be created.
804      * @throws Zend_Gdata_App_Exception
805      * @return Zend_Gdata_App_Feed
806      */
807     public static function importString($string,
808         $className='Zend_Gdata_App_Feed', $majorProtocolVersion = null,
809         $minorProtocolVersion = null)
810     {
811         if (!class_exists($className, false)) {
812           require_once 'Zend/Loader.php';
813           @Zend_Loader::loadClass($className);
814         }
815
816         // Load the feed as an XML DOMDocument object
817         @ini_set('track_errors', 1);
818         $doc = new DOMDocument();
819         $success = @$doc->loadXML($string);
820         @ini_restore('track_errors');
821
822         if (!$success) {
823             require_once 'Zend/Gdata/App/Exception.php';
824             throw new Zend_Gdata_App_Exception(
825                 "DOMDocument cannot parse XML: $php_errormsg");
826         }
827
828         $feed = new $className();
829         $feed->setMajorProtocolVersion($majorProtocolVersion);
830         $feed->setMinorProtocolVersion($minorProtocolVersion);
831         $feed->transferFromXML($string);
832         $feed->setHttpClient(self::getstaticHttpClient());
833         return $feed;
834     }
835
836
837     /**
838      * Imports a feed from a file located at $filename.
839      *
840      * @param  string $filename
841      * @param  string $className The class which is used as the return type
842      * @param  string $useIncludePath Whether the include_path should be searched
843      * @throws Zend_Gdata_App_Exception
844      * @return Zend_Gdata_App_Feed
845      */
846     public static function importFile($filename,
847             $className='Zend_Gdata_App_Feed', $useIncludePath = false)
848     {
849         @ini_set('track_errors', 1);
850         $feed = @file_get_contents($filename, $useIncludePath);
851         @ini_restore('track_errors');
852         if ($feed === false) {
853             require_once 'Zend/Gdata/App/Exception.php';
854             throw new Zend_Gdata_App_Exception(
855                 "File could not be loaded: $php_errormsg");
856         }
857         return self::importString($feed, $className);
858     }
859
860     /**
861      * GET a URI using client object.
862      *
863      * @param string $uri GET URI
864      * @param array $extraHeaders Extra headers to add to the request, as an
865      *        array of string-based key/value pairs.
866      * @throws Zend_Gdata_App_HttpException
867      * @return Zend_Http_Response
868      */
869     public function get($uri, $extraHeaders = array())
870     {
871         $requestData = $this->prepareRequest('GET', $uri, $extraHeaders);
872         return $this->performHttpRequest(
873             $requestData['method'], $requestData['url'],
874             $requestData['headers']);
875     }
876
877     /**
878      * POST data with client object
879      *
880      * @param mixed $data The Zend_Gdata_App_Entry or XML to post
881      * @param string $uri POST URI
882      * @param array $headers Additional HTTP headers to insert.
883      * @param string $contentType Content-type of the data
884      * @param array $extraHeaders Extra headers to add to the request, as an
885      *        array of string-based key/value pairs.
886      * @return Zend_Http_Response
887      * @throws Zend_Gdata_App_Exception
888      * @throws Zend_Gdata_App_HttpException
889      * @throws Zend_Gdata_App_InvalidArgumentException
890      */
891     public function post($data, $uri = null, $remainingRedirects = null,
892             $contentType = null, $extraHeaders = null)
893     {
894         $requestData = $this->prepareRequest(
895             'POST', $uri, $extraHeaders, $data, $contentType);
896         return $this->performHttpRequest(
897                 $requestData['method'], $requestData['url'],
898                 $requestData['headers'], $requestData['data'],
899                 $requestData['contentType']);
900     }
901
902     /**
903      * PUT data with client object
904      *
905      * @param mixed $data The Zend_Gdata_App_Entry or XML to post
906      * @param string $uri PUT URI
907      * @param array $headers Additional HTTP headers to insert.
908      * @param string $contentType Content-type of the data
909      * @param array $extraHeaders Extra headers to add to the request, as an
910      *        array of string-based key/value pairs.
911      * @return Zend_Http_Response
912      * @throws Zend_Gdata_App_Exception
913      * @throws Zend_Gdata_App_HttpException
914      * @throws Zend_Gdata_App_InvalidArgumentException
915      */
916     public function put($data, $uri = null, $remainingRedirects = null,
917             $contentType = null, $extraHeaders = null)
918     {
919         $requestData = $this->prepareRequest(
920             'PUT', $uri, $extraHeaders, $data, $contentType);
921         return $this->performHttpRequest(
922                 $requestData['method'], $requestData['url'],
923                 $requestData['headers'], $requestData['data'],
924                 $requestData['contentType']);
925     }
926
927     /**
928      * DELETE entry with client object
929      *
930      * @param mixed $data The Zend_Gdata_App_Entry or URL to delete
931      * @return void
932      * @throws Zend_Gdata_App_Exception
933      * @throws Zend_Gdata_App_HttpException
934      * @throws Zend_Gdata_App_InvalidArgumentException
935      */
936     public function delete($data, $remainingRedirects = null)
937     {
938         if (is_string($data)) {
939             $requestData = $this->prepareRequest('DELETE', $data);
940         } else {
941             $headers = array();
942
943             $requestData = $this->prepareRequest(
944                 'DELETE', null, $headers, $data);
945         }
946         return $this->performHttpRequest($requestData['method'],
947                                          $requestData['url'],
948                                          $requestData['headers'],
949                                          '',
950                                          $requestData['contentType'],
951                                          $remainingRedirects);
952     }
953
954     /**
955      * Inserts an entry to a given URI and returns the response as a
956      * fully formed Entry.
957      *
958      * @param mixed  $data The Zend_Gdata_App_Entry or XML to post
959      * @param string $uri POST URI
960      * @param string $className The class of entry to be returned.
961      * @param array $extraHeaders Extra headers to add to the request, as an
962      *        array of string-based key/value pairs.
963      * @return Zend_Gdata_App_Entry The entry returned by the service after
964      *         insertion.
965      */
966     public function insertEntry($data, $uri, $className='Zend_Gdata_App_Entry',
967         $extraHeaders = array())
968     {
969         if (!class_exists($className, false)) {
970           require_once 'Zend/Loader.php';
971           @Zend_Loader::loadClass($className);
972         }
973
974         $response = $this->post($data, $uri, null, null, $extraHeaders);
975
976         $returnEntry = new $className($response->getBody());
977         $returnEntry->setHttpClient(self::getstaticHttpClient());
978
979         $etag = $response->getHeader('ETag');
980         if ($etag !== null) {
981             $returnEntry->setEtag($etag);
982         }
983
984         return $returnEntry;
985     }
986
987     /**
988      * Update an entry
989      *
990      * @param mixed $data Zend_Gdata_App_Entry or XML (w/ID and link rel='edit')
991      * @param string|null The URI to send requests to, or null if $data
992      *        contains the URI.
993      * @param string|null The name of the class that should be deserialized
994      *        from the server response. If null, then 'Zend_Gdata_App_Entry'
995      *        will be used.
996      * @param array $extraHeaders Extra headers to add to the request, as an
997      *        array of string-based key/value pairs.
998      * @return Zend_Gdata_App_Entry The entry returned from the server
999      * @throws Zend_Gdata_App_Exception
1000      */
1001     public function updateEntry($data, $uri = null, $className = null,
1002         $extraHeaders = array())
1003     {
1004         if ($className === null && $data instanceof Zend_Gdata_App_Entry) {
1005             $className = get_class($data);
1006         } elseif ($className === null) {
1007             $className = 'Zend_Gdata_App_Entry';
1008         }
1009
1010         if (!class_exists($className, false)) {
1011           require_once 'Zend/Loader.php';
1012           @Zend_Loader::loadClass($className);
1013         }
1014
1015         $response = $this->put($data, $uri, null, null, $extraHeaders);
1016         $returnEntry = new $className($response->getBody());
1017         $returnEntry->setHttpClient(self::getstaticHttpClient());
1018
1019         $etag = $response->getHeader('ETag');
1020         if ($etag !== null) {
1021             $returnEntry->setEtag($etag);
1022         }
1023
1024         return $returnEntry;
1025     }
1026
1027     /**
1028      * Provides a magic factory method to instantiate new objects with
1029      * shorter syntax than would otherwise be required by the Zend Framework
1030      * naming conventions.  For instance, to construct a new
1031      * Zend_Gdata_Calendar_Extension_Color, a developer simply needs to do
1032      * $gCal->newColor().  For this magic constructor, packages are searched
1033      * in the same order as which they appear in the $_registeredPackages
1034      * array
1035      *
1036      * @param string $method The method name being called
1037      * @param array $args The arguments passed to the call
1038      * @throws Zend_Gdata_App_Exception
1039      */
1040     public function __call($method, $args)
1041     {
1042         if (preg_match('/^new(\w+)/', $method, $matches)) {
1043             $class = $matches[1];
1044             $foundClassName = null;
1045             foreach ($this->_registeredPackages as $name) {
1046                  try {
1047                      // Autoloading disabled on next line for compatibility
1048                      // with magic factories. See ZF-6660.
1049                      if (!class_exists($name . '_' . $class, false)) {
1050                         require_once 'Zend/Loader.php';
1051                         @Zend_Loader::loadClass($name . '_' . $class);
1052                      }
1053                      $foundClassName = $name . '_' . $class;
1054                      break;
1055                  } catch (Zend_Exception $e) {
1056                      // package wasn't here- continue searching
1057                  }
1058             }
1059             if ($foundClassName != null) {
1060                 $reflectionObj = new ReflectionClass($foundClassName);
1061                 $instance = $reflectionObj->newInstanceArgs($args);
1062                 if ($instance instanceof Zend_Gdata_App_FeedEntryParent) {
1063                     $instance->setHttpClient($this->_httpClient);
1064
1065                     // Propogate version data
1066                     $instance->setMajorProtocolVersion(
1067                             $this->_majorProtocolVersion);
1068                     $instance->setMinorProtocolVersion(
1069                             $this->_minorProtocolVersion);
1070                 }
1071                 return $instance;
1072             } else {
1073                 require_once 'Zend/Gdata/App/Exception.php';
1074                 throw new Zend_Gdata_App_Exception(
1075                         "Unable to find '${class}' in registered packages");
1076             }
1077         } else {
1078             require_once 'Zend/Gdata/App/Exception.php';
1079             throw new Zend_Gdata_App_Exception("No such method ${method}");
1080         }
1081     }
1082
1083     /**
1084      * Retrieve all entries for a feed, iterating through pages as necessary.
1085      * Be aware that calling this function on a large dataset will take a
1086      * significant amount of time to complete. In some cases this may cause
1087      * execution to timeout without proper precautions in place.
1088      *
1089      * @param $feed The feed to iterate through.
1090      * @return mixed A new feed of the same type as the one originally
1091      *          passed in, containing all relevent entries.
1092      */
1093     public function retrieveAllEntriesForFeed($feed) {
1094         $feedClass = get_class($feed);
1095         $reflectionObj = new ReflectionClass($feedClass);
1096         $result = $reflectionObj->newInstance();
1097         do {
1098             foreach ($feed as $entry) {
1099                 $result->addEntry($entry);
1100             }
1101
1102             $next = $feed->getLink('next');
1103             if ($next !== null) {
1104                 $feed = $this->getFeed($next->href, $feedClass);
1105             } else {
1106                 $feed = null;
1107             }
1108         }
1109         while ($feed != null);
1110         return $result;
1111     }
1112
1113     /**
1114      * This method enables logging of requests by changing the
1115      * Zend_Http_Client_Adapter used for performing the requests.
1116      * NOTE: This will not work if you have customized the adapter
1117      * already to use a proxy server or other interface.
1118      *
1119      * @param $logfile The logfile to use when logging the requests
1120      */
1121     public function enableRequestDebugLogging($logfile)
1122     {
1123         $this->_httpClient->setConfig(array(
1124             'adapter' => 'Zend_Gdata_App_LoggingHttpClientAdapterSocket',
1125             'logfile' => $logfile
1126             ));
1127     }
1128
1129     /**
1130      * Retrieve next set of results based on a given feed.
1131      *
1132      * @param Zend_Gdata_App_Feed $feed The feed from which to
1133      *          retreive the next set of results.
1134      * @param string $className (optional) The class of feed to be returned.
1135      *          If null, the next feed (if found) will be the same class as
1136      *          the feed that was given as the first argument.
1137      * @return Zend_Gdata_App_Feed|null Returns a
1138      *          Zend_Gdata_App_Feed or null if no next set of results
1139      *          exists.
1140      */
1141     public function getNextFeed($feed, $className = null)
1142     {
1143         $nextLink = $feed->getNextLink();
1144         if (!$nextLink) {
1145             return null;
1146         }
1147         $nextLinkHref = $nextLink->getHref();
1148
1149         if ($className === null) {
1150             $className = get_class($feed);
1151         }
1152
1153         return $this->getFeed($nextLinkHref, $className);
1154     }
1155
1156     /**
1157      * Retrieve previous set of results based on a given feed.
1158      *
1159      * @param Zend_Gdata_App_Feed $feed The feed from which to
1160      *          retreive the previous set of results.
1161      * @param string $className (optional) The class of feed to be returned.
1162      *          If null, the previous feed (if found) will be the same class as
1163      *          the feed that was given as the first argument.
1164      * @return Zend_Gdata_App_Feed|null Returns a
1165      *          Zend_Gdata_App_Feed or null if no previous set of results
1166      *          exists.
1167      */
1168     public function getPreviousFeed($feed, $className = null)
1169     {
1170         $previousLink = $feed->getPreviousLink();
1171         if (!$previousLink) {
1172             return null;
1173         }
1174         $previousLinkHref = $previousLink->getHref();
1175
1176         if ($className === null) {
1177             $className = get_class($feed);
1178         }
1179
1180         return $this->getFeed($previousLinkHref, $className);
1181     }
1182
1183     /**
1184      * Returns the data for an If-Match header based on the current Etag
1185      * property. If Etags are not supported by the server or cannot be
1186      * extracted from the data, then null will be returned.
1187      *
1188      * @param boolean $allowWeak If false, then if a weak Etag is detected,
1189      *        then return null rather than the Etag.
1190      * @return string|null $data
1191      */
1192     public function generateIfMatchHeaderData($data, $allowWeek)
1193     {
1194         $result = '';
1195         // Set an If-Match header if an ETag has been set (version >= 2 only)
1196         if ($this->_majorProtocolVersion >= 2 &&
1197                 $data instanceof Zend_Gdata_App_Entry) {
1198             $etag = $data->getEtag();
1199             if (($etag !== null) &&
1200                     ($allowWeek || substr($etag, 0, 2) != 'W/')) {
1201                 $result = $data->getEtag();
1202             }
1203         }
1204         return $result;
1205     }
1206
1207     /**
1208      * Determine whether service object is using XML to object mapping.
1209      *
1210      * @return boolean True if service object is using XML to object mapping,
1211      *                 false otherwise.
1212      */
1213     public function usingObjectMapping()
1214     {
1215         return $this->_useObjectMapping;
1216     }
1217
1218     /**
1219      * Enable/disable the use of XML to object mapping.
1220      *
1221      * @param boolean $value Pass in true to use the XML to object mapping.
1222      *                       Pass in false or null to disable it.
1223      * @return void
1224      */
1225     public function useObjectMapping($value)
1226     {
1227         if ($value === True) {
1228             $this->_useObjectMapping = true;
1229         } else {
1230             $this->_useObjectMapping = false;
1231         }
1232     }
1233
1234 }