FeedWordPress - Version 0.8

Version Description

Download this release

Release Info

Developer radgeek
Plugin Icon wp plugin FeedWordPress
Version 0.8
Comparing to
See all releases

Version 0.8

OPTIONAL/wp-includes/rss-functions.php ADDED
@@ -0,0 +1,1203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* Project: MagpieRSS: a simple RSS integration tool
3
+ * File: A compiled file for RSS syndication
4
+ * Author: Kellan Elliot-McCrea <kellan@protest.net>
5
+ * WordPress development team <http://www.wordpress.org/>
6
+ * Charles Johnson <technophilia@radgeek.com>
7
+ * Version: 0.7wp
8
+ * License: GPL
9
+ *
10
+ * Provenance:
11
+ *
12
+ * This is a drop-in replacement for the `rss-functions.php` provided with the
13
+ * WordPress 1.5 distribution, which upgrades the version of MagpieRSS from 0.51
14
+ * to a modification of 0.7. In addition to improved handling of character
15
+ * encoding and other updates, this branch of MagpieRSS 0.7 also supports
16
+ * multiple categorization of posts (using <dc:subject> or <category>). The
17
+ * file is, therefore, derived from four sources: (1) Kellan's MagpieRSS 0.51,
18
+ * (2) the WordPress development team's modifications to MagpieRSS 0.51,
19
+ * (3) Kellan's MagpieRSS 0.7, and (4) Charles Johnson's modifications to
20
+ * MagpieRSS 0.7. All possible because of the GPL. Yay for free software!
21
+ *
22
+ * Differences from the main branch of MagpieRSS:
23
+ *
24
+ * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and
25
+ * rss_utils.inc is included in one file.
26
+ *
27
+ * 2. MagpieRSS returns the WordPress version as the user agent, rather than
28
+ * Magpie
29
+ *
30
+ * 3. class RSSCache is a modified version by WordPress developers, which
31
+ * caches feeds in the WordPress database (in the options table), rather
32
+ * than writing external files directly.
33
+ *
34
+ * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
35
+ *
36
+ * 5. New cases added to MagpieRSS::feed_start_element(),
37
+ * MagpieRSS::feed_end_element(), and MagpieRSS::normalize() to handle
38
+ * multiple categories correctly.
39
+ */
40
+
41
+ define('RSS', 'RSS');
42
+ define('ATOM', 'Atom');
43
+ define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version);
44
+
45
+ # UPDATED: rss_parse.inc: class MagpieRSS, function map_attrs
46
+ # --- cut here ---
47
+ /**
48
+ * Hybrid parser, and object, takes RSS as a string and returns a simple object.
49
+ *
50
+ * see: rss_fetch.inc for a simpler interface with integrated caching support
51
+ *
52
+ */
53
+ class MagpieRSS {
54
+ var $parser;
55
+
56
+ var $current_item = array(); // item currently being parsed
57
+ var $items = array(); // collection of parsed items
58
+ var $channel = array(); // hash of channel fields
59
+ var $textinput = array();
60
+ var $image = array();
61
+ var $feed_type;
62
+ var $feed_version;
63
+ var $encoding = ''; // output encoding of parsed rss
64
+
65
+ var $_source_encoding = ''; // only set if we have to parse xml prolog
66
+
67
+ var $ERROR = "";
68
+ var $WARNING = "";
69
+
70
+ // define some constants
71
+
72
+ var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
73
+ var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
74
+
75
+ // parser variables, useless if you're not a parser, treat as private
76
+ var $stack = array(); // parser stack
77
+ var $inchannel = false;
78
+ var $initem = false;
79
+ var $incontent = false; // if in Atom <content mode="xml"> field
80
+ var $intextinput = false;
81
+ var $inimage = false;
82
+ var $current_namespace = false;
83
+
84
+ var $incategory = false;
85
+ var $current_category = 0;
86
+
87
+ /**
88
+ * Set up XML parser, parse source, and return populated RSS object..
89
+ *
90
+ * @param string $source string containing the RSS to be parsed
91
+ *
92
+ * NOTE: Probably a good idea to leave the encoding options alone unless
93
+ * you know what you're doing as PHP's character set support is
94
+ * a little weird.
95
+ *
96
+ * NOTE: A lot of this is unnecessary but harmless with PHP5
97
+ *
98
+ *
99
+ * @param string $output_encoding output the parsed RSS in this character
100
+ * set defaults to ISO-8859-1 as this is PHP's
101
+ * default.
102
+ *
103
+ * NOTE: might be changed to UTF-8 in future
104
+ * versions.
105
+ *
106
+ * @param string $input_encoding the character set of the incoming RSS source.
107
+ * Leave blank and Magpie will try to figure it
108
+ * out.
109
+ *
110
+ *
111
+ * @param bool $detect_encoding if false Magpie won't attempt to detect
112
+ * source encoding. (caveat emptor)
113
+ *
114
+ */
115
+ function MagpieRSS ($source, $output_encoding='ISO-8859-1',
116
+ $input_encoding=null, $detect_encoding=true)
117
+ {
118
+ # if PHP xml isn't compiled in, die
119
+ #
120
+ if (!function_exists('xml_parser_create')) {
121
+ $this->error( "Failed to load PHP's XML Extension. " .
122
+ "http://www.php.net/manual/en/ref.xml.php",
123
+ E_USER_ERROR );
124
+ }
125
+
126
+ list($parser, $source) = $this->create_parser($source,
127
+ $output_encoding, $input_encoding, $detect_encoding);
128
+
129
+
130
+ if (!is_resource($parser)) {
131
+ $this->error( "Failed to create an instance of PHP's XML parser. " .
132
+ "http://www.php.net/manual/en/ref.xml.php",
133
+ E_USER_ERROR );
134
+ }
135
+
136
+
137
+ $this->parser = $parser;
138
+
139
+ # pass in parser, and a reference to this object
140
+ # setup handlers
141
+ #
142
+ xml_set_object( $this->parser, $this );
143
+ xml_set_element_handler($this->parser,
144
+ 'feed_start_element', 'feed_end_element' );
145
+
146
+ xml_set_character_data_handler( $this->parser, 'feed_cdata' );
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
+ $this->normalize();
165
+ }
166
+
167
+ function feed_start_element($p, $element, &$attrs) {
168
+ $el = $element = strtolower($element);
169
+ $attrs = array_change_key_case($attrs, CASE_LOWER);
170
+
171
+ // check for a namespace, and split if found
172
+ $ns = false;
173
+ if ( strpos( $element, ':' ) ) {
174
+ list($ns, $el) = split( ':', $element, 2);
175
+ }
176
+ if ( $ns and $ns != 'rdf' ) {
177
+ $this->current_namespace = $ns;
178
+ }
179
+
180
+ # if feed type isn't set, then this is first element of feed
181
+ # identify feed from root element
182
+ #
183
+ if (!isset($this->feed_type) ) {
184
+ if ( $el == 'rdf' ) {
185
+ $this->feed_type = RSS;
186
+ $this->feed_version = '1.0';
187
+ }
188
+ elseif ( $el == 'rss' ) {
189
+ $this->feed_type = RSS;
190
+ $this->feed_version = $attrs['version'];
191
+ }
192
+ elseif ( $el == 'feed' ) {
193
+ $this->feed_type = ATOM;
194
+ $this->feed_version = $attrs['version'];
195
+ $this->inchannel = true;
196
+ }
197
+ return;
198
+ }
199
+
200
+ if ( $el == 'channel' )
201
+ {
202
+ $this->inchannel = true;
203
+ }
204
+ elseif ($el == 'item' or $el == 'entry' )
205
+ {
206
+ $this->initem = true;
207
+ if ( isset($attrs['rdf:about']) ) {
208
+ $this->current_item['about'] = $attrs['rdf:about'];
209
+ }
210
+ }
211
+
212
+ elseif ($this->initem and ($el == 'category' or ($this->current_namespace == 'dc' and $el == 'subject'))) {
213
+ $this->incategory = true;
214
+ array_unshift( $this->stack, $el );
215
+ }
216
+
217
+ // if we're in the default namespace of an RSS feed,
218
+ // record textinput or image fields
219
+ elseif (
220
+ $this->feed_type == RSS and
221
+ $this->current_namespace == '' and
222
+ $el == 'textinput' )
223
+ {
224
+ $this->intextinput = true;
225
+ }
226
+
227
+ elseif (
228
+ $this->feed_type == RSS and
229
+ $this->current_namespace == '' and
230
+ $el == 'image' )
231
+ {
232
+ $this->inimage = true;
233
+ }
234
+
235
+ # handle atom content constructs
236
+ elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
237
+ {
238
+ // avoid clashing w/ RSS mod_content
239
+ if ($el == 'content' ) {
240
+ $el = 'atom_content';
241
+ }
242
+
243
+ $this->incontent = $el;
244
+
245
+
246
+ }
247
+
248
+ // if inside an Atom content construct (e.g. content or summary) field treat tags as text
249
+ elseif ($this->feed_type == ATOM and $this->incontent )
250
+ {
251
+ // if tags are inlined, then flatten
252
+ $attrs_str = join(' ',
253
+ array_map('map_attrs',
254
+ array_keys($attrs),
255
+ array_values($attrs) ) );
256
+
257
+ $this->append_content( "<$element $attrs_str>" );
258
+
259
+ array_unshift( $this->stack, $el );
260
+ }
261
+
262
+ // Atom support many links per containging element.
263
+ // Magpie treats link elements of type rel='alternate'
264
+ // as being equivalent to RSS's simple link element.
265
+ //
266
+ elseif ($this->feed_type == ATOM and $el == 'link' )
267
+ {
268
+ if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
269
+ {
270
+ $link_el = 'link';
271
+ }
272
+ else {
273
+ $link_el = 'link_' . $attrs['rel'];
274
+ }
275
+
276
+ $this->append($link_el, $attrs['href']);
277
+ }
278
+
279
+ // set stack[0] to current element
280
+ else {
281
+ array_unshift($this->stack, $el);
282
+ }
283
+ }
284
+
285
+
286
+
287
+ function feed_cdata ($p, $text) {
288
+ if ($this->feed_type == ATOM and $this->incontent)
289
+ {
290
+ $this->append_content( $text );
291
+ }
292
+ else {
293
+ $current_el = join('_', array_reverse($this->stack));
294
+ $this->append($current_el, $text);
295
+ }
296
+ }
297
+
298
+ function feed_end_element ($p, $el) {
299
+ $el = strtolower($el);
300
+
301
+ if ( $el == 'item' or $el == 'entry' )
302
+ {
303
+ $this->items[] = $this->current_item;
304
+ $this->current_item = array();
305
+ $this->initem = false;
306
+
307
+ $this->current_category = 0;
308
+ }
309
+ elseif ($this->initem and ($el == 'category' or $el == 'dc:subject')) {
310
+ $this->incategory = false;
311
+ $this->current_category = $this->current_category + 1;
312
+ array_shift( $this->stack );
313
+ }
314
+ elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
315
+ {
316
+ $this->intextinput = false;
317
+ }
318
+ elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
319
+ {
320
+ $this->inimage = false;
321
+ }
322
+ elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
323
+ {
324
+ $this->incontent = false;
325
+ }
326
+ elseif ($el == 'channel' or $el == 'feed' )
327
+ {
328
+ $this->inchannel = false;
329
+ }
330
+ elseif ($this->feed_type == ATOM and $this->incontent ) {
331
+ // balance tags properly
332
+ // note: i don't think this is actually neccessary
333
+ if ( $this->stack[0] == $el )
334
+ {
335
+ $this->append_content("</$el>");
336
+ }
337
+ else {
338
+ $this->append_content("<$el />");
339
+ }
340
+
341
+ array_shift( $this->stack );
342
+ }
343
+ else {
344
+ array_shift( $this->stack );
345
+ }
346
+
347
+ $this->current_namespace = false;
348
+ }
349
+
350
+ function concat (&$str1, $str2="") {
351
+ if (!isset($str1) ) {
352
+ $str1="";
353
+ }
354
+ $str1 .= $str2;
355
+ }
356
+
357
+
358
+
359
+ function append_content($text) {
360
+ if ( $this->initem ) {
361
+ $this->concat( $this->current_item[ $this->incontent ], $text );
362
+ }
363
+ elseif ( $this->inchannel ) {
364
+ $this->concat( $this->channel[ $this->incontent ], $text );
365
+ }
366
+ }
367
+
368
+ // smart append - field and namespace aware
369
+ function append($el, $text) {
370
+ if (!$el) {
371
+ return;
372
+ }
373
+ if ( $this->current_namespace )
374
+ {
375
+ if ( $this->incategory ) {
376
+ $this->concat( $this->current_item['categories'][$this->current_category], $text );
377
+ }
378
+ elseif ( $this->initem ) {
379
+ $this->concat(
380
+ $this->current_item[ $this->current_namespace ][ $el ], $text );
381
+ }
382
+ elseif ($this->inchannel) {
383
+ $this->concat(
384
+ $this->channel[ $this->current_namespace][ $el ], $text );
385
+ }
386
+ elseif ($this->intextinput) {
387
+ $this->concat(
388
+ $this->textinput[ $this->current_namespace][ $el ], $text );
389
+ }
390
+ elseif ($this->inimage) {
391
+ $this->concat(
392
+ $this->image[ $this->current_namespace ][ $el ], $text );
393
+ }
394
+ }
395
+ else {
396
+ if ( $this->incategory ) {
397
+ $this->concat( $this->current_item['categories'][$this->current_category], $text );
398
+ }
399
+ elseif ( $this->initem ) {
400
+ $this->concat(
401
+ $this->current_item[ $el ], $text);
402
+ }
403
+ elseif ($this->intextinput) {
404
+ $this->concat(
405
+ $this->textinput[ $el ], $text );
406
+ }
407
+ elseif ($this->inimage) {
408
+ $this->concat(
409
+ $this->image[ $el ], $text );
410
+ }
411
+ elseif ($this->inchannel) {
412
+ $this->concat(
413
+ $this->channel[ $el ], $text );
414
+ }
415
+
416
+ }
417
+ }
418
+
419
+ function normalize () {
420
+ // if atom populate rss fields
421
+ if ( $this->is_atom() ) {
422
+ $this->channel['description'] = $this->channel['tagline'];
423
+ for ( $i = 0; $i < count($this->items); $i++) {
424
+ $item = $this->items[$i];
425
+ if ( isset($item['summary']) )
426
+ $item['description'] = $item['summary'];
427
+ if ( isset($item['atom_content']))
428
+ $item['content']['encoded'] = $item['atom_content'];
429
+
430
+ $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified'];
431
+ if ( $atom_date ) {
432
+ $epoch = @parse_w3cdtf($item['modified']);
433
+ if ($epoch and $epoch > 0) {
434
+ $item['date_timestamp'] = $epoch;
435
+ }
436
+ }
437
+
438
+ if ( is_array($item['categories']) ) {
439
+ $item['category'] = $item['categories'][0];
440
+ $item['dc']['subjects'] = $item['categories'];
441
+ $item['dc']['subject'] = $item['category'];
442
+ }
443
+
444
+ $this->items[$i] = $item;
445
+ }
446
+ }
447
+ elseif ( $this->is_rss() ) {
448
+ $this->channel['tagline'] = $this->channel['description'];
449
+ for ( $i = 0; $i < count($this->items); $i++) {
450
+ $item = $this->items[$i];
451
+ if ( isset($item['description']))
452
+ $item['summary'] = $item['description'];
453
+ if ( isset($item['content']['encoded'] ) )
454
+ $item['atom_content'] = $item['content']['encoded'];
455
+
456
+ if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
457
+ $epoch = @parse_w3cdtf($item['dc']['date']);
458
+ if ($epoch and $epoch > 0) {
459
+ $item['date_timestamp'] = $epoch;
460
+ }
461
+ }
462
+ elseif ( isset($item['pubdate']) ) {
463
+ $epoch = @strtotime($item['pubdate']);
464
+ if ($epoch > 0) {
465
+ $item['date_timestamp'] = $epoch;
466
+ }
467
+ }
468
+
469
+ if ( is_array($item['categories']) ) {
470
+ $item['category'] = $item['categories'][0];
471
+ $item['dc']['subjects'] = $item['categories'];
472
+ $item['dc']['subject'] = $item['category'];
473
+ }
474
+
475
+ $this->items[$i] = $item;
476
+ }
477
+ }
478
+ }
479
+
480
+
481
+ function is_rss () {
482
+ if ( $this->feed_type == RSS ) {
483
+ return $this->feed_version;
484
+ }
485
+ else {
486
+ return false;
487
+ }
488
+ }
489
+
490
+ function is_atom() {
491
+ if ( $this->feed_type == ATOM ) {
492
+ return $this->feed_version;
493
+ }
494
+ else {
495
+ return false;
496
+ }
497
+ }
498
+
499
+ /**
500
+ * return XML parser, and possibly re-encoded source
501
+ *
502
+ */
503
+ function create_parser($source, $out_enc, $in_enc, $detect) {
504
+ if ( substr(phpversion(),0,1) == 5) {
505
+ $parser = $this->php5_create_parser($in_enc, $detect);
506
+ }
507
+ else {
508
+ list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
509
+ }
510
+ if ($out_enc) {
511
+ $this->encoding = $out_enc;
512
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
513
+ }
514
+
515
+ return array($parser, $source);
516
+ }
517
+
518
+ /**
519
+ * Instantiate an XML parser under PHP5
520
+ *
521
+ * PHP5 will do a fine job of detecting input encoding
522
+ * if passed an empty string as the encoding.
523
+ *
524
+ * All hail libxml2!
525
+ *
526
+ */
527
+ function php5_create_parser($in_enc, $detect) {
528
+ // by default php5 does a fine job of detecting input encodings
529
+ if(!$detect && $in_enc) {
530
+ return xml_parser_create($in_enc);
531
+ }
532
+ else {
533
+ return xml_parser_create('');
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Instaniate an XML parser under PHP4
539
+ *
540
+ * Unfortunately PHP4's support for character encodings
541
+ * and especially XML and character encodings sucks. As
542
+ * long as the documents you parse only contain characters
543
+ * from the ISO-8859-1 character set (a superset of ASCII,
544
+ * and a subset of UTF-8) you're fine. However once you
545
+ * step out of that comfy little world things get mad, bad,
546
+ * and dangerous to know.
547
+ *
548
+ * The following code is based on SJM's work with FoF
549
+ * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
550
+ *
551
+ */
552
+ function php4_create_parser($source, $in_enc, $detect) {
553
+ if ( !$detect ) {
554
+ return array(xml_parser_create($in_enc), $source);
555
+ }
556
+
557
+ if (!$in_enc) {
558
+ if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
559
+ $in_enc = strtoupper($m[1]);
560
+ $this->source_encoding = $in_enc;
561
+ }
562
+ else {
563
+ $in_enc = 'UTF-8';
564
+ }
565
+ }
566
+
567
+ if ($this->known_encoding($in_enc)) {
568
+ return array(xml_parser_create($in_enc), $source);
569
+ }
570
+
571
+ // the dectected encoding is not one of the simple encodings PHP knows
572
+
573
+ // attempt to use the iconv extension to
574
+ // cast the XML to a known encoding
575
+ // @see http://php.net/iconv
576
+
577
+ if (function_exists('iconv')) {
578
+ $encoded_source = iconv($in_enc,'UTF-8', $source);
579
+ if ($encoded_source) {
580
+ return array(xml_parser_create('UTF-8'), $encoded_source);
581
+ }
582
+ }
583
+
584
+ // iconv didn't work, try mb_convert_encoding
585
+ // @see http://php.net/mbstring
586
+ if(function_exists('mb_convert_encoding')) {
587
+ $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
588
+ if ($encoded_source) {
589
+ return array(xml_parser_create('UTF-8'), $encoded_source);
590
+ }
591
+ }
592
+
593
+ // else
594
+ $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
595
+ "You may see strange artifacts, and mangled characters.",
596
+ E_USER_NOTICE);
597
+
598
+ return array(xml_parser_create(), $source);
599
+ }
600
+
601
+ function known_encoding($enc) {
602
+ $enc = strtoupper($enc);
603
+ if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
604
+ return $enc;
605
+ }
606
+ else {
607
+ return false;
608
+ }
609
+ }
610
+
611
+ function error ($errormsg, $lvl=E_USER_WARNING) {
612
+ // append PHP's error message if track_errors enabled
613
+ if ( $php_errormsg ) {
614
+ $errormsg .= " ($php_errormsg)";
615
+ }
616
+ if ( MAGPIE_DEBUG ) {
617
+ trigger_error( $errormsg, $lvl);
618
+ }
619
+ else {
620
+ error_log( $errormsg, 0);
621
+ }
622
+
623
+ $notices = E_USER_NOTICE|E_NOTICE;
624
+ if ( $lvl&$notices ) {
625
+ $this->WARNING = $errormsg;
626
+ } else {
627
+ $this->ERROR = $errormsg;
628
+ }
629
+ }
630
+ } // end class RSS
631
+
632
+ function map_attrs($k, $v) {
633
+ return "$k=\"$v\"";
634
+ }
635
+ # ---- cut here ----
636
+
637
+ require_once( dirname(__FILE__) . '/class-snoopy.php');
638
+
639
+ # -- UPDATED from rss_fetch.inc: fetch_rss, error, debug, magpie_error
640
+ # --- cut here ---
641
+ function fetch_rss ($url) {
642
+ // initialize constants
643
+ init();
644
+
645
+ if ( !isset($url) ) {
646
+ error("fetch_rss called without a url");
647
+ return false;
648
+ }
649
+
650
+ // if cache is disabled
651
+ if ( !MAGPIE_CACHE_ON ) {
652
+ // fetch file, and parse it
653
+ $resp = _fetch_remote_file( $url );
654
+ if ( is_success( $resp->status ) ) {
655
+ return _response_to_rss( $resp );
656
+ }
657
+ else {
658
+ error("Failed to fetch $url and cache is off");
659
+ return false;
660
+ }
661
+ }
662
+ // else cache is ON
663
+ else {
664
+ // Flow
665
+ // 1. check cache
666
+ // 2. if there is a hit, make sure its fresh
667
+ // 3. if cached obj fails freshness check, fetch remote
668
+ // 4. if remote fails, return stale object, or error
669
+
670
+ $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
671
+
672
+ if (MAGPIE_DEBUG and $cache->ERROR) {
673
+ debug($cache->ERROR, E_USER_WARNING);
674
+ }
675
+
676
+
677
+ $cache_status = 0; // response of check_cache
678
+ $request_headers = array(); // HTTP headers to send with fetch
679
+ $rss = 0; // parsed RSS object
680
+ $errormsg = 0; // errors, if any
681
+
682
+ // store parsed XML by desired output encoding
683
+ // as character munging happens at parse time
684
+ $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
685
+
686
+ if (!$cache->ERROR) {
687
+ // return cache HIT, MISS, or STALE
688
+ $cache_status = $cache->check_cache( $cache_key);
689
+ }
690
+
691
+ // if object cached, and cache is fresh, return cached obj
692
+ if ( $cache_status == 'HIT' ) {
693
+ $rss = $cache->get( $cache_key );
694
+ if ( isset($rss) and $rss ) {
695
+ // should be cache age
696
+ $rss->from_cache = 1;
697
+ if ( MAGPIE_DEBUG > 1) {
698
+ debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
699
+ }
700
+ return $rss;
701
+ }
702
+ }
703
+
704
+ // else attempt a conditional get
705
+
706
+ // setup headers
707
+ if ( $cache_status == 'STALE' ) {
708
+ $rss = $cache->get( $cache_key );
709
+ if ( $rss and $rss->etag and $rss->last_modified ) {
710
+ $request_headers['If-None-Match'] = $rss->etag;
711
+ $request_headers['If-Last-Modified'] = $rss->last_modified;
712
+ }
713
+ }
714
+
715
+ $resp = _fetch_remote_file( $url, $request_headers );
716
+
717
+ if (isset($resp) and $resp) {
718
+ if ($resp->status == '304' ) {
719
+ // we have the most current copy
720
+ if ( MAGPIE_DEBUG > 1) {
721
+ debug("Got 304 for $url");
722
+ }
723
+ // reset cache on 304 (at minutillo insistent prodding)
724
+ $cache->set($cache_key, $rss);
725
+ return $rss;
726
+ }
727
+ elseif ( is_success( $resp->status ) ) {
728
+ $rss = _response_to_rss( $resp );
729
+ if ( $rss ) {
730
+ if (MAGPIE_DEBUG > 1) {
731
+ debug("Fetch successful");
732
+ }
733
+ // add object to cache
734
+ $cache->set( $cache_key, $rss );
735
+ return $rss;
736
+ }
737
+ }
738
+ else {
739
+ $errormsg = "Failed to fetch $url ";
740
+ if ( $resp->status == '-100' ) {
741
+ $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
742
+ }
743
+ elseif ( $resp->error ) {
744
+ # compensate for Snoopy's annoying habbit to tacking
745
+ # on '\n'
746
+ $http_error = substr($resp->error, 0, -2);
747
+ $errormsg .= "(HTTP Error: $http_error)";
748
+ }
749
+ else {
750
+ $errormsg .= "(HTTP Response: " . $resp->response_code .')';
751
+ }
752
+ }
753
+ }
754
+ else {
755
+ $errormsg = "Unable to retrieve RSS file for unknown reasons.";
756
+ }
757
+
758
+ // else fetch failed
759
+
760
+ // attempt to return cached object
761
+ if ($rss) {
762
+ if ( MAGPIE_DEBUG ) {
763
+ debug("Returning STALE object for $url");
764
+ }
765
+ return $rss;
766
+ }
767
+
768
+ // else we totally failed
769
+ error( $errormsg );
770
+
771
+ return false;
772
+
773
+ } // end if ( !MAGPIE_CACHE_ON ) {
774
+ } // end fetch_rss()
775
+
776
+ /*=======================================================================*\
777
+ Function: error
778
+ Purpose: set MAGPIE_ERROR, and trigger error
779
+ \*=======================================================================*/
780
+
781
+ function error ($errormsg, $lvl=E_USER_WARNING) {
782
+ global $MAGPIE_ERROR;
783
+
784
+ // append PHP's error message if track_errors enabled
785
+ if ( isset($php_errormsg) ) {
786
+ $errormsg .= " ($php_errormsg)";
787
+ }
788
+ if ( $errormsg ) {
789
+ $errormsg = "MagpieRSS: $errormsg";
790
+ $MAGPIE_ERROR = $errormsg;
791
+ trigger_error( $errormsg, $lvl);
792
+ }
793
+ }
794
+
795
+ function debug ($debugmsg, $lvl=E_USER_NOTICE) {
796
+ trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
797
+ }
798
+
799
+ /*=======================================================================*\
800
+ Function: magpie_error
801
+ Purpose: accessor for the magpie error variable
802
+ \*=======================================================================*/
803
+ function magpie_error ($errormsg="") {
804
+ global $MAGPIE_ERROR;
805
+
806
+ if ( isset($errormsg) and $errormsg ) {
807
+ $MAGPIE_ERROR = $errormsg;
808
+ }
809
+
810
+ return $MAGPIE_ERROR;
811
+ }
812
+ # --- cut here ---
813
+
814
+ # UPDATED FROM: rss_fetch.inc: _fetch_remote_file, _response_to_rss, init
815
+ # --- cut here ---
816
+ /*=======================================================================*\
817
+ Function: _fetch_remote_file
818
+ Purpose: retrieve an arbitrary remote file
819
+ Input: url of the remote file
820
+ headers to send along with the request (optional)
821
+ Output: an HTTP response object (see Snoopy.class.inc)
822
+ \*=======================================================================*/
823
+ function _fetch_remote_file ($url, $headers = "" ) {
824
+ // Snoopy is an HTTP client in PHP
825
+ $client = new Snoopy();
826
+ $client->agent = MAGPIE_USER_AGENT;
827
+ $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
828
+ $client->use_gzip = MAGPIE_USE_GZIP;
829
+ if (is_array($headers) ) {
830
+ $client->rawheaders = $headers;
831
+ }
832
+
833
+ @$client->fetch($url);
834
+ return $client;
835
+
836
+ }
837
+
838
+ /*=======================================================================*\
839
+ Function: _response_to_rss
840
+ Purpose: parse an HTTP response object into an RSS object
841
+ Input: an HTTP response object (see Snoopy)
842
+ Output: parsed RSS object (see rss_parse)
843
+ \*=======================================================================*/
844
+ function _response_to_rss ($resp) {
845
+ $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING );
846
+
847
+ // if RSS parsed successfully
848
+ if ( $rss and !$rss->ERROR) {
849
+
850
+ // find Etag, and Last-Modified
851
+ foreach($resp->headers as $h) {
852
+ // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
853
+ if (strpos($h, ": ")) {
854
+ list($field, $val) = explode(": ", $h, 2);
855
+ }
856
+ else {
857
+ $field = $h;
858
+ $val = "";
859
+ }
860
+
861
+ if ( $field == 'ETag' ) {
862
+ $rss->etag = $val;
863
+ }
864
+
865
+ if ( $field == 'Last-Modified' ) {
866
+ $rss->last_modified = $val;
867
+ }
868
+ }
869
+
870
+ return $rss;
871
+ } // else construct error message
872
+ else {
873
+ $errormsg = "Failed to parse RSS file.";
874
+
875
+ if ($rss) {
876
+ $errormsg .= " (" . $rss->ERROR . ")";
877
+ }
878
+ error($errormsg);
879
+
880
+ return false;
881
+ } // end if ($rss and !$rss->error)
882
+ }
883
+
884
+ /*=======================================================================*\
885
+ Function: init
886
+ Purpose: setup constants with default values
887
+ check for user overrides
888
+ \*=======================================================================*/
889
+ function init () {
890
+ if ( defined('MAGPIE_INITALIZED') ) {
891
+ return;
892
+ }
893
+ else {
894
+ define('MAGPIE_INITALIZED', true);
895
+ }
896
+
897
+ if ( !defined('MAGPIE_CACHE_ON') ) {
898
+ define('MAGPIE_CACHE_ON', true);
899
+ }
900
+
901
+ if ( !defined('MAGPIE_CACHE_DIR') ) {
902
+ define('MAGPIE_CACHE_DIR', './cache');
903
+ }
904
+
905
+ if ( !defined('MAGPIE_CACHE_AGE') ) {
906
+ define('MAGPIE_CACHE_AGE', 60*60); // one hour
907
+ }
908
+
909
+ if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
910
+ define('MAGPIE_CACHE_FRESH_ONLY', false);
911
+ }
912
+
913
+ if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
914
+ define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
915
+ }
916
+
917
+ if ( !defined('MAGPIE_INPUT_ENCODING') ) {
918
+ define('MAGPIE_INPUT_ENCODING', null);
919
+ }
920
+
921
+ if ( !defined('MAGPIE_DETECT_ENCODING') ) {
922
+ define('MAGPIE_DETECT_ENCODING', true);
923
+ }
924
+
925
+ if ( !defined('MAGPIE_DEBUG') ) {
926
+ define('MAGPIE_DEBUG', 0);
927
+ }
928
+
929
+ if ( !defined('MAGPIE_USER_AGENT') ) {
930
+ # WORDPRESS MODIFICATION: send WordPress as user-agent
931
+ # --- cut here ---
932
+ $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org';
933
+ # --- cut here ---
934
+
935
+ if ( MAGPIE_CACHE_ON ) {
936
+ $ua = $ua . ')';
937
+ }
938
+ else {
939
+ $ua = $ua . '; No cache)';
940
+ }
941
+
942
+ define('MAGPIE_USER_AGENT', $ua);
943
+ }
944
+
945
+ if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
946
+ define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
947
+ }
948
+
949
+ // use gzip encoding to fetch rss files if supported?
950
+ if ( !defined('MAGPIE_USE_GZIP') ) {
951
+ define('MAGPIE_USE_GZIP', true);
952
+ }
953
+ }
954
+ # --- cut here ---
955
+
956
+ function is_info ($sc) {
957
+ return $sc >= 100 && $sc < 200;
958
+ }
959
+
960
+ function is_success ($sc) {
961
+ return $sc >= 200 && $sc < 300;
962
+ }
963
+
964
+ function is_redirect ($sc) {
965
+ return $sc >= 300 && $sc < 400;
966
+ }
967
+
968
+ function is_error ($sc) {
969
+ return $sc >= 400 && $sc < 600;
970
+ }
971
+
972
+ function is_client_error ($sc) {
973
+ return $sc >= 400 && $sc < 500;
974
+ }
975
+
976
+ function is_server_error ($sc) {
977
+ return $sc >= 500 && $sc < 600;
978
+ }
979
+
980
+ # WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database)
981
+ # --- cut here ---
982
+ class RSSCache {
983
+ var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
984
+ var $MAX_AGE = 43200; // when are files stale, default twelve hours
985
+ var $ERROR = ''; // accumulate error messages
986
+
987
+ function RSSCache ($base='', $age='') {
988
+ if ( $base ) {
989
+ $this->BASE_CACHE = $base;
990
+ }
991
+ if ( $age ) {
992
+ $this->MAX_AGE = $age;
993
+ }
994
+
995
+ }
996
+
997
+ /*=======================================================================*\
998
+ Function: set
999
+ Purpose: add an item to the cache, keyed on url
1000
+ Input: url from wich the rss file was fetched
1001
+ Output: true on sucess
1002
+ \*=======================================================================*/
1003
+ function set ($url, $rss) {
1004
+ global $wpdb;
1005
+ $cache_option = 'rss_' . $this->file_name( $url );
1006
+ $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1007
+
1008
+ if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1009
+ add_option($cache_option, '', '', 'no');
1010
+ if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1011
+ add_option($cache_timestamp, '', '', 'no');
1012
+
1013
+ update_option($cache_option, $rss);
1014
+ update_option($cache_timestamp, time() );
1015
+
1016
+ return $cache_option;
1017
+ }
1018
+
1019
+ /*=======================================================================*\
1020
+ Function: get
1021
+ Purpose: fetch an item from the cache
1022
+ Input: url from wich the rss file was fetched
1023
+ Output: cached object on HIT, false on MISS
1024
+ \*=======================================================================*/
1025
+ function get ($url) {
1026
+ $this->ERROR = "";
1027
+ $cache_option = 'rss_' . $this->file_name( $url );
1028
+
1029
+ if ( ! get_option( $cache_option ) ) {
1030
+ $this->debug(
1031
+ "Cache doesn't contain: $url (cache option: $cache_option)"
1032
+ );
1033
+ return 0;
1034
+ }
1035
+
1036
+ $rss = get_option( $cache_option );
1037
+
1038
+ return $rss;
1039
+ }
1040
+
1041
+ /*=======================================================================*\
1042
+ Function: check_cache
1043
+ Purpose: check a url for membership in the cache
1044
+ and whether the object is older then MAX_AGE (ie. STALE)
1045
+ Input: url from wich the rss file was fetched
1046
+ Output: cached object on HIT, false on MISS
1047
+ \*=======================================================================*/
1048
+ function check_cache ( $url ) {
1049
+ $this->ERROR = "";
1050
+ $cache_option = $this->file_name( $url );
1051
+ $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1052
+
1053
+ if ( $mtime = get_option($cache_timestamp) ) {
1054
+ // find how long ago the file was added to the cache
1055
+ // and whether that is longer then MAX_AGE
1056
+ $age = time() - $mtime;
1057
+ if ( $this->MAX_AGE > $age ) {
1058
+ // object exists and is current
1059
+ return 'HIT';
1060
+ }
1061
+ else {
1062
+ // object exists but is old
1063
+ return 'STALE';
1064
+ }
1065
+ }
1066
+ else {
1067
+ // object does not exist
1068
+ return 'MISS';
1069
+ }
1070
+ }
1071
+
1072
+ /*=======================================================================*\
1073
+ Function: serialize
1074
+ \*=======================================================================*/
1075
+ function serialize ( $rss ) {
1076
+ return serialize( $rss );
1077
+ }
1078
+
1079
+ /*=======================================================================*\
1080
+ Function: unserialize
1081
+ \*=======================================================================*/
1082
+ function unserialize ( $data ) {
1083
+ return unserialize( $data );
1084
+ }
1085
+
1086
+ /*=======================================================================*\
1087
+ Function: file_name
1088
+ Purpose: map url to location in cache
1089
+ Input: url from wich the rss file was fetched
1090
+ Output: a file name
1091
+ \*=======================================================================*/
1092
+ function file_name ($url) {
1093
+ return md5( $url );
1094
+ }
1095
+
1096
+ /*=======================================================================*\
1097
+ Function: error
1098
+ Purpose: register error
1099
+ \*=======================================================================*/
1100
+ function error ($errormsg, $lvl=E_USER_WARNING) {
1101
+ // append PHP's error message if track_errors enabled
1102
+ if ( isset($php_errormsg) ) {
1103
+ $errormsg .= " ($php_errormsg)";
1104
+ }
1105
+ $this->ERROR = $errormsg;
1106
+ if ( MAGPIE_DEBUG ) {
1107
+ trigger_error( $errormsg, $lvl);
1108
+ }
1109
+ else {
1110
+ error_log( $errormsg, 0);
1111
+ }
1112
+ }
1113
+ function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1114
+ if ( MAGPIE_DEBUG ) {
1115
+ $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1116
+ }
1117
+ }
1118
+ }
1119
+ # --- cut here ---
1120
+
1121
+ function parse_w3cdtf ( $date_str ) {
1122
+
1123
+ # regex to match wc3dtf
1124
+ $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
1125
+
1126
+ if ( preg_match( $pat, $date_str, $match ) ) {
1127
+ list( $year, $month, $day, $hours, $minutes, $seconds) =
1128
+ array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
1129
+
1130
+ # calc epoch for current date assuming GMT
1131
+ $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1132
+
1133
+ $offset = 0;
1134
+ if ( $match[10] == 'Z' ) {
1135
+ # zulu time, aka GMT
1136
+ }
1137
+ else {
1138
+ list( $tz_mod, $tz_hour, $tz_min ) =
1139
+ array( $match[8], $match[9], $match[10]);
1140
+
1141
+ # zero out the variables
1142
+ if ( ! $tz_hour ) { $tz_hour = 0; }
1143
+ if ( ! $tz_min ) { $tz_min = 0; }
1144
+
1145
+ $offset_secs = (($tz_hour*60)+$tz_min)*60;
1146
+
1147
+ # is timezone ahead of GMT? then subtract offset
1148
+ #
1149
+ if ( $tz_mod == '+' ) {
1150
+ $offset_secs = $offset_secs * -1;
1151
+ }
1152
+
1153
+ $offset = $offset_secs;
1154
+ }
1155
+ $epoch = $epoch + $offset;
1156
+ return $epoch;
1157
+ }
1158
+ else {
1159
+ return -1;
1160
+ }
1161
+ }
1162
+
1163
+ # WORDPRESS-SPECIFIC: wp_rss (), get_rss ()
1164
+ # --- cut here ---
1165
+ function wp_rss ($url, $num) {
1166
+ //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1167
+ $num_items = $num;
1168
+ $rss = fetch_rss($url);
1169
+ if ( $rss ) {
1170
+ echo "<ul>";
1171
+ $rss->items = array_slice($rss->items, 0, $num_items);
1172
+ foreach ($rss->items as $item ) {
1173
+ echo "<li>\n";
1174
+ echo "<a href='$item[link]' title='$item[description]'>";
1175
+ echo htmlentities($item['title']);
1176
+ echo "</a><br />\n";
1177
+ echo "</li>\n";
1178
+ }
1179
+ echo "</ul>";
1180
+ }
1181
+ else {
1182
+ echo "an error has occured the feed is probably down, try again later.";
1183
+ }
1184
+ }
1185
+
1186
+ function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
1187
+ $rss = fetch_rss($url);
1188
+ if ( $rss ) {
1189
+ $rss->items = array_slice($rss->items, 0, $num_items);
1190
+ foreach ($rss->items as $item ) {
1191
+ echo "<li>\n";
1192
+ echo "<a href='$item[link]' title='$item[description]'>";
1193
+ echo htmlentities($item['title']);
1194
+ echo "</a><br />\n";
1195
+ echo "</li>\n";
1196
+ }
1197
+ return $posts;
1198
+ } else {
1199
+ return false;
1200
+ }
1201
+ }
1202
+ # --- cut here ---
1203
+ ?>
README.text ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FeedWordPress
2
+ =============
3
+
4
+ * Author: [Charles Johnson](http://www.radgeek.com/contact)
5
+ * Version: 0.8
6
+ * Project URI: <http://projects.radgeek.com/feedwordpress>
7
+ * License: GPL. See License below for copyright jots and tittles.
8
+
9
+ Introduction
10
+ ------------
11
+ FeedWordPress is an Atom/RSS aggregator for WordPress. It syndicates content
12
+ from newsfeeds that you select; if you syndicate several newsfeeds then you can
13
+ WordPress's posts database and templating engine as the back-end of an
14
+ aggregation ("planet") website.
15
+
16
+ FeedWordPress is similar in conception to software such as [Planet][]--in fact,
17
+ I started developing it because I needed a more flexible replacement for
18
+ Planet at to run [Feminist Blogs](http://www.feministblogs.org/). Since it
19
+ works on top of WordPress's database and templating system, however, it boasts
20
+ far more flexibility than many other aggregators. It is also designed with ease
21
+ of configuration and use in mind.
22
+
23
+ You'll need a working installation of [WordPress 1.5][] and FTP or SFTP access
24
+ to your web host. The ability to create cron jobs on your web host would be very
25
+ helpful but it's not absolutely necessary. You *don't* need to tweak any
26
+ plain-text configuration files and you *don't* need shell access to your web
27
+ host to make it work. (Although, I should point out, web hosts that *don't*
28
+ offer shell access are *bad web hosts*.)
29
+
30
+ [WordPress 1.5]: http://wordpress.org/development/2005/02/strayhorn/
31
+ [Planet]: http://www.planetplanet.org/ "Planet Planet"
32
+
33
+ Installation & Requirements
34
+ ---------------------------
35
+ You'll need a website with WordPress 1.5 installed and configured and FTP or
36
+ SFTP access to your web space. You'll probably also want to have either (1)
37
+ the ability to create cron jobs on your web host, or (2) a computer of your
38
+ own that has always-on Internet access.
39
+
40
+ 1. Install `feedwordpress.php` in your WordPress `plugins` directory and
41
+ `update.php` in your WordPress `wp-content` directory.
42
+
43
+ 2. (Optional) Upgrade the copy of MagpieRSS packaged with WordPress by
44
+ installing the new `rss-functions.php` (archived in
45
+ `OPTIONAL/wp-includes`) into your WordPress `wp-includes` directory.
46
+
47
+ 3. Log in to the WordPress Dashboard and activate the FeedWordPress plugin.
48
+ Go to Options --> Syndication to set up initial settings for the
49
+ syndication link category ("Contributors") by default and the RPC secret
50
+ word (blank by default, but you should probably set it to something.)
51
+
52
+ 4. Set up links for syndication from the WordPress Dashboard using
53
+ Links --> Syndicated or Links --> Import.
54
+
55
+ 5. FeedWordPress is now *ready* to feed syndicated content into WordPress.
56
+ In order for it to actually receive that content, either (1) have your
57
+ contributors add WordPress's XML-RPC URI to their blog's list of URIs
58
+ to ping when they update posts, (2) set up a cron job to check in on all
59
+ the feeds on a regular basis, or (3) both. If you do (2), you can either
60
+ set up a job to run `php update-feeds.php` on your web host, or set one
61
+ up on any computer with always-on Internet access to request
62
+ `update-feeds.php` over the web.
63
+
64
+ If your copy of WordPress is installed at <http://www.zyx.com/blog>, and
65
+ you set the secret word for XML-RPC pings to "foo", then your XML-RPC
66
+ URI will be <http://www.zyx.com/blog/xmlrpc.php>, and the URI to request
67
+ for `update-feeds.php` to update all feeds will be
68
+ <http://www.zyx.com/blog/wp-content/update-feeds.php?shibboleth=foo>
69
+
70
+ For detailed installation instructions, point your web browser to
71
+ <http://projects.radgeek.com/feedwordpress/install>.
72
+
73
+ Feed Settings
74
+ -------------
75
+ ### Feed Settings ###
76
+
77
+ Once you have your links configured and regular feed updates scheduled, you can
78
+ mostly leave FeedWordPress to run on its own. If you need to add, remove, or
79
+ change information for any contributors, you can do so from the WordPress
80
+ Dashboard under Links --> Syndicated. If you want to distribute the labor of
81
+ adding, updating, and managing feeds, you can use the WordPress login and access
82
+ privileges system. Users with an access level of 5 or greater can add or modify
83
+ syndicated links, and change syndication options.
84
+
85
+ All the information for a syndicated feed is managed through the WordPress Links
86
+ database. Feeds in the category to be syndicated (by default, "Contributors")
87
+ use several fields of the standard WordPress Link record:
88
+
89
+ - The Link URI is used to store a URI to the front page (*not* the feed!)
90
+ of the syndicated website.
91
+
92
+ - The Link Name is used to store the title of the syndicated website.
93
+
94
+ - The Short Description is used to store the tagline of the syndicated
95
+ website.
96
+
97
+ - The RSS URI is used to store the URI for the feed to be syndicated.
98
+
99
+ - The Link Notes are used to store a collection of manually-encoded and
100
+ automatically-generated settings that apply to this feed. The format of
101
+ settings in Link Notes is:
102
+
103
+ key1: value1
104
+ key2: value2
105
+ key3: value3
106
+ feed/key1: value1
107
+ feed/key2: value2
108
+
109
+ And so on. Values that are prefixed by 'feed/' are automatically
110
+ generated from feed data every time the feed syndicated by this link is
111
+ checked for updates. Values without the prefix are set manually by the
112
+ user.
113
+
114
+ Most settings in the Link Notes have no effect on FeedWordPress, but can be
115
+ accessed from templates using the ``get_feed_meta()`` template function in a
116
+ post context. For example, many aggregator sites use a "face" image for each
117
+ feed to visually distinguish posts from different feeds. To implement a face
118
+ feature, you could add a line like this to each feed's Link Notes section:
119
+
120
+ face: http://www.zyx.com/mugs/ugly
121
+
122
+ The URI should be changed out for each feed to point to the appropriate image,
123
+ of course. Then, to use the setting from within a template:
124
+
125
+ // In a post context
126
+ <?php $img = get_feed_meta('face'); if strlen($img) > 0): ?>
127
+ <img src="<?=$img?>" alt="" />
128
+ <?php endif; ?>
129
+
130
+ ... which will display the image, if any, whose URI is set in the "face" setting
131
+ for the feed that post comes from. If there is no "face" setting for a
132
+ particular feed, ``get_feed_meta()`` will return an empty string and no image
133
+ will be displayed.
134
+
135
+ Not all feed settings are only for templates. Some affect how FeedWordPress
136
+ processes posts from that feed. Currently, the special settings are:
137
+
138
+ - `cats:` a colon-separated list of default categories for any post coming
139
+ from this feed. So, for example, a this line in its Notes section:
140
+
141
+ cats: computers:web
142
+
143
+ ... will make FeedWordPress place any posts syndicated from that feed in
144
+ the "computers" and "web" categories (*in addition to*, not *instead of*
145
+ any categories that are applied to the post in the feed)
146
+
147
+ - `hardcode name: (yes|no)`
148
+
149
+ A yes/no setting. By default, FeedWordPress updates the value of the
150
+ Link Name field automatically to reflect the title that is reported by a
151
+ syndicated feed. (So, for example, if one of your contributors changes
152
+ the title of her weblog, the change will be reflected on your
153
+ Contributors list after the next update.) To override that behavior for
154
+ a particular feed (e.g. to force WordPress to use an abbreviated form of
155
+ the site's title for reasons of space), add a line like this to the Link
156
+ Notes section:
157
+
158
+ hardcode name: yes
159
+
160
+ If `hardcode name` is absent, or set to a value other than `yes`,
161
+ FeedWordPress will take that as a 'no' and follow the default behavior.
162
+
163
+ - `post status:` sets the default post status for posts from this feed
164
+ This can be 'publish', 'draft', or 'private'. By default, it is set to
165
+ 'publish' (syndicated posts go online immediately).
166
+
167
+ - `comment status:` sets the default status for comments on posts
168
+ syndicated from this feed. By default, all comments on syndicated posts
169
+ are closed, but you can set it to 'open', 'closed', or 'registered_only'
170
+ for particular feeds.
171
+
172
+ - `ping status:` sets the default status for receiving TrackBack and
173
+ PingBack pings on posts syndicated from this feed. By default,
174
+ syndicated posts are closed to pings, but you can set this to 'open' or
175
+ 'closed' for particular feeds.
176
+
177
+ Template API
178
+ ------------
179
+ When activated, FeedWordPress makes the following functions available for use by
180
+ themes/templates:
181
+
182
+ * ``is_syndicated()``: in a post context, returns ``TRUE`` if the post was
183
+ syndicated from another website, or ``FALSE`` if it was originally
184
+ posted here
185
+
186
+ * ``get_syndication_permalink()``: in a post context, returns the URI of
187
+ the permalink for this post *on the website it was syndicated from*
188
+
189
+ * ``the_syndication_permalink()``: in a post context, outputs the value
190
+ returned by ``get_syndication_permalink()``
191
+
192
+ * ``get_syndication_source_link()``: in a post context, returns the URI of
193
+ the front page (*not* the feed) of the website this post was syndicated
194
+ from
195
+
196
+ * ``the_syndication_source_link()``: in a post context, outputs the URI
197
+ returned by ``get_syndication_source_link()``
198
+
199
+ * ``get_syndication_source()``: in a post context, returns the
200
+ human-readable title of the website that a syndicated post was
201
+ syndicated from
202
+
203
+ * ``the_syndication_source()``: in a post context, outputs the value
204
+ returned by ``get_syndication_source()``
205
+
206
+ * ``get_syndication_feed():`` in a post context, returns the URI of the
207
+ feed (*not* the front page) that this post was syndicated from
208
+
209
+ * ``the_syndication_feed()``: in a post context, outputs the value
210
+ returned by ``get_syndication_feed()``
211
+
212
+ * ``get_feed_meta($key)``: in a post context, returns the value, if any,
213
+ of the feed setting ``$key`` for the feed that this post was syndicated
214
+ from
215
+
216
+ By default, FeedWordPress also places a filter on the standard functions
217
+ ``get_permalink()`` and ``the_permalink()`` that substitutes the URI returned by
218
+ ``get_syndication_permalink()`` for the URI generated by WordPress. This means
219
+ that by default the permalinks listed on your website and in your newsfeed will
220
+ link to the location of the posts on the source website, *not* to their location
221
+ on your website. You can switch this behavior on or off at Options -->
222
+ Syndication in the WordPress Dashboard.
223
+
224
+ License
225
+ -------
226
+ The FeedWordPress plugin is copyright (c) 2005 by Charles Johnson. It uses code
227
+ derived or translated from:
228
+
229
+ - [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](kellan@protest.net)
230
+ - [HTTP Navigator 2][] by [Keyvan Minoukadeh](keyvan@k1m.com)
231
+ - [Ultra-Liberal Feed Finder][] by [Mark Pilgrim](mark@diveintomark.org)
232
+
233
+ according to the terms of the [GNU General Public License][].
234
+
235
+ This program is free software; you can redistribute it and/or modify it under
236
+ the terms of the [GNU General Public License][] as published by the Free Software
237
+ Foundation; either version 2 of the License, or (at your option) any later
238
+ version.
239
+
240
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY
241
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
242
+ PARTICULAR PURPOSE. See the GNU General Public License for more details.
243
+
244
+ [wp-rss-aggregate.php]: http://laughingmeme.org/archives/002203.html
245
+ [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/
246
+ [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/
247
+ [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html
248
+
wp-content/plugins/feedwordpress.php ADDED
@@ -0,0 +1,1492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: FeedWordPress
4
+ Plugin URI: http://projects.radgeek.com/feedwordpress
5
+ Description: simple and flexible Atom/RSS syndication for WordPress
6
+ Version: 0.8
7
+ Author: Charles Johnson
8
+ Author URI: http://www.radgeek.com/
9
+ */
10
+
11
+ # Author: Charles Johnson <technophilia@radgeek.com>
12
+ # License: GPL
13
+ # Last modified: 2005-03-21
14
+ #
15
+ # This uses code derived from:
16
+ # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
17
+ # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
18
+ # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
19
+ # according to the terms of the GNU General Public License.
20
+ #
21
+ # INSTALLATION: see README.text or <http://projects.radgeek.com/install>
22
+ #
23
+ # USAGE: once FeedWordPress is installed, you manage just about everything from
24
+ # the WordPress Dashboard, under Options --> Syndication or Links --> Syndicated
25
+ # To ensure that fresh content is added as it becomes available, get your
26
+ # contributors to put your XML-RPC URI (if WordPress is installed at
27
+ # <http://www.zyx.com/blog>, XML-RPC requests should be sent to
28
+ # <http://www.zyx.com/blog/xmlrpc.php>), or see `update-feeds.php`
29
+
30
+ # -- Change these as you please
31
+ define ('FEEDWORDPRESS_LOG_UPDATES', true); // Make false if you hate status updates sent to error_log()
32
+
33
+ # -- Don't change these unless you know what you're doing...
34
+ define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/');
35
+ define ('FEEDWORDPRESS_VERSION', '0.8');
36
+ define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
37
+
38
+ // Note that the rss-functions.php that comes prepackaged with WordPress is
39
+ // old & busted. For the new hotness, drop a copy of rss-functions.php from
40
+ // this archive into wp-includes/rss-functions.php
41
+ require_once (ABSPATH . WPINC . '/rss-functions.php');
42
+
43
+ // Is this being loaded from within WordPress?
44
+ if (!isset($wp_version)):
45
+ echo "FeedWordPress/".FEEDWORDPRESS_VERSION.": an Atom/RSS aggregator plugin for WordPress 1.5\n";
46
+ exit;
47
+ endif;
48
+
49
+ # Remove default WordPress auto-paragraph filter.
50
+ remove_filter('the_content', 'wpautop');
51
+ remove_filter('the_excerpt', 'wpautop');
52
+ remove_filter('comment_text', 'wpautop');
53
+
54
+ # What really should happen here is that we create our own ueber-filter,
55
+ # to run with the highest possible priority, which would intercept
56
+ # and check whether or not the post comes from off the wire, and
57
+ # pre-empty any further formatting filters if it does. Then we could
58
+ # leave wpautop in peace and not worry about Markdown, Textile, etc.
59
+ # Sadly, WordPress 1.5 gives you no way to pre-empt downstream filters
60
+ # (and no way for the furthest downstream filter to recover the original
61
+ # content, either.)
62
+
63
+ # add_filter('the_content', 'feedwordpress_preempt', 10);
64
+ # add_filter('the_excerpt', 'feedwordpress_preempt', 10);
65
+ # add_filter('comment_text', 'feedwordpress_preempt', 30);
66
+
67
+ add_filter('post_link', 'syndication_permalink', 1);
68
+
69
+ # Admin menu
70
+ add_action('admin_menu', 'fwp_add_pages');
71
+
72
+ # Inbound XML-RPC update methods
73
+ add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
74
+
75
+ # Outbound XML-RPC reform
76
+ remove_action('publish_post', 'generic_ping');
77
+ add_action('publish_post', 'fwp_catch_ping');
78
+
79
+ # -- Template functions for syndication sites
80
+ function is_syndicated () { return (strlen(get_syndication_feed()) > 0); }
81
+
82
+ function the_syndication_source_link () { echo get_syndication_source_link(); }
83
+ function get_syndication_source_link () { list($n) = get_post_custom_values('syndication_source_uri'); return $n; }
84
+
85
+ function get_syndication_source () { list($n) = get_post_custom_values('syndication_source'); return $n; }
86
+ function the_syndication_source () { echo get_syndication_source(); }
87
+
88
+ function get_syndication_feed () { list($u) = get_post_custom_values('syndication_feed'); return $u; }
89
+ function the_syndication_feed () { echo get_syndication_feed (); }
90
+
91
+ function get_feed_meta ($key) {
92
+ global $wpdb;
93
+ $feed = get_syndication_feed();
94
+
95
+ $ret = NULL;
96
+ if (strlen($feed) > 0):
97
+ $result = $wpdb->get_var("
98
+ SELECT link_notes FROM $wpdb->links
99
+ WHERE link_rss = '".$wpdb->escape($feed)."'"
100
+ );
101
+
102
+ $notes = explode("\n", $result);
103
+ foreach ($notes as $note):
104
+ list($k, $v) = explode(': ', $note, 2);
105
+ $meta[$k] = $v;
106
+ endforeach;
107
+ $ret = $meta[$key];
108
+ endif; /* if */
109
+ return $ret;
110
+ }
111
+
112
+ function get_syndication_permalink () {
113
+ list($u) = get_post_custom_values('syndication_permalink'); return $u;
114
+ }
115
+ function the_syndication_permalink () {
116
+ echo get_syndication_permalink();
117
+ }
118
+
119
+ # -- Filters for templates and feeds
120
+ function syndication_permalink ($permalink = '') {
121
+ if (get_settings('feedwordpress_munge_permalink') != 'no'):
122
+ $uri = get_syndication_permalink();
123
+ return ((strlen($uri) > 0) ? $uri : $permalink);
124
+ else:
125
+ return $permalink;
126
+ endif;
127
+ } // function syndication_permalink ()
128
+
129
+ # -- Admin menu add-ons
130
+ function fwp_add_pages () {
131
+ add_submenu_page('link-manager.php', 'Syndicated Sites', 'Syndicated', 5, __FILE__, 'fwp_syndication_manage_page');
132
+ add_options_page('Syndication', 'Syndication', 5, __FILE__, 'fwp_syndication_options_page');
133
+ } // function fwp_add_pages () */
134
+
135
+ function fwp_syndication_options_page () {
136
+ global $wpdb, $user_level;
137
+
138
+ $caption = 'Save Changes';
139
+ if (isset($_REQUEST['action']) and $_REQUEST['action']=$caption):
140
+ check_admin_referer();
141
+
142
+ if ($user_level < 5):
143
+ die (__("Cheatin' uh ?"));
144
+ else:
145
+ update_option('feedwordpress_rpc_secret', $_REQUEST['rpc_secret']);
146
+ update_option('feedwordpress_cat_id', $_REQUEST['syndication_category']);
147
+ update_option('feedwordpress_munge_permalink', $_REQUEST['munge_permalink']);
148
+ ?>
149
+ <div class="updated">
150
+ <p><?php _e('Options saved.')?></p>
151
+ </div>
152
+ <?php
153
+ endif;
154
+ endif;
155
+
156
+ $cat_id = FeedWordPress::link_category_id();
157
+ $rpc_secret = FeedWordPress::rpc_secret();
158
+ $munge_permalink = get_settings('feedwordpress_munge_permalink');
159
+ $results = $wpdb->get_results("SELECT cat_id, cat_name, auto_toggle FROM $wpdb->linkcategories ORDER BY cat_id");
160
+ ?>
161
+ <div class="wrap">
162
+ <h2>Syndication Options</h2>
163
+ <form action="" method="post">
164
+ <fieldset class="options">
165
+ <legend>Template Options</legend>
166
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
167
+ <tr>
168
+ <th width="33%" scope="row">Permalinks for syndicated posts point to:</th>
169
+ <td width="67%"><select name="munge_permalink" size="1">
170
+ <option value="yes"<?=($munge_permalink=='yes')?' selected="selected"':''?>>source website</option>
171
+ <option value="no"<?=($munge_permalink=='no')?' selected="selected"':''?>>this website</option>
172
+ </select></td>
173
+ </tr>
174
+ </table>
175
+ <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
176
+ </fieldset>
177
+
178
+ <fieldset class="options">
179
+ <legend>Syndication Options</legend>
180
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
181
+ <tr>
182
+ <th width="33%" scope="row">Syndicate links in category:</th>
183
+ <td width="67%"><?php
184
+ echo "\n<select name=\"syndication_category\" size=\"1\">";
185
+ foreach ($results as $row) {
186
+ echo "\n\t<option value=\"$row->cat_id\"";
187
+ if ($row->cat_id == $cat_id)
188
+ echo " selected='selected'";
189
+ echo ">$row->cat_id: ".wp_specialchars($row->cat_name);
190
+ if ('Y' == $row->auto_toggle)
191
+ echo ' (auto toggle)';
192
+ echo "</option>\n";
193
+ }
194
+ echo "\n</select>\n";
195
+ ?></td>
196
+ </tr>
197
+ </table>
198
+ <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
199
+ </fieldset>
200
+
201
+ <fieldset class="options">
202
+ <legend>Back-end Options</legend>
203
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
204
+ <tr>
205
+ <th width="33%" scope="row">XML-RPC update secret word:</th>
206
+ <td width="67%"><input id="rpc_secret" name="rpc_secret" value="<?=$rpc_secret?>" />
207
+ </td>
208
+ </table>
209
+ <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
210
+ </fieldset>
211
+ </form>
212
+ </div>
213
+ <?php
214
+ }
215
+
216
+ function fwp_syndication_manage_page () {
217
+ global $user_level, $wpdb;
218
+ ?>
219
+ <?php $cont = true;
220
+ if (isset($_REQUEST['action'])):
221
+ //die("ACTION: '".$_REQUEST['action']."'");
222
+ if ($_REQUEST['action'] == 'feedfinder'): $cont = fwp_feedfinder_page();
223
+ elseif ($_REQUEST['action'] == 'switchfeed'): $cont = fwp_switchfeed_page();
224
+ elseif ($_REQUEST['action'] == 'Delete Checked'): $cont = fwp_multidelete_page();
225
+ endif;
226
+ endif;
227
+
228
+ if ($cont):
229
+ ?>
230
+ <?php
231
+ $links = get_linkobjects(FeedWordPress::link_category_id());
232
+ ?>
233
+ <div class="wrap">
234
+ <form action="link-manager.php?page=feedwordpress.php" method="post">
235
+ <h2>Syndicate a new site:</h2>
236
+ <div>
237
+ <label for="add-uri">Website or newsfeed:</label>
238
+ <input type="text" name="lookup" id="add-uri" value="URI" size="64" />
239
+ <input type="hidden" name="action" value="feedfinder" />
240
+ </div>
241
+ <div class="submit"><input type="submit" value="Syndicate &raquo;" /></div>
242
+ </form>
243
+ </div>
244
+
245
+ <form action="link-manager.php?page=feedwordpress.php" method="post">
246
+ <div class="wrap">
247
+ <h2>Syndicated Sites</h2>
248
+ <?php $alt_row = true;
249
+ if ($links): ?>
250
+
251
+ <table width="100%" cellpadding="3" cellspacing="3">
252
+ <tr>
253
+ <th width="20%"><?php _e('Name'); ?></th>
254
+ <th width="50%"><?php _e('Feed'); ?></th>
255
+ <th colspan="4"><?php _e('Action'); ?></th>
256
+ </tr>
257
+
258
+ <?php foreach ($links as $link):
259
+ $alt_row = !$alt_row; ?>
260
+ <tr<?=($alt_row?' class="alternate"':'')?>>
261
+ <td><a href="<?=wp_specialchars($link->link_url)?>"><?=wp_specialchars($link->link_name)?></a></td>
262
+ <?php if (strlen($link->link_rss) > 0): $caption='Switch Feed'; ?>
263
+ <td style="font-size:smaller;text-align:center">
264
+ <strong><a href="<?=$link->link_rss?>"><?=wp_specialchars($link->link_rss)?></a></strong>
265
+ <br/><em>check validity</em> <a style="vertical-align:middle"
266
+ title="Check feed &lt;<?=wp_specialchars($link->link_rss)?>&gt; for validity"
267
+ href="http://feedvalidator.org/check.cgi?url=<?=urlencode($link->link_rss)?>"><img
268
+ src="../wp-images/smilies/icon_arrow.gif" alt="&rarr;" /></a></td>
269
+ <?php else: $caption='Find Feed'; ?>
270
+ <td style="background-color:#FFFFD0"><p><strong>no
271
+ feed assigned</strong></p></td>
272
+ <? endif; ?>
273
+ <?php if (($link->user_level <= $user_level)): ?>
274
+ <td><a href="link-manager.php?page=feedwordpress.php&amp;link_id=<?=$link->link_id?>&amp;action=feedfinder" class="edit"><?=$caption?></a></div></td>
275
+ <td><a href="link-manager.php?link_id=<?=$link->link_id?>&amp;action=linkedit" class="edit"><?php _e('Edit')?></a></td>
276
+ <td><a href="link-manager.php?link_id=<?=$link->link_id?>&amp;action=Delete" onclick="return confirm('You are about to delete this link.\\n \'Cancel\' to stop, \'OK\' to delete.');" class="delete"><?php _e('Delete'); ?></a></td>
277
+ <td><input type="checkbox" name="linkcheck[]" value="<?=$link->link_id?>" /></td>
278
+ <?php else:
279
+ echo "<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>\n";
280
+ endif;
281
+ echo "\n\t</tr>";
282
+ ?>
283
+ </tr>
284
+ <?php
285
+ endforeach;
286
+ else: ?>
287
+
288
+ <p>There are no websites currently listed for syndication.</p>
289
+
290
+ <?php endif; ?>
291
+ </table>
292
+ </div>
293
+
294
+ <div class="wrap">
295
+ <h2>Manage Multiple Links</h2>
296
+ <div class="submit">
297
+ <input type="submit" class="delete" name="action" value="Delete Checked" />
298
+ </div>
299
+ </div>
300
+ </form>
301
+ <?php
302
+ endif;
303
+ }
304
+
305
+ function fwp_feedfinder_page () {
306
+ global $user_level, $wpdb;
307
+
308
+ $lookup = (isset($_REQUEST['lookup'])?$_REQUEST['lookup']:NULL);
309
+
310
+ if (isset($_REQUEST['link_id']) and ($_REQUEST['link_id']!=0)):
311
+ $link_id = $_REQUEST['link_id'];
312
+ $link = $wpdb->get_row("SELECT * FROM $wpdb->links WHERE link_id='".$wpdb->escape($link_id)."'");
313
+ if (is_object($link)):
314
+ if (is_null($lookup)) $lookup = $link->link_url;
315
+ $name = wp_specialchars($link->link_name);
316
+ else:
317
+ die (__("Cheatin' uh ?"));
318
+ endif;
319
+ else:
320
+ $name = "New Syndicated Feed";
321
+ $link_id = 0;
322
+ endif;
323
+ ?>
324
+ <div class="wrap">
325
+ <h2>Feed Finder: <?=$name?></h2>
326
+ <?php $f =& new FeedFinder($lookup);
327
+ $feeds = $f->find();
328
+ if (count($feeds) > 0):
329
+ foreach ($feeds as $key => $f):
330
+ $rss = fetch_rss($f);
331
+ if ($rss):
332
+ $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link'];
333
+ $feed_link = isset($rss->channel['link'])?$rss->channel['link']:'';
334
+ ?>
335
+ <form action="link-manager.php?page=feedwordpress.php" method="post">
336
+ <fieldset style="clear: both">
337
+ <legend><?=$rss->feed_type?> <?=$rss->feed_version?> feed</legend>
338
+
339
+ <?php if ($link_id===0): ?>
340
+ <input type="hidden" name="feed_title" value="<?=wp_specialchars($feed_title)?>" />
341
+ <input type="hidden" name="feed_link" value="<?=wp_specialchars($feed_link)?>" />
342
+ <?php endif; ?>
343
+
344
+ <input type="hidden" name="link_id" value="<?=$link_id?>" />
345
+ <input type="hidden" name="feed" value="<?=wp_specialchars($f)?>" />
346
+ <input type="hidden" name="action" value="switchfeed" />
347
+
348
+ <div>
349
+ <div style="float:right; background-color:#D0D0D0; color: black; width:45%; font-size:70%; border-left: 1px dotted #A0A0A0; padding-left: 0.5em; margin-left: 1.0em">
350
+ <?php if (count($rss->items) > 0): ?>
351
+ <?php $item = $rss->items[0]; ?>
352
+ <h3>Sample Item</h3>
353
+ <ul>
354
+ <li><strong>Title:</strong> <a href="<?=$item['link']?>"><?=$item['title']?></a></li>
355
+ <li><strong>Date:</strong> <?=isset($item['date_timestamp']) ? date('d-M-y g:i:s a', $item['date_timestamp']) : 'unknown'?></li>
356
+ </ul>
357
+ <div class="entry">
358
+ <?=(isset($item['content']['encoded'])?$item['content']['encoded']:$item['description'])?>
359
+ </div>
360
+ <?php else: ?>
361
+ <h3>No Items</h3>
362
+ <p>FeedWordPress found no posts on this feed.</p>
363
+ <?php endif; ?>
364
+ </div>
365
+
366
+ <div>
367
+ <h3>Feed Information</h3>
368
+ <ul>
369
+ <li><strong>Website:</strong> <a href="<?=$feed_link?>"><?=is_null($feed_title)?'<em>Unknown</em>':$feed_title?></a></li>
370
+ <li><strong>Feed URI:</strong> <a href="<?=wp_specialchars($f)?>"><?=wp_specialchars($f)?></a> <a title="Check feed &lt;<?=wp_specialchars($f)?>&gt; for validity" href="http://feedvalidator.org/check.cgi?url=<?=urlencode($f)?>"><img src="../wp-images/smilies/icon_arrow.gif" alt="(&rarr;)" /></a></li>
371
+ <li><strong>Encoding:</strong> <?=isset($rss->encoding)?wp_specialchars($rss->encoding):"<em>Unknown</em>"?></li>
372
+ <li><strong>Description:</strong> <?=isset($rss->channel['description'])?wp_specialchars($rss->channel['description']):"<em>Unknown</em>"?></li>
373
+ </ul>
374
+ <div class="submit"><input type="submit" name="Use" value="&laquo; Use this feed" /></div>
375
+ <div class="submit"><input type="submit" name="Cancel" value="&laquo; Cancel" /></div>
376
+ </div>
377
+ </div>
378
+ </fieldset>
379
+ </form>
380
+ <?php endif;
381
+ endforeach;
382
+ else:
383
+ echo "<p><strong>no feed found</strong></p>";
384
+ endif;
385
+ ?>
386
+ </div>
387
+
388
+ <form action="link-manager.php?page=feedwordpress.php" method="post">
389
+ <div class="wrap">
390
+ <h2>Use another feed</h2>
391
+ <div><label>Feed:</label>
392
+ <input type="text" name="lookup" value="URI" />
393
+ <input type="hidden" name="link_id" value="<?=$link_id?>" />
394
+ <input type="hidden" name="action" value="feedfinder" /></div>
395
+ <div class="submit"><input type="submit" value="Use this feed &raquo;" /></div>
396
+ </div>
397
+ </form>
398
+ <?php
399
+ return false; // Don't continue
400
+ }
401
+
402
+ function fwp_switchfeed_page () {
403
+ global $wpdb, $user_level;
404
+
405
+ check_admin_referer();
406
+ if (!isset($_REQUEST['Cancel'])):
407
+ if ($user_level < 5):
408
+ die (__("Cheatin' uh ?"));
409
+ elseif (isset($_REQUEST['link_id']) and ($_REQUEST['link_id']==0)):
410
+ // Get the category ID#
411
+ $cat_id = FeedWordPress::link_category_id();
412
+ $result = $wpdb->query("
413
+ INSERT INTO $wpdb->links
414
+ SET
415
+ link_name = '".$wpdb->escape($_REQUEST['feed_title'])."',
416
+ link_url = '".$wpdb->escape($_REQUEST['feed_link'])."',
417
+ link_category = '".$wpdb->escape($cat_id)."',
418
+ link_rss = '".$wpdb->escape($_REQUEST['feed'])."'
419
+ ");
420
+
421
+ if ($result): ?>
422
+ <div class="updated"><p><a href="<?=$_REQUEST['feed_link']?>"><?=wp_specialchars($_REQUEST['feed_title'])?></a>
423
+ has been added as a contributing site, using the newfeed at &lt;<a href="<?=$_REQUEST['feed']?>"><?=wp_specialchars($_REQUEST['feed'])?></a>&gt;.</p></div>
424
+ <?php else: ?>
425
+ <div class="updated"><p>There was a problem adding the newsfeed. [SQL: <?=wp_specialchars(mysql_error())?>]</p></div>
426
+ <?php endif;
427
+ elseif (isset($_REQUEST['link_id'])):
428
+ // Update link_rss
429
+ $result = $wpdb->query("
430
+ UPDATE $wpdb->links
431
+ SET
432
+ link_rss = '".$wpdb->escape($_REQUEST['feed'])."'
433
+ WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."'
434
+ ");
435
+
436
+ if ($result):
437
+ $result = $wpdb->get_row("
438
+ SELECT link_name, link_url FROM $wpdb->links
439
+ WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."'
440
+ ");
441
+ ?>
442
+ <div class="updated"><p>Feed for <a href="<?=$result->link_url?>"><?=wp_specialchars($result->link_name)?></a>
443
+ updated to &lt;<a href="<?=$_REQUEST['feed']?>"><?=wp_specialchars($_REQUEST['feed'])?></a>&gt;.</p></div>
444
+ <?php else: ?>
445
+ <div class="updated"><p>Nothing was changed.</p></div>
446
+ <?php endif;
447
+ endif;
448
+ endif;
449
+ return true; // Continue
450
+ }
451
+
452
+ function fwp_multidelete_page () {
453
+ global $wpdb, $user_level;
454
+ check_admin_referer();
455
+ if ($user_level < 5):
456
+ die (__("Cheatin' uh ?"));
457
+ else:
458
+ // Update link_rss
459
+ $result = $wpdb->query("
460
+ DELETE FROM $wpdb->links
461
+ WHERE link_id IN (".implode(',',$_REQUEST['linkcheck']).")
462
+ ");
463
+
464
+ if ($result):
465
+ $mesg = "Sites deleted from syndication list.";
466
+ else:
467
+ $mesg = "There was a problem deleting the sites from the syndication list. [SQL: ".mysql_error()."]";
468
+ endif;
469
+ echo "<div class=\"updated\">$mesg</div>\n";
470
+ endif;
471
+ return true;
472
+ }
473
+
474
+ # -- Outbound XML-RPC ping reform
475
+ # 'coz it's rude to send 500 pings the first time your aggregator runs
476
+ $fwp_held_ping = NULL; // NULL: not holding pings yet
477
+
478
+ function fwp_hold_pings () {
479
+ global $fwp_held_ping;
480
+ if (is_null($fwp_held_ping)):
481
+ $fwp_held_ping = 0; // 0: ready to hold pings; none yet received
482
+ endif;
483
+ }
484
+
485
+ function fwp_release_pings () {
486
+ global $fwp_held_ping;
487
+ if ($fwp_held_ping):
488
+ generic_ping($fwp_held_ping);
489
+ endif;
490
+ $fwp_held_ping = NULL; // NULL: not holding pings anymore
491
+ }
492
+
493
+ function fwp_catch_ping ($post_id = 0) {
494
+ global $fwp_held_ping;
495
+ if (!is_null($fwp_held_ping) and $post_id):
496
+ $fwp_held_ping = $post_id;
497
+ else:
498
+ generic_ping($fwp_held_ping);
499
+ endif;
500
+ }
501
+
502
+ // class FeedWordPress: handle the updating of the feeds and plug in to the
503
+ // XML-RPC interface
504
+ class FeedWordPress {
505
+ var $strip_attrs = array (
506
+ array('[a-z]+', 'style'),
507
+ array('[a-z]+', 'target'),
508
+ );
509
+ var $uri_attrs = array (
510
+ array('a', 'href'),
511
+ array('applet', 'codebase'),
512
+ array('area', 'href'),
513
+ array('blockquote', 'cite'),
514
+ array('body', 'background'),
515
+ array('del', 'cite'),
516
+ array('form', 'action'),
517
+ array('frame', 'longdesc'),
518
+ array('frame', 'src'),
519
+ array('iframe', 'longdesc'),
520
+ array('iframe', 'src'),
521
+ array('head', 'profile'),
522
+ array('img', 'longdesc'),
523
+ array('img', 'src'),
524
+ array('img', 'usemap'),
525
+ array('input', 'src'),
526
+ array('input', 'usemap'),
527
+ array('ins', 'cite'),
528
+ array('link', 'href'),
529
+ array('object', 'classid'),
530
+ array('object', 'codebase'),
531
+ array('object', 'data'),
532
+ array('object', 'usemap'),
533
+ array('q', 'cite'),
534
+ array('script', 'src')
535
+ );
536
+
537
+ var $_base = NULL;
538
+ var $feeds = NULL;
539
+
540
+ # function FeedWordPress (): Contructor; gain list of feeds
541
+ #
542
+ # To keep things compact and editable from within WordPress, we use a
543
+ # category of the WordPress "Links" for our list of feeds to syndicate
544
+ # By default, we use all the links in the "Contributors" category.
545
+ # Fields used are:
546
+ #
547
+ # * link_rss: the URI of the Atom/RSS feed to syndicate
548
+ #
549
+ # * link_notes: user-configurable options, with keys and values
550
+ # like so:
551
+ #
552
+ # key: value
553
+ # cats: computers:web
554
+ # feed/key: value
555
+ #
556
+ # Keys that start with "feed/" are gleaned from the data supplied
557
+ # by the feed itself, and will be overwritten with each update.
558
+ #
559
+ # The value of `cats` is used as a colon-separated (:) list of
560
+ # default categories for any post coming from a particular feed.
561
+ # (In the example above, any posts from this feed will be placed
562
+ # in the "computers" and "web" categories--*in addition to* any
563
+ # categories that may already be applied to the posts.)
564
+ #
565
+ # Values of keys in link_notes are accessible from templates using
566
+ # the function `get_feed_meta($key)` if this plugin is activated.
567
+
568
+ function FeedWordPress () {
569
+ $result = get_linkobjects(FeedWordPress::link_category_id());
570
+
571
+ $feeds = array ();
572
+ if ($result): foreach ($result as $link):
573
+ $sec = array ();
574
+
575
+ if (strlen($link->link_rss) > 0):
576
+ $notes = explode("\n", $link->link_notes);
577
+ foreach ($notes as $note):
578
+ list($key, $value) = explode(": ", $note, 2);
579
+ if (strlen($key) > 0) $sec[$key] = $value;
580
+ endforeach;
581
+
582
+ $sec['url'] = $link->link_rss;
583
+ $sec['name'] = $link->link_name;
584
+
585
+ if (isset($sec['cats'])):
586
+ $sec['cats'] = explode(':',$sec['cats']);
587
+ endif;
588
+ $sec['link_id'] = $link->link_id;
589
+
590
+ $feeds[] = $sec;
591
+ endif;
592
+ endforeach; endif;
593
+
594
+ $this->feeds = $feeds;
595
+ } // function acquire_feeds ()
596
+
597
+ function update ($uri) {
598
+ if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
599
+
600
+ global $wpdb;
601
+
602
+ // Secret voodoo tag: URI for updating *everything*.
603
+ $secret = RPC_MAGIC.FeedWordPress::rpc_secret();
604
+
605
+ fwp_hold_pings();
606
+
607
+ // Loop through and check for new posts
608
+ $delta = NULL;
609
+ foreach ($this->feeds as $feed) {
610
+ if (($uri === $secret)
611
+ or ($uri === $feed['url'])
612
+ or ($uri === $feed['feed/link'])) {
613
+ if (is_null($delta)) $delta = array('new' => 0, 'updated' => 0);
614
+ if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $feed[name] <$feed[url]>");
615
+ $added = $this->feed2wp($wpdb, $feed);
616
+ if (isset($added['new'])) $delta['new'] += $added['new'];
617
+ if (isset($added['updated'])) $delta['updated'] += $added['updated'];
618
+ } /* if */
619
+ } /* foreach */
620
+
621
+ fwp_release_pings();
622
+ if (FEEDWORDPRESS_LOG_UPDATES):
623
+ $mesg = array();
624
+ if (isset($delta['new'])) { $mesg[] = 'added '.$delta['new'].' new posts'; }
625
+ if (isset($delta['updated'])) { $mesg[] = 'updated '.$delta['updated'].' existing posts'; }
626
+ if (empty($mesg)) { $mesg[] = 'nothing changed'; }
627
+
628
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
629
+ .(is_null($delta) ? "I don't syndicate <$uri>"
630
+ : implode(' and ', $mesg)));
631
+ endif;
632
+ return $delta;
633
+ }
634
+
635
+ function feed2wp ($wpdb, $f) {
636
+ $feed = fetch_rss($f['url']);
637
+ $new_count = array('new' => 0, 'updated' => 0);
638
+
639
+ $this->update_feed($wpdb, $feed->channel, $f);
640
+
641
+ if (is_array($feed->items)) {
642
+ foreach ($feed->items as $item) {
643
+ $post = $this->item_to_post($wpdb, $item, $feed->channel, $f);
644
+ $new = $this->add_post($wpdb, $post);
645
+ if ( $new !== false ) { $new_count[$new]++; }
646
+ } // foreach
647
+ } // if
648
+ return $new_count;
649
+ } // function feed2wp ()
650
+
651
+ // FeedWordPress::flatten_array (): flatten an array. Useful for
652
+ // hierarchical and namespaced elements.
653
+ //
654
+ // Given an array which may contain array or object elements in it,
655
+ // return a "flattened" array: a one-dimensional array of scalars
656
+ // containing each of the scalar elements contained within the array
657
+ // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
658
+ // returned array for FeedWordPress::flatten_array($a) will contain a key
659
+ // $a['feed/b/c/d'] with value 'e'.
660
+ function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
661
+ $ret = array ();
662
+ if (is_array($arr)):
663
+ foreach ($arr as $key => $value) {
664
+ if (is_scalar($value)) {
665
+ $ret[$prefix.$key] = $value;
666
+ } else {
667
+ $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
668
+ } /* if */
669
+ } /* foreach */
670
+ endif;
671
+ return $ret;
672
+ } // function FeedWordPress::flatten_array ()
673
+
674
+ function resolve_relative_uri ($matches) {
675
+ return $matches[1].Relative_URI::resolve($matches[2], $this->_base).$matches[3];
676
+ } // function FeedWordPress::resolve_relative_uri ()
677
+
678
+ function update_feed ($wpdb, $channel, $f) {
679
+ $affirmo = array ('y', 'yes', 't', 'true', 1);
680
+
681
+ $link_id = $f['link_id'];
682
+
683
+ if (!isset($channel['id'])) {
684
+ $channel['id'] = $f['url'];
685
+ }
686
+
687
+ $update = array();
688
+ if (isset($channel['link'])) {
689
+ $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
690
+ }
691
+ if (isset($channel['title']) and (!isset($f['hardcode name'])
692
+ or in_array(trim(strtolower($f['hardcode name'])), $affirmo))) {
693
+ $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
694
+ }
695
+
696
+ if (isset($channel['tagline'])) {
697
+ $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
698
+ } elseif (isset($channel['description'])) {
699
+ $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
700
+ }
701
+
702
+ if (is_array($f['cats'])) {
703
+ $f['cats'] = implode(':',$f['cats']);
704
+ } /* if */
705
+
706
+ $f = array_merge($f, $this->flatten_array($channel));
707
+
708
+ # -- A few things we don't want to save in the notes
709
+ unset($f['link_id']); unset($f['uri']); unset($f['url']);
710
+
711
+ $notes = '';
712
+ foreach ($f as $key => $value) {
713
+ $notes .= "${key}: $value\n";
714
+ }
715
+ $update[] = "link_notes = '".$wpdb->escape($notes)."'";
716
+
717
+ $update_set = implode(',', $update);
718
+
719
+ // if we've already have this feed, update
720
+ $result = $wpdb->query("
721
+ UPDATE $wpdb->links
722
+ SET $update_set
723
+ WHERE link_id='$link_id'
724
+ ");
725
+ } // function FeedWordPress::update_feed ()
726
+
727
+ function item_to_post($wpdb, $item, $channel, $f) {
728
+ $post = array();
729
+ $post['post_title'] = $wpdb->escape($item['title']);
730
+
731
+ $author = array ();
732
+ if (isset($item['dc']['creator'])):
733
+ $author['name'] = $item['dc']['creator'];
734
+ elseif (isset($item['dc']['creator'])):
735
+ $author['name'] = $item['dc']['contributor'];
736
+ elseif (isset($item['author_name'])):
737
+ $author['name'] = $item['author_name'];
738
+ else:
739
+ $author['name'] = $channel['title'];
740
+ endif;
741
+
742
+ if (isset($item['author_email'])):
743
+ $author['email'] = $item['author_email'];
744
+ endif;
745
+
746
+ if (isset($item['author_url'])):
747
+ $author['url'] = $item['author_url'];
748
+ else:
749
+ $author['url'] = $channel['link'];
750
+ endif;
751
+
752
+ $post['post_author'] = $this->author_to_id($wpdb, $author['name'], $author['email'], $author['url']);
753
+
754
+ # Identify content and sanitize it.
755
+ # ---------------------------------
756
+ if (isset($item['content']['encoded']) and $item['content']['encoded']):
757
+ $content = $item['content']['encoded'];
758
+ else:
759
+ $content = $item['description'];
760
+ endif;
761
+
762
+ # Resolve relative URIs in post content
763
+ #
764
+ # N.B.: We *might* get screwed over by xml:base. But I don't see
765
+ # any way to get that information out of MagpieRSS if it's
766
+ # in the feed, and if it's in the content itself we'd have
767
+ # to do yet more XML parsing to do things right. For now
768
+ # this will have to do.
769
+
770
+ $this->_base = $item['link']; // Reset the base for resolving relative URIs
771
+ foreach ($this->uri_attrs as $pair):
772
+ list($tag,$attr) = $pair;
773
+ $content = preg_replace_callback (
774
+ ":(<$tag [^>]*$attr=\")([^\">]*)(\"[^>]*>):i",
775
+ array(&$this,'resolve_relative_uri'),
776
+ $content
777
+ );
778
+ endforeach;
779
+
780
+ # Sanitize problematic attributes
781
+ foreach ($this->strip_attrs as $pair):
782
+ list($tag,$attr) = $pair;
783
+ $content = preg_replace (
784
+ ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i",
785
+ "\\1\\4",
786
+ $content
787
+ );
788
+ endforeach;
789
+
790
+ $post['post_content'] = $wpdb->escape($content);
791
+
792
+ $post['post_name'] = sanitize_title($post['post_title']);
793
+
794
+ # RSS is a fucking mess. Figure out whether we have a date in
795
+ # dc:date, <issued>, <pubDate>, etc., and get it into Unix epoch
796
+ # format for reformatting. If you can't find anything, use the
797
+ # current time.
798
+ if (isset($item['dc']['date'])):
799
+ $post['epoch']['issued'] = parse_w3cdtf($item['dc']['date']);
800
+ elseif (isset($item['issued'])):
801
+ $post['epoch']['issued'] = parse_w3cdtf($item['issued']);
802
+ elseif (isset($item['pubdate'])):
803
+ $post['epoch']['issued'] = strtotime($item['pubdate']);
804
+ else:
805
+ $post['epoch']['issued'] = time();
806
+ endif;
807
+
808
+ # As far as I know, only atom currently has a reliable way to
809
+ # specify when something was *modified* last
810
+ if (isset($item['modified'])):
811
+ $post['epoch']['modified'] = parse_w3cdtf($item['modified']);
812
+ else:
813
+ $post['epoch']['modified'] = $post['epoch']['issued'];
814
+ endif;
815
+
816
+ $post['post_date'] = date('Y-m-d H:i:s', $post['epoch']['issued']);
817
+ $post['post_modified'] = date('Y-m-d H:i:s', $post['epoch']['modified']);
818
+ $post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['issued']);
819
+ $post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['modified']);
820
+
821
+ # Use feed-level preferences or a sensible default.
822
+ $post['post_status'] = (isset($f['post status']) ? $wpdb->escape(trim(strtolower($f['post status']))) : 'publish');
823
+ $post['comment_status'] = (isset($f['comment status']) ? $wpdb->escape(trim(strtolower($f['comment status']))) : 'closed');
824
+ $post['ping_status'] = (isset($f['ping status']) ? $wpdb->escape(trim(strtolower($f['ping status']))) : 'closed');
825
+
826
+ // Unique ID (hopefully a unique tag: URI); failing that, the permalink
827
+ if (isset($item['id'])):
828
+ $post['guid'] = $wpdb->escape($item['id']);
829
+ else:
830
+ $post['guid'] = $wpdb->escape($item['link']);
831
+ endif;
832
+
833
+ if (isset($channel['title'])) $post['syndication_source'] = $channel['title'];
834
+ if (isset($channel['link'])) $post['syndication_source_uri'] = $channel['link'];
835
+ $post['syndication_feed'] = $f['url'];
836
+
837
+ // In case you want to know the external permalink...
838
+ $post['syndication_permalink'] = $item['link'];
839
+
840
+ // Categories: start with default categories
841
+ $item_cats = $f['cats'];
842
+
843
+ // Now add categories from the post, if we have 'em
844
+ if (is_array($item['categories'])):
845
+ foreach ($item['categories'] as $cat):
846
+ if ( strpos($f['url'], 'del.icio.us') !== false ):
847
+ $item_cats = array_merge($item_cats, explode(' ', $cat));
848
+ else:
849
+ $item_cats[] = $cat;
850
+ endif;
851
+ endforeach;
852
+ endif;
853
+ $post['post_category'] = $this->lookup_categories($wpdb, $item_cats);
854
+
855
+ return $post;
856
+ } // function FeedWordPress::item_to_post ()
857
+
858
+ function add_post ($wpdb, $post) {
859
+ $guid = $post['guid'];
860
+ $result = $wpdb->get_row("
861
+ SELECT id, guid, UNIX_TIMESTAMP(post_modified) AS modified
862
+ FROM $wpdb->posts WHERE guid='$guid'
863
+ ");
864
+
865
+ if (!$result):
866
+ // The item has not yet been added.
867
+ # The right way to do this would be to use:
868
+ #
869
+ # $postId = wp_insert_post($post);
870
+ # $result = $wpdb->query("
871
+ # UPDATE $wpdb->posts
872
+ # SET
873
+ # guid='$guid'
874
+ # WHERE post_id='$postId'
875
+ # ");
876
+ #
877
+ # in place of everything in the cut below. Alas,
878
+ # wp_insert_post seems to be a memory hog; using it
879
+ # to insert several posts in one session makes php
880
+ # segfault after inserting 50-100 posts. This can get
881
+ # pretty annoying, especially if you are trying to
882
+ # update your feeds for the first time.
883
+ #
884
+ # --- cut here ---
885
+ $result = $wpdb->query("
886
+ INSERT INTO $wpdb->posts
887
+ SET
888
+ guid = '$guid',
889
+ post_author = '".$post['post_author']."',
890
+ post_date = '".$post['post_date']."',
891
+ post_date_gmt = '".$post['post_date_gmt']."',
892
+ post_content = '".$post['post_content']."',
893
+ post_title = '".$post['post_title']."',
894
+ post_name = '".$post['post_name']."',
895
+ post_modified = '".$post['post_modified']."',
896
+ post_modified_gmt = '".$post['post_modified_gmt']."',
897
+ comment_status = '".$post['comment_status']."',
898
+ ping_status = '".$post['ping_status']."',
899
+ post_status = '".$post['post_status']."'
900
+ ");
901
+ $postId = $wpdb->insert_id;
902
+ $this->add_to_category($wpdb, $postId, $post['post_category']);
903
+
904
+ // Since we are not going through official channels, we need to
905
+ // manually tell WordPress that we've published a new post.
906
+ // We need to make sure to do this in order for FeedWordPress
907
+ // to play well with the staticize-reloaded plugin (something
908
+ // that a large aggregator website is going to *want* to be
909
+ // able to use).
910
+ do_action('publish_post', $postId);
911
+ # --- cut here ---
912
+
913
+ if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted '".$post['post_title']."' (".$post['post_date'].") from '".$post['syndication_source']."'");
914
+ $this->add_rss_meta($wpdb, $postId, $post);
915
+ $ret = 'new';
916
+ elseif ($post['epoch']['modified'] > $result->modified):
917
+ $postId = $result->id; $modified = $result->modified;
918
+
919
+ $result = $wpdb->query("
920
+ UPDATE $wpdb->posts
921
+ SET
922
+ post_author = '".$post['post_author']."',
923
+ post_content = '".$post['post_content']."',
924
+ post_title = '".$post['post_title']."',
925
+ post_name = '".$post['post_name']."',
926
+ post_modified = '".$post['post_modified']."',
927
+ post_modified_gmt = '".$post['post_modified_gmt']."'
928
+ WHERE guid='$guid'
929
+ ");
930
+ $this->add_to_category($wpdb, $postId, $post['post_category']);
931
+
932
+ // Since we are not going through official channels, we need to
933
+ // manually tell WordPress that we've published a new post.
934
+ // We need to make sure to do this in order for FeedWordPress
935
+ // to play well with the staticize-reloaded plugin (something
936
+ // that a large aggregator website is going to *want* to be
937
+ // able to use).
938
+ do_action('edit_post', $postId);
939
+
940
+ if (FEEDWORDPRESS_LOG_UPDATES):
941
+ error_log("[".date('Y-m-d H:i:s')
942
+ ."][feedwordpress] updated '"
943
+ .$post['post_title']
944
+ ."' (".$post['post_date']
945
+ .") from '".$post['syndication_source']
946
+ ."' (".date('Y-m-d H:i:s', $modified)
947
+ ." ==> "
948
+ .date('Y-m-d H:i:s', $post['epoch']['modified'])
949
+ .')');
950
+ endif;
951
+ $this->add_rss_meta($wpdb, $postId, $post);
952
+ $ret = 'updated';
953
+ else:
954
+ $ret = false;
955
+ endif;
956
+
957
+ return $ret;
958
+ } // function FeedWordPress::add_post ()
959
+
960
+ # function FeedWordPress::add_to_category ()
961
+ #
962
+ # If there is a way to properly hook in to wp_insert_post, then this
963
+ # function will no longer be needed. In the meantime, here it is.
964
+ # --- cut here ---
965
+ function add_to_category($wpdb, $postId, $post_categories) {
966
+ // Default to category 1 ("Uncategorized"), if nothing else
967
+ if (!$post_categories) $post_categories[] = 1;
968
+
969
+ // Clean the slate (in case we're updating)
970
+ $results = $wpdb->query("
971
+ DELETE FROM $wpdb->post2cat
972
+ WHERE post_id = $postId
973
+ ");
974
+
975
+ foreach ($post_categories as $post_category):
976
+ $results = $wpdb->query("
977
+ INSERT INTO $wpdb->post2cat
978
+ SET
979
+ post_id = $postId,
980
+ category_id = $post_category
981
+ ");
982
+ endforeach;
983
+ } // function FeedWordPress::add_to_category ()
984
+ # --- cut here ---
985
+
986
+ // FeedWordPress::add_rss_meta: adds feed meta-data to user-defined keys
987
+ // for each entry. Interesting feed meta-data is tagged in the $post
988
+ // array using the prefix 'syndication_'. This should be used for
989
+ // anything that the WordPress user might want to access about a post's
990
+ // original source that isn't provided for by standard WP meta-data
991
+ // (i.e., beyond author, title, timestamp, and categories)
992
+ function add_rss_meta ($wpdb, $postId, $post) {
993
+ foreach ($post as $key => $value):
994
+ if (strpos($key, "syndication_") === 0):
995
+ $value = $wpdb->escape($value);
996
+
997
+ $result = $wpdb->query("
998
+ DELETE FROM $wpdb->postmeta
999
+ WHERE post_id='$postId' AND meta_key='$key'
1000
+ ");
1001
+
1002
+ $result = $wpdb->query("
1003
+ INSERT INTO $wpdb->postmeta
1004
+ SET
1005
+ post_id='$postId',
1006
+ meta_key='$key',
1007
+ meta_value='$value'
1008
+ ");
1009
+ endif;
1010
+ endforeach;
1011
+ } /* FeedWordPress::add_rss_meta () */
1012
+
1013
+ // FeedWordPress::author_to_id (): get the ID for an author name from
1014
+ // the feed. Create the author if necessary.
1015
+ function author_to_id ($wpdb, $author, $email, $url) {
1016
+ // Never can be too careful...
1017
+ $nice_author = sanitize_title($author);
1018
+ $author = $wpdb->escape($author);
1019
+ $email = $wpdb->escape($email);
1020
+ $url = $wpdb->escape($url);
1021
+
1022
+ $id = $wpdb->get_var(
1023
+ "SELECT ID from $wpdb->users
1024
+ WHERE
1025
+ user_login = '$author' OR
1026
+ user_firstname = '$author' OR
1027
+ user_nickname = '$author' OR
1028
+ user_description = '$author' OR
1029
+ user_nicename = '$nice_author'");
1030
+
1031
+ if (is_null($id)):
1032
+ $wpdb->query (
1033
+ "INSERT INTO $wpdb->users
1034
+ SET
1035
+ ID='0',
1036
+ user_login='$author',
1037
+ user_firstname='$author',
1038
+ user_nickname='$author',
1039
+ user_nicename='$nice_author',
1040
+ user_description='$author',
1041
+ user_email='$email',
1042
+ user_url='$url'");
1043
+ $id = $wpdb->insert_id;
1044
+ endif;
1045
+ return $id;
1046
+ } // function FeedWordPress::author_to_id ()
1047
+
1048
+ // look up (and create) category ids from a list of categories
1049
+ function lookup_categories ($wpdb, $cats) {
1050
+ if ( !count($cats) ) return array();
1051
+
1052
+ # i'd kill for a decent map function in PHP
1053
+ # but that would require functiosn to be first class object, or at least
1054
+ # coderef support
1055
+ $cat_strs = array();
1056
+ foreach ( $cats as $c ) {
1057
+ $c = $wpdb->escape($c); $c = "'$c'";
1058
+ $cat_strs[] = $c;
1059
+ }
1060
+
1061
+ $cat_sql = join(',', $cat_strs);
1062
+ $sql = "SELECT cat_ID,cat_name from $wpdb->categories WHERE cat_name IN ($cat_sql)";
1063
+ $results = $wpdb->get_results($sql);
1064
+
1065
+ $cat_ids = array();
1066
+ $cat_found = array();
1067
+
1068
+ if (!is_null($results)):
1069
+ foreach ( $results as $row ) {
1070
+ $cat_ids[] = $row->cat_ID;
1071
+ $cat_found[] = strtolower($row->cat_name); // Normalize to avoid case problems
1072
+ }
1073
+ endif;
1074
+
1075
+ foreach ($cats as $new_cat):
1076
+ $sql = "INSERT INTO $wpdb->categories (cat_name, category_nicename)
1077
+ VALUES ('%s', '%s')";
1078
+ if (!in_array(strtolower($new_cat), $cat_found)):
1079
+ $nice_cat = sanitize_title($new_cat);
1080
+ $wpdb->query(sprintf($sql, $wpdb->escape($new_cat), $nice_cat));
1081
+ $cat_ids[] = $wpdb->insert_id;
1082
+ endif;
1083
+ endforeach;
1084
+ return $cat_ids;
1085
+ } // function FeedWordPress::lookup_categories ()
1086
+
1087
+ function rpc_secret () {
1088
+ return get_settings('feedwordpress_rpc_secret');
1089
+ } // function FeedWordPress::rpc_secret ()
1090
+
1091
+ function link_category_id () {
1092
+ global $wpdb;
1093
+
1094
+ $cat_id = get_settings('feedwordpress_cat_id');
1095
+
1096
+ // If we don't yet *have* the category, we'll have to create it
1097
+ if ($cat_id === false) {
1098
+ $cat = $wpdb->escape(DEFAULT_SYNDICATION_CATEGORY);
1099
+
1100
+ // Look for something with the right name...
1101
+ $cat_id = $wpdb->get_var("
1102
+ SELECT cat_id FROM $wpdb->linkcategories
1103
+ WHERE cat_name='$cat'
1104
+ ");
1105
+
1106
+ // If you still can't find anything, make it for yourself.
1107
+ if (!$cat_id) {
1108
+ $result = $wpdb->query("
1109
+ INSERT INTO $wpdb->linkcategories
1110
+ SET
1111
+ cat_id = 0,
1112
+ cat_name='$cat',
1113
+ show_images='N',
1114
+ show_description='N',
1115
+ show_rating='N',
1116
+ show_updated='N',
1117
+ sort_order='name'
1118
+ ");
1119
+ $cat_id = $wpdb->insert_id;
1120
+ }
1121
+
1122
+ update_option('feedwordpress_cat_id', $cat_id);
1123
+ }
1124
+ return $cat_id;
1125
+ }
1126
+
1127
+ function link_category () {
1128
+ global $wpdb;
1129
+
1130
+ $cat_id = FeedWordPress::link_category_id();
1131
+
1132
+ // Get the ID# for the category name...
1133
+ $cat_name = $wpdb->get_var("
1134
+ SELECT cat_name FROM $wpdb->linkcategories
1135
+ WHERE cat_id='$cat_id'
1136
+ ");
1137
+ return $cat_name;
1138
+ }
1139
+ } // class FeedWordPress
1140
+
1141
+ # -- Inbound XML-RPC plugin interface
1142
+ function feedwordpress_xmlrpc_hook ($args = array ()) {
1143
+ $args['weblogUpdates.ping'] = 'feedwordpress_pong';
1144
+ return $args;
1145
+ }
1146
+
1147
+ function feedwordpress_pong ($args) {
1148
+ $feedwordpress =& new FeedWordPress;
1149
+ $delta = @$feedwordpress->update($args[1]);
1150
+ if (is_null($delta)):
1151
+ return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
1152
+ else:
1153
+ $mesg = array();
1154
+ if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
1155
+ if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
1156
+
1157
+ return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
1158
+ endif;
1159
+ }
1160
+
1161
+ class FeedFinder {
1162
+ var $uri = NULL;
1163
+ var $_cache_uri = NULL;
1164
+
1165
+ var $verify = FALSE;
1166
+
1167
+ var $_data = NULL;
1168
+ var $_head = NULL;
1169
+
1170
+ # -- Recognition patterns
1171
+ var $_feed_types = array(
1172
+ 'application/rss+xml',
1173
+ 'text/xml',
1174
+ 'application/atom+xml',
1175
+ 'application/x.atom+xml',
1176
+ 'application/x-atom+xml'
1177
+ );
1178
+ var $_feed_markers = array('\\<feed', '\\<rss', 'xmlns="http://purl.org/rss/1.0');
1179
+ var $_html_markers = array('\\<html');
1180
+ var $_obvious_feed_url = array('[./]rss', '[./]rdf', '[./]atom', '[./]feed', '\.xml');
1181
+ var $_maybe_feed_url = array ('rss', 'rdf', 'atom', 'feed', 'xml');
1182
+
1183
+ function FeedFinder ($uri = NULL, $verify = TRUE) {
1184
+ $this->uri = $uri; $this->verify = $verify;
1185
+ } /* FeedFinder::FeedFinder () */
1186
+
1187
+ function find ($uri = NULL) {
1188
+ $ret = array ();
1189
+ if (!is_null($this->data($uri))) {
1190
+ if ($this->is_feed($uri)) {
1191
+ $ret = array($this->uri);
1192
+ } else {
1193
+ // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?)
1194
+
1195
+ // Autodiscovery is the preferred method
1196
+ $href = $this->_link_rel_feeds();
1197
+
1198
+ // ... but we'll also take the little orange buttons
1199
+ $href = array_merge($href, $this->_a_href_feeds(TRUE));
1200
+
1201
+ // If all that failed, look harder
1202
+ if (count($href) == 0) $href = $this->_a_href_feeds(FALSE);
1203
+
1204
+ // Verify feeds and resolve relative URIs
1205
+ foreach ($href as $u) {
1206
+ $the_uri = Relative_URI::resolve($u, $this->uri);
1207
+ if ($this->verify) {
1208
+ $feed =& new FeedFinder($the_uri);
1209
+ if ($feed->is_feed()) $ret[] = $the_uri;
1210
+ $feed = NULL;
1211
+ } else {
1212
+ $ret[] = $the_uri;
1213
+ }
1214
+ } /* foreach */
1215
+ } /* if */
1216
+ } /* if */
1217
+ return array_unique($ret);
1218
+ } /* FeedFinder::find () */
1219
+
1220
+ function data ($uri = NULL) {
1221
+ $this->_get($uri);
1222
+ return $this->_data;
1223
+ }
1224
+
1225
+ function is_feed ($uri = NULL) {
1226
+ $data = $this->data($uri);
1227
+ return (
1228
+ preg_match (
1229
+ "\007(".implode('|',$this->_feed_markers).")\007i",
1230
+ $data
1231
+ ) and !preg_match (
1232
+ "\007(".implode('|',$this->_html_markers).")\007i",
1233
+ $data
1234
+ )
1235
+ );
1236
+ } /* FeedFinder::is_feed () */
1237
+
1238
+ # --- Private methods ---
1239
+ function _get ($uri = NULL) {
1240
+ if ($uri) $this->uri = $uri;
1241
+
1242
+ // Is the result not yet cached?
1243
+ if ($this->_cache_uri !== $this->uri) {
1244
+ // Retrieve, with headers, using cURL
1245
+ $ch = curl_init($this->uri);
1246
+ curl_setopt($ch, CURLOPT_HEADER, false);
1247
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
1248
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: close'));
1249
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('User-Agent: feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress'));
1250
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1251
+ curl_setopt($ch, CURLOPT_TIMEOUT, 15);
1252
+ $response = curl_exec($ch);
1253
+ curl_close($ch);
1254
+
1255
+ // Split into headers and content
1256
+ $this->_data = $response;
1257
+
1258
+ // Kilroy was here
1259
+ $this->_cache_uri = $this->uri;
1260
+ } /* if */
1261
+ } /* FeedFinder::_get () */
1262
+
1263
+ function _link_rel_feeds () {
1264
+ $links = $this->_tags('link');
1265
+ $link_count = count($links);
1266
+
1267
+ // now figure out which one points to the RSS file
1268
+ $href = array ();
1269
+ for ($n=0; $n<$link_count; $n++) {
1270
+ if (strtolower($links[$n]['rel']) == 'alternate') {
1271
+ if (in_array(strtolower($links[$n]['type']), $this->_feed_types)) {
1272
+ $href[] = $links[$n]['href'];
1273
+ } /* if */
1274
+ } /* if */
1275
+ } /* for */
1276
+ return $href;
1277
+ }
1278
+
1279
+ function _a_href_feeds ($obvious = TRUE) {
1280
+ $pattern = ($obvious ? $this->_obvious_feed_url : $this->_maybe_feed_url);
1281
+
1282
+ $links = $this->_tags('a');
1283
+ $link_count = count($links);
1284
+
1285
+ // now figure out which one points to the RSS file
1286
+ $href = array ();
1287
+ for ($n=0; $n<$link_count; $n++) {
1288
+ if (preg_match("\007(".implode('|',$pattern).")\007i", $links[$n]['href'])) {
1289
+ $href[] = $links[$n]['href'];
1290
+ } /* if */
1291
+ } /* for */
1292
+ return $href;
1293
+ }
1294
+
1295
+ function _tags ($tag) {
1296
+ $html = $this->data();
1297
+
1298
+ // search through the HTML, save all <link> tags
1299
+ // and store each link's attributes in an associative array
1300
+ preg_match_all('/<'.$tag.'\s+(.*?)\s*\/?>/si', $html, $matches);
1301
+
1302
+ $links = $matches[1];
1303
+ $ret = array();
1304
+ $link_count = count($links);
1305
+ for ($n=0; $n<$link_count; $n++) {
1306
+ $attributes = preg_split('/\s+/s', $links[$n]);
1307
+ foreach($attributes as $attribute) {
1308
+ $att = preg_split('/\s*=\s*/s', $attribute, 2);
1309
+ if (isset($att[1])) {
1310
+ $att[1] = preg_replace('/([\'"]?)(.*)\1/', '$2', $att[1]);
1311
+ $final_link[strtolower($att[0])] = $att[1];
1312
+ } /* if */
1313
+ } /* foreach */
1314
+ $ret[$n] = $final_link;
1315
+ } /* for */
1316
+ return $ret;
1317
+ }
1318
+ } /* class FeedFinder */
1319
+
1320
+ # Relative URI static class: PHP class for resolving relative URLs
1321
+ #
1322
+ # This class is derived (under the terms of the GPL) from URL Class 0.3 by
1323
+ # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
1324
+ # for FeedWordPress's purposes. The class has been stripped down to a single
1325
+ # public method: Relative_URI::resolve($url, $base), which resolves the URI in
1326
+ # $url relative to the URI in $base
1327
+
1328
+ class Relative_URI
1329
+ {
1330
+ // Resolve relative URI in $url against the base URI in $base. If $base
1331
+ // is not supplied, then we use the REQUEST_URI of this script.
1332
+ //
1333
+ // I'm hoping this method reflects RFC 2396 Section 5.2
1334
+ function resolve ($url, $base = NULL)
1335
+ {
1336
+ if (is_null($base)):
1337
+ $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
1338
+ endif;
1339
+
1340
+ $base = Relative_URI::_encode(trim($base));
1341
+ $uri_parts = Relative_URI::_parse_url($base);
1342
+
1343
+ $url = Relative_URI::_encode(trim($url));
1344
+ $parts = Relative_URI::_parse_url($url);
1345
+
1346
+ $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
1347
+ $uri_parts['query'] = $parts['query'];
1348
+
1349
+ // if path is empty, and scheme, host, and query are undefined,
1350
+ // the URL is referring the base URL
1351
+
1352
+ if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
1353
+ // If the URI is empty or only a fragment, return the base URI
1354
+ return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
1355
+ } elseif (isset($parts['scheme'])) {
1356
+ // If the scheme is set, then the URI is absolute.
1357
+ return $url;
1358
+ } elseif (isset($parts['host'])) {
1359
+ $uri_parts['host'] = $parts['host'];
1360
+ $uri_parts['path'] = $parts['path'];
1361
+ } else {
1362
+ // We have a relative path but not a host.
1363
+
1364
+ // start ugly fix:
1365
+ // prepend slash to path if base host is set, base path is not set, and url path is not absolute
1366
+ if ($uri_parts['host'] && ($uri_parts['path'] == '')
1367
+ && (strlen($parts['path']) > 0)
1368
+ && (substr($parts['path'], 0, 1) != '/')) {
1369
+ $parts['path'] = '/'.$parts['path'];
1370
+ } // end ugly fix
1371
+
1372
+ if (substr($parts['path'], 0, 1) == '/') {
1373
+ $uri_parts['path'] = $parts['path'];
1374
+ } else {
1375
+ // copy base path excluding any characters after the last (right-most) slash character
1376
+ $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
1377
+ // append relative path
1378
+ $buffer .= $parts['path'];
1379
+ // remove "./" where "." is a complete path segment.
1380
+ $buffer = str_replace('/./', '/', $buffer);
1381
+ if (substr($buffer, 0, 2) == './') {
1382
+ $buffer = substr($buffer, 2);
1383
+ }
1384
+ // if buffer ends with "." as a complete path segment, remove it
1385
+ if (substr($buffer, -2) == '/.') {
1386
+ $buffer = substr($buffer, 0, -1);
1387
+ }
1388
+ // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
1389
+ $search_finished = false;
1390
+ $segment = explode('/', $buffer);
1391
+ while (!$search_finished) {
1392
+ for ($x=0; $x+1 < count($segment);) {
1393
+ if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
1394
+ if ($x+2 == count($segment)) $segment[] = '';
1395
+ unset($segment[$x], $segment[$x+1]);
1396
+ $segment = array_values($segment);
1397
+ continue 2;
1398
+ } else {
1399
+ $x++;
1400
+ }
1401
+ }
1402
+ $search_finished = true;
1403
+ }
1404
+ $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
1405
+ $uri_parts['path'] = $buffer;
1406
+
1407
+ }
1408
+ }
1409
+
1410
+ // If we've gotten to this point, we can try to put the pieces
1411
+ // back together.
1412
+ $ret = '';
1413
+ if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
1414
+ if (isset($uri_parts['user'])) {
1415
+ $ret .= $uri_parts['user'];
1416
+ if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
1417
+ $ret .= '@';
1418
+ }
1419
+ if (isset($uri_parts['host'])) {
1420
+ $ret .= '//'.$uri_parts['host'];
1421
+ if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
1422
+ }
1423
+ $ret .= $uri_parts['path'];
1424
+ if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
1425
+ if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
1426
+
1427
+ return $ret;
1428
+ }
1429
+
1430
+ /**
1431
+ * Parse URL
1432
+ *
1433
+ * Regular expression grabbed from RFC 2396 Appendix B.
1434
+ * This is a replacement for PHPs builtin parse_url().
1435
+ * @param string $url
1436
+ * @access private
1437
+ * @return array
1438
+ */
1439
+ function _parse_url($url)
1440
+ {
1441
+ // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
1442
+ // generates a warning.
1443
+ if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
1444
+ $parts = array();
1445
+ if ($match[1] != '') $parts['scheme'] = $match[2];
1446
+ if ($match[3] != '') $parts['auth'] = $match[4];
1447
+ // parse auth
1448
+ if (isset($parts['auth'])) {
1449
+ // store user info
1450
+ if (($at_pos = strpos($parts['auth'], '@')) !== false) {
1451
+ $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
1452
+ $parts['user'] = $userinfo[0];
1453
+ if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
1454
+ $parts['auth'] = substr($parts['auth'], $at_pos+1);
1455
+ }
1456
+ // get port number
1457
+ if ($port_pos = strrpos($parts['auth'], ':')) {
1458
+ $parts['host'] = substr($parts['auth'], 0, $port_pos);
1459
+ $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
1460
+ if ($parts['port'] < 1) $parts['port'] = null;
1461
+ } else {
1462
+ $parts['host'] = $parts['auth'];
1463
+ }
1464
+ }
1465
+ unset($parts['auth']);
1466
+ $parts['path'] = $match[5];
1467
+ if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
1468
+ if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
1469
+ return $parts;
1470
+ }
1471
+ // shouldn't reach here
1472
+ return array('path'=>'');
1473
+ }
1474
+
1475
+ function _encode($string)
1476
+ {
1477
+ static $replace = array();
1478
+ if (!count($replace)) {
1479
+ $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
1480
+ $find = array_merge(range(0, 31), $find);
1481
+ $find = array_map('chr', $find);
1482
+ foreach ($find as $char) {
1483
+ $replace[$char] = '%'.bin2hex($char);
1484
+ }
1485
+ }
1486
+ // escape control characters and a few other characters
1487
+ $encoded = strtr($string, $replace);
1488
+ // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
1489
+ return preg_replace('/[^\x21-\x7e]/', '', $encoded);
1490
+ }
1491
+ }
1492
+ ?>
wp-content/update-feeds.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ # update-feeds.php: Instruct FeedWordPress to scan for fresh content
3
+ #
4
+ # Project: FeedWordPress
5
+ # URI: <http://projects.radgeek.com/feedwordpress>
6
+ # Author: Charles Johnson <technophilia@radgeek.com>
7
+ # License: GPL
8
+ # Version: 0.8
9
+ # Last modified: 2005-03-21
10
+ #
11
+ # USAGE
12
+ # -----
13
+ # update-feeds.php is a useful script for instructing the FeedWordPress plugin
14
+ # to scan for fresh content on the feeds that it syndicates. This is handy if
15
+ # you want your syndication site to actually syndicate content, and you can't
16
+ # rely on all your contributors to send you an XML-RPC ping when they update.
17
+ #
18
+ # 1. Install FeedWordPress and activate the FeedWordPress plugin. (See
19
+ # <http://projects.radgeek.com/feedwordpress/install> if you need a guide
20
+ # for the perplexed.)
21
+ #
22
+ # 2. Set up a cron job to run update-feeds.php locally:
23
+ #
24
+ # cd <your-wordpress>/wp-content ; php update-feeds.php
25
+ #
26
+ # or to send an HTTP GET request to the appropriate URI:
27
+ #
28
+ # curl http://xyz.com/wp-content/update-feeds.php?shibboleth=foo
29
+ #
30
+ # 4. If you want to update *one* of the feeds rather than *all* of them, then
31
+ # pass the URI and title as command-line arguments:
32
+ #
33
+ # $ php update-feeds.php http://www.radgeek.com "Geekery Today"
34
+ #
35
+ # or in the GET query:
36
+ #
37
+ # $ curl http://www.xyz.com/wp-content/update-feeds.php?uri=http://www.radgeek.com\&title=Geekery+Today\&shibboleth=yourshibboleth
38
+ #
39
+
40
+ require_once ('../wp-config.php');
41
+ require_once (ABSPATH . WPINC . '/class-IXR.php');
42
+
43
+ # -- CHANGE THESE TO REFLECT YOUR SITE SETTINGS!
44
+ define ('RPC_URI', NULL); // Change this setting to ping a URI of your own devising
45
+
46
+ # -- Don't change these unless you know what you're doing...
47
+ define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/'); // update all
48
+
49
+ if (is_null(RPC_URI)):
50
+ $rpc_uri = get_settings('siteurl');
51
+ if (substr($rpc_uri,-1)!='/') $rpc_uri .= '/';
52
+ $rpc_uri .= 'xmlrpc.php';
53
+ else:
54
+ $rpc_uri = RPC_URI;
55
+ endif;
56
+
57
+ # -- Are we running from an HTTP GET or from the command line?
58
+ if (isset($_SERVER['REQUEST_URI'])) {
59
+ $rpc_secret = (isset($_REQUEST['shibboleth'])?$_REQUEST['shibboleth']:'');
60
+ $uri = (isset($_REQUEST['uri']) ? $_REQUEST['uri'] : RPC_MAGIC.$rpc_secret);
61
+ $blog = (isset($_REQUEST['title']) ? $_REQUEST['title'] : 'Refresh');
62
+
63
+ echo <<< EOHTML
64
+ <html>
65
+ <head>
66
+ <title>update-feeds :: FeedWordPress</title>
67
+ </head>
68
+
69
+ <body>
70
+ <h1>update-feeds: instruct FeedWordPress to look for new syndicated content</h1>
71
+
72
+ <p>Sending ping to &lt;$rpc_uri&gt;...</p>
73
+
74
+ <p>
75
+ EOHTML;
76
+ } else {
77
+ // Query secret word from database
78
+ $rpc_secret = get_settings('feedwordpress_rpc_secret');
79
+
80
+ $uri = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : RPC_MAGIC.$rpc_secret);
81
+ $blog = (isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 'Refresh');
82
+ }
83
+
84
+ $client =& new IXR_Client($rpc_uri);
85
+ $ret = $client->query('weblogUpdates.ping', $blog, $uri);
86
+
87
+ if (!$ret):
88
+ if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
89
+ echo "The XML-RPC ping failed (local): ".wp_specialchars($client->getErrorMessage())."\n";
90
+ else:
91
+ $response = $client->getResponse();
92
+ if ($response['flerror']):
93
+ if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
94
+ echo "The XML-RPC ping failed (remote): ".wp_specialchars($response['message'])."\n";
95
+ elseif (isset($_SERVER['REQUEST_URI'])):
96
+ if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
97
+ echo "The XML-RPC ping succeeded: ".wp_specialchars($response['message'])."\n";
98
+ endif;
99
+ endif;
100
+
101
+ if (isset($_SERVER['REQUEST_URI'])) {
102
+ echo <<<EOHTML
103
+ </p>
104
+
105
+ <p><a href="../wp-admin">&larr; Return to WordPress Dashboard</a></p>
106
+ </body>
107
+ </html>
108
+ EOHTML;
109
+ }
110
+ ?>