File indexing completed on 2024-04-28 06:00:01

0001 <?php
0002 
0003 /**
0004  * Zend Framework
0005  *
0006  * LICENSE
0007  *
0008  * This source file is subject to the new BSD license that is bundled
0009  * with this package in the file LICENSE.txt.
0010  * It is also available through the world-wide-web at this URL:
0011  * http://framework.zend.com/license/new-bsd
0012  * If you did not receive a copy of the license and are unable to
0013  * obtain it through the world-wide-web, please send an email
0014  * to license@zend.com so we can send you a copy immediately.
0015  *
0016  * @category   Zend
0017  * @package    Zend_Feed
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** @see Zend_Xml_Security */
0024 // require_once 'Zend/Xml/Security.php';
0025 
0026 /**
0027  * Feed utility class
0028  *
0029  * Base Zend_Feed class, containing constants and the Zend_Http_Client instance
0030  * accessor.
0031  *
0032  * @category   Zend
0033  * @package    Zend_Feed
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  */
0037 class Zend_Feed
0038 {
0039 
0040     /**
0041      * HTTP client object to use for retrieving feeds
0042      *
0043      * @var Zend_Http_Client
0044      */
0045     protected static $_httpClient = null;
0046 
0047     /**
0048      * Override HTTP PUT and DELETE request methods?
0049      *
0050      * @var boolean
0051      */
0052     protected static $_httpMethodOverride = false;
0053 
0054     /**
0055      * @var array
0056      */
0057     protected static $_namespaces = array(
0058         'opensearch' => 'http://a9.com/-/spec/opensearchrss/1.0/',
0059         'atom'       => 'http://www.w3.org/2005/Atom',
0060         'rss'        => 'http://blogs.law.harvard.edu/tech/rss',
0061     );
0062 
0063 
0064     /**
0065      * Set the HTTP client instance
0066      *
0067      * Sets the HTTP client object to use for retrieving the feeds.
0068      *
0069      * @param  Zend_Http_Client $httpClient
0070      * @return void
0071      */
0072     public static function setHttpClient(Zend_Http_Client $httpClient)
0073     {
0074         self::$_httpClient = $httpClient;
0075     }
0076 
0077 
0078     /**
0079      * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used.
0080      *
0081      * @return Zend_Http_Client_Abstract
0082      */
0083     public static function getHttpClient()
0084     {
0085         if (!self::$_httpClient instanceof Zend_Http_Client) {
0086             /**
0087              * @see Zend_Http_Client
0088              */
0089             // require_once 'Zend/Http/Client.php';
0090             self::$_httpClient = new Zend_Http_Client();
0091         }
0092 
0093         return self::$_httpClient;
0094     }
0095 
0096 
0097     /**
0098      * Toggle using POST instead of PUT and DELETE HTTP methods
0099      *
0100      * Some feed implementations do not accept PUT and DELETE HTTP
0101      * methods, or they can't be used because of proxies or other
0102      * measures. This allows turning on using POST where PUT and
0103      * DELETE would normally be used; in addition, an
0104      * X-Method-Override header will be sent with a value of PUT or
0105      * DELETE as appropriate.
0106      *
0107      * @param  boolean $override Whether to override PUT and DELETE.
0108      * @return void
0109      */
0110     public static function setHttpMethodOverride($override = true)
0111     {
0112         self::$_httpMethodOverride = $override;
0113     }
0114 
0115 
0116     /**
0117      * Get the HTTP override state
0118      *
0119      * @return boolean
0120      */
0121     public static function getHttpMethodOverride()
0122     {
0123         return self::$_httpMethodOverride;
0124     }
0125 
0126 
0127     /**
0128      * Get the full version of a namespace prefix
0129      *
0130      * Looks up a prefix (atom:, etc.) in the list of registered
0131      * namespaces and returns the full namespace URI if
0132      * available. Returns the prefix, unmodified, if it's not
0133      * registered.
0134      *
0135      * @return string
0136      */
0137     public static function lookupNamespace($prefix)
0138     {
0139         return isset(self::$_namespaces[$prefix]) ?
0140             self::$_namespaces[$prefix] :
0141             $prefix;
0142     }
0143 
0144 
0145     /**
0146      * Add a namespace and prefix to the registered list
0147      *
0148      * Takes a prefix and a full namespace URI and adds them to the
0149      * list of registered namespaces for use by
0150      * Zend_Feed::lookupNamespace().
0151      *
0152      * @param  string $prefix The namespace prefix
0153      * @param  string $namespaceURI The full namespace URI
0154      * @return void
0155      */
0156     public static function registerNamespace($prefix, $namespaceURI)
0157     {
0158         self::$_namespaces[$prefix] = $namespaceURI;
0159     }
0160 
0161 
0162     /**
0163      * Imports a feed located at $uri.
0164      *
0165      * @param  string $uri
0166      * @throws Zend_Feed_Exception
0167      * @return Zend_Feed_Abstract
0168      */
0169     public static function import($uri)
0170     {
0171         $client = self::getHttpClient();
0172         $client->setUri($uri);
0173         $response = $client->request('GET');
0174         if ($response->getStatus() !== 200) {
0175             /**
0176              * @see Zend_Feed_Exception
0177              */
0178             // require_once 'Zend/Feed/Exception.php';
0179             throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus());
0180         }
0181         $feed = $response->getBody();
0182         return self::importString($feed);
0183     }
0184 
0185 
0186     /**
0187      * Imports a feed represented by $string.
0188      *
0189      * @param  string $string
0190      * @throws Zend_Feed_Exception
0191      * @return Zend_Feed_Abstract
0192      */
0193     public static function importString($string)
0194     {
0195         if (trim($string) == '') {
0196             // require_once 'Zend/Feed/Exception.php';
0197             throw new Zend_Feed_Exception('Document/string being imported'
0198             . ' is an Empty string or comes from an empty HTTP response');
0199         }
0200         $doc = new DOMDocument;
0201         $doc = Zend_Xml_Security::scan($string, $doc);
0202 
0203         if (!$doc) {
0204             // prevent the class to generate an undefined variable notice (ZF-2590)
0205             // Build error message
0206             $error = libxml_get_last_error();
0207             if ($error && $error->message) {
0208                 $errormsg = "DOMDocument cannot parse XML: {$error->message}";
0209             } else {
0210                 $errormsg = "DOMDocument cannot parse XML";
0211             }
0212 
0213 
0214             /**
0215              * @see Zend_Feed_Exception
0216              */
0217             // require_once 'Zend/Feed/Exception.php';
0218             throw new Zend_Feed_Exception($errormsg);
0219         }
0220 
0221         // Try to find the base feed element or a single <entry> of an Atom feed
0222         if ($doc->getElementsByTagName('feed')->item(0) ||
0223             $doc->getElementsByTagName('entry')->item(0)) {
0224             /**
0225              * @see Zend_Feed_Atom
0226              */
0227             // require_once 'Zend/Feed/Atom.php';
0228             // return a newly created Zend_Feed_Atom object
0229             return new Zend_Feed_Atom(null, $string);
0230         }
0231 
0232         // Try to find the base feed element of an RSS feed
0233         if ($doc->getElementsByTagName('channel')->item(0)) {
0234             /**
0235              * @see Zend_Feed_Rss
0236              */
0237             // require_once 'Zend/Feed/Rss.php';
0238             // return a newly created Zend_Feed_Rss object
0239             return new Zend_Feed_Rss(null, $string);
0240         }
0241 
0242         // $string does not appear to be a valid feed of the supported types
0243         /**
0244          * @see Zend_Feed_Exception
0245          */
0246         // require_once 'Zend/Feed/Exception.php';
0247         throw new Zend_Feed_Exception('Invalid or unsupported feed format');
0248     }
0249 
0250 
0251     /**
0252      * Imports a feed from a file located at $filename.
0253      *
0254      * @param  string $filename
0255      * @throws Zend_Feed_Exception
0256      * @return Zend_Feed_Abstract
0257      */
0258     public static function importFile($filename)
0259     {
0260         @ini_set('track_errors', 1);
0261         $feed = @file_get_contents($filename);
0262         @ini_restore('track_errors');
0263         if ($feed === false) {
0264             /**
0265              * @see Zend_Feed_Exception
0266              */
0267             // require_once 'Zend/Feed/Exception.php';
0268             throw new Zend_Feed_Exception("File could not be loaded: $php_errormsg");
0269         }
0270         return self::importString($feed);
0271     }
0272 
0273 
0274     /**
0275      * Attempts to find feeds at $uri referenced by <link ... /> tags. Returns an
0276      * array of the feeds referenced at $uri.
0277      *
0278      * @todo Allow findFeeds() to follow one, but only one, code 302.
0279      *
0280      * @param  string $uri
0281      * @throws Zend_Feed_Exception
0282      * @return array
0283      */
0284     public static function findFeeds($uri)
0285     {
0286         // Get the HTTP response from $uri and save the contents
0287         $client = self::getHttpClient();
0288         $client->setUri($uri);
0289         $response = $client->request();
0290         if ($response->getStatus() !== 200) {
0291             /**
0292              * @see Zend_Feed_Exception
0293              */
0294             // require_once 'Zend/Feed/Exception.php';
0295             throw new Zend_Feed_Exception("Failed to access $uri, got response code " . $response->getStatus());
0296         }
0297         $contents = $response->getBody();
0298 
0299         // Parse the contents for appropriate <link ... /> tags
0300         @ini_set('track_errors', 1);
0301         $pattern = '~(<link[^>]+)/?>~i';
0302         $result = @preg_match_all($pattern, $contents, $matches);
0303         @ini_restore('track_errors');
0304         if ($result === false) {
0305             /**
0306              * @see Zend_Feed_Exception
0307              */
0308             // require_once 'Zend/Feed/Exception.php';
0309             throw new Zend_Feed_Exception("Internal error: $php_errormsg");
0310         }
0311 
0312         // Try to fetch a feed for each link tag that appears to refer to a feed
0313         $feeds = array();
0314         if (isset($matches[1]) && count($matches[1]) > 0) {
0315             foreach ($matches[1] as $link) {
0316                 // force string to be an utf-8 one
0317                 if (!mb_check_encoding($link, 'UTF-8')) {
0318                     $link = mb_convert_encoding($link, 'UTF-8');
0319                 }
0320                 $xml = @Zend_Xml_Security::scan(rtrim($link, ' /') . ' />');
0321                 if ($xml === false) {
0322                     continue;
0323                 }
0324                 $attributes = $xml->attributes();
0325                 if (!isset($attributes['rel']) || !@preg_match('~^(?:alternate|service\.feed)~i', $attributes['rel'])) {
0326                     continue;
0327                 }
0328                 if (!isset($attributes['type']) ||
0329                         !@preg_match('~^application/(?:atom|rss|rdf)\+xml~', $attributes['type'])) {
0330                     continue;
0331                 }
0332                 if (!isset($attributes['href'])) {
0333                     continue;
0334                 }
0335                 try {
0336                     // checks if we need to canonize the given uri
0337                     try {
0338                         $uri = Zend_Uri::factory((string) $attributes['href']);
0339                     } catch (Zend_Uri_Exception $e) {
0340                         // canonize the uri
0341                         $path = (string) $attributes['href'];
0342                         $query = $fragment = '';
0343                         if (substr($path, 0, 1) != '/') {
0344                             // add the current root path to this one
0345                             $path = rtrim($client->getUri()->getPath(), '/') . '/' . $path;
0346                         }
0347                         if (strpos($path, '?') !== false) {
0348                             list($path, $query) = explode('?', $path, 2);
0349                         }
0350                         if (strpos($query, '#') !== false) {
0351                             list($query, $fragment) = explode('#', $query, 2);
0352                         }
0353                         $uri = Zend_Uri::factory($client->getUri(true));
0354                         $uri->setPath($path);
0355                         $uri->setQuery($query);
0356                         $uri->setFragment($fragment);
0357                     }
0358 
0359                     $feed = self::import($uri);
0360                 } catch (Exception $e) {
0361                     continue;
0362                 }
0363                 $feeds[$uri->getUri()] = $feed;
0364             }
0365         }
0366 
0367         // Return the fetched feeds
0368         return $feeds;
0369     }
0370 
0371     /**
0372      * Construct a new Zend_Feed_Abstract object from a custom array
0373      *
0374      * @param  array  $data
0375      * @param  string $format (rss|atom) the requested output format
0376      * @return Zend_Feed_Abstract
0377      */
0378     public static function importArray(array $data, $format = 'atom')
0379     {
0380         $obj = 'Zend_Feed_' . ucfirst(strtolower($format));
0381         if (!class_exists($obj)) {
0382             // require_once 'Zend/Loader.php';
0383             Zend_Loader::loadClass($obj);
0384         }
0385 
0386         /**
0387          * @see Zend_Feed_Builder
0388          */
0389         // require_once 'Zend/Feed/Builder.php';
0390         return new $obj(null, null, new Zend_Feed_Builder($data));
0391     }
0392 
0393     /**
0394      * Construct a new Zend_Feed_Abstract object from a Zend_Feed_Builder_Interface data source
0395      *
0396      * @param  Zend_Feed_Builder_Interface $builder this object will be used to extract the data of the feed
0397      * @param  string                      $format (rss|atom) the requested output format
0398      * @return Zend_Feed_Abstract
0399      */
0400     public static function importBuilder(Zend_Feed_Builder_Interface $builder, $format = 'atom')
0401     {
0402         $obj = 'Zend_Feed_' . ucfirst(strtolower($format));
0403         if (!class_exists($obj)) {
0404             // require_once 'Zend/Loader.php';
0405             Zend_Loader::loadClass($obj);
0406         }
0407         return new $obj(null, null, $builder);
0408     }
0409 }