PageRenderTime 76ms CodeModel.GetById 14ms app.highlight 52ms RepoModel.GetById 2ms app.codeStats 0ms

/library/Zend/Gdata/App.php

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