1: <?php
2: /**
3: * Portions Copyright 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
4: * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
5: *
6: * @author Chuck Hagenbuch <chuck@horde.org>
7: * @license http://www.horde.org/licenses/bsd BSD
8: * @category Horde
9: * @package Feed
10: */
11:
12: /**
13: * @author Chuck Hagenbuch <chuck@horde.org>
14: * @license http://www.horde.org/licenses/bsd BSD
15: * @category Horde
16: * @package Feed
17: */
18: class Horde_Feed
19: {
20: /**
21: * Create a Feed object based on a DOMDocument.
22: *
23: * @param DOMDocument $doc The DOMDocument object to import.
24: *
25: * @throws Horde_Feed_Exception
26: *
27: * @return Horde_Feed_Base The feed object imported from $doc
28: */
29: public static function create(DOMDocument $doc, $uri = null)
30: {
31: // Try to find the base feed element or a single <entry> of an
32: // Atom feed.
33: if ($feed = $doc->getElementsByTagName('feed')->item(0)) {
34: // Return an Atom feed.
35: return new Horde_Feed_Atom($feed, $uri);
36: } elseif ($entry = $doc->getElementsByTagName('entry')->item(0)) {
37: // Return an Atom single-entry feed.
38: $feeddoc = new DOMDocument($doc->version,
39: $doc->actualEncoding);
40: $feed = $feeddoc->appendChild($feeddoc->createElement('feed'));
41: $feed->appendChild($feeddoc->importNode($entry, true));
42:
43: return new Horde_Feed_Atom($feed, $uri);
44: }
45:
46: // Try to find the base feed element of an RSS feed.
47: if ($channel = $doc->getElementsByTagName('channel')->item(0)) {
48: // Return an RSS feed.
49: return new Horde_Feed_Rss($channel, $uri);
50: }
51:
52: // Try to find an outline element of an OPML blogroll.
53: if ($outline = $doc->getElementsByTagName('outline')->item(0)) {
54: // Return a blogroll feed.
55: return new Horde_Feed_Blogroll($doc->documentElement, $uri);
56: }
57:
58: // $doc does not appear to be a valid feed of the supported
59: // types.
60: throw new Horde_Feed_Exception('Invalid or unsupported feed format: '
61: . substr($doc->saveXML(), 0, 80) . '...');
62: }
63:
64: /**
65: * Reads a feed represented by $string.
66: *
67: * @param string $string The XML content of the feed.
68: * @param string $uri The feed's URI location, if known.
69: *
70: * @throws Horde_Feed_Exception
71: *
72: * @return Horde_Feed_Base
73: */
74: public static function read($string, $uri = null)
75: {
76: // Load the feed as a DOMDocument object.
77: libxml_use_internal_errors(true);
78: $doc = new DOMDocument;
79: $doc->recover = true;
80: $loaded = $doc->loadXML($string);
81: if (!$loaded) {
82: $loaded = $doc->loadHTML($string);
83: if (!$loaded) {
84: self::_exception('DOMDocument cannot parse XML', libxml_get_last_error());
85: }
86: }
87:
88: return self::create($doc);
89: }
90:
91: /**
92: * Read a feed located at $uri
93: *
94: * @param string $uri The URI to fetch the feed from.
95: * @param Horde_Http_Client $httpclient The HTTP client to use.
96: *
97: * @throws Horde_Feed_Exception
98: *
99: * @return Horde_Feed_Base
100: */
101: public static function readUri($uri, Horde_Http_Client $httpclient = null)
102: {
103: if (is_null($httpclient)) {
104: $httpclient = new Horde_Http_Client();
105: }
106:
107: try {
108: $response = $httpclient->get($uri);
109: } catch (Horde_Http_Exception $e) {
110: throw new Horde_Feed_Exception('Error reading feed: ' . $e->getMessage());
111: }
112: if ($response->code != 200) {
113: throw new Horde_Feed_Exception('Unable to read feed, got response code ' . $response->code);
114: }
115: $feed = $response->getBody();
116: return self::read($feed, $uri);
117: }
118:
119: /**
120: * Read a feed from $filename
121: *
122: * @param string $filename The location of the feed file on an accessible
123: * filesystem or through an available stream wrapper.
124: *
125: * @throws Horde_Feed_Exception
126: *
127: * @return Horde_Feed_Base
128: */
129: public static function readFile($filename)
130: {
131: libxml_use_internal_errors(true);
132: $doc = new DOMDocument;
133: $doc->recover = true;
134: $filename = urlencode($filename);
135: $loaded = $doc->load($filename);
136: if (!$loaded) {
137: $loaded = $doc->loadHTMLFile($filename);
138: if (!$loaded) {
139: self::_exception('File could not be read or parsed', libxml_get_last_error());
140: }
141: }
142:
143: return self::create($doc);
144: }
145:
146: /**
147: * Builds an exception message from a libXMLError object.
148: *
149: * @param string $msg An error message.
150: * @param libXMLError $error An error object.
151: *
152: * @throws Horde_Feed_Exception
153: */
154: protected static function _exception($msg, $error)
155: {
156: if ($error) {
157: $msg .= ': ' . $error->message;
158: if ($error->file) {
159: $msg .= sprintf(' in file %s, line %d, column %d',
160: $error->file, $error->line, $error->column);
161: }
162: }
163: throw new Horde_Feed_Exception($msg);
164: }
165: }
166: