File indexing completed on 2024-12-22 05:37:16
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 }