Updated for better reporting when loading from remote url fails. Now displays error...
[moodle.git] / rss / class.RSS.php
CommitLineData
0eb35827 1<?php
2/*
3 * Project: MagpieRSS: a simple RSS integration tool
4 * File: rss_parse.inc includes code for parsing
5 * RSS, and returning an RSS object
6 * Author: Kellan Elliott-McCrea <kellan@protest.net>
7 * Version: 0.51
8 * License: GPL
9 *
10 * The lastest version of MagpieRSS can be obtained from:
11 * http://magpierss.sourceforge.net
12 *
13 * For questions, help, comments, discussion, etc., please join the
14 * Magpie mailing list:
15 * magpierss-general@lists.sourceforge.net
16 *
17 */
18
19
20/*
21 * NOTES ON RSS PARSING PHILOSOPHY (moderately important):
22 * MagpieRSS parse all versions of RSS with a few limitation (mod_content, and
23 * mod_taxonomy support is shaky) into a simple object, with 2 fields,
24 * the hash 'channel', and the array 'items'.
25 *
26 * MagpieRSS is a forgiving and inclusive parser. It currently makes no
27 * attempt to enforce the validity on an RSS feed. It will include any
28 * properly formatted tags it finds, allowing to you to mix RSS 0.93, with RSS
29 * 1.0, with tags or your own imagining. This sort of witches brew is a bad
30 * bad idea! But Magpie is less pendantic then I am.
31 *
32 * RSS validators are readily available on the web at:
33 * http://feeds.archive.org/validator/
34 * http://www.ldodds.com/rss_validator/1.0/validator.html
35 *
36 */
37
38/*
39 * EXAMPLE PARSE RESULTS:
40 *
41 * Magpie tries to parse RSS into ease to use PHP datastructures.
42 *
43 * For example, Magpie on encountering RSS 1.0 item entry:
44 *
45 * <item rdf:about="http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257">
46 * <title>Weekly Peace Vigil</title>
47 * <link>http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257</link>
48 * <description>Wear a white ribbon</description>
49 * <dc:subject>Peace</dc:subject>
50 * <ev:startdate>2002-06-01T11:00:00</ev:startdate>
51 * <ev:location>Northampton, MA</ev:location>
52 * <ev:enddate>2002-06-01T12:00:00</ev:enddate>
53 * <ev:type>Protest</ev:type>
54 * </item>
55 *
56 * Would transform it into the following associative array, and push it
57 * onto the array $rss-items
58 *
59 * array(
60 * title => 'Weekly Peace Vigil',
61 * link =>
62 * 'http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257',
63 * description => 'Wear a white ribbon',
64 * dc => array (
65 * subject => 'Peace'
66 * ),
67 * ev => array (
68 * startdate => '2002-06-01T11:00:00',
69 * enddate => '2002-06-01T12:00:00',
70 * type => 'Protest',
71 * location => 'Northampton, MA'
72 * )
73 * )
74 *
75 */
76
77class MagpieRSS {
78 /*
79 * Hybrid parser, and object. (probably a bad idea! :)
80 *
81 * Useage Example:
82 *
83 * $some_rss = "<?xml version="1.0"......
84 *
85 * $rss = new MagpieRSS( $some_rss );
86 *
87 * // print rss chanel title
88 * echo $rss->channel['title'];
89 *
90 * // print the title of each item
91 * foreach ($rss->items as $item ) {
92 * echo $item[title];
93 * }
94 *
95 * see rss_fetch.inc for a simpler interface
96 */
97
98 var $parser;
99
100 var $current_item = array(); // item currently being parsed
101 var $items = array(); // collection of parsed items
102 var $channel = array(); // hash of channel fields
103 var $textinput = array();
104 var $image = array();
105
106 var $parent_field = array('RDF');
107 var $current_field = '';
108 var $current_namespace = false;
109
110 var $ERROR = "";
111
112 /*======================================================================*\
113 Function: MagpieRSS
114 Purpose: Constructor, sets up XML parser,parses source,
115 and populates object..
116 Input: String containing the RSS to be parsed
117 \*======================================================================*/
118 function MagpieRSS ($source) {
119
120 # if PHP xml isn't compiled in, die
121 #
122 if (!function_exists('xml_parser_create')) {
123 $this->error( "Failed to load PHP's XML Extension. " .
124 "http://www.php.net/manual/en/ref.xml.php",
125 E_USER_ERROR );
126 }
127
128 $parser = @xml_parser_create();
129
130 if (!is_resource($parser))
131 {
132 $this->error( "Failed to create an instance of PHP's XML parser. " .
133 "http://www.php.net/manual/en/ref.xml.php",
134 E_USER_ERROR );
135 }
136
137
138 $this->parser = $parser;
139
140 # pass in parser, and a reference to this object
141 # setup handlers
142 #
143 xml_set_object( $this->parser, $this );
144 xml_set_element_handler($this->parser, 'start_element', 'end_element');
145 xml_set_character_data_handler( $this->parser, 'cdata' );
146
147
148 $status = xml_parse( $this->parser, $source );
149
150 if (! $status ) {
151 $errorcode = xml_get_error_code( $this->parser );
152 if ( $errorcode != XML_ERROR_NONE ) {
153 $xml_error = xml_error_string( $errorcode );
154 $error_line = xml_get_current_line_number($this->parser);
155 $error_col = xml_get_current_column_number($this->parser);
156 $errormsg = "$xml_error at line $error_line, column $error_col";
157
158 $this->error( $errormsg );
159 }
160 }
161
162 xml_parser_free( $this->parser );
163 }
164
165 function start_element ($p, $element, &$attrs) {
166 $element = strtolower( $element );
167 # check for a namespace, and split if found
168 #
169 $namespace = false;
170 if ( strpos( $element, ':' ) ) {
171 list($namespace, $element) = split( ':', $element, 2);
172 }
173 $this->current_field = $element;
174 if ( $namespace and $namespace != 'rdf' ) {
175 $this->current_namespace = $namespace;
176 }
177
178 if ( $element == 'channel' ) {
179 array_unshift( $this->parent_field, 'channel' );
180 }
181 elseif ( $element == 'items' ) {
182 array_unshift( $this->parent_field, 'items' );
183 }
184 elseif ( $element == 'item' ) {
185 array_unshift( $this->parent_field, 'item' );
186 }
187 elseif ( $element == 'textinput' ) {
188 array_unshift( $this->parent_field, 'textinput' );
189 }
190 elseif ( $element == 'image' ) {
191 array_unshift( $this->parent_field, 'image' );
192 }
193
194 }
195
196 function end_element ($p, $element) {
197 $element = strtolower($element);
198
199 if ( $element == 'item' ) {
200 $this->items[] = $this->current_item;
201 $this->current_item = array();
202 array_shift( $this->parent_field );
203 }
204 elseif ( $element == 'channel' or $element == 'items' or
205 $element == 'textinput' or $element == 'image' ) {
206 array_shift( $this->parent_field );
207 }
208
209 $this->current_field = '';
210 $this->current_namespace = false;
211 }
212
213 function cdata ($p, $text) {
214 # skip item, channel, items first time we see them
215 #
216 if ( $this->parent_field[0] == $this->current_field or
217 ! $this->current_field ) {
218 return;
219 }
220 elseif ( $this->parent_field[0] == 'channel') {
221 if ( $this->current_namespace ) {
222 $this->append(
223 $this->channel[ $this->current_namespace ][ $this->current_field ],
224 $text);
225 }
226 else {
227 $this->append($this->channel[ $this->current_field ], $text);
228 }
229
230 }
231 elseif ( $this->parent_field[0] == 'item' ) {
232 if ( $this->current_namespace ) {
233 $this->append(
234 $this->current_item[ $this->current_namespace ][$this->current_field ],
235 $text);
236 }
237 else {
238 $this->append(
239 $this->current_item[ $this->current_field ],
240 $text );
241 }
242 }
243 elseif ( $this->parent_field[0] == 'textinput' ) {
244 if ( $this->current_namespace ) {
245 $this->append(
246 $this->textinput[ $this->current_namespace ][ $this->current_field ],
247 $text );
248 }
249 else {
250 $this->append(
251 $this->textinput[ $this->current_field ],
252 $text );
253 }
254 }
255 elseif ( $this->parent_field[0] == 'image' ) {
256 if ( $this->current_namespace ) {
257 $this->append(
258 $this->image[ $this->current_namespace ][ $this->current_field ],
259 $text );
260 }
261 else {
262 $this->append(
263 $this->image[ $this->current_field ],
264 $text );
265 }
266 }
267 }
268
269 function append (&$str1, $str2="") {
270 if (!isset($str1) ) {
271 $str1="";
272 }
273 $str1 .= $str2;
274 }
275
276 function error ($errormsg, $lvl=E_USER_WARNING) {
277 // append PHP's error message if track_errors enabled
278 if ( isset($php_errormsg) ) {
279 $errormsg .= " ($php_errormsg)";
280 }
281 $this->ERROR = $errormsg;
282 //if ( MAGPIE_DEBUG ) {
283 // trigger_error( $errormsg, $lvl);
284 //}
285 //else {
286 error_log( $errormsg, 0);
287 //}
288 }
289
290
291 /*======================================================================*\
292 EVERYTHING BELOW HERE IS FOR DEBUGGING PURPOSES
293 \*======================================================================*/
294 function show_list () {
295 echo "<ol>\n";
296 foreach ($this->items as $item) {
297 echo "<li>", $this->show_item( $item );
298 }
299 echo "</ol>";
300 }
301
302 function show_channel () {
303 echo "channel:<br>";
304 echo "<ul>";
305 while ( list($key, $value) = each( $this->channel ) ) {
306 echo "<li> $key: $value";
307 }
308 echo "</ul>";
309 }
310
311 function show_item ($item) {
312 echo "item: $item[title]";
313 echo "<ul>";
314 while ( list($key, $value) = each($item) ) {
315 if ( is_array($value) ) {
316 echo "<br><b>$key</b>";
317 echo "<ul>";
318 while ( list( $ns_key, $ns_value) = each( $value ) ) {
319 echo "<li>$ns_key: $ns_value";
320 }
321 echo "</ul>";
322 }
323 else {
324 echo "<li> $key: $value";
325 }
326 }
327 echo "</ul>";
328 }
329
330 /*======================================================================*\
331 END DEBUGGING FUNCTIONS
332 \*======================================================================*/
333
334} # end class RSS
335?>