PageRenderTime 70ms CodeModel.GetById 41ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/include/library/Zend/Gdata/App.php

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