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.
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license http://framework.zend.com/license/new-bsd New BSD License
27 * Base Zend_Feed class, containing constants and the Zend_Http_Client instance
32 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
33 * @license http://framework.zend.com/license/new-bsd New BSD License
39 * HTTP client object to use for retrieving feeds
41 * @var Zend_Http_Client
43 protected static $_httpClient = null;
46 * Override HTTP PUT and DELETE request methods?
50 protected static $_httpMethodOverride = false;
55 protected static $_namespaces = array(
56 'opensearch' => 'http://a9.com/-/spec/opensearchrss/1.0/',
57 'atom' => 'http://www.w3.org/2005/Atom',
58 'rss' => 'http://blogs.law.harvard.edu/tech/rss',
63 * Set the HTTP client instance
65 * Sets the HTTP client object to use for retrieving the feeds.
67 * @param Zend_Http_Client $httpClient
70 public static function setHttpClient(Zend_Http_Client
$httpClient)
72 self
::$_httpClient = $httpClient;
77 * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used.
79 * @return Zend_Http_Client_Abstract
81 public static function getHttpClient()
83 if (!self
::$_httpClient instanceof Zend_Http_Client
) {
85 * @see Zend_Http_Client
87 require_once 'Zend/Http/Client.php';
88 self
::$_httpClient = new Zend_Http_Client();
91 return self
::$_httpClient;
96 * Toggle using POST instead of PUT and DELETE HTTP methods
98 * Some feed implementations do not accept PUT and DELETE HTTP
99 * methods, or they can't be used because of proxies or other
100 * measures. This allows turning on using POST where PUT and
101 * DELETE would normally be used; in addition, an
102 * X-Method-Override header will be sent with a value of PUT or
103 * DELETE as appropriate.
105 * @param boolean $override Whether to override PUT and DELETE.
108 public static function setHttpMethodOverride($override = true)
110 self
::$_httpMethodOverride = $override;
115 * Get the HTTP override state
119 public static function getHttpMethodOverride()
121 return self
::$_httpMethodOverride;
126 * Get the full version of a namespace prefix
128 * Looks up a prefix (atom:, etc.) in the list of registered
129 * namespaces and returns the full namespace URI if
130 * available. Returns the prefix, unmodified, if it's not
135 public static function lookupNamespace($prefix)
137 return isset(self
::$_namespaces[$prefix]) ?
138 self
::$_namespaces[$prefix] :
144 * Add a namespace and prefix to the registered list
146 * Takes a prefix and a full namespace URI and adds them to the
147 * list of registered namespaces for use by
148 * Zend_Feed::lookupNamespace().
150 * @param string $prefix The namespace prefix
151 * @param string $namespaceURI The full namespace URI
154 public static function registerNamespace($prefix, $namespaceURI)
156 self
::$_namespaces[$prefix] = $namespaceURI;
161 * Imports a feed located at $uri.
164 * @throws Zend_Feed_Exception
165 * @return Zend_Feed_Abstract
167 public static function import($uri)
169 $client = self
::getHttpClient();
170 $client->setUri($uri);
171 $response = $client->request('GET');
172 if ($response->getStatus() !== 200) {
174 * @see Zend_Feed_Exception
176 require_once 'Zend/Feed/Exception.php';
177 throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus());
179 $feed = $response->getBody();
180 return self
::importString($feed);
185 * Imports a feed represented by $string.
187 * @param string $string
188 * @throws Zend_Feed_Exception
189 * @return Zend_Feed_Abstract
191 public static function importString($string)
193 // Load the feed as an XML DOMDocument object
194 $libxml_errflag = libxml_use_internal_errors(true);
195 $doc = new DOMDocument
;
196 if (trim($string) == '') {
197 require_once 'Zend/Feed/Exception.php';
198 throw new Zend_Feed_Exception('Document/string being imported'
199 . ' is an Empty string or comes from an empty HTTP response');
201 $status = $doc->loadXML($string);
202 libxml_use_internal_errors($libxml_errflag);
206 // prevent the class to generate an undefined variable notice (ZF-2590)
207 // Build error message
208 $error = libxml_get_last_error();
209 if ($error && $error->message
) {
210 $errormsg = "DOMDocument cannot parse XML: {$error->message}";
212 $errormsg = "DOMDocument cannot parse XML";
217 * @see Zend_Feed_Exception
219 require_once 'Zend/Feed/Exception.php';
220 throw new Zend_Feed_Exception($errormsg);
223 // Try to find the base feed element or a single <entry> of an Atom feed
224 if ($doc->getElementsByTagName('feed')->item(0) ||
225 $doc->getElementsByTagName('entry')->item(0)) {
227 * @see Zend_Feed_Atom
229 require_once 'Zend/Feed/Atom.php';
230 // return a newly created Zend_Feed_Atom object
231 return new Zend_Feed_Atom(null, $string);
234 // Try to find the base feed element of an RSS feed
235 if ($doc->getElementsByTagName('channel')->item(0)) {
239 require_once 'Zend/Feed/Rss.php';
240 // return a newly created Zend_Feed_Rss object
241 return new Zend_Feed_Rss(null, $string);
244 // $string does not appear to be a valid feed of the supported types
246 * @see Zend_Feed_Exception
248 require_once 'Zend/Feed/Exception.php';
249 throw new Zend_Feed_Exception('Invalid or unsupported feed format');
254 * Imports a feed from a file located at $filename.
256 * @param string $filename
257 * @throws Zend_Feed_Exception
258 * @return Zend_Feed_Abstract
260 public static function importFile($filename)
262 @ini_set
('track_errors', 1);
263 $feed = @file_get_contents
($filename);
264 @ini_restore
('track_errors');
265 if ($feed === false) {
267 * @see Zend_Feed_Exception
269 require_once 'Zend/Feed/Exception.php';
270 throw new Zend_Feed_Exception("File could not be loaded: $php_errormsg");
272 return self
::importString($feed);
277 * Attempts to find feeds at $uri referenced by <link ... /> tags. Returns an
278 * array of the feeds referenced at $uri.
280 * @todo Allow findFeeds() to follow one, but only one, code 302.
283 * @throws Zend_Feed_Exception
286 public static function findFeeds($uri)
288 // Get the HTTP response from $uri and save the contents
289 $client = self
::getHttpClient();
290 $client->setUri($uri);
291 $response = $client->request();
292 if ($response->getStatus() !== 200) {
294 * @see Zend_Feed_Exception
296 require_once 'Zend/Feed/Exception.php';
297 throw new Zend_Feed_Exception("Failed to access $uri, got response code " . $response->getStatus());
299 $contents = $response->getBody();
301 // Parse the contents for appropriate <link ... /> tags
302 @ini_set
('track_errors', 1);
303 $pattern = '~(<link[^>]+)/?>~i';
304 $result = @preg_match_all
($pattern, $contents, $matches);
305 @ini_restore
('track_errors');
306 if ($result === false) {
308 * @see Zend_Feed_Exception
310 require_once 'Zend/Feed/Exception.php';
311 throw new Zend_Feed_Exception("Internal error: $php_errormsg");
314 // Try to fetch a feed for each link tag that appears to refer to a feed
316 if (isset($matches[1]) && count($matches[1]) > 0) {
317 foreach ($matches[1] as $link) {
318 // force string to be an utf-8 one
319 if (!mb_check_encoding($link, 'UTF-8')) {
320 $link = mb_convert_encoding($link, 'UTF-8');
322 $xml = @simplexml_load_string
(rtrim($link, ' /') . ' />');
323 if ($xml === false) {
326 $attributes = $xml->attributes();
327 if (!isset($attributes['rel']) ||
!@preg_match
('~^(?:alternate|service\.feed)~i', $attributes['rel'])) {
330 if (!isset($attributes['type']) ||
331 !@preg_match
('~^application/(?:atom|rss|rdf)\+xml~', $attributes['type'])) {
334 if (!isset($attributes['href'])) {
338 // checks if we need to canonize the given uri
340 $uri = Zend_Uri
::factory((string) $attributes['href']);
341 } catch (Zend_Uri_Exception
$e) {
343 $path = (string) $attributes['href'];
344 $query = $fragment = '';
345 if (substr($path, 0, 1) != '/') {
346 // add the current root path to this one
347 $path = rtrim($client->getUri()->getPath(), '/') . '/' . $path;
349 if (strpos($path, '?') !== false) {
350 list($path, $query) = explode('?', $path, 2);
352 if (strpos($query, '#') !== false) {
353 list($query, $fragment) = explode('#', $query, 2);
355 $uri = Zend_Uri
::factory($client->getUri(true));
356 $uri->setPath($path);
357 $uri->setQuery($query);
358 $uri->setFragment($fragment);
361 $feed = self
::import($uri);
362 } catch (Exception
$e) {
365 $feeds[$uri->getUri()] = $feed;
369 // Return the fetched feeds
374 * Construct a new Zend_Feed_Abstract object from a custom array
377 * @param string $format (rss|atom) the requested output format
378 * @return Zend_Feed_Abstract
380 public static function importArray(array $data, $format = 'atom')
382 $obj = 'Zend_Feed_' . ucfirst(strtolower($format));
383 if (!class_exists($obj)) {
384 require_once 'Zend/Loader.php';
385 Zend_Loader
::loadClass($obj);
389 * @see Zend_Feed_Builder
391 require_once 'Zend/Feed/Builder.php';
392 return new $obj(null, null, new Zend_Feed_Builder($data));
396 * Construct a new Zend_Feed_Abstract object from a Zend_Feed_Builder_Interface data source
398 * @param Zend_Feed_Builder_Interface $builder this object will be used to extract the data of the feed
399 * @param string $format (rss|atom) the requested output format
400 * @return Zend_Feed_Abstract
402 public static function importBuilder(Zend_Feed_Builder_Interface
$builder, $format = 'atom')
404 $obj = 'Zend_Feed_' . ucfirst(strtolower($format));
405 if (!class_exists($obj)) {
406 require_once 'Zend/Loader.php';
407 Zend_Loader
::loadClass($obj);
409 return new $obj(null, null, $builder);