FeedWordPress - Version 0.97

Version Description

Download this release

Release Info

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

Code changes from version 0.96 to 0.97

ChangeLog.text CHANGED
@@ -1,5 +1,173 @@
1
- Change Log
2
- ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  Changes from 0.95 to 0.96
5
  -------------------------
1
+ FeedWordPress Change Log
2
+ ========================
3
+
4
+ Changes from 0.96 to 0.97
5
+ -------------------------
6
+
7
+ * INSTALLATION PROCEDURE: Some of the changes between 0.96 and 0.97
8
+ require upgrades to the meta-data stored by FeedWordPress to work
9
+ properly. Thus, if you are upgrading from 0.96 or earlier to 0.97, most
10
+ FeedWordPress operations (including updates and template functions)
11
+ WILL BE DISABLED until you run the upgrade procedure. Fortunately,
12
+ running the upgrade procedure is easy: just go to either Options -->
13
+ Syndication or Links --> Syndicated in the WordPress Dashboard and press
14
+ the button.
15
+
16
+ * FEED FORMAT SUPPORT: Support has been added for the Atom 1.0 IETF
17
+ standard. Several other elements are also newly supported
18
+ (dcterms:created, dcterms:issued, dcterms:modified, dc:identifier,
19
+ proper support for the RSS 2.0 guid element, the RSS 2.0 author element,
20
+ the use of Atom author or Dublin Core dc:creator constructs at the feed
21
+ level to identify the author of individual items, etc.)
22
+
23
+ N.B.: full support of several Atom 1.0 features, such as categories
24
+ and enclosures, requires you to install the optional rss-functions.php
25
+ upgrade in your wp-includes directory.
26
+
27
+ * BUG FIX: Running `update-feeds.php` from command line or crontab
28
+ returned "I don't syndicate..." errors. It turns out that WordPress
29
+ sometimes tramples on the internal PHP superglobals that I depended on
30
+ to determine whether or not the script was being invoked from the
31
+ command line. This has been fixed (the variables are now checked
32
+ *before* WordPress can trample them). Note that `update-feeds.php` has
33
+ been thoroughly overhauled anyway; see below for details.
34
+
35
+ * BUG FIX: Duplicate categories or author names. Fixed two bugs that could
36
+ create duplicate author and/or category names when the name contained
37
+ either (a) certain international characters (causing a mismatch between
38
+ MySQL and PHP's handling of lowercasing text), or (b) characters that
39
+ have a special meaning in regular expressions (causing MySQL errors when
40
+ looking for the author or category due to regexp syntax errors). These
41
+ should now be fixed thanks to careful escaping of names that go into
42
+ regular expressions and careful matching of lowercasing functions
43
+ (comparing results from PHP only to other results from PHP, and results
44
+ from MySQL only to other results from MySQL).
45
+
46
+ * BUG FIX: Items dated Decembr 31, 1969 should appear less often. The
47
+ function for parsing W3C date-time format dates that ships with
48
+ MagpieRSS can only correctly parse fully-specified dates with a
49
+ fully-specified time, but valid W3C date-time format dates may omit the
50
+ time, the day of the month, or even the month. Some feeds in the wild
51
+ date their items with coarse-grained dates, so the optional
52
+ `rss-functions.php` upgrade now includes a more flexible parse_w3cdtf()
53
+ function that will work with both coarse-grained and fully-specified
54
+ dates. (If parts of the date or the time are omitted, they are filled in
55
+ with values based on the current time, so '2005-09-10' will be dated to
56
+ the current time on that day; '2004' will be dated to this day and time
57
+ one year ago.
58
+
59
+ N.B.: This fix is only available in the optional `rss-functions.php`
60
+ upgrade.
61
+
62
+ * BUG FIX: Evil use of HTTP GET has been undone. The WordPress interface
63
+ is riddled with inappropriate (non-idempotent) uses of HTTP GET queries
64
+ (ordinary links that make the server do something with significant
65
+ side-effects, such as deleting a post or a link from the database).
66
+ FeedWordPress did some of this too, especially in places where it aped
67
+ the WordPress interface (e.g. the "Delete" links in Links -->
68
+ Syndicated). That's bad business, though. I've changed the interface so
69
+ that all the examples of improper side-effects that I can find now
70
+ require an HTTP POST to take effect. I think I got pretty much
71
+ everything; if there's anything that I missed, let me know.
72
+
73
+ Further reading: [Sam Ruby 2005-05-06: This Stuff Matters] (http://www.intertwingly.net/blog/2005/05/06/This-Stuff-Matters)
74
+
75
+ * BUG FIX: Categories applied by `cats` setting should no longer prevent
76
+ category-based filtering from working. In FeedWordPress, you can (1)
77
+ apply certain categories to all syndicated posts, or all posts from
78
+ a particular feed; and (2) filter out all posts that don't match one
79
+ of the categories that are already in the WordPress database (allowing
80
+ for simple category-based filtering; just load up WordPress with the
81
+ categories you want to accept, and then tell FeedWordPress not to create
82
+ new ones). However, the way that (1) and (2) were implemented meant that
83
+ you couldn't effectively use them together; once you applied a known
84
+ category to all syndicated posts from a particular feed, it meant that
85
+ they'd have at least one familiar category (the category or categories
86
+ you were applying), and that would get all posts past the filter no
87
+ matter what categories they were originally from.
88
+
89
+ Well, no longer. You can still apply categories to all syndicated posts
90
+ (using either Syndication --> Options, or the feed-level settings under
91
+ Links --> Syndicated). But these categories are not applied to the post
92
+ until *after* it has already passed by the "familiar categories" filter.
93
+ So now, if you want, you can do category filtering and *then* apply as
94
+ many categories as you please to all and only posts that pass the filter.
95
+
96
+ * BUG FIX: Other minor typos and HTML gaffes were fixed along the way.
97
+
98
+ * PERFORMANCE: get_feed_meta() no longer hits the database for information
99
+ on every call; it now caches link data in memory, so FeedWordPress only
100
+ goes to the database once for each syndicated link. This may
101
+ substantially improve performance if your database server resources
102
+ are tight and your templates make a lot of use of custom settings from
103
+ get_feed_meta().
104
+
105
+ * API CHANGE: Link ID numbers, rather than RSS URIs, are now used to
106
+ identify the feed from which a post is syndicated when you use template
107
+ functions such as get_feed_meta(). The practical upshot of this is you
108
+ can switch feeds, or change the feed address for a particular syndicated
109
+ site, without breaking your templates for all the posts that were
110
+ syndicated from the earlier URI.
111
+
112
+ * API CHANGE: if you have plugins or templates that make use of the
113
+ get_feed_meta() function or the $fwp_feedmeta global, note that the
114
+ data formerly located under the `uri` and `name` fields is now located
115
+ under the `link/uri` field and the `link/name` field, respectively. Note
116
+ also that you can access the link ID number for any given feed under the
117
+ global $fwp_feedmeta['link/id'] (in plugins) or
118
+ get_feed_meta('link/id') (in a template in post contexts).
119
+
120
+ * FEATURE: the settings for individual feeds can now be edited using a
121
+ humane interface (where formerly you had to tweak key-value pairs in the
122
+ Link Notes section). To edit settings for a feed, pick the feed that you
123
+ want under Links --> Syndicated and click the Edit link.
124
+
125
+ * FEATURE: The "Unsubscribe" button (formerly "Delete") in Links -->
126
+ Syndicated now offers three options for unsubscribing from a feed: (1)
127
+ turning off the subscription without deleting the feed data or affecting
128
+ posts that were syndicated from the feed (this works by setting the Link
129
+ for the feed as "invisible"); (2) deleting the feed data and all of the
130
+ posts that were syndicated from the feed; or (3) deleting the feed data
131
+ and *keeping* the posts that were syndicated from the feed
132
+ setting the Link to "Invisible" (meaning that it will not be displayed
133
+ in lists of the site links on the front page, and it won't be checked
134
+ for updates; (2) deleting the Link and all of the posts that were
135
+ syndicated from its feed; or (3) deleting the feed data but keeping the
136
+ posts that were syndicated (which will henceforward be treated as if
137
+ they were local rather than syndicated posts). (Note that (1) is usually
138
+ the best option for aggregator sites, unless you want to clean up the
139
+ results of an error or a test.)
140
+
141
+ * FEATURE / BUG FIX: If you have been receiving mysterious "I don't
142
+ syndicate...", or "(local) HTTP status code was not 200", or "(local)
143
+ transport error - could not open socket", or "parse error - not well
144
+ formed" errors, then this update may solve your problems, and if it does
145
+ *not* solve them, it will at least make the reasons for the problems
146
+ easier to understand. That's because I've overhauled the way that
147
+ FeedWordPress goes about updating feeds.
148
+
149
+ If you use the command-line PHP scripting method to run scheduled
150
+ updates, then not much should change for you, except for fewer
151
+ mysterious errors. If you have done updates by sending periodic HTTP
152
+ requests to <http://your-blog.com/path/wp-content/update-feeds.php>,
153
+ then the details have changed somewhat; mostly in such a way as to make
154
+ things easier on you. See the README file or online documentation on
155
+ Staying Current for the details.
156
+
157
+ * FEATURE: FeedWordPress now features a more sophisticated system for
158
+ timed updates. Instead of polling *every* subscribed feed for updates
159
+ *each* time `update-feeds.php` is run, FeedWordPress now keeps track of
160
+ the last time it polled each feed, and only polls them again after a
161
+ certain period of time has passed. The amount of time is normally set
162
+ randomly for each feed, in a period between 30 minutes and 2 hours (so
163
+ as to stagger updates over time rather than polling all of the feeds at once. However, the length of time between updates can also be set
164
+ directly by the feed, which brings us to ...
165
+
166
+ * FEATURE: FeedWordPress now respects the settings in the `ttl` and
167
+ Syndication Module RSS elements. Feeds with these elements set will not
168
+ be polled any more frequently than they indicate with these feeds unless
169
+ the user manually forces FeedWordPress to poll the feed (see Links -->
170
+ Syndicated --> Edit settings).
171
 
172
  Changes from 0.95 to 0.96
173
  -------------------------
OPTIONAL/wp-includes/rss-functions.php CHANGED
@@ -4,20 +4,21 @@
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 (2005.05.07)
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
  *
@@ -33,37 +34,74 @@
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
  *
39
- * (a) Multiple categories
40
- * (b) RSS 2.0 and Atom 0.6+ enclosures
 
 
 
41
  *
42
- * Categories are stored in $item['category'], $item['categories'],
43
- * $item['dc']['subject'], and $item['dc']['subjects'] (the singular keys
44
- * point to string values for the first category used; the plural keys
45
- * point to an array of all the categories the item is in)
46
  *
47
- * Enclosures are stored in the array $item['enclosure'] as suggested
48
- * at <http://magpie.laughingmeme.org/blog/?p=101>. So the URL of the first
49
- * enclosure is $item['enclosure'][0]['url']; the length is
50
- * $item['enclosure'][0]['length']; and the type is
51
- * $item['enclosure'][0]['type']
 
 
 
 
52
  *
53
- * Note that these are hacked-in solutions for inherited problems with
54
- * MagpieRSS as of version 0.7. They are not guaranteed to be
55
- * forward-compatible when/if Kellan solves these problems in the official
56
- * Magpie branch in the future. If you have filters (for example) that
57
- * depend on either categories or enclosures working as they currently do,
58
- * keep an eye on the ChangeLog in future releases.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  */
60
 
61
  define('RSS', 'RSS');
62
  define('ATOM', 'Atom');
63
- define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version);
64
 
65
- # UPDATED: rss_parse.inc: class MagpieRSS, function map_attrs
66
- # --- cut here ---
 
 
 
 
 
 
 
 
 
 
 
67
  /**
68
  * Hybrid parser, and object, takes RSS as a string and returns a simple object.
69
  *
@@ -89,21 +127,26 @@ class MagpieRSS {
89
 
90
  // define some constants
91
 
92
- var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
 
 
 
 
 
93
  var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
94
 
95
  // parser variables, useless if you're not a parser, treat as private
96
  var $stack = array(); // parser stack
97
  var $inchannel = false;
98
  var $initem = false;
99
- var $incontent = false; // if in Atom <content mode="xml"> field
 
 
 
100
  var $intextinput = false;
101
  var $inimage = false;
102
  var $current_namespace = false;
103
 
104
- var $incategory = false;
105
- var $current_category = 0;
106
-
107
  /**
108
  * Set up XML parser, parse source, and return populated RSS object..
109
  *
@@ -164,7 +207,7 @@ class MagpieRSS {
164
  'feed_start_element', 'feed_end_element' );
165
 
166
  xml_set_character_data_handler( $this->parser, 'feed_cdata' );
167
-
168
  $status = xml_parse( $this->parser, $source );
169
 
170
  if (! $status ) {
@@ -189,14 +232,16 @@ class MagpieRSS {
189
  $attrs = array_change_key_case($attrs, CASE_LOWER);
190
 
191
  // check for a namespace, and split if found
192
- $ns = false;
193
- if ( strpos( $element, ':' ) ) {
194
- list($ns, $el) = split( ':', $element, 2);
195
- }
196
- if ( $ns and $ns != 'rdf' ) {
197
- $this->current_namespace = $ns;
198
- }
199
-
 
 
200
  # if feed type isn't set, then this is first element of feed
201
  # identify feed from root element
202
  #
@@ -211,16 +256,38 @@ class MagpieRSS {
211
  }
212
  elseif ( $el == 'feed' ) {
213
  $this->feed_type = ATOM;
214
- $this->feed_version = $attrs['version'];
 
 
 
 
 
215
  $this->inchannel = true;
216
  }
217
  return;
218
  }
219
 
220
- if ( $el == 'channel' )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  {
222
  $this->inchannel = true;
223
  }
 
224
  elseif ($el == 'item' or $el == 'entry' )
225
  {
226
  $this->initem = true;
@@ -228,12 +295,7 @@ class MagpieRSS {
228
  $this->current_item['about'] = $attrs['rdf:about'];
229
  }
230
  }
231
-
232
- elseif ($this->initem and ($el == 'category' or ($this->current_namespace == 'dc' and $el == 'subject'))) {
233
- $this->incategory = true;
234
- array_unshift( $this->stack, $el );
235
- }
236
-
237
  // if we're in the default namespace of an RSS feed,
238
  // record textinput or image fields
239
  elseif (
@@ -252,82 +314,87 @@ class MagpieRSS {
252
  $this->inimage = true;
253
  }
254
 
255
- # -- Handle RSS 2 enclosures. Suggested by <http://magpie.laughingmeme.org/blog/?p=101>
256
- elseif (
257
- $this->feed_type == RSS and
258
- $el == 'enclosure' )
259
- {
260
- $this->current_item[$el][] = $attrs;
261
- $this->incontent = $el;
262
- }
263
 
264
- # handle atom content constructs
265
- elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
266
- {
267
- // avoid clashing w/ RSS mod_content
268
- if ($el == 'content' ) {
269
- $el = 'atom_content';
270
- }
271
-
272
- $this->incontent = $el;
273
-
274
-
275
- }
 
 
276
 
277
- // if inside an Atom content construct (e.g. content or summary) field treat tags as text
278
- elseif ($this->feed_type == ATOM and $this->incontent )
279
- {
280
- // if tags are inlined, then flatten
281
- $attrs_str = join(' ',
282
- array_map('map_attrs',
283
- array_keys($attrs),
284
- array_values($attrs) ) );
285
-
286
- $this->append_content( "<$element $attrs_str>" );
287
-
288
- array_unshift( $this->stack, $el );
289
- }
290
-
291
- // Atom support many links per containging element.
292
- // Magpie treats link elements of type rel='alternate'
293
- // as being equivalent to RSS's simple link element.
294
- //
295
- elseif ($this->feed_type == ATOM and $el == 'link' )
296
- {
297
- # -- CWJ: Treat <link> elements without explicit rel as rel="alternate"
298
- if ( !isset($attrs['rel']) or isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
299
- {
300
- $link_el = 'link';
301
- }
302
- # -- CWJ: support Atom 0.6+ enclosures
303
- elseif ( isset($attrs['rel']) and $attrs['rel'] == 'enclosure' )
304
- {
305
- $link_el = 'link_' . $attrs['rel'];
306
-
307
- # -- CWJ: Normalize to RSS 2.0 enclosure handling
308
- $n = count($this->current_item[$attrs['rel']]);
309
- $this->current_item[$attrs['rel']][$n] = $attrs;
310
- $this->current_item[$attrs['rel']][$n]['url'] =
311
- $this->current_item[$attrs['rel']][$n]['href'];
312
- }
313
- else {
314
- $link_el = 'link_' . $attrs['rel'];
315
- }
316
-
317
- $this->append($link_el, $attrs['href']);
318
- }
319
 
320
- // set stack[0] to current element
321
- else {
322
- array_unshift($this->stack, $el);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  }
324
  }
325
 
326
 
327
 
328
  function feed_cdata ($p, $text) {
329
- if ($this->feed_type == ATOM and $this->incontent)
330
- {
331
  $this->append_content( $text );
332
  }
333
  else {
@@ -339,7 +406,32 @@ class MagpieRSS {
339
  function feed_end_element ($p, $el) {
340
  $el = strtolower($el);
341
 
342
- if ( $el == 'item' or $el == 'entry' )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  {
344
  $this->items[] = $this->current_item;
345
  $this->current_item = array();
@@ -347,12 +439,7 @@ class MagpieRSS {
347
 
348
  $this->current_category = 0;
349
  }
350
- elseif ($this->initem and ($el == 'category' or $el == 'dc:subject')) {
351
- $this->incategory = false;
352
- $this->current_category = $this->current_category + 1;
353
- array_shift( $this->stack );
354
- }
355
- elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
356
  {
357
  $this->intextinput = false;
358
  }
@@ -360,32 +447,17 @@ class MagpieRSS {
360
  {
361
  $this->inimage = false;
362
  }
363
- elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
364
- {
365
- $this->incontent = false;
366
- }
367
  elseif ($el == 'channel' or $el == 'feed' )
368
  {
369
  $this->inchannel = false;
370
  }
371
- elseif ($this->feed_type == ATOM and $this->incontent ) {
372
- // balance tags properly
373
- // note: i don't think this is actually neccessary
374
- if ( $this->stack[0] == $el )
375
- {
376
- $this->append_content("</$el>");
377
- }
378
- else {
379
- $this->append_content("<$el />");
380
- }
381
-
382
- array_shift( $this->stack );
383
- }
384
  else {
385
- array_shift( $this->stack );
386
  }
387
 
388
- $this->current_namespace = false;
 
 
389
  }
390
 
391
  function concat (&$str1, $str2="") {
@@ -395,15 +467,21 @@ class MagpieRSS {
395
  $str1 .= $str2;
396
  }
397
 
398
-
399
-
400
  function append_content($text) {
401
- if ( $this->initem ) {
402
- $this->concat( $this->current_item[ $this->incontent ], $text );
403
- }
404
- elseif ( $this->inchannel ) {
405
- $this->concat( $this->channel[ $this->incontent ], $text );
406
- }
 
 
 
 
 
 
 
 
407
  }
408
 
409
  // smart append - field and namespace aware
@@ -413,17 +491,14 @@ class MagpieRSS {
413
  }
414
  if ( $this->current_namespace )
415
  {
416
- if ( $this->incategory ) {
417
- $this->concat( $this->current_item['categories'][$this->current_category], $text );
418
- }
419
- elseif ( $this->initem ) {
420
- $this->concat(
421
- $this->current_item[ $this->current_namespace ][ $el ], $text );
422
  }
423
  elseif ($this->inchannel) {
424
- $this->concat(
425
- $this->channel[ $this->current_namespace][ $el ], $text );
426
- }
427
  elseif ($this->intextinput) {
428
  $this->concat(
429
  $this->textinput[ $this->current_namespace][ $el ], $text );
@@ -434,12 +509,9 @@ class MagpieRSS {
434
  }
435
  }
436
  else {
437
- if ( $this->incategory ) {
438
- $this->concat( $this->current_item['categories'][$this->current_category], $text );
439
- }
440
- elseif ( $this->initem ) {
441
- $this->concat(
442
- $this->current_item[ $el ], $text);
443
  }
444
  elseif ($this->intextinput) {
445
  $this->concat(
@@ -450,50 +522,267 @@ class MagpieRSS {
450
  $this->image[ $el ], $text );
451
  }
452
  elseif ($this->inchannel) {
453
- $this->concat(
454
- $this->channel[ $el ], $text );
455
  }
456
 
457
  }
458
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  function normalize () {
461
- // if atom populate rss fields
462
  if ( $this->is_atom() ) {
463
- $this->channel['description'] = $this->channel['tagline'];
464
- for ( $i = 0; $i < count($this->items); $i++) {
465
- $item = $this->items[$i];
466
- if ( isset($item['summary']) )
467
- $item['description'] = $item['summary'];
468
- if ( isset($item['atom_content']))
469
- $item['content']['encoded'] = $item['atom_content'];
470
-
471
- $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified'];
472
- if ( $atom_date ) {
473
- $epoch = @parse_w3cdtf($item['modified']);
474
- if ($epoch and $epoch > 0) {
475
- $item['date_timestamp'] = $epoch;
476
- }
477
- }
478
 
479
- if ( is_array($item['categories']) ) {
480
- $item['category'] = $item['categories'][0];
481
- $item['dc']['subjects'] = $item['categories'];
482
- $item['dc']['subject'] = $item['category'];
483
- }
484
-
485
- $this->items[$i] = $item;
486
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  }
488
  elseif ( $this->is_rss() ) {
489
- $this->channel['tagline'] = $this->channel['description'];
 
 
 
 
490
  for ( $i = 0; $i < count($this->items); $i++) {
491
  $item = $this->items[$i];
492
- if ( isset($item['description']))
493
- $item['summary'] = $item['description'];
494
- if ( isset($item['content']['encoded'] ) )
495
- $item['atom_content'] = $item['content']['encoded'];
496
-
 
 
 
 
 
 
 
 
 
 
497
  if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
498
  $epoch = @parse_w3cdtf($item['dc']['date']);
499
  if ($epoch and $epoch > 0) {
@@ -506,12 +795,6 @@ class MagpieRSS {
506
  $item['date_timestamp'] = $epoch;
507
  }
508
  }
509
-
510
- if ( is_array($item['categories']) ) {
511
- $item['category'] = $item['categories'][0];
512
- $item['dc']['subjects'] = $item['categories'];
513
- $item['dc']['subject'] = $item['category'];
514
- }
515
 
516
  $this->items[$i] = $item;
517
  }
@@ -651,7 +934,7 @@ class MagpieRSS {
651
 
652
  function error ($errormsg, $lvl=E_USER_WARNING) {
653
  // append PHP's error message if track_errors enabled
654
- if ( $php_errormsg ) {
655
  $errormsg .= " ($php_errormsg)";
656
  }
657
  if ( MAGPIE_DEBUG ) {
@@ -668,17 +951,71 @@ class MagpieRSS {
668
  $this->ERROR = $errormsg;
669
  }
670
  }
 
 
 
 
 
 
671
  } // end class RSS
672
 
673
  function map_attrs($k, $v) {
674
  return "$k=\"$v\"";
675
  }
676
- # ---- cut here ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
 
678
  require_once( dirname(__FILE__) . '/class-snoopy.php');
679
 
680
- # -- UPDATED from rss_fetch.inc: fetch_rss, error, debug, magpie_error
681
- # --- cut here ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  function fetch_rss ($url) {
683
  // initialize constants
684
  init();
@@ -733,7 +1070,7 @@ function fetch_rss ($url) {
733
  if ( $cache_status == 'HIT' ) {
734
  $rss = $cache->get( $cache_key );
735
  if ( isset($rss) and $rss ) {
736
- // should be cache age
737
  $rss->from_cache = 1;
738
  if ( MAGPIE_DEBUG > 1) {
739
  debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
@@ -850,10 +1187,7 @@ function magpie_error ($errormsg="") {
850
 
851
  return $MAGPIE_ERROR;
852
  }
853
- # --- cut here ---
854
 
855
- # UPDATED FROM: rss_fetch.inc: _fetch_remote_file, _response_to_rss, init
856
- # --- cut here ---
857
  /*=======================================================================*\
858
  Function: _fetch_remote_file
859
  Purpose: retrieve an arbitrary remote file
@@ -952,11 +1286,7 @@ function init () {
952
  }
953
 
954
  if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
955
- # WORDPRESS MODIFICATION: use whatever charset the blog is using
956
- # --- cut here ---
957
- $wp_encoding = get_settings('blog_charset');
958
- define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
959
- # --- cut here ---
960
  }
961
 
962
  if ( !defined('MAGPIE_INPUT_ENCODING') ) {
@@ -972,11 +1302,8 @@ function init () {
972
  }
973
 
974
  if ( !defined('MAGPIE_USER_AGENT') ) {
975
- # WORDPRESS MODIFICATION: send WordPress as user-agent
976
- # --- cut here ---
977
- $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org';
978
- # --- cut here ---
979
-
980
  if ( MAGPIE_CACHE_ON ) {
981
  $ua = $ua . ')';
982
  }
@@ -996,34 +1323,73 @@ function init () {
996
  define('MAGPIE_USE_GZIP', true);
997
  }
998
  }
999
- # --- cut here ---
1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  function is_info ($sc) {
1002
- return $sc >= 100 && $sc < 200;
1003
  }
1004
 
 
 
 
 
1005
  function is_success ($sc) {
1006
- return $sc >= 200 && $sc < 300;
1007
  }
1008
 
 
 
 
 
1009
  function is_redirect ($sc) {
1010
- return $sc >= 300 && $sc < 400;
1011
  }
1012
 
 
 
 
 
1013
  function is_error ($sc) {
1014
- return $sc >= 400 && $sc < 600;
1015
  }
1016
 
 
 
 
 
1017
  function is_client_error ($sc) {
1018
- return $sc >= 400 && $sc < 500;
1019
  }
1020
 
 
 
 
 
1021
  function is_server_error ($sc) {
1022
- return $sc >= 500 && $sc < 600;
1023
  }
1024
 
1025
- # WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database)
1026
- # --- cut here ---
 
 
1027
  class RSSCache {
1028
  var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1029
  var $MAX_AGE = 43200; // when are files stale, default twelve hours
@@ -1161,52 +1527,74 @@ class RSSCache {
1161
  }
1162
  }
1163
  }
1164
- # --- cut here ---
 
 
 
 
 
 
 
 
 
 
1165
 
1166
  function parse_w3cdtf ( $date_str ) {
1167
-
1168
- # regex to match wc3dtf
1169
- $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
1170
-
1171
- if ( preg_match( $pat, $date_str, $match ) ) {
1172
- list( $year, $month, $day, $hours, $minutes, $seconds) =
1173
- array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
1174
-
1175
- # calc epoch for current date assuming GMT
1176
- $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1177
-
1178
- $offset = 0;
1179
- if ( $match[10] == 'Z' ) {
1180
- # zulu time, aka GMT
1181
- }
1182
- else {
1183
- list( $tz_mod, $tz_hour, $tz_min ) =
1184
- array( $match[8], $match[9], $match[10]);
1185
-
1186
- # zero out the variables
1187
- if ( ! $tz_hour ) { $tz_hour = 0; }
1188
- if ( ! $tz_min ) { $tz_min = 0; }
1189
-
1190
- $offset_secs = (($tz_hour*60)+$tz_min)*60;
1191
-
1192
- # is timezone ahead of GMT? then subtract offset
1193
- #
1194
- if ( $tz_mod == '+' ) {
1195
- $offset_secs = $offset_secs * -1;
1196
- }
1197
-
1198
- $offset = $offset_secs;
1199
- }
1200
- $epoch = $epoch + $offset;
1201
- return $epoch;
1202
- }
1203
- else {
1204
- return -1;
1205
- }
 
 
 
 
 
 
 
 
 
 
1206
  }
1207
 
1208
- # WORDPRESS-SPECIFIC: wp_rss (), get_rss ()
1209
- # --- cut here ---
 
 
1210
  function wp_rss ($url, $num) {
1211
  //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1212
  $num_items = $num;
@@ -1244,5 +1632,4 @@ function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
1244
  return false;
1245
  }
1246
  }
1247
- # --- cut here ---
1248
  ?>
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.8wp (2005.10.14)
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 0.8a. The update improves handling of character encoding, supports
15
+ * multiple categories for posts (using <dc:subject> or <category>), supports
16
+ * Atom 1.0, and implements many other useful features. The file is derived from
17
+ * a combination of (1) the WordPress development team's modifications to
18
+ * MagpieRSS 0.51 and (2) the latest bleeding-edge updates to the "official"
19
+ * MagpieRSS software, including Kellan's original work and some substantial
20
+ * updates by Charles Johnson. All possible through the magic of the GPL. Yay
21
+ * for free software!
22
  *
23
  * Differences from the main branch of MagpieRSS:
24
  *
34
  *
35
  * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
36
  *
37
+ * Differences from the version of MagpieRSS packaged with WordPress:
 
38
  *
39
+ * 1. Support for translation between multiple character encodings. Under
40
+ * PHP 5 this is very nicely handled by the XML parsing library. Under PHP
41
+ * 4 we need to do a little bit of work ourselves, using either iconv or
42
+ * mb_convert_encoding if it is not one of the (extremely limited) number
43
+ * of character sets that PHP 4's XML module can handle natively.
44
  *
45
+ * 2. Numerous bug fixes.
 
 
 
46
  *
47
+ * 3. The parser class MagpieRSS has been substantially revised to better
48
+ * support popular features such as enclosures and multiple categories,
49
+ * and to support the new Atom 1.0 IETF standard. (Atom feeds are
50
+ * normalized so as to make the data available using terminology from
51
+ * either Atom 0.3 or Atom 1.0. Atom 0.3 backward-compatibility is provided
52
+ * to allow existing software to easily begin accepting Atom 1.0 data; new
53
+ * software SHOULD NOT depend on the 0.3 terminology, but rather use the
54
+ * normalization as a convenient way to keep supporting 0.3 feeds while
55
+ * they linger in the world.)
56
  *
57
+ * The upgraded MagpieRSS can also now handle some content constructs that
58
+ * had not been handled well by previous versions of Magpie (such as the
59
+ * use of namespaced XHTML in <xhtml:body> or <xhtml:div> elements to
60
+ * provide the full content of posts in RSS 2.0 feeds).
61
+ *
62
+ * Unlike previous versions of MagpieRSS, this version can parse multiple
63
+ * instances of the same child element in item/entry and channel/feed
64
+ * containers. This is done using simple counters next to the element
65
+ * names: the first <category> element on an RSS item, for example, can be
66
+ * found in $item['category'] (thus preserving backward compatibility); the
67
+ * second in $item['category#2'], the third in $item['category#3'], and so
68
+ * on. The number of categories applied to the item can be found in
69
+ * $item['category#']
70
+ *
71
+ * Also unlike previous versions of MagpieRSS, this version allows you to
72
+ * access the values of elements' attributes as well as the content they
73
+ * contain. This can be done using a simple syntax inspired by XPath: to
74
+ * access the type attribute of an RSS 2.0 enclosure, for example, you
75
+ * need only access `$item['enclosure@type']`. A comma-separated list of
76
+ * attributes for the enclosure element is stored in `$item['enclosure@']`.
77
+ * (This syntax interacts easily with the syntax for multiple categories;
78
+ * for example, the value of the `scheme` attribute for the fourth category
79
+ * element on a particular item is stored in `$item['category#4@scheme']`.)
80
+ *
81
+ * Note also that this implementation IS NOT backward-compatible with the
82
+ * kludges that were used to hack in support for multiple categories and
83
+ * for enclosures in upgraded versions of MagpieRSS distributed with
84
+ * previous versions of FeedWordPress. If your hacks or filter plugins
85
+ * depended on the old way of doing things... well, I warned you that they
86
+ * might not be permanent. Sorry!
87
  */
88
 
89
  define('RSS', 'RSS');
90
  define('ATOM', 'Atom');
 
91
 
92
+ ################################################################################
93
+ ## WordPress: make some settings WordPress-appropriate #########################
94
+ ################################################################################
95
+
96
+ define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)');
97
+
98
+ $wp_encoding = get_settings('blog_charset');
99
+ define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
100
+
101
+ ################################################################################
102
+ ## rss_parse.inc: from MagpieRSS 0.8a ##########################################
103
+ ################################################################################
104
+
105
  /**
106
  * Hybrid parser, and object, takes RSS as a string and returns a simple object.
107
  *
127
 
128
  // define some constants
129
 
130
+ var $_ATOM_CONTENT_CONSTRUCTS = array(
131
+ 'content', 'summary', 'title', /* common */
132
+ 'info', 'tagline', 'copyright', /* Atom 0.3 */
133
+ 'rights', 'subtitle', /* Atom 1.0 */
134
+ );
135
+ var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div');
136
  var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
137
 
138
  // parser variables, useless if you're not a parser, treat as private
139
  var $stack = array(); // parser stack
140
  var $inchannel = false;
141
  var $initem = false;
142
+
143
+ var $incontent = array(); // non-empty if in namespaced XML content field
144
+ var $exclude_top = false; // true when Atom 1.0 type="xhtml"
145
+
146
  var $intextinput = false;
147
  var $inimage = false;
148
  var $current_namespace = false;
149
 
 
 
 
150
  /**
151
  * Set up XML parser, parse source, and return populated RSS object..
152
  *
207
  'feed_start_element', 'feed_end_element' );
208
 
209
  xml_set_character_data_handler( $this->parser, 'feed_cdata' );
210
+
211
  $status = xml_parse( $this->parser, $source );
212
 
213
  if (! $status ) {
232
  $attrs = array_change_key_case($attrs, CASE_LOWER);
233
 
234
  // check for a namespace, and split if found
235
+ if ( empty($this->incontent) ) { // Don't munge content tags
236
+ $ns = false;
237
+ if ( strpos( $element, ':' ) ) {
238
+ list($ns, $el) = split( ':', $element, 2);
239
+ }
240
+ if ( $ns and $ns != 'rdf' ) {
241
+ $this->current_namespace = $ns;
242
+ }
243
+ }
244
+
245
  # if feed type isn't set, then this is first element of feed
246
  # identify feed from root element
247
  #
256
  }
257
  elseif ( $el == 'feed' ) {
258
  $this->feed_type = ATOM;
259
+ if ($attrs['xmlns'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0
260
+ $this->feed_version = '1.0';
261
+ }
262
+ else { // Atom 0.3, probably.
263
+ $this->feed_version = $attrs['version'];
264
+ }
265
  $this->inchannel = true;
266
  }
267
  return;
268
  }
269
 
270
+ // if we're inside a namespaced content construct, treat tags as text
271
+ if ( !empty($this->incontent) )
272
+ {
273
+ if ((count($this->incontent) > 1) or !$this->exclude_top) {
274
+ // if tags are inlined, then flatten
275
+ $attrs_str = join(' ',
276
+ array_map('map_attrs',
277
+ array_keys($attrs),
278
+ array_values($attrs) ) );
279
+ if (strlen($attrs_str) > 0) $attrs_str = ' '.$attrs_str;
280
+
281
+ $this->append_content( "<{$element}{$attrs_str}>" );
282
+ }
283
+ array_push($this->incontent, $el); // stack for parsing content XML
284
+ }
285
+
286
+ elseif ( $el == 'channel' )
287
  {
288
  $this->inchannel = true;
289
  }
290
+
291
  elseif ($el == 'item' or $el == 'entry' )
292
  {
293
  $this->initem = true;
295
  $this->current_item['about'] = $attrs['rdf:about'];
296
  }
297
  }
298
+
 
 
 
 
 
299
  // if we're in the default namespace of an RSS feed,
300
  // record textinput or image fields
301
  elseif (
314
  $this->inimage = true;
315
  }
316
 
317
+ // set stack[0] to current element
318
+ else {
319
+ // Atom support many links per containing element.
320
+ // Magpie treats link elements of type rel='alternate'
321
+ // as being equivalent to RSS's simple link element.
 
 
 
322
 
323
+ $atom_link = false;
324
+ if ($this->feed_type == ATOM and $el == 'link') {
325
+ $atom_link = true;
326
+ if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') {
327
+ $el = $el . "_" . $attrs['rel']; // pseudo-element names for Atom link elements
328
+ }
329
+ }
330
+ # handle atom content constructs
331
+ elseif ( $this->feed_type == ATOM and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) )
332
+ {
333
+ // avoid clashing w/ RSS mod_content
334
+ if ($el == 'content' ) {
335
+ $el = 'atom_content';
336
+ }
337
 
338
+ // assume that everything accepts namespaced XML
339
+ // (that will pass through some non-validating feeds;
340
+ // but so what? this isn't a validating parser)
341
+ $this->incontent = array();
342
+ array_push($this->incontent, $el); // start a stack
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
+ if (
345
+ isset($attrs['type'])
346
+ and trim(strtolower($attrs['type']))=='xhtml'
347
+ ) {
348
+ $this->exclude_top = true;
349
+ } else {
350
+ $this->exclude_top = false;
351
+ }
352
+ }
353
+ # Handle inline XHTML body elements --CWJ
354
+ elseif (
355
+ ($this->current_namespace=='xhtml' or (isset($attrs['xmlns']) and $attrs['xmlns'] == 'http://www.w3.org/1999/xhtml'))
356
+ and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS) )
357
+ {
358
+ $this->current_namespace = 'xhtml';
359
+ $this->incontent = array();
360
+ array_push($this->incontent, $el); // start a stack
361
+ $this->exclude_top = false;
362
+ }
363
+
364
+ array_unshift($this->stack, $el);
365
+ $elpath = join('_', array_reverse($this->stack));
366
+
367
+ $n = $this->element_count($elpath);
368
+ $this->element_count($elpath, $n+1);
369
+
370
+ if ($n > 0) {
371
+ array_shift($this->stack);
372
+ array_unshift($this->stack, $el.'#'.($n+1));
373
+ $elpath = join('_', array_reverse($this->stack));
374
+ }
375
+
376
+ // this makes the baby Jesus cry, but we can't do it in normalize()
377
+ // because we've made the element name for Atom links unpredictable
378
+ // by tacking on the relation to the end. -CWJ
379
+ if ($atom_link and isset($attrs['href'])) {
380
+ $this->append($elpath, $attrs['href']);
381
+ }
382
+
383
+ // add attributes
384
+ if (count($attrs) > 0) {
385
+ $this->append($elpath.'@', join(',', array_keys($attrs)));
386
+ foreach ($attrs as $attr => $value) {
387
+ $this->append($elpath.'@'.$attr, $value);
388
+ }
389
+ }
390
  }
391
  }
392
 
393
 
394
 
395
  function feed_cdata ($p, $text) {
396
+
397
+ if ($this->incontent) {
398
  $this->append_content( $text );
399
  }
400
  else {
406
  function feed_end_element ($p, $el) {
407
  $el = strtolower($el);
408
 
409
+ if ( $this->incontent ) {
410
+ $opener = array_pop($this->incontent);
411
+
412
+ // Don't get bamboozled by namespace voodoo
413
+ if (strpos($el, ':')) { list($ns, $closer) = split(':', $el); }
414
+ else { $ns = false; $closer = $el; }
415
+
416
+ // Don't get bamboozled by our munging of <atom:content>, either
417
+ if ($this->feed_type == ATOM and $closer == 'content') {
418
+ $closer = 'atom_content';
419
+ }
420
+
421
+ // balance tags properly
422
+ // note: i don't think this is actually neccessary
423
+ if ($opener != $closer) {
424
+ array_push($this->incontent, $opener);
425
+ $this->append_content("<$el />");
426
+ } elseif ($this->incontent) { // are we in the content construct still?
427
+ if ((count($this->incontent) > 1) or !$this->exclude_top) {
428
+ $this->append_content("</$el>");
429
+ }
430
+ } else { // shift the opening of the content construct off the normal stack
431
+ array_shift( $this->stack );
432
+ }
433
+ }
434
+ elseif ( $el == 'item' or $el == 'entry' )
435
  {
436
  $this->items[] = $this->current_item;
437
  $this->current_item = array();
439
 
440
  $this->current_category = 0;
441
  }
442
+ elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
 
 
 
 
 
443
  {
444
  $this->intextinput = false;
445
  }
447
  {
448
  $this->inimage = false;
449
  }
 
 
 
 
450
  elseif ($el == 'channel' or $el == 'feed' )
451
  {
452
  $this->inchannel = false;
453
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  else {
455
+ array_shift( $this->stack );
456
  }
457
 
458
+ if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ
459
+ $this->current_namespace = false;
460
+ }
461
  }
462
 
463
  function concat (&$str1, $str2="") {
467
  $str1 .= $str2;
468
  }
469
 
 
 
470
  function append_content($text) {
471
+ if ( $this->initem ) {
472
+ if ($this->current_namespace) {
473
+ $this->concat( $this->current_item[$this->current_namespace][ reset($this->incontent) ], $text );
474
+ } else {
475
+ $this->concat( $this->current_item[ reset($this->incontent) ], $text );
476
+ }
477
+ }
478
+ elseif ( $this->inchannel ) {
479
+ if ($this->current_namespace) {
480
+ $this->concat( $this->channel[$this->current_namespace][ reset($this->incontent) ], $text );
481
+ } else {
482
+ $this->concat( $this->channel[ reset($this->incontent) ], $text );
483
+ }
484
+ }
485
  }
486
 
487
  // smart append - field and namespace aware
491
  }
492
  if ( $this->current_namespace )
493
  {
494
+ if ( $this->initem ) {
495
+ $this->concat(
496
+ $this->current_item[ $this->current_namespace ][ $el ], $text);
 
 
 
497
  }
498
  elseif ($this->inchannel) {
499
+ $this->concat(
500
+ $this->channel[ $this->current_namespace][ $el ], $text );
501
+ }
502
  elseif ($this->intextinput) {
503
  $this->concat(
504
  $this->textinput[ $this->current_namespace][ $el ], $text );
509
  }
510
  }
511
  else {
512
+ if ( $this->initem ) {
513
+ $this->concat(
514
+ $this->current_item[ $el ], $text);
 
 
 
515
  }
516
  elseif ($this->intextinput) {
517
  $this->concat(
522
  $this->image[ $el ], $text );
523
  }
524
  elseif ($this->inchannel) {
525
+ $this->concat(
526
+ $this->channel[ $el ], $text );
527
  }
528
 
529
  }
530
  }
531
+
532
+ // smart count - field and namespace aware
533
+ function element_count ($el, $set = NULL) {
534
+ if (!$el) {
535
+ return;
536
+ }
537
+ if ( $this->current_namespace )
538
+ {
539
+ if ( $this->initem ) {
540
+ if (!is_null($set)) { $this->current_item[ $this->current_namespace ][ $el.'#' ] = $set; }
541
+ $ret = (isset($this->current_item[ $this->current_namespace ][ $el.'#' ]) ?
542
+ $this->current_item[ $this->current_namespace ][ $el.'#' ] : 0);
543
+ }
544
+ elseif ($this->inchannel) {
545
+ if (!is_null($set)) { $this->channel[ $this->current_namespace ][ $el.'#' ] = $set; }
546
+ $ret = (isset($this->channel[ $this->current_namespace][ $el.'#' ]) ?
547
+ $this->channel[ $this->current_namespace][ $el.'#' ] : 0);
548
+ }
549
+ }
550
+ else {
551
+ if ( $this->initem ) {
552
+ if (!is_null($set)) { $this->current_item[ $el.'#' ] = $set; }
553
+ $ret = (isset($this->current_item[ $el.'#' ]) ?
554
+ $this->current_item[ $el.'#' ] : 0);
555
+ }
556
+ elseif ($this->inchannel) {
557
+ if (!is_null($set)) {$this->channel[ $el.'#' ] = $set; }
558
+ $ret = (isset($this->channel[ $el.'#' ]) ?
559
+ $this->channel[ $el.'#' ] : 0);
560
+ }
561
+ }
562
+ return $ret;
563
+ }
564
+
565
+ function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
566
+ $id_from = $this->element_id($from, $i);
567
+ $id_to = $this->element_id($to, $i);
568
+ if (isset($source["{$id_from}@"])) {
569
+ foreach (explode(',', $source["{$id_from}@"]) as $attr) {
570
+ if ($from=='link_enclosure' and $attr=='href') { // from Atom
571
+ $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
572
+ $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
573
+ }
574
+ elseif ($from=='enclosure' and $attr=='url') { // from RSS
575
+ $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
576
+ $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
577
+ }
578
+ else {
579
+ $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
580
+ }
581
+ }
582
+ }
583
+ }
584
+
585
+ function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
586
+ $id = $this->element_id($person, $i);
587
+ $id_to = $this->element_id($to, $i);
588
+
589
+ // Atom 0.3 <=> Atom 1.0
590
+ if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; }
591
+ else { $used = 'url'; $norm = 'uri'; }
592
+
593
+ if (isset($source["{$id}_{$used}"])) {
594
+ $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
595
+ }
596
+
597
+ // Atom to RSS 2.0 and Dublin Core
598
+ // RSS 2.0 person strings should be valid e-mail addresses if possible.
599
+ if (isset($source["{$id}_email"])) {
600
+ $rss_author = $source["{$id}_email"];
601
+ }
602
+ if (isset($source["{$id}_name"])) {
603
+ $rss_author = $source["{$id}_name"]
604
+ . (isset($rss_author) ? " <$rss_author>" : '');
605
+ }
606
+ if (isset($rss_author)) {
607
+ $source[$id] = $rss_author; // goes to top-level author or contributor
608
+ $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
609
+ }
610
+ }
611
+
612
+ // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core...
613
+ function normalize_category (&$source, $from, &$dest, $to, $i) {
614
+ $cat_id = $this->element_id($from, $i);
615
+ $dc_id = $this->element_id($to, $i);
616
+
617
+ // first normalize category elements: Atom 1.0 <=> RSS 2.0
618
+ if ( isset($source["{$cat_id}@term"]) ) { // category identifier
619
+ $source[$cat_id] = $source["{$cat_id}@term"];
620
+ } elseif ( $this->feed_type == RSS ) {
621
+ $source["{$cat_id}@term"] = $source[$cat_id];
622
+ }
623
+
624
+ if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy
625
+ $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
626
+ } elseif ( isset($source["{$cat_id}@domain"]) ) {
627
+ $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
628
+ }
629
+
630
+ // Now put the identifier into dc:subject
631
+ $dest[$dc_id] = $source[$cat_id];
632
+ }
633
 
634
+ // ... or vice versa
635
+ function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
636
+ $dc_id = $this->element_id($from, $i);
637
+ $cat_id = $this->element_id($to, $i);
638
+
639
+ $dest[$cat_id] = $source[$dc_id]; // RSS 2.0
640
+ $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0
641
+ }
642
+
643
+ // simplify the logic for normalize(). Makes sure that count of elements and
644
+ // each of multiple elements is normalized properly. If you need to mess
645
+ // with things like attributes or change formats or the like, pass it a
646
+ // callback to handle each element.
647
+ function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
648
+ if (isset($source[$from]) or isset($source["{$from}#"])) {
649
+ if (isset($source["{$from}#"])) {
650
+ $n = $source["{$from}#"];
651
+ $dest["{$to}#"] = $source["{$from}#"];
652
+ }
653
+ else { $n = 1; }
654
+
655
+ for ($i = 1; $i <= $n; $i++) {
656
+ if (isset($via)) { // custom callback for ninja attacks
657
+ $this->{$via}($source, $from, $dest, $to, $i);
658
+ }
659
+ else { // just make it the same
660
+ $from_id = $this->element_id($from, $i);
661
+ $to_id = $this->element_id($to, $i);
662
+ $dest[$to_id] = $source[$from_id];
663
+ }
664
+ }
665
+ }
666
+ }
667
+
668
  function normalize () {
669
+ // if atom populate rss fields and normalize 0.3 and 1.0 feeds
670
  if ( $this->is_atom() ) {
671
+ // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
672
+ if ($this->feed_version < 1.0) {
673
+ $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
674
+ $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
675
+ $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
676
+ } else {
677
+ $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
678
+ $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
679
+ $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
680
+ }
681
+ $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
682
+ $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
 
 
 
683
 
684
+ // Atom elements to RSS elements
685
+ $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
686
+
687
+ if ( isset($this->channel['logo']) ) {
688
+ $this->normalize_element($this->channel, 'logo', $this->image, 'url');
689
+ $this->normalize_element($this->channel, 'link', $this->image, 'link');
690
+ $this->normalize_element($this->channel, 'title', $this->image, 'title');
691
+ }
692
+
693
+ for ( $i = 0; $i < count($this->items); $i++) {
694
+ $item = $this->items[$i];
695
+
696
+ // Atom 1.0 elements <=> Atom 0.3 elements
697
+ if ($this->feed_version < 1.0) {
698
+ $this->normalize_element($item, 'modified', $item, 'updated');
699
+ $this->normalize_element($item, 'issued', $item, 'published');
700
+ } else {
701
+ $this->normalize_element($item, 'updated', $item, 'modified');
702
+ $this->normalize_element($item, 'published', $item, 'issued');
703
+ }
704
+
705
+ // "If an atom:entry element does not contain
706
+ // atom:author elements, then the atom:author elements
707
+ // of the contained atom:source element are considered
708
+ // to apply. In an Atom Feed Document, the atom:author
709
+ // elements of the containing atom:feed element are
710
+ // considered to apply to the entry if there are no
711
+ // atom:author elements in the locations described
712
+ // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
713
+ if (!isset($item["author#"])) {
714
+ if (isset($item["source_author#"])) { // from aggregation source
715
+ $source = $item;
716
+ $author = "source_author";
717
+ } elseif (isset($this->channel["author#"])) { // from containing feed
718
+ $source = $this->channel;
719
+ $author = "author";
720
+ }
721
+
722
+ $item["author#"] = $source["{$author}#"];
723
+ for ($au = 1; $au <= $item["author#"]; $au++) {
724
+ $id_to = $this->element_id('author', $au);
725
+ $id_from = $this->element_id($author, $au);
726
+
727
+ $item[$id_to] = $source[$id_from];
728
+ foreach (array('name', 'email', 'uri', 'url') as $what) {
729
+ if (isset($source["{$id_from}_{$what}"])) {
730
+ $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"];
731
+ }
732
+ }
733
+ }
734
+ }
735
+
736
+ // Atom elements to RSS elements
737
+ $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
738
+ $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
739
+ $this->normalize_element($item, 'summary', $item, 'description');
740
+ $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
741
+ $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
742
+
743
+ // Categories
744
+ if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories
745
+ $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
746
+ }
747
+ elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
748
+ $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
749
+ }
750
+
751
+ // Normalized item timestamp
752
+ $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
753
+ if ( $atom_date ) {
754
+ $epoch = @parse_w3cdtf($atom_date);
755
+ if ($epoch and $epoch > 0) {
756
+ $item['date_timestamp'] = $epoch;
757
+ }
758
+ }
759
+
760
+ $this->items[$i] = $item;
761
+ }
762
  }
763
  elseif ( $this->is_rss() ) {
764
+ // RSS elements to Atom elements
765
+ $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
766
+ $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
767
+ $this->normalize_element($this->image, 'url', $this->channel, 'logo');
768
+
769
  for ( $i = 0; $i < count($this->items); $i++) {
770
  $item = $this->items[$i];
771
+
772
+ // RSS elements to Atom elements
773
+ $this->normalize_element($item, 'description', $item, 'summary');
774
+ $this->normalize_element($item['content'], 'encoded', $item, 'atom_content');
775
+ $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
776
+
777
+ // Categories
778
+ if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories
779
+ $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
780
+ }
781
+ elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
782
+ $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
783
+ }
784
+
785
+ // Normalized item timestamp
786
  if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
787
  $epoch = @parse_w3cdtf($item['dc']['date']);
788
  if ($epoch and $epoch > 0) {
795
  $item['date_timestamp'] = $epoch;
796
  }
797
  }
 
 
 
 
 
 
798
 
799
  $this->items[$i] = $item;
800
  }
934
 
935
  function error ($errormsg, $lvl=E_USER_WARNING) {
936
  // append PHP's error message if track_errors enabled
937
+ if ( isset($php_errormsg) ) {
938
  $errormsg .= " ($php_errormsg)";
939
  }
940
  if ( MAGPIE_DEBUG ) {
951
  $this->ERROR = $errormsg;
952
  }
953
  }
954
+
955
+ // magic ID function for multiple elemenets.
956
+ // can be called as static MagpieRSS::element_id()
957
+ function element_id ($el, $counter) {
958
+ return $el . (($counter > 1) ? '#'.$counter : '');
959
+ }
960
  } // end class RSS
961
 
962
  function map_attrs($k, $v) {
963
  return "$k=\"$v\"";
964
  }
965
+
966
+ // patch to support medieval versions of PHP4.1.x,
967
+ // courtesy, Ryan Currie, ryan@digibliss.com
968
+
969
+ if (!function_exists('array_change_key_case')) {
970
+ define("CASE_UPPER",1);
971
+ define("CASE_LOWER",0);
972
+
973
+
974
+ function array_change_key_case($array,$case=CASE_LOWER) {
975
+ if ($case==CASE_LOWER) $cmd='strtolower';
976
+ elseif ($case==CASE_UPPER) $cmd='strtoupper';
977
+ foreach($array as $key=>$value) {
978
+ $output[$cmd($key)]=$value;
979
+ }
980
+ return $output;
981
+ }
982
+
983
+ }
984
+
985
+ ################################################################################
986
+ ## WordPress: Load in Snoopy from wp-includes ##################################
987
+ ################################################################################
988
 
989
  require_once( dirname(__FILE__) . '/class-snoopy.php');
990
 
991
+ ################################################################################
992
+ ## rss_fetch.inc: from MagpieRSS 0.8a ##########################################
993
+ ################################################################################
994
+
995
+ /*=======================================================================*\
996
+ Function: fetch_rss:
997
+ Purpose: return RSS object for the give url
998
+ maintain the cache
999
+ Input: url of RSS file
1000
+ Output: parsed RSS object (see rss_parse.inc)
1001
+
1002
+ NOTES ON CACHEING:
1003
+ If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
1004
+
1005
+ NOTES ON RETRIEVING REMOTE FILES:
1006
+ If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
1007
+ return a cached object, and touch the cache object upon recieving a
1008
+ 304.
1009
+
1010
+ NOTES ON FAILED REQUESTS:
1011
+ If there is an HTTP error while fetching an RSS object, the cached
1012
+ version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
1013
+ \*=======================================================================*/
1014
+
1015
+ define('MAGPIE_VERSION', '0.7');
1016
+
1017
+ $MAGPIE_ERROR = "";
1018
+
1019
  function fetch_rss ($url) {
1020
  // initialize constants
1021
  init();
1070
  if ( $cache_status == 'HIT' ) {
1071
  $rss = $cache->get( $cache_key );
1072
  if ( isset($rss) and $rss ) {
1073
+ // should be cache age
1074
  $rss->from_cache = 1;
1075
  if ( MAGPIE_DEBUG > 1) {
1076
  debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
1187
 
1188
  return $MAGPIE_ERROR;
1189
  }
 
1190
 
 
 
1191
  /*=======================================================================*\
1192
  Function: _fetch_remote_file
1193
  Purpose: retrieve an arbitrary remote file
1286
  }
1287
 
1288
  if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
1289
+ define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
 
 
 
 
1290
  }
1291
 
1292
  if ( !defined('MAGPIE_INPUT_ENCODING') ) {
1302
  }
1303
 
1304
  if ( !defined('MAGPIE_USER_AGENT') ) {
1305
+ $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
1306
+
 
 
 
1307
  if ( MAGPIE_CACHE_ON ) {
1308
  $ua = $ua . ')';
1309
  }
1323
  define('MAGPIE_USE_GZIP', true);
1324
  }
1325
  }
 
1326
 
1327
+ // NOTE: the following code should really be in Snoopy, or at least
1328
+ // somewhere other then rss_fetch!
1329
+
1330
+ /*=======================================================================*\
1331
+ HTTP STATUS CODE PREDICATES
1332
+ These functions attempt to classify an HTTP status code
1333
+ based on RFC 2616 and RFC 2518.
1334
+
1335
+ All of them take an HTTP status code as input, and return true or false
1336
+
1337
+ All this code is adapted from LWP's HTTP::Status.
1338
+ \*=======================================================================*/
1339
+
1340
+
1341
+ /*=======================================================================*\
1342
+ Function: is_info
1343
+ Purpose: return true if Informational status code
1344
+ \*=======================================================================*/
1345
  function is_info ($sc) {
1346
+ return $sc >= 100 && $sc < 200;
1347
  }
1348
 
1349
+ /*=======================================================================*\
1350
+ Function: is_success
1351
+ Purpose: return true if Successful status code
1352
+ \*=======================================================================*/
1353
  function is_success ($sc) {
1354
+ return $sc >= 200 && $sc < 300;
1355
  }
1356
 
1357
+ /*=======================================================================*\
1358
+ Function: is_redirect
1359
+ Purpose: return true if Redirection status code
1360
+ \*=======================================================================*/
1361
  function is_redirect ($sc) {
1362
+ return $sc >= 300 && $sc < 400;
1363
  }
1364
 
1365
+ /*=======================================================================*\
1366
+ Function: is_error
1367
+ Purpose: return true if Error status code
1368
+ \*=======================================================================*/
1369
  function is_error ($sc) {
1370
+ return $sc >= 400 && $sc < 600;
1371
  }
1372
 
1373
+ /*=======================================================================*\
1374
+ Function: is_client_error
1375
+ Purpose: return true if Error status code, and its a client error
1376
+ \*=======================================================================*/
1377
  function is_client_error ($sc) {
1378
+ return $sc >= 400 && $sc < 500;
1379
  }
1380
 
1381
+ /*=======================================================================*\
1382
+ Function: is_client_error
1383
+ Purpose: return true if Error status code, and its a server error
1384
+ \*=======================================================================*/
1385
  function is_server_error ($sc) {
1386
+ return $sc >= 500 && $sc < 600;
1387
  }
1388
 
1389
+ ################################################################################
1390
+ ## rss_cache.inc: from WordPress 1.5 ###########################################
1391
+ ################################################################################
1392
+
1393
  class RSSCache {
1394
  var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1395
  var $MAX_AGE = 43200; // when are files stale, default twelve hours
1527
  }
1528
  }
1529
  }
1530
+
1531
+ ################################################################################
1532
+ ## rss_utils.inc: from MagpieRSS 0.8a ##########################################
1533
+ ################################################################################
1534
+
1535
+ /*======================================================================*\
1536
+ Function: parse_w3cdtf
1537
+ Purpose: parse a W3CDTF date into unix epoch
1538
+
1539
+ NOTE: http://www.w3.org/TR/NOTE-datetime
1540
+ \*======================================================================*/
1541
 
1542
  function parse_w3cdtf ( $date_str ) {
1543
+
1544
+ # regex to match wc3dtf
1545
+ $pat = "/^\s*(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.\d+)?)?(?:([-+])(\d{2}):?(\d{2})|(Z))?)?)?)?\s*\$/";
1546
+
1547
+ if ( preg_match( $pat, $date_str, $match ) ) {
1548
+ list( $year, $month, $day, $hours, $minutes, $seconds) =
1549
+ array( $match[1], $match[3], $match[5], $match[7], $match[8], $match[9]);
1550
+
1551
+ # W3C dates can omit the time, the day of the month, or even the month.
1552
+ # Fill in any blanks using information from the present moment. --CWJ
1553
+ $default['hr'] = (int) gmdate('H');
1554
+ $default['day'] = (int) gmdate('d');
1555
+ $default['month'] = (int) gmdate('m');
1556
+
1557
+ if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif;
1558
+ if (is_null($day)) : $day = $default['day']; endif;
1559
+ if (is_null($month)) : $month = $default['month']; endif;
1560
+
1561
+ # calc epoch for current date assuming GMT
1562
+ $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1563
+
1564
+ $offset = 0;
1565
+ if ( $match[14] == 'Z' ) {
1566
+ # zulu time, aka GMT
1567
+ }
1568
+ else {
1569
+ list( $tz_mod, $tz_hour, $tz_min ) =
1570
+ array( $match[12], $match[13], $match[14]);
1571
+
1572
+ # zero out the variables
1573
+ if ( ! $tz_hour ) { $tz_hour = 0; }
1574
+ if ( ! $tz_min ) { $tz_min = 0; }
1575
+
1576
+ $offset_secs = (($tz_hour*60)+$tz_min)*60;
1577
+
1578
+ # is timezone ahead of GMT? then subtract offset
1579
+ #
1580
+ if ( $tz_mod == '+' ) {
1581
+ $offset_secs = $offset_secs * -1;
1582
+ }
1583
+
1584
+ $offset = $offset_secs;
1585
+ }
1586
+ $epoch = $epoch + $offset;
1587
+ return $epoch;
1588
+ }
1589
+ else {
1590
+ return -1;
1591
+ }
1592
  }
1593
 
1594
+ ################################################################################
1595
+ ## WordPress: wp_rss(), get_rss() ##############################################
1596
+ ################################################################################
1597
+
1598
  function wp_rss ($url, $num) {
1599
  //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1600
  $num_items = $num;
1632
  return false;
1633
  }
1634
  }
 
1635
  ?>
README.text CHANGED
@@ -1,16 +1,16 @@
1
  FeedWordPress
2
  =============
3
 
4
- * Author: [Charles Johnson](http://www.radgeek.com/contact)
5
- * Version: 0.96
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 into your WordPress blog; if you syndicate
13
- several newsfeeds then you can WordPress's posts database and templating
14
  engine as the back-end of an aggregation ("planet") website. I originally
15
  developed it because I needed a more flexible replacement for [Planet][] to
16
  use at [Feminist Blogs][].
@@ -29,22 +29,68 @@ hosts*.)
29
 
30
  [WordPress 1.5]: http://wordpress.org/development/2005/02/strayhorn/
31
 
32
- Installation & Requirements
33
- ---------------------------
34
- You'll need a website with WordPress 1.5 installed and configured and FTP or
35
- SFTP access to your web space. You'll probably also want to have either (1)
36
- the ability to create cron jobs on your web host, or (2) a computer of your
37
- own that has always-on Internet access.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  1. Install `feedwordpress.php` in your WordPress `plugins` directory
40
- and `update.php` in your WordPress `wp-content` directory.
41
 
42
  2. (Optional) Upgrade the copy of MagpieRSS packaged with WordPress by
43
  installing the new `rss-functions.php` (archived in
44
  `OPTIONAL/wp-includes`) into your WordPress `wp-includes` directory.
45
  Upgrading MagpieRSS is necessary if you want to take advantage of
46
- support for multiple post categories, RSS enclosures, and multiple
47
- character encodings. (Note, however, that support for
48
  transliterating between character encodings is a very complex and
49
  iffy prospect in some PHP environments, so if you intend to use
50
  a lot of feeds with alternate encodings you should make sure that
@@ -52,170 +98,190 @@ own that has always-on Internet access.
52
  the old MagpieRSS around to compare results.)
53
 
54
  3. Log in to the WordPress Dashboard and activate the FeedWordPress
55
- plugin. Go to Options --> Syndication to set up initial settings for
56
- the syndication link category ("Contributors") by default and the
57
- RPC secret word (blank by default, but you should probably set it to
58
- something.)
59
-
60
- 4. Set up links for syndication from the WordPress Dashboard using
61
- Links --> Syndicated or Links --> Import.
62
-
63
- 5. FeedWordPress is now *ready* to feed syndicated content into
64
- WordPress. In order for it to actually receive that content, either
65
- (1) have your contributors add WordPress's XML-RPC URI to their
66
- blog's list of URIs to ping when they update posts, (2) set up a
67
- cron job to check in on all the feeds on a regular basis, or (3)
68
- both. If you do (2), you can either set up a job to run `php
69
- update-feeds.php` on your web host, or set one up on any computer
70
- with always-on Internet access to request `update-feeds.php` over
71
- the web.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- If your copy of WordPress is installed at <http://www.zyx.com/blog>,
74
- and you set the secret word for XML-RPC pings to "foo", then your
75
- XML-RPC URI will be <http://www.zyx.com/blog/xmlrpc.php>, and the
76
- URI to request for `update-feeds.php` to update all feeds will be
77
- <http://www.zyx.com/blog/wp-content/update-feeds.php?shibboleth=foo>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- For detailed installation instructions, point your web browser to
80
- <http://projects.radgeek.com/feedwordpress/install>.
 
 
 
81
 
82
  Basic Concepts
83
  --------------
84
- FeedWordPress is written as a plugin for [WordPress 1.5][]. It is designed
85
- to store all the data it needs within the WordPress database and to make
86
- that data easy to manage from within the WordPress Dashboard.
87
 
88
  ### Contributors / Newsfeeds ###
89
 
90
- FeedWordPress uses the WordPress Links database to keep a list of the feeds
91
- from which it will syndicate content. WordPress allows you to place links in
92
- categories; FeedWordPress will treat all and only the links in one category
93
- (by default, this is a category named "Contributors"; you can change the
94
- category that FeedWordPress will use using Options --> Syndication).
95
 
96
  From WordPress's perspective, the list of Contributors are normal links, and
97
- they can be manipulated like other links through the WordPress Dashboard. If
98
- you need to add, remove, or change information for any contributors, you can
99
- do so easily under Links --> Syndicated. If you want to distribute the labor
100
- of adding, updating, and managing feeds, you can use the WordPress login and
101
- access privileges system. Users with an access level of 5 or greater can
102
- add, delete, and modify Contributors; users with an access level of 6 or
103
- greater can change syndication options.
104
-
105
- When FeedWordPress looks for new posts, it does so by retrieving one or all
106
- of the links from the Contributors category (depending on whether it has
107
- been told to scan for new posts on one or all of the feeds).
108
-
109
- __Feed settings:__ All of the information for a syndicated feed is stored in
110
- the WordPress Links database. Feeds in the category to be syndicated (by
111
- default, "Contributors") use several fields of the standard WordPress Link
112
- record:
113
-
114
- - The RSS URI is used to store the URI for the feed to be syndicated.
115
- (Note that this is *not* the same as the Link URI. The Link URI
116
- points to the human-readable *front page* of the website that the
117
- feed syndicates.)
118
-
119
- - The Link URI is used to store a URI to the human-readable front page
120
- (*not* the feed!) of the syndicated website. FeedWordPress
121
- automatically updates this URI using the URI that is reported by the
122
- newsfeed whenever it checks the feed for new posts, so if the page
123
- moves this will be reflected automatically on your Contributors
124
- links list.
125
-
126
- - The Link Name is used to store the title of the syndicated website.
127
- By default, FeedWordPress automatically updates the name of the link
128
- whenever it checks for new posts, using the title that the
129
- newsfeed reports (so that if a Contributor changes the title of her
130
- website, this is reflected automatically on your Contributors links
131
- list). This behavior can be turned off for all feeds through the
132
- settings in Options --> Syndication. The default behavior can be
133
- overridden for specific feeds using the feed setting
134
- `hardcode name`.
135
-
136
- - The Short Description is used to store the tagline of the syndicated
137
- website. By default, FeedWordPress automatically updates the
138
- description whenever it checks for new posts, using the tagline or
139
- description that the newsfeed reports (so that if a Contributor
140
- changes the tagline of her website, this is reflected automatically
141
- on your Contributors links list). his behavior can be turned off for
142
- all feeds through the settings in Options --> Syndication. The
143
- default behavior can be overridden for specific feeds using the feed
144
- setting `hardcode description`.
145
-
146
- - The Link Notes are used to store a collection of manually-encoded
147
- and automatically-generated settings that apply to this feed. The
148
- format of settings in Link Notes is:
149
-
150
- key1: value1
151
- key2: value2
152
- feed/key1: value1
153
- feed/key2: value2
154
-
155
- And so on. Values that are prefixed by 'feed/' are automatically
156
- generated from feed data every time the feed syndicated by this link
157
- is checked for updates. Values without the prefix are set manually
158
- by the user.
159
-
160
- The Link Notes section can be used to add, remove, and change custom feed
161
- settings. For example, if you want to *add* the feed setting for `unfamiliar
162
- author` with the value `filter`, you can do so by going to Links -->
163
- Syndicated, clicking the "Edit" link for the feed that you wish to add this
164
- setting for, and then adding the following, on a line by itself, to the Link
165
- Notes section:
166
-
167
- unfamiliar author: filter
168
-
169
- To remove the setting, follow the same procedure, but find the line for the
170
- feed setting that you want to remove and remove it. To change the value,
171
- simply change the text that follows after the colon.
172
-
173
- Most settings in the Link Notes have no effect on FeedWordPress, but you can
174
- use them to store information for templates to retrieve using the
175
- `get_feed_meta()` template function in a post context (see Template API
176
- below). For example, many aggregator sites use a "face" image for each feed
177
- to visually distinguish posts from different feeds. To implement a face
178
- feature, you could add something like this to each feed's Link Notes, on a
179
- line by itself:
180
-
181
- face: http://www.zyx.com/mugs/ugly
182
-
183
- The URI should be changed out for each feed to point to the appropriate
184
- image, of course. Then, to use the setting from within a template:
185
 
186
  // In a post context
187
  <?php $img = get_feed_meta('face'); if (strlen($img) > 0): ?>
188
  <img src="<?=$img?>" alt="" />
189
  <?php endif; ?>
190
 
191
- ... which will display the image, if any, whose URI is set in the "face"
192
- setting for the feed that post comes from. If there is no "face" setting for
193
- a particular feed, ``get_feed_meta()`` will return an empty string and no
194
- image will be displayed.
195
 
196
- Not all feed settings are only for templates. Some affect how FeedWordPress
197
- processes posts from that feed. Currently, the settings with special effects
198
- on FeedWordPress are `cats`, `hardcode name`, `hardcode description`,
199
- `hardcode categories`, `post status`, `comment status`, `ping status`,
200
- `unfamiliar author`, and `unfamiliar categories`. For descriptions of their
201
- effects, see Special Feed Settings below.
202
 
203
  ### Syndicated Posts ###
204
 
205
  Whenever FeedWordPress updates, it scans one or more of the feeds in its
206
  Contributors list and adds any new posts that it finds to the WordPress
207
- database. Syndicated posts are displayed on your WordPress pages like any
208
- other posts: they can be listed in archives by category, author, or date;
209
- they can be found with the search box; and they are included in the newsfeed
210
- of your blog.
211
-
212
- In your WordPress templates (Presentation --> Theme Editor) you can access
213
- special information about syndicated posts using functions provided by
214
- FeedWordPress, such as `is_syndicated()`, `the_syndication_source()`,
215
- `the_syndication_source_link()`, and `get_feed_meta()`. For example, here is
216
- the template code that I use (in a post context) to display both the
217
- author's name and the original source of the post in the templates for
218
- [Feminist Blogs][]:
219
 
220
  <cite class="feed">from <?php the_author_posts_link()?><?php
221
  if (is_syndicated() and (get_the_author() !== get_syndication_source())):
@@ -224,472 +290,220 @@ author's name and the original source of the post in the templates for
224
  echo '</a>';
225
  endif; ?></cite>
226
 
227
- For more information on template functions, see Template API below.
228
 
229
  ### Categories ###
230
 
231
- WordPress allows for posts to be placed in *categories*. Each syndicated
232
- post that FeedWordPress adds to the WordPress database is placed into a set
233
- of categories. FeedWordPress gets the list of category names to use from two
234
  sources:
235
 
236
- 1. Categories (or "tags") that the original author placed the post in
237
- on her blog
238
 
239
- 2. Categories that you set explicitly for each feed using the `cats`
240
- feed setting. For example, if you wanted all the posts from Alas, A
241
- Blog to be placed in the "Pacific Northwest" category and the
242
- "Cartoonists" category (in addition to any categories that they were
243
- placed in on Alas, A Blog), you could do this by going to Links -->
244
- Syndicated, clicking the "Edit" link for Alas, A Blog, and adding
245
- the following line to the Link Notes section:
246
-
247
- cats: Pacific Northwest:Cartoonists
248
-
249
- (The colon separates one category name from the next.)
250
 
251
  Given the list of category names, FeedWordPress looks for categories in the
252
  WordPress database with the same name as either (1) the category name, or
253
  (2) one of the "aliases" listed in the category description.
254
 
255
- __Aliases:__ Different often authors use slightly different names for
256
- categories that mean the same thing (contributors to Feminist Blogs, for
257
- example, used categories including "Feminism", "feministy stuff", "Women's
258
- Issues", "Gender Issues", "Gender Equality", and so on). If you want
259
- FeedWordPress to treat one category name as a synonym for another, you can
260
- do so by creating an "alias" for the category. For example, to make
261
- FeedWordPress treat posts that are placed in the category "feministy stuff"
262
- as if they had been placed in the category "Feminism", go to Manage -->
263
- Categories, find the category "Feminism" and click the "Edit" link for it,
264
- and then add the following to the Description field, on a line by itself:
265
 
266
  a.k.a.: feministy stuff
267
 
268
- You can add as many aliases as you like. You can also add any other text
269
- that you like to the Description without interfering with FeedWordPress's
270
- ability to use the aliases. Each alias must be on a line by itself.
271
-
272
- __Unfamiliar categories:__ By default, if one of the category names that a
273
- newsfeed provides is unfamiliar -- that is, if there is not yet any category
274
- with that name (or with that name as an alias) in your WordPress database --
275
- then by default FeedWordPress will *automatically create* a new category
276
- with that name and place the current post in it. The default behavior can be
277
- changed, using either the global settings in Options --> Syndication or the
278
- `unfamiliar categories` feed setting (see Feed Settings above), so that
279
- unfamiliar categories will not be added to the database. If you choose to
280
- disable the creation of new categories, you can also choose whether or not
281
- FeedWordPress should syndicate posts that do not match *any* of the
282
- categories that are currently in the database.
283
-
284
- One of the uses of this feature is filtering posts by category: if you want
285
- to your blog to syndicate only the posts in one particular category from a
286
- feed that has several categories, you could do so by creating a category by
287
- that name, adding the new feed(s), and then adding the following line to the
288
- Link Notes section of the feed(s) that you want to filter:
289
-
290
- unfamiliar categories: filter
291
-
292
- Since only posts in categories that are in your database will be included,
293
- and only the category or categories that you wanted posts from has been
294
- added to your database, this will filter out all the posts that aren't in
295
- the category or categories that you defined ahead of time. (Similarly, you
296
- could set up FeedWordPress so that *all* the feeds are filtered by author by
297
- creating the set of users named after the authors you want to syndicate, and
298
- then setting the default behavior for *all* feeds at Options -->
299
- Syndication).
300
-
301
- If you need a category filter with more complex logic, you can always create
302
- a `syndicated_item` filter in PHP (see Plugin API below) that manipulates
303
- the `['categories']` array of a syndicated item.
304
 
305
  ### Authors ###
306
 
307
  Most newsfeeds include information about the author of the items on them.
308
  (If a feed doesn't, then FeedWordPress will create an author's name based on
309
- the title of the feed from which the item was taken.) This information is
310
- used to determine the WordPress user that the post will be attributed to.
311
- Given the name of the author, FeedWordPress looks for authors in the
312
- WordPress database with the same name as either (1) their login, (2) their
313
- first name, (3) their nickname, (4) their full name, or (5) one of the
314
- "aliases" listed in the user's profile.
315
 
316
  __Aliases:__ If there is an author who posts under more than one name (for
317
  example, one of our contributors at [Feminist Blogs][] posts on several
318
  different blogs, sometimes using her full name and sometimes using only her
319
- first name), then you can ensure that FeedWordPress will attribute those
320
- posts to the same author by creating "aliases" for the author. For example,
321
- to make FeedWordPress treat posts by "Joseph Cardinal Ratzinger" and posts
322
- by "Pope Benedict XVI" as having the same author, go to Users --> Authors &
323
- Users, click on the "Edit" link for Pope Benedict XVI, and add a line like
324
- this to the Profile text:
325
 
326
- a.k.a.: Joseph Cardinal Ratzinger
327
 
328
- You can add as many aliases as you like. You can also add any other text
329
- that you like to the Profile without interfering with FeedWordPress's
330
- ability to use the aliases. Each alias must be on a line by itself.
331
 
332
  __Unfamiliar authors:__ By default, if the author named by the newsfeed is
333
  unfamiliar -- that is, if there is no-one with that name registered in the
334
- WordPress author's database -- then by default FeedWordPress will
335
- automatically create a new user account with the given name and attribute
336
- the post to the new user. The default behavior can be changed, using either
337
- the global settings in Options --> Syndication or the `unfamiliar author`
338
- feed setting (see Feed Settings above), so that posts by unfamiliar authors
339
- will either be attributed to a default author (instead of creating a new
340
- user account to attribute them to), or filtered out and not syndicated at
341
- all.
342
-
343
- One of the uses of this feature is filtering posts by author: if you want to
344
- your blog to syndicate only the posts by one particular author from a feed
345
- that has several authors, you could do so by creating a user account with
346
- that author's name, adding the new feed(s), and then adding the following
347
- line to the Link Notes section of the feed(s) that you want to filter:
348
-
349
- unfamiliar author: filter
350
-
351
- Since only posts by authors that are in your database will be included, and
352
- only the author that you wanted posts from has been added to your database,
353
- this will filter out posts by anyone else on the feeds with that setting.
354
- (Similarly, you could set up FeedWordPress so that *all* the feeds are
355
- filtered by author by creating the set of users named after the authors you
356
- want to syndicate, and then setting the default behavior for *all* feeds at
357
- Options --> Syndication).
358
-
359
- If you need an author filter with more complex logic than this allows, you
360
- can always create a `syndicated_item` filter in PHP (see Plugin API below)
361
- that manipulates the `['author_name']` or `['dc']['creator']` elements of a
362
- syndicated item.
363
-
364
- Special Feed Settings
365
- ---------------------
366
- Most feed settings (see Feed Settings above) have no effect on FeedWordPress
367
- itself, but can be useful because they can be accessed from templates using
368
- the `get_feed_meta()` template function in a post context (see Feed Settings
369
- above for an example). However, you can use some feed settings to affect how
370
- FeedWordPress will process posts from that particular feed. Currently, these
371
- special settings are:
372
-
373
- - `cats:` a colon-separated list of default categories for any post
374
- coming from this feed. So, for example, a this line in its Notes
375
- section:
376
-
377
- cats: computers:web
378
-
379
- ... will make FeedWordPress place any posts syndicated from that
380
- feed in the "computers" and "web" categories. Note that by default,
381
- FeedWordPress will place them in those categories *in addition to*
382
- any categories that the author of the post put them in on her own
383
- website. If you want to place posts from a feed *only* in the
384
- categories you explicitly set, then you should use the `cats`
385
- setting together with the `unfamiliar categories` setting. (See
386
- Categories above and `unfamiliar categories` below for the
387
- nitty-gritty.)
388
-
389
- - `hardcode name: (yes|no)`
390
-
391
- By default, FeedWordPress updates the value of the Link Name field
392
- automatically to reflect the title that is reported by a syndicated
393
- feed--so that if one of your contributors changes the title of her
394
- website, the change will be reflected on your Contributors list
395
- after the next update. If you want to stop this behavior (so that
396
- you can set the title of a Contributor link manually -- e.g. so that
397
- you can use an abbreviated form for reasons of space), you can
398
- change the default behavior for *all* feeds using the settings in
399
- Options --> Syndication. If you want to override the default
400
- behavior for only *one* feed, you can use the `hardcode name` feed
401
- setting. If FeedWordPress updates the title of all feeds by default,
402
- but you wish to use a manually-set title for one particular feed,
403
- you can add a line like this to the Link Notes section of the feed
404
- that you want to manually set the title for:
405
-
406
- hardcode name: yes
407
-
408
- Similarly, if FeedWordPress uses your manually-entered titles for
409
- all feeds by default, but you wish to use an automatically updated
410
- title for one particular feed, you can add a line like this to the
411
- Link Notes section of the feed that you want to manually set the
412
- title for:
413
-
414
- hardcode name: no
415
-
416
- If `hardcode name` is absent, or set to a value other than `yes` or
417
- `no`, FeedWordPress will follow the default behavior set under
418
- Options --> Syndication.
419
-
420
- - `hardcode description: (yes|no)`
421
-
422
- By default, FeedWordPress updates the value of the Link Description
423
- field automatically to reflect the tagline or description that is
424
- reported by a syndicated feed--so that if one of your contributors
425
- changes the tagline for her website, the change will be reflected on
426
- your Contributors list after the next update. If you want to stop
427
- this behavior (so that you can set the tagline of a Contributor link
428
- manually -- e.g. so that you can use it to provide information of
429
- another sort or use an abbreviated form for reasons of space), you
430
- can change the default behavior for *all* feeds using the settings
431
- in Options --> Syndication. If you want to override the default
432
- behavior for only *one* feed, you can use the `hardcode description`
433
- feed setting. If FeedWordPress updates the description of all feeds
434
- by default, but you wish to use a manually-set description for one
435
- particular feed, you can add a line like this to the Link Notes
436
- section of that particular feed:
437
-
438
- hardcode description: yes
439
-
440
- Similarly, if FeedWordPress uses your manually-entered descriptions
441
- for all feeds by default, but you wish to use an automatically
442
- updated description for one particular feed, you can add a line like
443
- this to the Link Notes section of that particular feed:
444
-
445
- hardcode description: no
446
-
447
- If `hardcode description` is absent, or set to a value other than
448
- `yes` or `no`, FeedWordPress will follow the default behavior set
449
- under Options --> Syndication.
450
-
451
- - `hardcode url: (yes|no)`
452
-
453
- By default, FeedWordPress updates the value of the Link URI field
454
- automatically to reflect the link to the front page of the website
455
- you are syndicating, as reported by the syndicated feed--so that if
456
- one of your contributors changes the front page of her website (from
457
- <http://www.zyx.com/blog/> to <http://www.zyx.com/>, say), the
458
- change will be reflected on your Contributors list after the next
459
- update. If you want to stop this behavior (so that you can set the
460
- human-readable URI that a Contributor link points to manually), you
461
- can change the default behavior for *all* feeds using the settings
462
- in Options --> Syndication. If you want to override the default
463
- behavior for only *one* feed, you can use the `hardcode url` feed
464
- setting. If FeedWordPress updates the human-readable URI of all
465
- feeds by default, but you wish to use a manually-set URI for one
466
- particular feed, you can add a line like this to the Link Notes
467
- section of that particular feed:
468
-
469
- hardcode url: yes
470
-
471
- Similarly, if FeedWordPress uses your manually-entered URIs for all
472
- feeds by default, but you wish to use an automatically updated URI
473
- for one particular feed, you can add a line like this to the Link
474
- Notes section of that particular feed:
475
-
476
- hardcode url: no
477
-
478
- If `hardcode url` is absent, or set to a value other than `yes` or
479
- `no`, FeedWordPress will follow the default behavior set under
480
- Options --> Syndication.
481
-
482
- - `hardcode categories: (yes|no)`
483
-
484
- **This setting has been deprecated.** If set to `yes` it is now
485
- treated as equivalent to `unfamiliar categories: default`. (See
486
- below.)
487
-
488
- - `post status: (publish|draft|private)`
489
-
490
- By default, FeedWordPress sets all new syndicated posts to be
491
- published immediately. If you want syndicated posts to have some
492
- other status (for example, to hold them as drafts for moderation, or
493
- to hold them as private posts), you can change the default behavior
494
- using Options --> Syndication. If you want to override the default
495
- post status for syndicated posts from *one particular feed*, you
496
- can do so using the `post status` feed setting. So, for example, if
497
- you have FeedWordPress set to publish new syndicated posts
498
- immediately, but you want posts from one particular feed to be put
499
- into the drafts pile for moderation, you can do so by placing the
500
- following line in the Link Notes section of that feed's Contributor
501
- link:
502
-
503
- post status: draft
504
-
505
- - `comment status: (open|closed)`
506
-
507
- By default, FeedWordPress sets all new syndicated posts to be
508
- closed for comments--if users want to comment on posts then it's
509
- often best for them to comment on the *original* website rather than
510
- your syndication site. But if you want syndicated posts to be open
511
- for comments, you can change the default behavior for all syndicated
512
- posts using the settings in Options --> Syndication. If you want to
513
- override the default comment status for *one particular feed*, you
514
- can do so using the `comment status` feed setting. So, for example,
515
- if you have FeedWordPress set not to open up new syndicated posts
516
- for comments, but you want posts from *one particular feed* to be
517
- opened for comments, then you can do so by placing the following
518
- line in the Link Notes section of that feed's Contributor link:
519
-
520
- comment status: open
521
-
522
- - `ping status: (open|closed)`
523
-
524
- By default, FeedWordPress sets all new syndicated posts *not* to
525
- accept PingBack or TrackBack link notifications ("pings"). If you
526
- want syndicated posts on your syndication site to accept pings, you
527
- can change the default behavior for all syndicated posts using the
528
- settings in Options --> Syndication. If you want to override the
529
- default ping status for *one particular feed*, you can do so using
530
- the `ping status` feed setting. So, for example, if you have
531
- FeedWordPress set not to accept pings for new syndicated posts, but
532
- you want posts from *one particular feed* to accept pings, then you
533
- can do so by placing the following line in the Link Notes section of
534
- that feed's Contributor link:
535
-
536
- ping status: open
537
-
538
- - `unfamiliar authors: (create|default|filter)`
539
-
540
- By default, FeedWordPress creates new author accounts whenever it
541
- finds a new post that is by an author whose name is not already in
542
- the WordPress database, and uses that account for this post and any
543
- future posts by an author of that name. FeedWordPress also allows
544
- you to attribute posts by unfamiliar authors to a *default* user
545
- account (currently, this means the System Administrator account),
546
- *instead of* creating a new author, or simply not to syndicate posts
547
- by unfamiliar authors (thus only syndicating posts by authors who
548
- are already in the database).
549
-
550
- Which of these FeedWordPress does by default can be set using the
551
- settings in Options --> Syndication. You can also override the
552
- default behavior for specific feeds by adding the `unfamiliar
553
- author` feed setting to the Link Notes section of a feed. For
554
- example, to ensure that FeedWordPress filters out posts by
555
- unfamiliar authors for one particular feed, add the setting:
556
-
557
- unfamiliar author: filter
558
-
559
- To ensure that FeedWordPress assigns posts by unfamiliar authors to
560
- the default user account instead of creating a new user account, add
561
- the setting:
562
-
563
- unfamiliar author: default
564
-
565
- If you changed the default behavior under Options --> Syndication
566
- but want to ensure that FeedWordPress creates new author accounts
567
- for unfamiliar authors on one specific feed, add the setting:
568
-
569
- unfamiliar author: create
570
-
571
- If the setting is anything other than `create`, `default`, or
572
- `filter`, FeedWordPress will ignore the setting and follow the
573
- default behavior that you specified under Options --> Syndication.
574
-
575
- - `unfamiliar categories: (create|default|filter)`
576
-
577
- By default, FeedWordPress creates categories whenever it finds a new
578
- post that is placed in categories whose names are not already in the
579
- WordPress database. FeedWordPress allows you to change this
580
- behavior, so that it will *not* create new category names. It also
581
- allows you to choose whether or not posts must match *at least one*
582
- familiar category to be syndicated at all.
583
-
584
- Which of these FeedWordPress does by default can be set using the
585
- settings in Options --> Syndication. You can also override the
586
- default behavior for specific feeds by adding the `unfamiliar
587
- categories` feed setting to the Link Notes section of a feed. For
588
- example, to ensure that, when adding new posts from *one particular
589
- feed*, FeedWordPress does *not* create new categories, and filters
590
- out any posts that don't match *at least one* of the categories that
591
- you have already defined, add the setting:
592
-
593
- unfamiliar categories: filter
594
-
595
- To ensure that FeedWordPress does *not* create new categories, but
596
- *will* still syndicate categories even if they don't match any of
597
- the pre-defined categories, add the following to the feed settings:
598
-
599
- unfamiliar categories: default
600
-
601
- If you changed the default behavior under Options --> Syndication
602
- but want to ensure that FeedWordPress creates new categories for
603
- posts from one particular feed, add the setting:
604
-
605
- unfamiliar categories: create
606
 
607
- If the setting is anything other than `create`, `default`, or
608
- `filter`, FeedWordPress will ignore the setting and follow the
609
- default behavior that you specified under Options --> Syndication.
 
610
 
611
  Template API
612
  ------------
613
- When activated, FeedWordPress makes the following functions available for
614
- use by themes/templates:
615
 
616
- * ``is_syndicated()``: in a post context, returns ``TRUE`` if the post
617
- was syndicated from another website, or ``FALSE`` if it was
618
- originally posted here
619
 
620
- * ``get_syndication_permalink()``: in a post context, returns the URI
621
- of the permalink for this post *on the website it was syndicated from*
622
 
623
- * ``the_syndication_permalink()``: in a post context, outputs the
624
- value returned by ``get_syndication_permalink()``
625
 
626
- * ``get_syndication_source_link()``: in a post context, returns the
627
- URI of the front page (*not* the feed) of the website this post was
628
- syndicated from
629
 
630
- * ``the_syndication_source_link()``: in a post context, outputs the
631
- URI returned by ``get_syndication_source_link()``
 
632
 
633
  * ``get_syndication_source()``: in a post context, returns the
634
  human-readable title of the website that a syndicated post was
635
  syndicated from
636
 
637
  * ``the_syndication_source()``: in a post context, outputs the value
638
- returned by ``get_syndication_source()``
639
 
640
- * ``get_syndication_feed():`` in a post context, returns the URI of
641
- the feed (*not* the front page) that this post was syndicated from
642
 
643
  * ``the_syndication_feed()``: in a post context, outputs the value
644
- returned by ``get_syndication_feed()``
645
 
646
- * ``get_feed_meta($key)``: in a post context, returns the value, if
647
- any, of the feed setting ``$key`` for the feed that this post was
648
- syndicated from
649
 
650
  By default, FeedWordPress also places a filter on the standard functions
651
- ``get_permalink()`` and ``the_permalink()`` that substitutes the URI
652
- returned by ``get_syndication_permalink()`` for the URI generated by
653
- WordPress. This means that by default the permalinks listed on your website
654
- and in your newsfeed will link to the location of the posts on the source
655
- website, *not* to their location on your website. You can switch this
656
- behavior on or off at Options --> Syndication in the WordPress Dashboard.
657
-
658
- ### Plugin API ###
659
-
660
- FeedWordPress creates five hooks through the WordPress plugin architecture
661
- that you can plug in to using PHP WordPress plugins, to supplement ordinary
662
- FeedWordPress behavior, or to filter posts according to criteria that you
663
- set. The hooks are the action ``feedwordpress_update``, the action
664
- ``feedwordpress_check_feed``, the action ``feedwordpress_update_complete``,
665
- the filter ``syndicated_item``, the filter ``syndicated_post``, the action
666
  ``post_syndicated_item``, and the action ``update_syndicated_item``.
667
 
668
  For more information, see <http://projects.radgeek.com/feedwordpress/api>.
669
 
670
  License
671
  -------
672
- The FeedWordPress plugin is copyright (c) 2005 by Charles Johnson. It uses
673
- code derived or translated from:
674
 
675
- - [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](mailto:kellan@protest.net)
676
- - [HTTP Navigator 2][] by [Keyvan Minoukadeh](mailto:keyvan@k1m.com)
677
- - [Ultra-Liberal Feed Finder][] by [Mark Pilgrim](mailto:mark@diveintomark.org)
 
678
 
679
  according to the terms of the [GNU General Public License][].
680
 
681
- This program is free software; you can redistribute it and/or modify it
682
- under the terms of the [GNU General Public License][] as published by the
683
- Free Software Foundation; either version 2 of the License, or (at your
684
- option) any later version.
685
 
686
- This program is distributed in the hope that it will be useful, but WITHOUT
687
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
688
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
689
- more details.
690
 
691
  [wp-rss-aggregate.php]: http://laughingmeme.org/archives/002203.html
 
692
  [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/
693
  [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/
 
694
  [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html
695
 
1
  FeedWordPress
2
  =============
3
 
4
+ * Author: [Charles Johnson](http://radgeek.com/contact)
5
+ * Version: 0.97
6
  * Project URI: <http://projects.radgeek.com/feedwordpress>
7
+ * License: GPL 2. See License below for copyright jots and tittles.
8
 
9
  Introduction
10
  ------------
11
+ FeedWordPress is an Atom/RSS aggregator for WordPress 1.5. It syndicates content
12
+ from newsfeeds that you choose into your WordPress blog; if you syndicate
13
+ several newsfeeds then you can use WordPress's posts database and templating
14
  engine as the back-end of an aggregation ("planet") website. I originally
15
  developed it because I needed a more flexible replacement for [Planet][] to
16
  use at [Feminist Blogs][].
29
 
30
  [WordPress 1.5]: http://wordpress.org/development/2005/02/strayhorn/
31
 
32
+ Installation
33
+ ------------
34
+ ### Requirements ###
35
+
36
+ To use version 0.97 of FeedWordPress, you will need:
37
+
38
+ 1. an installed configured copy of WordPress 1.5.x. (It *won't work* with
39
+ WP 1.2 or WP 1.6 development builds.)
40
+
41
+ 2. FTP or SFTP access to your web host
42
+
43
+ And you'll probably also want to have either:
44
+
45
+ 1. the ability to create cron jobs on your web host, or at least
46
+
47
+ 2. a computer of your own and always-on Internet access
48
+
49
+ ### Installation ###
50
+
51
+ #### Upgrades ####
52
+
53
+ To *upgrade* an existing installation of FeedWordPress to version 0.97:
54
+
55
+ 1. Download the FeedWordPress archive in zip or gzipped tar format and
56
+ extract the files on your computer. Replace your existing FeedWordPress
57
+ files with the new files. Be sure to upgrade `rss-functions.php` if you
58
+ use the optional MagpieRSS upgrade, or don't use it yet but do want to
59
+ syndicate Atom 1.0 feeds.
60
+
61
+ 2. **Immediately** log in to the WordPress Dashboard, and go to Options -->
62
+ Syndicated. Follow the directions to launch the database upgrade
63
+ procedure. The new version of FeedWordPress incorporates some
64
+ long-needed improvements, but old meta-data needs to be updated to
65
+ prevent duplicate posts and other possible maladies. If you're upgrading
66
+ an existing installation, updates and FeedWordPress template functions
67
+ *will not work* until you've done the upgrade.
68
+
69
+ 3. Take a coffee break while the upgrade runs. It should, hopefully, finish
70
+ within a few minutes even on relatively large databases.
71
+
72
+ 4. `update-feeds.php` has been overhauled to improve performance and ease
73
+ of use, and also to make errors easier to detect and eliminate. The
74
+ overhaul doesn't require any changes to your set up *if* you used
75
+ XML-RPC pings, or command-line PHP, to do scheduled updates. It *does*
76
+ affect you if you used curl or some other tool to send HTTP requests to
77
+ `update-feeds.php`: your old cron job will probably not work anymore.
78
+ See [Setting Up Feed Updates][] below to get scheduled updates back on
79
+ track.
80
+
81
+ 5. Enjoy your new installation of FeedWordPress.
82
+
83
+ #### New Installations ####
84
 
85
  1. Install `feedwordpress.php` in your WordPress `plugins` directory
86
+ and `update-feeds.php` in your WordPress `wp-content` directory.
87
 
88
  2. (Optional) Upgrade the copy of MagpieRSS packaged with WordPress by
89
  installing the new `rss-functions.php` (archived in
90
  `OPTIONAL/wp-includes`) into your WordPress `wp-includes` directory.
91
  Upgrading MagpieRSS is necessary if you want to take advantage of
92
+ support for Atom 1.0, multiple post categories, RSS enclosures, and
93
+ multiple character encodings. (Note, however, that support for
94
  transliterating between character encodings is a very complex and
95
  iffy prospect in some PHP environments, so if you intend to use
96
  a lot of feeds with alternate encodings you should make sure that
98
  the old MagpieRSS around to compare results.)
99
 
100
  3. Log in to the WordPress Dashboard and activate the FeedWordPress
101
+ plugin.
102
+
103
+ 4. While you're at the Dashboard, once the plugin is activated, you can
104
+ go to Options --> Syndication and set (1) the link category that
105
+ FeedWordPress will syndicate links from (by default, "Contributors"),
106
+ and (2) a "secret word" for your RPC-XML updating interface. This
107
+ provides some light security by keeping passing ruffians from saying
108
+ "Update all the feeds" at will to your FeedWordPress installation.
109
+
110
+ 5. Go to Links --> Syndicated to set up the list of sites that you want
111
+ FeedWordPress to syndicate onto your blog. (If you have the feeds you
112
+ want to aggregate in a service such as Bloglines, you may prefer to
113
+ export them to an OPML file and use WordPress's Links --> Import to
114
+ import them into the contributors category.)
115
+
116
+ #### Setting Up Feed Updates ####
117
+
118
+ FeedWordPress is now ready to accept posts from its syndication sources.
119
+ Unfortunately, it doesn't yet know *when* to go get them. (**This may be true
120
+ even if you are upgrading an existing installation of FeedWordPress:** your old
121
+ cron job will still work if you used command-line PHP or blogging software pings
122
+ to do updates, but it will need to be fixed if you used curl or another tool to
123
+ send HTTP requests to `update-feeds.php`.)
124
+
125
+ You can load in syndicated posts for the first time by pointing your web browser
126
+ to `update-feeds.php`. If you have WordPress installed at, say,
127
+ <http://www.zyx.com/blog> then you should point your browser to
128
+ <http://www.zyx.com/blog/wp-content/update-feeds.php> and log in as any user in
129
+ the user database. (You may want to create a new "dummy" user for doing
130
+ scheduled updates, using **Users --> Authors & Users --> Add New User**. Tell
131
+ FeedWordPress to update all feeds, and you'll get the first wave of posts
132
+ imported into the database.
133
+
134
+ Congratulations! You should now have an aggregator site full of delicious
135
+ syndicated content hot off of the newswires. Now you just need a way to *keep*
136
+ the content freshly updated. Unless you enjoy manually browsing to
137
+ `update-feeds.php` every hour on the hour, you'll probably want to do this by
138
+ setting up your site for automated updates.
139
+
140
+ You can pull that off in one of two ways, or by a mixture of both:
141
+
142
+ 1. **The Blogging Software Ping Method:** You can get all of your
143
+ contributors to add you to the list of URIs that they notify of updates:
144
+ while FeedWordPress is activated, it will accept XML-RPC "recently
145
+ updated" pings in the standard format accepted by Weblogs.com,
146
+ Ping-O-Matic, Technorati, and other services. Most blogging software
147
+ allows users to add a URI to the list of URIs that get pinged on each
148
+ update. (See, for example, Options --> Writing --> Update Services in
149
+ WordPress, or Configuration --> Preferences --> Publicity / Remote
150
+ Interfaces / TrackBack in Movable Type.)
151
+
152
+ If you can get a contributor to add your XML-RPC URI to her
153
+ services-to-ping list (if you have WordPress installed at
154
+ <http://www.zyx.com/blog>, say, the URI to add should be
155
+ <http://www.zyx.com/blog/xmlrpc.php>), then whenever she updates her
156
+ blog, her blogging software will ping your FeedWordPress installation,
157
+ and FeedWordPress will look up her feed to grab the new posts off of
158
+ it.
159
+
160
+ 2. **The Scheduled Update Job Method:** You may very well not be able to
161
+ get all your contributors to add your site to their blogging software's
162
+ ping list, and even if you do you may want to have a back-up option to
163
+ catch updates later even if the ping fails to go through on one
164
+ particular occasion. You'll need to create a scheduled job to
165
+ periodically check for updates on *all* the feeds. You'll need either
166
+ (a) the ability to create cron jobs on your web host or (b) access to
167
+ another computer with a reliable, always-on Internet connection.
168
+
169
+ If you *can* create a crontab on your web host, then the best thing to
170
+ do is to create a cron job that will run update-feeds.php through the
171
+ PHP command-line interface. For example, if you have WordPress installed
172
+ in `~/www/wp` (where ~ is your home directory), you might insert the
173
+ following line into your crontab:
174
 
175
+ 25 * * * * cd $HOME/www/wp/wp-content ; php -q update-feeds.php
176
+
177
+ If you *don't* have access to (a), you can still save the day using
178
+ another computer with always-on Internet access that sends a POST
179
+ request to the `update-feeds.php` URI on a regular schedule. So, for
180
+ example, if you have WordPress installed at <http://www.zyx.com/blog>,
181
+ and you have a dummy user in your WordPress database with the login name
182
+ 'login' and the password 'pass', then you could add the following line
183
+ to the crontab on a home Linux box:
184
+
185
+ 25 * * * * curl --user login:pass http://www.zyx.com/blog/wp-content/update-feeds.php -d update=quiet
186
+
187
+ The `-d update=quiet` switch ensures that (1) `update-feeds.php` will
188
+ receive an HTTP POST request rather than an HTTP GET request (which
189
+ is important, since it won't take any actions with side-effects -- such
190
+ as checking for new posts -- unless it receives an HTTP POST); it also
191
+ tells it to suppress the HTML output that it would generate for normal
192
+ web browsers, and only to output text if it encounters errors (this will
193
+ keep the number of e-mails you receive from the Cron Daemon to a
194
+ minimum).
195
 
196
+ If you are using Windows XP and have a version of curl (such as the
197
+ version included in [Cygwin][]), you can create a Scheduled Task to
198
+ similar effect.
199
+
200
+ [Cygwin]: http://www.cygwin.com/
201
 
202
  Basic Concepts
203
  --------------
204
+ FeedWordPress is written as a plugin for [WordPress 1.5][]. It is designed to
205
+ store all the data it needs within the WordPress database and to make that data
206
+ easy to manage from within the WordPress Dashboard.
207
 
208
  ### Contributors / Newsfeeds ###
209
 
210
+ FeedWordPress uses the WordPress Links database to keep a list of the feeds from
211
+ which it will syndicate content. WordPress allows you to place links in
212
+ categories; FeedWordPress will make use of all and only the links in one
213
+ category (by default, this is a category named "Contributors"; you can change
214
+ the category that FeedWordPress will use using **Options --> Syndication**).
215
 
216
  From WordPress's perspective, the list of Contributors are normal links, and
217
+ they can be manipulated like other links through the WordPress Dashboard.
218
+ However, FeedWordPress provides a nicer interface for adding, removing, or
219
+ changing information for the Contributor Links from the WordPress Dashboard,
220
+ under **Links --> Syndicated**.
221
+
222
+ If you want to distribute the labor of adding, updating, and managing feeds
223
+ between several people, you can use the WordPress login andaccess privileges
224
+ system. Any users with an access level of 5 or greater can add, delete, and
225
+ modify Contributors; users with an access level of 6 or greater can change
226
+ syndication options.
227
+
228
+ When FeedWordPress looks for new posts, it retrieves one or all of the links
229
+ from the Contributors category (depending on whether it has been told to scan
230
+ for new posts on one or all of the feeds), determines which of them should be
231
+ polled for updates (based on how long it has been since the last time each feed
232
+ was polled for updates), and then uses an HTTP conditional GET to check for
233
+ updates at the "RSS URI" for each Link that it selects. Any new posts are added
234
+ to the database, and old posts that have been updated since the last poll are
235
+ updated to reflect the new version.
236
+
237
+ __Feed settings:__ All of the information for a syndicated feed is stored in the
238
+ WordPress Links database, and can be easily edited using an interface that
239
+ FeedWordPress provides under **Links --> Syndicated**. (If you're curious about
240
+ the technical details of how the information is stored, you can find out more
241
+ under [API: How feed information is stored][].)
242
+
243
+ You can use a feed's **Edit** link under **Links --> Syndicated** to affect how
244
+ FeedWordPress prcesses posts from that feed. (Most of these options can either
245
+ be set for *one particular feed* using **Links --> Syndicated --> Edit**, or set
246
+ as the default for *all feeds* using **Options --> Syndication**.) The **Edit**
247
+ link also allows you to set **Custom Feed Settings** for use in templates,
248
+ through the use of the [`get_feed_meta()`][get_feed_meta] template function in a
249
+ post context (see [Template API][]). For example, many aggregator sites use a
250
+ "face" image for each feed to visually distinguish posts from different feeds.
251
+ To implement a face feature, you could add a custom setting for each Contributor
252
+ Link, with the key of "face" and a URI such as "http://www.zyx.com/mugs/ugly"
253
+ for the value. (The URI should be changed out for each feed to point to the
254
+ appropriate image, of course.) Then, to use the setting from within a template,
255
+ add something like:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
  // In a post context
258
  <?php $img = get_feed_meta('face'); if (strlen($img) > 0): ?>
259
  <img src="<?=$img?>" alt="" />
260
  <?php endif; ?>
261
 
262
+ ... which will display the image, if any, whose URI is set in the "face" setting
263
+ for the feed that post comes from. If there is no "face" setting for a
264
+ particular feed, [`get_feed_meta()`][get_feed_meta] will return an empty string
265
+ and no image will be displayed.
266
 
267
+ [API: How feed information is stored]: http://projects.radgeek.com/feedwordpress/api#how-feed-information-is-stored
 
 
 
 
 
268
 
269
  ### Syndicated Posts ###
270
 
271
  Whenever FeedWordPress updates, it scans one or more of the feeds in its
272
  Contributors list and adds any new posts that it finds to the WordPress
273
+ database. Syndicated posts are displayed on your WordPress pages like any other
274
+ posts: they can be listed in archives by category, author, or date; they can be
275
+ found with the search box; and they are included in the newsfeed of your blog.
276
+
277
+ In your WordPress templates (**Presentation --> Theme Editor**) you can access
278
+ special information about syndicated posts using [functions provided by
279
+ FeedWordPress][Template API], such as [`is_syndicated()`][is_syndicated],
280
+ [`the_syndication_source()`][the_syndication_source],
281
+ [`the_syndication_source_link()`][the_syndication_source_link], and
282
+ [`get_feed_meta()`][get_feed_meta]. For example, here is the template code that
283
+ I use (in a post context) to display both the author's name and the original
284
+ source of the post in the templates for [Feminist Blogs][]:
285
 
286
  <cite class="feed">from <?php the_author_posts_link()?><?php
287
  if (is_syndicated() and (get_the_author() !== get_syndication_source())):
290
  echo '</a>';
291
  endif; ?></cite>
292
 
293
+ For more information on template functions, see [Template API][].
294
 
295
  ### Categories ###
296
 
297
+ WordPress allows for posts to be placed in *categories*. Each syndicated post
298
+ that FeedWordPress adds to the WordPress database is placed into a set of
299
+ categories. FeedWordPress gets the list of category names to use from two
300
  sources:
301
 
302
+ 1. Categories (or "tags") that the original author placed the post in on
303
+ her blog
304
 
305
+ 2. Categories that you set explicitly for each feed using the
306
+ **Categories** checkbox under **Links --> Syndicated --> Edit**. For
307
+ example, if you wanted all the posts from Alas, A Blog to be placed in
308
+ the "Pacific Northwest" category and the "Cartoonists" category (*in
309
+ addition to* any other categories that they were placed in on Alas, A
310
+ Blog), you could do this by creating the categories, going to **Links
311
+ --> Syndicated**, clicking the "Edit" link for Alas, A Blog, and
312
+ checking those two categories under the checkbox captioned "Categories."
 
 
 
313
 
314
  Given the list of category names, FeedWordPress looks for categories in the
315
  WordPress database with the same name as either (1) the category name, or
316
  (2) one of the "aliases" listed in the category description.
317
 
318
+ __Aliases:__ Different often authors use slightly different names for categories
319
+ that mean the same thing (contributors to Feminist Blogs, for example, used
320
+ categories including "Feminism", "feministy stuff", "Women's Issues", "Gender
321
+ Issues", "Gender Equality", and so on). If you want FeedWordPress to treat one
322
+ category name as a synonym for another, you can do so by creating an "alias" for
323
+ the category. For example, to make FeedWordPress treat posts that are placed in
324
+ the category "feministy stuff" as if they had been placed in the category
325
+ "Feminism", go to **Manage --> Categories**, find the category "Feminism" and
326
+ click the "Edit" link for it, and then add the following to the Description
327
+ field, on a line by itself:
328
 
329
  a.k.a.: feministy stuff
330
 
331
+ You can add as many aliases as you like. You can also add any other text that
332
+ you like to the Description without interfering with FeedWordPress's ability to
333
+ use the aliases. Each alias must be on a line by itself.
334
+
335
+ __Unfamiliar categories:__ If one of the category names that a newsfeed provides
336
+ is *unfamiliar* -- that is, if there is not yet any category in your WordPress
337
+ database that either has that name, or uses that name as an alias -- then by
338
+ default FeedWordPress will *automatically create* a new category with that name
339
+ and place the current post in it. The default behavior can be changed so that
340
+ unfamiliar categories will *not* be added to the database, using the
341
+ **Unfamiliar categories** setting, either for *all* feeds (under **Options -->
342
+ Syndication**) or for *one particular feed* (under **Links --> Syndicated**).
343
+
344
+ If you choose to disable the creation of new categories, either for all feeds or
345
+ for one particular feed, then you can also choose whether or not FeedWordPress
346
+ should syndicate posts that do not match *any* of the categories that are
347
+ currently in the database. This allows you to do some simple filtering of posts
348
+ by category: if you want to your blog to syndicate only the posts in one
349
+ particular category from a feed that has several categories, you could do so by
350
+ creating a category by that name, adding the new feed(s), and then setting
351
+ **Unfamiliar categories** under **Links --> Syndicated --> Edit** to "don't
352
+ create new categories and don't syndicate posts unless they match at least one
353
+ familiar category".
354
+
355
+ Since only posts in categories that are in your database will be included, and
356
+ only the category or categories that you wanted posts from has been added to
357
+ your database, this will filter out all the posts that aren't in the category or
358
+ categories that you defined ahead of time. (Similarly, you could set up
359
+ FeedWordPress so that *all* the feeds are filtered by author by creating the set
360
+ of users named after the authors you want to syndicate, and then setting the
361
+ default behavior for *all* feeds at **Options --> Syndication**).
362
+
363
+ If you need a category filter with more complex logic, you can always create a
364
+ `syndicated_item` filter in PHP (see [Plugin API][]) that manipulates the
365
+ `['categories']` array of a syndicated item.
 
366
 
367
  ### Authors ###
368
 
369
  Most newsfeeds include information about the author of the items on them.
370
  (If a feed doesn't, then FeedWordPress will create an author's name based on
371
+ the title of the feed from which the item was taken.) This information is used
372
+ to determine the WordPress user that the post will be attributed to. Given the
373
+ name of the author, FeedWordPress looks for authors in the WordPress database
374
+ with the same name as either (1) their login, (2) their first name, (3) their
375
+ nickname, (4) their full name, or (5) one of the "aliases" listed in the
376
+ user's profile.
377
 
378
  __Aliases:__ If there is an author who posts under more than one name (for
379
  example, one of our contributors at [Feminist Blogs][] posts on several
380
  different blogs, sometimes using her full name and sometimes using only her
381
+ first name), then you can ensure that FeedWordPress will attribute those posts
382
+ to the same author by creating "aliases" for the author. For example, to make
383
+ FeedWordPress treat posts by "Joseph Cardinal Ratzinger" and posts by "Pope
384
+ Benedict XVI" as having the same author, go to **Users --> Authors & Users**,
385
+ click on the "Edit" link for Pope Benedict XVI, and add a line like this to the
386
+ Profile text:
387
 
388
+ a.k.a.: Joseph Cardinal Ratzinger
389
 
390
+ You can add as many aliases as you like. You can also add any other text that
391
+ you like to the Profile without interfering with FeedWordPress's ability to use
392
+ the aliases. Each alias must be on a line by itself.
393
 
394
  __Unfamiliar authors:__ By default, if the author named by the newsfeed is
395
  unfamiliar -- that is, if there is no-one with that name registered in the
396
+ WordPress author's database -- then by default FeedWordPress will automatically
397
+ create a new user account with the given name and attribute the post to the new
398
+ user. The default behavior can be changed, using either the global settings in
399
+ **Options --> Syndication** or the [feed settings][] under **Links -->
400
+ Syndicated --> Edit**, so that posts by unfamiliar authors will either be
401
+ attributed to a default author (instead of creating a new user account to
402
+ attribute them to), or filtered out and not syndicated at all.
403
+
404
+ One of the uses of this feature is to filtering posts by author: if you want to
405
+ your blog to syndicate only the posts by one particular author from a feed that
406
+ has several authors, you could do so by creating a user account with that
407
+ author's name, adding the new feed(s), and then setting **Unfamiliar authors**
408
+ under **Links --> Syndicated --> Edit** to "don't syndicate the post". Since
409
+ only posts by authors that are in your database will be included, and only the
410
+ author that you wanted posts from has been added to your database, this will
411
+ filter out posts by anyone else on the feeds with that setting. (Similarly, you
412
+ could set up FeedWordPress so that *all* the feeds are filtered by author by
413
+ creating the set of users named after the authors you want to syndicate, and
414
+ then setting the default behavior for *all* feeds at **Options -->
415
+ Syndication**).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
+ If you need an author filter with more complex logic than this allows, you can
418
+ always create a `syndicated_item` filter in PHP (see [Plugin API][]) that
419
+ manipulates the `['author_name']` or `['dc']['creator']` elements of a
420
+ syndicated item.
421
 
422
  Template API
423
  ------------
424
+ When activated, FeedWordPress makes the following functions available for use by
425
+ themes/templates:
426
 
427
+ * ``is_syndicated()``: in a post context, returns ``TRUE`` if the post was
428
+ syndicated from another website, or ``FALSE`` if it was originally
429
+ posted here
430
 
431
+ * ``get_syndication_permalink()``: in a post context, returns the URI of
432
+ the permalink for this post *on the website it was syndicated from*
433
 
434
+ * ``the_syndication_permalink()``: in a post context, outputs the value
435
+ returned by [``get_syndication_permalink()``][get_syndication_permalink]
436
 
437
+ * ``get_syndication_source_link()``: in a post context, returns the URI of
438
+ the front page (*not* the feed) of the website this post was syndicated
439
+ from
440
 
441
+ * ``the_syndication_source_link()``: in a post context, outputs the URI
442
+ returned by
443
+ [``get_syndication_source_link()``][get_syndication_source_link]
444
 
445
  * ``get_syndication_source()``: in a post context, returns the
446
  human-readable title of the website that a syndicated post was
447
  syndicated from
448
 
449
  * ``the_syndication_source()``: in a post context, outputs the value
450
+ returned by [``get_syndication_source()``][get_syndication_source]
451
 
452
+ * ``get_syndication_feed():`` in a post context, returns the URI of the
453
+ feed (*not* the front page) that this post was syndicated from
454
 
455
  * ``the_syndication_feed()``: in a post context, outputs the value
456
+ returned by [``get_syndication_feed()``][get_syndication_feed]
457
 
458
+ * ``get_feed_meta($key)``: in a post context, returns the value, if any,
459
+ of the feed setting ``$key`` for the feed that this post was syndicated
460
+ from
461
 
462
  By default, FeedWordPress also places a filter on the standard functions
463
+ ``get_permalink()`` and ``the_permalink()`` that substitutes the URI returned by
464
+ [``get_syndication_permalink()``][get_syndication_permalink] for the URI
465
+ generated by WordPress. This means that by default the permalinks listed on your
466
+ website and in your newsfeed will link to the location of the posts on the
467
+ source website, *not* to their location on your website. You can switch this
468
+ behavior on or off at **Options --> Syndication** in the WordPress Dashboard.
469
+
470
+ Plugin API
471
+ ----------
472
+ FeedWordPress creates five hooks through the WordPress plugin architecture that
473
+ you can plug in to using PHP WordPress plugins, to supplement ordinary
474
+ FeedWordPress behavior, or to filter posts according to criteria that you set.
475
+ The hooks are the action ``feedwordpress_update``, the action
476
+ ``feedwordpress_check_feed``, the action ``feedwordpress_update_complete``, the
477
+ filter ``syndicated_item``, the filter ``syndicated_post``, the action
478
  ``post_syndicated_item``, and the action ``update_syndicated_item``.
479
 
480
  For more information, see <http://projects.radgeek.com/feedwordpress/api>.
481
 
482
  License
483
  -------
484
+ The FeedWordPress plugin is copyright (c) 2005 by Charles Johnson. It uses code
485
+ derived or translated from:
486
 
487
+ - [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](kellan@protest.net)
488
+ - [MagpieRSS][] by [Kellan Elliot-McCrea](kellan@protest.net)
489
+ - [HTTP Navigator 2][] by [Keyvan Minoukadeh](keyvan@k1m.com)
490
+ - [Ultra-Liberal Feed Finder][] by [Mark Pilgrim](mark@diveintomark.org)
491
 
492
  according to the terms of the [GNU General Public License][].
493
 
494
+ This program is free software; you can redistribute it and/or modify it under
495
+ the terms of the [GNU General Public License][] as published by the Free
496
+ Software Foundation; either version 2 of the License, or (at your option) any
497
+ later version.
498
 
499
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY
500
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
501
+ PARTICULAR PURPOSE. See the GNU General Public License for more details.
 
502
 
503
  [wp-rss-aggregate.php]: http://laughingmeme.org/archives/002203.html
504
+ [MagpieRSS]: http://magpierss.sourceforge.net/
505
  [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/
506
  [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/
507
+
508
  [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html
509
 
wp-content/plugins/feedwordpress.php CHANGED
@@ -3,11 +3,11 @@
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.96
7
  Author: Charles Johnson
8
- Author URI: http://www.radgeek.com/
9
  License: GPL
10
- Last modified: 2005-05-08 1:54pm EDT
11
  */
12
 
13
  # This uses code derived from:
@@ -27,9 +27,14 @@ Last modified: 2005-05-08 1:54pm EDT
27
 
28
  # -- Don't change these unless you know what you're doing...
29
  define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/');
30
- define ('FEEDWORDPRESS_VERSION', '0.96');
31
  define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
32
 
 
 
 
 
 
33
  // Note that the rss-functions.php that comes prepackaged with WordPress is
34
  // old & busted. For the new hotness, drop a copy of rss-functions.php from
35
  // this archive into wp-includes/rss-functions.php
@@ -38,82 +43,113 @@ require_once (ABSPATH . WPINC . '/rss-functions.php');
38
  // Is this being loaded from within WordPress 1.5 or later?
39
  if (isset($wp_version) and $wp_version >= 1.5):
40
 
41
- # Syndicated items should not be folded, crumpled, mutilated, or
42
- # spindled by WordPress formatting filters. But we don't want to
43
- # interfere with filters for any locally-authored posts.
44
- #
45
- # What WordPress should really have is a way for upstream filters to
46
- # stop downstream filters from running at all. Since it doesn't, and
47
- # since a downstream filter can't access the original copy of the text
48
- # that is being filtered, what we will do here is (1) save a copy of the
49
- # original text upstream, before any other filters run, and then (2)
50
- # retrieve that copy downstream, after all the other filters run, if
51
- # this is a syndicated post
52
- #
53
- add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
54
- add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
 
 
 
 
55
 
56
- # Filter in original permalinks if the user wants that
57
- add_filter('post_link', 'syndication_permalink', 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Admin menu
60
- add_action('admin_menu', 'fwp_add_pages');
 
61
 
62
- # Inbound XML-RPC update methods
63
- add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
 
 
 
64
 
65
- # Outbound XML-RPC reform
66
- remove_action('publish_post', 'generic_ping');
67
- add_action('publish_post', 'fwp_catch_ping');
 
 
 
68
 
69
- $update_logging = get_settings('feedwordpress_update_logging');
 
 
70
 
71
- # -- Logging status updates to error_log, if you want it
72
- if ($update_logging == 'yes') :
73
- add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
74
- add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
75
- add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
76
- add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
77
- add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
78
-
79
- function log_feedwordpress_post ($id) {
80
- $post = wp_get_single_post($id);
81
- error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
82
- ."'{$post->post_title}' ({$post->post_date})");
83
- }
84
-
85
- function log_feedwordpress_update_post ($id) {
86
- $post = wp_get_single_post($id);
87
- error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
88
- ."'{$post->post_title}' ({$post->post_date})"
89
- ." (as of {$post->post_modified})");
90
- }
91
-
92
- function log_feedwordpress_update_feeds ($uri) {
93
- error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
94
- }
95
-
96
- function log_feedwordpress_check_feed ($feed) {
97
- $uri = $feed['uri']; $name = $feed['name'];
98
- error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
99
- }
100
-
101
- function log_feedwordpress_update_complete ($delta) {
102
- $mesg = array();
103
- if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
104
- if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
105
- if (empty($mesg)) $mesg[] = 'nothing changed';
106
-
107
- error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
108
- .(is_null($delta) ? "I don't syndicate <$uri>"
109
- : implode(' and ', $mesg)));
110
- }
111
- endif;
112
 
113
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- # -- Template functions for syndication sites
116
- function is_syndicated () { return (strlen(get_syndication_feed()) > 0); }
117
 
118
  function the_syndication_source_link () { echo get_syndication_source_link(); }
119
  function get_syndication_source_link () { list($n) = get_post_custom_values('syndication_source_uri'); return $n; }
@@ -124,18 +160,28 @@ function the_syndication_source () { echo get_syndication_source(); }
124
  function get_syndication_feed () { list($u) = get_post_custom_values('syndication_feed'); return $u; }
125
  function the_syndication_feed () { echo get_syndication_feed (); }
126
 
 
 
 
 
 
127
  function get_feed_meta ($key) {
128
- global $wpdb;
129
- $feed = get_syndication_feed();
130
-
131
  $ret = NULL;
132
- if (strlen($feed) > 0):
133
- $result = $wpdb->get_var("
134
- SELECT link_notes FROM $wpdb->links
135
- WHERE link_rss = '".$wpdb->escape($feed)."'"
136
- );
 
 
 
 
 
137
 
138
- $meta = FeedWordPress::notes_to_settings($result);
139
  $ret = $meta[$key];
140
  endif; /* if */
141
  return $ret;
@@ -148,7 +194,10 @@ function the_syndication_permalink () {
148
  echo get_syndication_permalink();
149
  }
150
 
151
- # -- Filters for templates and feeds
 
 
 
152
  $feedwordpress_the_syndicated_content = NULL;
153
 
154
  function feedwordpress_preserve_syndicated_content ($text) {
@@ -181,7 +230,59 @@ function syndication_permalink ($permalink = '') {
181
  endif;
182
  } // function syndication_permalink ()
183
 
184
- # -- Admin menu add-ons
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  function fwp_add_pages () {
186
  add_submenu_page('link-manager.php', 'Syndicated Sites', 'Syndicated', 5, basename(__FILE__), 'fwp_syndication_manage_page');
187
  add_options_page('Syndication Options', 'Syndication', 6, basename(__FILE__), 'fwp_syndication_options_page');
@@ -190,8 +291,13 @@ function fwp_add_pages () {
190
  function fwp_syndication_options_page () {
191
  global $wpdb, $user_level;
192
 
 
 
 
 
 
193
  $caption = 'Save Changes';
194
- if (isset($_REQUEST['action']) and $_REQUEST['action']=$caption):
195
  check_admin_referer();
196
 
197
  if ($user_level < 6):
@@ -205,6 +311,23 @@ function fwp_syndication_options_page () {
205
  update_option('feedwordpress_unfamiliar_category', $_REQUEST['unfamiliar_category']);
206
  update_option('feedwordpress_syndicated_post_status', $_REQUEST['post_status']);
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  if (isset($_REQUEST['comment_status']) and ($_REQUEST['comment_status'] == 'open')) :
209
  update_option('feedwordpress_syndicated_comment_status', 'open');
210
  else :
@@ -266,6 +389,20 @@ function fwp_syndication_options_page () {
266
  $unfamiliar_category[$uc] = ' checked="checked"';
267
  endif;
268
  $results = $wpdb->get_results("SELECT cat_id, cat_name, auto_toggle FROM $wpdb->linkcategories ORDER BY cat_id");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  ?>
270
  <div class="wrap">
271
  <h2>Syndication Options</h2>
@@ -300,44 +437,52 @@ function fwp_syndication_options_page () {
300
  </fieldset>
301
 
302
  <fieldset class="options">
303
- <legend>Syndicated Posts</egend>
304
- <table class="editform" width="100%" cellspacing="2" cellpadding="5">
305
- <tr><th width="33%" scope="row">Permalinks point to:</th>
306
- <td width="67%"><select name="munge_permalink" size="1">
307
- <option value="yes"<?=($munge_permalink=='yes')?' selected="selected"':''?>>original website</option>
308
- <option value="no"<?=($munge_permalink=='no')?' selected="selected"':''?>>this website</option>
309
- </select></td></tr>
310
 
311
- <tr><th width="33%" scope="row">Publication:</th>
312
- <td width="67%"><ul style="list-style:none">
 
 
 
 
 
 
 
 
313
  <li><label><input type="radio" name="post_status" value="publish"<?=($post_status=='publish')?' checked="checked"':''?> /> Publish syndicated posts immediately</label></li>
314
  <li><label><input type="radio" name="post_status" value="draft"<?=($post_status=='draft')?' checked="checked"':''?> /> Hold syndicated posts as drafts</label></li>
315
  <li><label><input type="radio" name="post_status" value="private"<?=($post_status=='private')?' checked="checked"':''?> /> Hold syndicated posts as private posts</label></li>
316
  </ul></td></tr>
317
 
318
- <tr><th width="33%" scope="row">Comments:</th>
319
- <td width="67%"><ul style="list-style:none">
320
- <li><input type="checkbox" name="comment_status" value="open"<?=($comment_status=='open')?' checked="checked"':''?> /> Allow comments on syndicated posts</label></li>
 
321
  </ul></td></tr>
322
 
323
- <tr><th width="33%" scope="row">Trackback and Pingback:</th>
324
- <td width="67%"><ul style="list-style:none">
325
- <li><input type="checkbox" name="ping_status" value="open"<?=($ping_status=='open')?' checked="checked"':''?> /> Accept pings on syndicated posts</li>
 
326
  </ul></td></tr>
327
 
328
- <tr><th width="33%" scope="row" style="vertical-align:top">Unfamiliar authors:</th>
329
- <td width="67%"><ul style="margin: 0;list-style:none">
330
  <li><label><input type="radio" name="unfamiliar_author" value="create"<?=$unfamiliar_author['create']?>/> create a new author account</label></li>
331
  <li><label><input type="radio" name="unfamiliar_author" value="default"<?=$unfamiliar_author['default']?> /> attribute the post to the default author</label></li>
332
  <li><label><input type="radio" name="unfamiliar_author" value="filter"<?=$unfamiliar_author['filter']?> /> don't syndicate the post</label></li>
333
  </ul></td></tr>
334
- <tr><th width="33%" scope="row" style="vertical-align:top">Unfamiliar categories:</th>
335
- <td width="67%"><ul style="margin: 0;list-style:none">
336
  <li><label><input type="radio" name="unfamiliar_category" value="create"<?=$unfamiliar_category['create']?>/> create any categories the post is in</label></li>
337
  <li><label><input type="radio" name="unfamiliar_category" value="default"<?=$unfamiliar_category['default']?>/> don't create new categories</li>
338
  <li><label><input type="radio" name="unfamiliar_category" value="filter"<?=$unfamiliar_category['filter']?>/> don't create new categories and don't syndicate posts unless they match at least one familiar category</label></li>
339
  </ul></td></tr>
340
 
 
 
 
 
341
  </select></td></tr>
342
  </table>
343
  <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
@@ -368,13 +513,19 @@ function fwp_syndication_options_page () {
368
 
369
  function fwp_syndication_manage_page () {
370
  global $user_level, $wpdb;
 
 
 
 
 
 
371
  ?>
372
  <?php $cont = true;
373
  if (isset($_REQUEST['action'])):
374
- //die("ACTION: '".$_REQUEST['action']."'");
375
  if ($_REQUEST['action'] == 'feedfinder') : $cont = fwp_feedfinder_page();
376
  elseif ($_REQUEST['action'] == 'switchfeed') : $cont = fwp_switchfeed_page();
377
- elseif ($_REQUEST['action'] == 'Delete Checked') : $cont = fwp_multidelete_page();
 
378
  endif;
379
  endif;
380
 
@@ -404,7 +555,7 @@ if ($cont):
404
  <table width="100%" cellpadding="3" cellspacing="3">
405
  <tr>
406
  <th width="20%"><?php _e('Name'); ?></th>
407
- <th width="50%"><?php _e('Feed'); ?></th>
408
  <th colspan="4"><?php _e('Action'); ?></th>
409
  </tr>
410
 
@@ -412,31 +563,44 @@ if ($cont):
412
  $alt_row = !$alt_row; ?>
413
  <tr<?=($alt_row?' class="alternate"':'')?>>
414
  <td><a href="<?=wp_specialchars($link->link_url)?>"><?=wp_specialchars($link->link_name)?></a></td>
415
- <?php if (strlen($link->link_rss) > 0): $caption='Switch Feed'; ?>
416
- <td style="font-size:smaller;text-align:center">
417
- <strong><a href="<?=$link->link_rss?>"><?=wp_specialchars($link->link_rss)?></a></strong>
418
- <em>check validity</em> <a style="vertical-align:middle"
419
- title="Check feed &lt;<?=wp_specialchars($link->link_rss)?>&gt; for validity"
420
- href="http://feedvalidator.org/check.cgi?url=<?=urlencode($link->link_rss)?>"><img
421
- src="../wp-images/smilies/icon_arrow.gif" alt="&rarr;" /></a></td>
422
- <?php else: $caption='Find Feed'; ?>
423
- <td style="background-color:#FFFFD0"><p><strong>no
424
- feed assigned</strong></p></td>
425
- <? endif; ?>
426
- <?php if (($link->user_level <= $user_level)): ?>
427
- <td><a href="link-manager.php?page=<?=basename(__FILE__)?>&amp;link_id=<?=$link->link_id?>&amp;action=feedfinder" class="edit"><?=$caption?></a></div></td>
428
- <td><a href="link-manager.php?link_id=<?=$link->link_id?>&amp;action=linkedit" class="edit"><?php _e('Edit')?></a></td>
429
- <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>
430
- <td><input type="checkbox" name="linkcheck[]" value="<?=$link->link_id?>" /></td>
431
- <?php else:
432
- echo "<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>\n";
433
- endif;
434
- echo "\n\t</tr>";
 
 
 
 
 
435
  ?>
436
- </tr>
 
 
 
437
  <?php
 
 
 
 
438
  endforeach;
439
- else: ?>
 
440
 
441
  <p>There are no websites currently listed for syndication.</p>
442
 
@@ -447,7 +611,7 @@ feed assigned</strong></p></td>
447
  <div class="wrap">
448
  <h2>Manage Multiple Links</h2>
449
  <div class="submit">
450
- <input type="submit" class="delete" name="action" value="Delete Checked" />
451
  </div>
452
  </div>
453
  </form>
@@ -573,7 +737,7 @@ function fwp_switchfeed_page () {
573
 
574
  if ($result): ?>
575
  <div class="updated"><p><a href="<?=$_REQUEST['feed_link']?>"><?=wp_specialchars($_REQUEST['feed_title'])?></a>
576
- has been added as a contributing site, using the newfeed at &lt;<a href="<?=$_REQUEST['feed']?>"><?=wp_specialchars($_REQUEST['feed'])?></a>&gt;.</p></div>
577
  <?php else: ?>
578
  <div class="updated"><p>There was a problem adding the newsfeed. [SQL: <?=wp_specialchars(mysql_error())?>]</p></div>
579
  <?php endif;
@@ -602,30 +766,616 @@ updated to &lt;<a href="<?=$_REQUEST['feed']?>"><?=wp_specialchars($_REQUEST['fe
602
  return true; // Continue
603
  }
604
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  function fwp_multidelete_page () {
606
  global $wpdb, $user_level;
607
- check_admin_referer();
 
 
 
 
 
608
  if ($user_level < 5):
609
  die (__("Cheatin' uh ?"));
610
- else:
611
- // Update link_rss
612
- $result = $wpdb->query("
613
- DELETE FROM $wpdb->links
614
- WHERE link_id IN (".implode(',',$_REQUEST['linkcheck']).")
615
- ");
616
 
617
- if ($result):
618
- $mesg = "Sites deleted from syndication list.";
619
- else:
620
- $mesg = "There was a problem deleting the sites from the syndication list. [SQL: ".mysql_error()."]";
 
 
 
 
621
  endif;
622
- echo "<div class=\"updated\">$mesg</div>\n";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  endif;
624
- return true;
625
  }
626
 
627
- # -- Outbound XML-RPC ping reform
628
- # 'coz it's rude to send 500 pings the first time your aggregator runs
 
 
 
629
  $fwp_held_ping = NULL; // NULL: not holding pings yet
630
 
631
  function fwp_hold_pings () {
@@ -652,8 +1402,11 @@ function fwp_catch_ping ($post_id = 0) {
652
  endif;
653
  }
654
 
655
- // class FeedWordPress: handle the updating of the feeds and plug in to the
656
- // XML-RPC interface
 
 
 
657
  class FeedWordPress {
658
  var $strip_attrs = array (
659
  array('[a-z]+', 'style'),
@@ -703,13 +1456,16 @@ class FeedWordPress {
703
  # like so:
704
  #
705
  # key: value
706
- # cats: computers:web
707
  # feed/key: value
708
  #
709
  # Keys that start with "feed/" are gleaned from the data supplied
710
  # by the feed itself, and will be overwritten with each update.
711
  #
712
- # The value of `cats` is used as a colon-separated (:) list of
 
 
 
713
  # default categories for any post coming from a particular feed.
714
  # (In the example above, any posts from this feed will be placed
715
  # in the "computers" and "web" categories--*in addition to* any
@@ -725,8 +1481,9 @@ class FeedWordPress {
725
  if ($result): foreach ($result as $link):
726
  if (strlen($link->link_rss) > 0):
727
  $sec = FeedWordPress::notes_to_settings($link->link_notes);
728
- $sec['uri'] = $link->link_rss;
729
- $sec['name'] = $link->link_name;
 
730
 
731
  // `hardcode categories` is deprecated in favor
732
  // of `unfamiliar categories`
@@ -738,9 +1495,8 @@ class FeedWordPress {
738
  endif;
739
 
740
  if (isset($sec['cats'])):
741
- $sec['cats'] = explode(':',$sec['cats']);
742
  endif;
743
- $sec['link_id'] = $link->link_id;
744
 
745
  $feeds[] = $sec;
746
  endif;
@@ -749,6 +1505,23 @@ class FeedWordPress {
749
  $this->feeds = $feeds;
750
  } // FeedWordPress::FeedWordPress ()
751
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  function notes_to_settings ($link_notes) {
753
  $notes = explode("\n", $link_notes);
754
 
@@ -766,45 +1539,121 @@ class FeedWordPress {
766
  return $sec;
767
  } // FeedWordPress::notes_to_settings ()
768
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  function update ($uri) {
770
  global $wpdb;
771
-
 
 
 
 
 
 
772
  do_action('feedwordpress_update', $uri);
773
 
774
  // Secret voodoo tag: URI for updating *everything*.
775
  $secret = RPC_MAGIC.FeedWordPress::rpc_secret();
776
 
777
- fwp_hold_pings();
778
 
779
  // Loop through and check for new posts
780
  $delta = NULL;
781
- foreach ($this->feeds as $feed) {
782
- if (($uri === $secret)
783
- or ($uri === $feed['uri'])
784
- or ($uri === $feed['feed/link'])) {
785
- if (is_null($delta)) $delta = array('new' => 0, 'updated' => 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786
  do_action('feedwordpress_check_feed', array($feed));
787
  $added = $this->feed2wp($wpdb, $feed);
788
- if (isset($added['new'])) $delta['new'] += $added['new'];
789
- if (isset($added['updated'])) $delta['updated'] += $added['updated'];
790
- } /* if */
791
- } /* foreach */
792
-
793
  do_action('feedwordpress_update_complete', array($delta));
794
- fwp_release_pings();
795
 
796
  return $delta;
797
  }
798
 
799
  function feed2wp ($wpdb, $f) {
800
- $feed = fetch_rss($f['uri']);
801
  $new_count = array('new' => 0, 'updated' => 0);
802
 
803
  $this->update_feed($wpdb, $feed->channel, $f);
804
 
805
  if (is_array($feed->items)) :
806
  foreach ($feed->items as $item) :
807
- $post = $this->item_to_post($wpdb, $item, $feed->channel, $f);
808
  if (!is_null($post)) :
809
  $new = $this->add_post($wpdb, $post);
810
  if ( $new !== false ) $new_count[$new]++;
@@ -879,11 +1728,51 @@ class FeedWordPress {
879
  return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
880
  }
881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
882
  function update_feed ($wpdb, $channel, $f) {
883
- $link_id = $f['link_id'];
884
 
885
  if (!isset($channel['id'])) :
886
- $channel['id'] = $f['uri'];
887
  endif;
888
 
889
  $update = array();
@@ -904,13 +1793,28 @@ class FeedWordPress {
904
  endif;
905
 
906
  if (is_array($f['cats'])) :
907
- $f['cats'] = implode(':',$f['cats']);
908
  endif;
909
 
910
  $f = array_merge($f, $this->flatten_array($channel));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
 
912
  # -- A few things we don't want to save in the notes
913
- unset($f['link_id']); unset($f['uri']); unset($f['name']);
 
914
  unset($f['hardcode categories']); // Deprecated
915
 
916
  $notes = '';
@@ -928,7 +1832,57 @@ class FeedWordPress {
928
  WHERE link_id='$link_id'
929
  ");
930
  } // function FeedWordPress::update_feed ()
931
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932
  // item_to_post(): convert information from a single item from an
933
  // Atom/RSS feed to a post for WordPress's database.
934
  //
@@ -943,8 +1897,9 @@ class FeedWordPress {
943
  // add_post()). If you want plugins that have side effects on the posts
944
  // database, you should probably hook into the action
945
  // post_syndicated_item
946
- //
947
- function item_to_post($wpdb, $item, $channel, $f) {
 
948
  $post = array();
949
 
950
  // This is ugly as all hell. I'd like to use apply_filters()'s
@@ -966,22 +1921,46 @@ class FeedWordPress {
966
  $post['post_title'] = $wpdb->escape($item['title']);
967
 
968
  $post['named']['author'] = array ();
969
- if (isset($item['dc']['creator'])):
970
- $post['named']['author']['name'] = $item['dc']['creator'];
 
971
  elseif (isset($item['dc']['creator'])):
 
 
972
  $post['named']['author']['name'] = $item['dc']['contributor'];
973
- elseif (isset($item['author_name'])):
974
- $post['named']['author']['name'] = $item['author_name'];
975
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  $post['named']['author']['name'] = $channel['title'];
977
  endif;
978
 
979
  if (isset($item['author_email'])):
980
  $post['named']['author']['email'] = $item['author_email'];
 
 
981
  endif;
982
 
983
  if (isset($item['author_url'])):
984
  $post['named']['author']['uri'] = $item['author_url'];
 
 
985
  else:
986
  $post['named']['author']['uri'] = $channel['link'];
987
  endif;
@@ -993,7 +1972,11 @@ class FeedWordPress {
993
 
994
  # Identify content and sanitize it.
995
  # ---------------------------------
996
- if (isset($item['content']['encoded']) and $item['content']['encoded']):
 
 
 
 
997
  $content = $item['content']['encoded'];
998
  else:
999
  $content = $item['description'];
@@ -1055,19 +2038,32 @@ class FeedWordPress {
1055
  # current time.
1056
  if (isset($item['dc']['date'])):
1057
  $post['epoch']['issued'] = parse_w3cdtf($item['dc']['date']);
1058
- elseif (isset($item['issued'])):
 
 
 
 
1059
  $post['epoch']['issued'] = parse_w3cdtf($item['issued']);
1060
- elseif (isset($item['pubdate'])):
1061
  $post['epoch']['issued'] = strtotime($item['pubdate']);
1062
  else:
1063
  $post['epoch']['issued'] = time();
1064
  endif;
1065
 
 
 
 
1066
  # As far as I know, only atom currently has a reliable way to
1067
  # specify when something was *modified* last
1068
- if (isset($item['modified'])):
1069
- $post['epoch']['modified'] = parse_w3cdtf($item['modified']);
1070
- else:
 
 
 
 
 
 
1071
  $post['epoch']['modified'] = $post['epoch']['issued'];
1072
  endif;
1073
 
@@ -1082,26 +2078,30 @@ class FeedWordPress {
1082
  $post['ping_status'] = FeedWordPress::syndicated_status('ping', $f, 'closed');
1083
 
1084
  // Unique ID (hopefully a unique tag: URI); failing that, the permalink
1085
- if (isset($item['id'])):
1086
- $post['guid'] = $wpdb->escape($item['id']);
1087
- else:
1088
- $post['guid'] = $wpdb->escape($item['link']);
1089
- endif;
1090
 
1091
- // RSS 2.0 / Atom 0.6+ enclosure support
1092
- if ( isset($item['enclosure']) and is_array($item['enclosure']) ) :
1093
- foreach ( $item['enclosure'] as $enclosure ) :
 
1094
  $post['meta']['enclosure'][] =
1095
- $enclosure['url']."\n".
1096
- $enclosure['length']."\n".
1097
- $enclosure['type'];
1098
- endforeach;
1099
  endif;
1100
 
1101
  // In case you want to point back to the blog this was syndicated from
1102
  if (isset($channel['title'])) $post['meta']['syndication_source'] = $channel['title'];
1103
  if (isset($channel['link'])) $post['meta']['syndication_source_uri'] = $channel['link'];
1104
- $post['meta']['syndication_feed'] = $f['uri'];
 
 
 
 
 
 
 
1105
 
1106
  // In case you want to know the external permalink...
1107
  $post['meta']['syndication_permalink'] = $item['link'];
@@ -1111,17 +2111,25 @@ class FeedWordPress {
1111
  $post['named']['unfamiliar']['category'] = $f['unfamiliar categories'];
1112
 
1113
  // Categories: start with default categories
1114
- $post['named']['category'] = $f['cats'];
 
 
 
 
1115
 
1116
  // Now add categories from the post, if we have 'em
1117
- if ( is_array($item['categories']) ) :
1118
- foreach ($item['categories'] as $cat):
1119
- if ( strpos($f['uri'], 'del.icio.us') !== false ):
 
 
 
 
1120
  $post['named']['category'] = array_merge($post['named']['category'], explode(' ', $cat));
1121
  else:
1122
  $post['named']['category'][] = $cat;
1123
  endif;
1124
- endforeach;
1125
  endif;
1126
  endif;
1127
  return $post;
@@ -1161,8 +2169,18 @@ class FeedWordPress {
1161
  $post['named']['category'],
1162
  FeedWordPress::on_unfamiliar('category', $post['named']['unfamiliar']['category'])
1163
  );
1164
- if (is_null($post['post_category'])) :
 
1165
  $freshness = 0;
 
 
 
 
 
 
 
 
 
1166
  endif;
1167
  endif;
1168
 
@@ -1254,7 +2272,7 @@ class FeedWordPress {
1254
  do_action('edit_post', $postId);
1255
 
1256
  $this->add_rss_meta($wpdb, $postId, $post);
1257
-
1258
  do_action('update_syndicated_item', $postId);
1259
 
1260
  $ret = 'updated';
@@ -1334,22 +2352,29 @@ class FeedWordPress {
1334
  function author_to_id ($wpdb, $author, $email, $url, $unfamiliar_author = 'create') {
1335
  // Never can be too careful...
1336
  $nice_author = sanitize_title($author);
 
1337
  $author = $wpdb->escape($author);
1338
  $email = $wpdb->escape($email);
1339
  $url = $wpdb->escape($url);
1340
-
1341
  $id = $wpdb->get_var(
1342
  "SELECT ID from $wpdb->users
1343
  WHERE
1344
- user_login = '$author' OR
1345
- user_firstname = '$author' OR
1346
- user_nickname = '$author' OR
1347
- user_nicename = '$nice_author' OR
1348
- user_description = '$author' OR
1349
- (LOWER(user_description) RLIKE
1350
- '(^|\n)a.k.a.( |\t)*:?( |\t)*".strtolower($author)."( |\t|\r)*(\n|\$)')
 
 
 
 
 
 
1351
  ");
1352
-
1353
  if (is_null($id)) :
1354
  if ($unfamiliar_author === 'create') :
1355
  $wpdb->query (
@@ -1390,24 +2415,22 @@ class FeedWordPress {
1390
  $cat_str = array ();
1391
  $cat_aka = array ();
1392
  foreach ( $cats as $c ) :
 
1393
  $esc = $wpdb->escape($c);
1394
  $cat_str[] = "'$esc'";
1395
- $cat_aka[] = "(LOWER(category_description) RLIKE '(^|\n)a.k.a.( |\t)*:?( |\t)*".strtolower($esc)."( |\t|\r)*(\n|\$)')";
 
 
1396
  endforeach;
1397
 
1398
  $match_cat_name = 'cat_name IN ('.join(',', $cat_str).')';
1399
  $match_cat_alias = join(' OR ', $cat_aka);
1400
 
1401
- // Normalizing case with LOWER() avoids conflicts in
1402
- // VARCHAR comparison between PHP (which has
1403
- // case-sensitive comparisons) and MySQL (which has
1404
- // case-insensitive comparisons for the field types used
1405
- // by WordPress)
1406
  $results = $wpdb->get_results(
1407
  "SELECT
1408
  cat_ID,
1409
- LOWER(TRIM(cat_name)) AS cat_name,
1410
- LOWER(category_description) AS category_description
1411
  FROM $wpdb->categories
1412
  WHERE ($match_cat_name) OR ($match_cat_alias)"
1413
  );
@@ -1422,15 +2445,27 @@ class FeedWordPress {
1422
  $cat_ids[] = $row->cat_ID;
1423
 
1424
  // Add name to list of categories not to
1425
- // create afresh.
1426
- $found[] = $row->cat_name;
 
 
 
 
 
 
1427
 
1428
  // Add name of any aliases to list of
1429
  // categories not to create afresh.
1430
  if (preg_match_all('/^a.k.a. \s* :? \s* (.*\S) \s*$/mx',
1431
  $row->category_description, $aka,
1432
  PREG_PATTERN_ORDER)) :
1433
- $found = array_merge ($found, $aka[1]);
 
 
 
 
 
 
1434
  endif;
1435
  endforeach;
1436
  endif;
@@ -1521,9 +2556,73 @@ class FeedWordPress {
1521
  ");
1522
  return $cat_name;
1523
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1524
  } // class FeedWordPress
1525
 
1526
- # -- Inbound XML-RPC plugin interface
 
 
 
1527
  function feedwordpress_xmlrpc_hook ($args = array ()) {
1528
  $args['weblogUpdates.ping'] = 'feedwordpress_pong';
1529
  return $args;
@@ -1543,6 +2642,10 @@ function feedwordpress_pong ($args) {
1543
  endif;
1544
  }
1545
 
 
 
 
 
1546
  class FeedFinder {
1547
  var $uri = NULL;
1548
  var $_cache_uri = NULL;
@@ -1873,4 +2976,31 @@ class Relative_URI
1873
  return preg_replace('/[^\x21-\x7e]/', '', $encoded);
1874
  }
1875
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1876
  ?>
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.97
7
  Author: Charles Johnson
8
+ Author URI: http://radgeek.com/
9
  License: GPL
10
+ Last modified: 2005-09-28 4:40pm EDT
11
  */
12
 
13
  # This uses code derived from:
27
 
28
  # -- Don't change these unless you know what you're doing...
29
  define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/');
30
+ define ('FEEDWORDPRESS_VERSION', '0.97');
31
  define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
32
 
33
+ define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
34
+ define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
35
+
36
+ define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi');
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
43
  // Is this being loaded from within WordPress 1.5 or later?
44
  if (isset($wp_version) and $wp_version >= 1.5):
45
 
46
+ $fwp_db_version = get_settings('feedwordpress_version');
47
+ $feedwordpress_needs_upgrade = false; // innocent until proven guilty
48
+ if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
49
+ // check to see whether this is a fresh install or an upgrade
50
+ $syn = $wpdb->get_col("
51
+ SELECT post_id
52
+ FROM $wpdb->postmeta
53
+ WHERE meta_key = 'syndication_feed'
54
+ ");
55
+ if (count($syn) > 0) : // contains at least one syndicated post
56
+ $feedwordpress_needs_upgrade = true;
57
+ else : // fresh install; brand it as ours
58
+ update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
59
+ if (!get_settings('feedwordpress_rpc_secret')) :
60
+ update_option('feedwordpress_rpc_secret', substr(md5(uniqid(microtime())), 0, 6));
61
+ endif;
62
+ endif;
63
+ endif;
64
 
65
+ if (!$feedwordpress_needs_upgrade) : // only work if the conditions are safe!
66
+
67
+ # Syndicated items are generally received in output-ready (X)HTML and
68
+ # should not be folded, crumpled, mutilated, or spindled by WordPress
69
+ # formatting filters. But we don't want to interfere with filters for
70
+ # any locally-authored posts, either.
71
+ #
72
+ # What WordPress should really have is a way for upstream filters to
73
+ # stop downstream filters from running at all. Since it doesn't, and
74
+ # since a downstream filter can't access the original copy of the text
75
+ # that is being filtered, what we will do here is (1) save a copy of the
76
+ # original text upstream, before any other filters run, and then (2)
77
+ # retrieve that copy downstream, after all the other filters run, *if*
78
+ # this is a syndicated post
79
+
80
+ add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
81
+ add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
82
+
83
+ # Filter in original permalinks if the user wants that
84
+ add_filter('post_link', 'syndication_permalink', 1);
85
+
86
+ # Admin menu
87
+ add_action('admin_menu', 'fwp_add_pages');
88
+
89
+ # Inbound XML-RPC update methods
90
+ add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
91
+
92
+ # Outbound XML-RPC ping reform
93
+ remove_action('publish_post', 'generic_ping');
94
+ add_action('publish_post', 'fwp_catch_ping');
95
+
96
+ # Hook in logging functions only if the logging option is ON
97
+ $update_logging = get_settings('feedwordpress_update_logging');
98
+ if ($update_logging == 'yes') :
99
+ add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
100
+ add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
101
+ add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
102
+ add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
103
+ add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
104
+ endif;
105
+ else :
106
+ # Hook in the menus, which will just point to the upgrade interface
107
+ add_action('admin_menu', 'fwp_add_pages');
108
+ endif; // if (!$feedwordpress_needs_upgrade)
109
+ endif;
110
 
111
+ ################################################################################
112
+ ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
113
+ ################################################################################
114
 
115
+ function log_feedwordpress_post ($id) {
116
+ $post = wp_get_single_post($id);
117
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
118
+ ."'{$post->post_title}' ({$post->post_date})");
119
+ }
120
 
121
+ function log_feedwordpress_update_post ($id) {
122
+ $post = wp_get_single_post($id);
123
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
124
+ ."'{$post->post_title}' ({$post->post_date})"
125
+ ." (as of {$post->post_modified})");
126
+ }
127
 
128
+ function log_feedwordpress_update_feeds ($uri) {
129
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
130
+ }
131
 
132
+ function log_feedwordpress_check_feed ($feed) {
133
+ $uri = $feed['link/uri']; $name = $feed['link/name'];
134
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
135
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ function log_feedwordpress_update_complete ($delta) {
138
+ $mesg = array();
139
+ if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
140
+ if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
141
+ if (empty($mesg)) $mesg[] = 'nothing changed';
142
+
143
+ error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
144
+ .(is_null($delta) ? "Error: I don't syndicate that URI"
145
+ : implode(' and ', $mesg)));
146
+ }
147
+
148
+ ################################################################################
149
+ ## TEMPLATE API: functions to make your templates syndication-aware ############
150
+ ################################################################################
151
 
152
+ function is_syndicated () { return (strlen(get_syndication_feed_id()) > 0); }
 
153
 
154
  function the_syndication_source_link () { echo get_syndication_source_link(); }
155
  function get_syndication_source_link () { list($n) = get_post_custom_values('syndication_source_uri'); return $n; }
160
  function get_syndication_feed () { list($u) = get_post_custom_values('syndication_feed'); return $u; }
161
  function the_syndication_feed () { echo get_syndication_feed (); }
162
 
163
+ function get_syndication_feed_id () { list($u) = get_post_custom_values('syndication_feed_id'); return $u; }
164
+ function the_syndication_feed_id () { echo get_syndication_feed_id(); }
165
+
166
+ $feedwordpress_linkcache = array (); // only load links from database once
167
+
168
  function get_feed_meta ($key) {
169
+ global $wpdb, $feedwordpress_linkcache;
170
+ $feed_id = get_syndication_feed_id();
171
+
172
  $ret = NULL;
173
+ if (strlen($feed_id) > 0):
174
+ if (isset($feedwordpress_linkcache[$feed_id])) :
175
+ $result = $feedwordpress_linkcache[$feed_id];
176
+ else :
177
+ $result = $wpdb->get_row("
178
+ SELECT * FROM $wpdb->links
179
+ WHERE (link_id = '".$wpdb->escape($feed_id)."')"
180
+ );
181
+ $feedwordpress_linkcache[$feed_id] = $result;
182
+ endif;
183
 
184
+ $meta = FeedWordPress::notes_to_settings($result->link_notes);
185
  $ret = $meta[$key];
186
  endif; /* if */
187
  return $ret;
194
  echo get_syndication_permalink();
195
  }
196
 
197
+ ################################################################################
198
+ ## FILTERS: syndication-aware handling of post data for templates and feeds ####
199
+ ################################################################################
200
+
201
  $feedwordpress_the_syndicated_content = NULL;
202
 
203
  function feedwordpress_preserve_syndicated_content ($text) {
230
  endif;
231
  } // function syndication_permalink ()
232
 
233
+ ################################################################################
234
+ ## UPGRADE INTERFACE: Have users upgrade DB from older versions of FWP #########
235
+ ################################################################################
236
+
237
+ function fwp_upgrade_page () {
238
+ if (isset($_POST['action']) and $_POST['action']=='Upgrade') :
239
+ $ver = get_settings('feedwordpress_version');
240
+ if (get_settings('feedwordpress_version') != FEEDWORDPRESS_VERSION) :
241
+ echo "<div class=\"wrap\">\n";
242
+ echo "<h2>Upgrading FeedWordPress...</h2>";
243
+
244
+ $feedwordpress =& new FeedWordPress;
245
+ $feedwordpress->upgrade_database();
246
+ echo "<p><strong>Done!</strong> Upgraded database to version ".FEEDWORDPRESS_VERSION.".</p>\n";
247
+ echo "<form action=\"\" method=\"get\">\n";
248
+ echo "<div class=\"submit\"><input type=\"hidden\" name=\"page\" value=\"".basename(__FILE__)."\" />";
249
+ echo "<input type=\"submit\" value=\"Continue &raquo;\" /></form></div>\n";
250
+ echo "</div>\n";
251
+ return;
252
+ else :
253
+ echo "<div class=\"updated\"><p>Already at version ".FEEDWORDPRESS_VERSION."!</p></div>";
254
+ endif;
255
+ endif;
256
+ ?>
257
+ <div class="wrap">
258
+ <h2>Upgrade FeedWordPress</h2>
259
+
260
+ <p>It appears that you have installed FeedWordPress
261
+ <?=FEEDWORDPRESS_VERSION?> as an upgrade to an existing installation of
262
+ FeedWordPress. That's no problem, but you will need to take a minute out first
263
+ to upgrade your database: some necessarily changes in how the software keeps
264
+ track of posts and feeds will cause problems such as duplicate posts and broken
265
+ templates if we were to continue without the upgrade.</p>
266
+
267
+ <p>Note that most of FeedWordPress's functionality is temporarily disabled
268
+ until we have successfully completed the upgrade. Everything should begin
269
+ working as normal again once the upgrade is complete. There's extraordinarily
270
+ little chance of any damage as the result of the upgrade, but if you're paranoid
271
+ like me you may want to back up your database before you proceed.</p>
272
+
273
+ <p>This may take several minutes for a large installation.</p>
274
+
275
+ <form action="" method="post">
276
+ <div class="submit"><input type="submit" name="action" value="Upgrade" /></div>
277
+ </form>
278
+ </div>
279
+ <?php
280
+ } // function fwp_upgrade_page ()
281
+
282
+ ################################################################################
283
+ ## ADMIN MENU ADD-ONS: implement Dashboard management pages ####################
284
+ ################################################################################
285
+
286
  function fwp_add_pages () {
287
  add_submenu_page('link-manager.php', 'Syndicated Sites', 'Syndicated', 5, basename(__FILE__), 'fwp_syndication_manage_page');
288
  add_options_page('Syndication Options', 'Syndication', 6, basename(__FILE__), 'fwp_syndication_options_page');
291
  function fwp_syndication_options_page () {
292
  global $wpdb, $user_level;
293
 
294
+ if ($GLOBALS['feedwordpress_needs_upgrade']) :
295
+ fwp_upgrade_page();
296
+ return;
297
+ endif;
298
+
299
  $caption = 'Save Changes';
300
+ if (isset($_POST['action']) and $_POST['action']==$caption):
301
  check_admin_referer();
302
 
303
  if ($user_level < 6):
311
  update_option('feedwordpress_unfamiliar_category', $_REQUEST['unfamiliar_category']);
312
  update_option('feedwordpress_syndicated_post_status', $_REQUEST['post_status']);
313
 
314
+ // Categories
315
+ $cats = array();
316
+ if (isset($_POST['post_category'])) :
317
+ $cat_set = "(".implode(",", $_POST['post_category']).")";
318
+ $cats = $wpdb->get_col(
319
+ "SELECT cat_name
320
+ FROM $wpdb->categories
321
+ WHERE cat_ID IN {$cat_set}
322
+ ");
323
+ endif;
324
+
325
+ if (!empty($cats)) :
326
+ update_option('feedwordpress_syndication_cats', implode("\n", $cats));
327
+ else :
328
+ delete_option('feedwordpress_syndication_cats');
329
+ endif;
330
+
331
  if (isset($_REQUEST['comment_status']) and ($_REQUEST['comment_status'] == 'open')) :
332
  update_option('feedwordpress_syndicated_comment_status', 'open');
333
  else :
389
  $unfamiliar_category[$uc] = ' checked="checked"';
390
  endif;
391
  $results = $wpdb->get_results("SELECT cat_id, cat_name, auto_toggle FROM $wpdb->linkcategories ORDER BY cat_id");
392
+
393
+ $cats = get_settings('feedwordpress_syndication_cats');
394
+ $dogs = get_nested_categories(-1, 0);
395
+ $cats = array_map('strtolower',
396
+ array_map('trim',
397
+ preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $cats)
398
+ ));
399
+
400
+ foreach ($dogs as $tag => $dog) :
401
+ if (in_array(strtolower(trim($dog['cat_name'])), $cats)) :
402
+ $dogs[$tag]['checked'] = true;
403
+ endif;
404
+ endforeach;
405
+
406
  ?>
407
  <div class="wrap">
408
  <h2>Syndication Options</h2>
437
  </fieldset>
438
 
439
  <fieldset class="options">
440
+ <legend>Syndicated Posts</legend>
 
 
 
 
 
 
441
 
442
+ <fieldset id="categorydiv" style="width: 20%; margin-right: 2em">
443
+ <legend>Categories</legend>
444
+ <p style="font-size:smaller;font-style:bold;margin:0">Place <em>all syndicated
445
+ posts</em> under...</p>
446
+ <div style="height: 20em"><?php write_nested_categories($dogs); ?></div>
447
+ </fieldset>
448
+
449
+ <table class="editform" width="75%" cellspacing="2" cellpadding="5">
450
+ <tr style="vertical-align: top"><th width="33%" scope="row">Publication:</th>
451
+ <td width="67%"><ul style="margin: 0; padding: 0; list-style:none">
452
  <li><label><input type="radio" name="post_status" value="publish"<?=($post_status=='publish')?' checked="checked"':''?> /> Publish syndicated posts immediately</label></li>
453
  <li><label><input type="radio" name="post_status" value="draft"<?=($post_status=='draft')?' checked="checked"':''?> /> Hold syndicated posts as drafts</label></li>
454
  <li><label><input type="radio" name="post_status" value="private"<?=($post_status=='private')?' checked="checked"':''?> /> Hold syndicated posts as private posts</label></li>
455
  </ul></td></tr>
456
 
457
+ <tr style="vertical-align: top"><th width="33%" scope="row">Comments:</th>
458
+ <td width="67%"><ul style="margin: 0; padding: 0; list-style:none">
459
+ <li><label><input type="radio" name="comment_status" value="open"<?=($comment_status=='open')?' checked="checked"':''?> /> Allow comments on syndicated posts</label></li>
460
+ <li><label><input type="radio" name="comment_status" value="closed"<?=($comment_status!='open')?' checked="checked"':''?> /> Don't allow comments on syndicated posts</label></li>
461
  </ul></td></tr>
462
 
463
+ <tr style="vertical-align: top"><th width="33%" scope="row">Trackback and Pingback:</th>
464
+ <td width="67%"><ul style="margin:0; padding: 0; list-style:none">
465
+ <li><label><input type="radio" name="ping_status" value="open"<?=($ping_status=='open')?' checked="checked"':''?> /> Accept pings on syndicated posts</label></li>
466
+ <li><label><input type="radio" name="ping_status" value="closed"<?=($ping_status!='open')?' checked="checked"':''?> /> Don't accept pings on syndicated posts</label></li>
467
  </ul></td></tr>
468
 
469
+ <tr style="vertical-align: top"><th width="33%" scope="row" style="vertical-align:top">Unfamiliar authors:</th>
470
+ <td width="67%"><ul style="margin: 0; padding: 0; list-style:none">
471
  <li><label><input type="radio" name="unfamiliar_author" value="create"<?=$unfamiliar_author['create']?>/> create a new author account</label></li>
472
  <li><label><input type="radio" name="unfamiliar_author" value="default"<?=$unfamiliar_author['default']?> /> attribute the post to the default author</label></li>
473
  <li><label><input type="radio" name="unfamiliar_author" value="filter"<?=$unfamiliar_author['filter']?> /> don't syndicate the post</label></li>
474
  </ul></td></tr>
475
+ <tr style="vertical-align: top"><th width="33%" scope="row" style="vertical-align:top">Unfamiliar categories:</th>
476
+ <td width="67%"><ul style="margin: 0; padding:0; list-style:none">
477
  <li><label><input type="radio" name="unfamiliar_category" value="create"<?=$unfamiliar_category['create']?>/> create any categories the post is in</label></li>
478
  <li><label><input type="radio" name="unfamiliar_category" value="default"<?=$unfamiliar_category['default']?>/> don't create new categories</li>
479
  <li><label><input type="radio" name="unfamiliar_category" value="filter"<?=$unfamiliar_category['filter']?>/> don't create new categories and don't syndicate posts unless they match at least one familiar category</label></li>
480
  </ul></td></tr>
481
 
482
+ <tr style="vertical-align: top"><th width="33%" scope="row">Permalinks point to:</th>
483
+ <td width="67%"><select name="munge_permalink" size="1">
484
+ <option value="yes"<?=($munge_permalink=='yes')?' selected="selected"':''?>>original website</option>
485
+ <option value="no"<?=($munge_permalink=='no')?' selected="selected"':''?>>this website</option>
486
  </select></td></tr>
487
  </table>
488
  <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
513
 
514
  function fwp_syndication_manage_page () {
515
  global $user_level, $wpdb;
516
+
517
+ if ($GLOBALS['feedwordpress_needs_upgrade']) :
518
+ fwp_upgrade_page();
519
+ return;
520
+ endif;
521
+
522
  ?>
523
  <?php $cont = true;
524
  if (isset($_REQUEST['action'])):
 
525
  if ($_REQUEST['action'] == 'feedfinder') : $cont = fwp_feedfinder_page();
526
  elseif ($_REQUEST['action'] == 'switchfeed') : $cont = fwp_switchfeed_page();
527
+ elseif ($_REQUEST['action'] == 'linkedit') : $cont = fwp_linkedit_page();
528
+ elseif ($_REQUEST['action'] == 'Unsubscribe from Checked' or $_REQUEST['action'] == 'Unsubscribe') : $cont = fwp_multidelete_page();
529
  endif;
530
  endif;
531
 
555
  <table width="100%" cellpadding="3" cellspacing="3">
556
  <tr>
557
  <th width="20%"><?php _e('Name'); ?></th>
558
+ <th width="40%"><?php _e('Feed'); ?></th>
559
  <th colspan="4"><?php _e('Action'); ?></th>
560
  </tr>
561
 
563
  $alt_row = !$alt_row; ?>
564
  <tr<?=($alt_row?' class="alternate"':'')?>>
565
  <td><a href="<?=wp_specialchars($link->link_url)?>"><?=wp_specialchars($link->link_name)?></a></td>
566
+ <?php
567
+ if (strlen($link->link_rss) > 0):
568
+ $caption='Switch Feed';
569
+ $uri_bits = parse_url($link->link_rss);
570
+ $uri_bits['host'] = preg_replace('/^www\./i', '', $uri_bits['host']);
571
+ $display_uri =
572
+ (isset($uri_bits['user'])?$uri_bits['user'].'@':'')
573
+ .(isset($uri_bits['host'])?$uri_bits['host']:'')
574
+ .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
575
+ .(isset($uri_bits['path'])?$uri_bits['path']:'')
576
+ .(isset($uri_bits['query'])?'?'.$uri_bits['query']:'');
577
+ if (strlen($display_uri) > 32) : $display_uri = substr($display_uri, 0, 32).'&#8230;'; endif;
578
+ ?>
579
+ <td>
580
+ <strong><a href="<?=$link->link_rss?>"><?=wp_specialchars($display_uri)?></a></strong></td>
581
+ <?php
582
+ else:
583
+ $caption='Find Feed';
584
+ ?>
585
+ <td style="background-color:#FFFFD0"><p><strong>no
586
+ feed assigned</strong></p></td>
587
+ <?php
588
+ endif;
589
+
590
+ if (($link->user_level <= $user_level)):
591
  ?>
592
+ <td><a href="link-manager.php?page=<?=basename(__FILE__)?>&amp;link_id=<?=$link->link_id?>&amp;action=linkedit" class="edit"><?php _e('Edit')?></a></td>
593
+ <td><a href="link-manager.php?page=<?=basename(__FILE__)?>&amp;link_id=<?=$link->link_id?>&amp;action=feedfinder" class="edit"><?=$caption?></a></td>
594
+ <td><a href="link-manager.php?page=<?=basename(__FILE__)?>&amp;link_id=<?=$link->link_id?>&amp;action=Unsubscribe" class="delete"><?php _e('Unsubscribe'); ?></a></td>
595
+ <td><input type="checkbox" name="link_ids[]" value="<?=$link->link_id?>" /></td>
596
  <?php
597
+ else:
598
+ echo "<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>\n";
599
+ endif;
600
+ echo "\n\t</tr>";
601
  endforeach;
602
+ else:
603
+ ?>
604
 
605
  <p>There are no websites currently listed for syndication.</p>
606
 
611
  <div class="wrap">
612
  <h2>Manage Multiple Links</h2>
613
  <div class="submit">
614
+ <input type="submit" class="delete" name="action" value="Unsubscribe from Checked" />
615
  </div>
616
  </div>
617
  </form>
737
 
738
  if ($result): ?>
739
  <div class="updated"><p><a href="<?=$_REQUEST['feed_link']?>"><?=wp_specialchars($_REQUEST['feed_title'])?></a>
740
+ has been added as a contributing site, using the newsfeed at &lt;<a href="<?=$_REQUEST['feed']?>"><?=wp_specialchars($_REQUEST['feed'])?></a>&gt;.</p></div>
741
  <?php else: ?>
742
  <div class="updated"><p>There was a problem adding the newsfeed. [SQL: <?=wp_specialchars(mysql_error())?>]</p></div>
743
  <?php endif;
766
  return true; // Continue
767
  }
768
 
769
+ function fwp_linkedit_page () {
770
+ global $wpdb, $user_level;
771
+
772
+ check_admin_referer(); // Make sure we arrived here from the Dashboard
773
+
774
+ $special_settings = array ( /* Regular expression syntax is OK here */
775
+ 'cats',
776
+ 'hardcode name',
777
+ 'hardcode url',
778
+ 'hardcode description',
779
+ 'hardcode categories', /* Deprecated */
780
+ 'post status',
781
+ 'comment status',
782
+ 'ping status',
783
+ 'unfamiliar author',
784
+ 'unfamliar categories',
785
+ 'update/.*',
786
+ 'feed/.*',
787
+ 'link/.*',
788
+ );
789
+
790
+ if ($user_level < 5) :
791
+ die (__("Cheatin' uh ?"));
792
+ elseif (isset($_REQUEST['feedfinder'])) :
793
+ return fwp_feedfinder_page(); // re-route to Feed Finder page
794
+ else :
795
+ $link_id = (int) $_REQUEST['link_id'];
796
+ $row = $wpdb->get_row("
797
+ SELECT * FROM $wpdb->links WHERE link_id = $link_id
798
+ ");
799
+
800
+ if ($row) :
801
+ if (isset($_POST['save'])) :
802
+ $alter = array ();
803
+
804
+ $meta = FeedWordPress::notes_to_settings($row->link_notes);
805
+ if (isset($meta['cats'])):
806
+ $meta['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $meta['cats']);
807
+ endif;
808
+
809
+ // custom feed settings first
810
+ foreach ($_POST['notes'] as $mn) :
811
+ $mn['key0'] = trim($mn['key0']);
812
+ $mn['key1'] = trim($mn['key1']);
813
+ if (preg_match("\007^(("
814
+ .implode(')|(',$special_settings)
815
+ ."))$\007i",
816
+ $mn['key1'])) :
817
+ $mn['key1'] = 'user/'.$mn['key1'];
818
+ endif;
819
+
820
+ if (strlen($mn['key0']) > 0) :
821
+ unset($meta[$mn['key0']]); // out with the old
822
+ endif;
823
+
824
+ if (($mn['action']=='update') and (strlen($mn['key1']) > 0)) :
825
+ $meta[$mn['key1']] = $mn['value']; // in with the new
826
+ endif;
827
+ endforeach;
828
+
829
+ // now stuff through the web form
830
+ // hardcoded feed info
831
+ if (isset($_POST['hardcode_name'])) :
832
+ $meta['hardcode name'] = $_POST['hardcode_name'];
833
+ if (FeedWordPress::affirmative($meta, 'hardcode name')) :
834
+ $alter[] = "link_name = '".$wpdb->escape($_POST['name'])."'";
835
+ endif;
836
+ endif;
837
+ if (isset($_POST['hardcode_description'])) :
838
+ $meta['hardcode description'] = $_POST['hardcode_description'];
839
+ if (FeedWordPress::affirmative($meta, 'hardcode description')) :
840
+ $alter[] = "link_description = '".$wpdb->escape($_POST['description'])."'";
841
+ endif;
842
+ endif;
843
+ if (isset($_POST['hardcode_url'])) :
844
+ $meta['hardcode url'] = $_POST['hardcode_url'];
845
+ if (FeedWordPress::affirmative($meta, 'hardcode url')) :
846
+ $alter[] = "link_url = '".$wpdb->escape($_POST['linkurl'])."'";
847
+ endif;
848
+ endif;
849
+
850
+ // Update scheduling
851
+ if (isset($_POST['update_schedule'])) :
852
+ $meta['update/hold'] = $_POST['update_schedule'];
853
+ endif;
854
+
855
+ // Categories
856
+ if (isset($_POST['post_category'])) :
857
+ $cat_set = "(".implode(",", $_POST['post_category']).")";
858
+ $meta['cats'] = $wpdb->get_col(
859
+ "SELECT cat_name
860
+ FROM $wpdb->categories
861
+ WHERE cat_ID IN {$cat_set}
862
+ ");
863
+ if (count($meta['cats']) == 0) :
864
+ unset($meta['cats']);
865
+ endif;
866
+ else :
867
+ unset($meta['cats']);
868
+ endif;
869
+
870
+ // Post status, comment status, ping status
871
+ foreach (array('post', 'comment', 'ping') as $what) :
872
+ $sfield = "feed_{$what}_status";
873
+ if (isset($_POST[$sfield])) :
874
+ if ($_POST[$sfield]=='site-default') :
875
+ unset($meta["{$what} status"]);
876
+ else :
877
+ $meta["{$what} status"] = $_POST[$sfield];
878
+ endif;
879
+ endif;
880
+ endforeach;
881
+
882
+ // Unfamiliar author, unfamiliar categories
883
+ foreach (array("author", "category") as $what) :
884
+ $sfield = "unfamiliar_{$what}";
885
+ if (isset($_POST[$sfield])) :
886
+ if ($_POST[$sfield]=='site-default') :
887
+ unset($meta["unfamiliar {$what}"]);
888
+ else :
889
+ $meta["unfamiliar {$what}"] = $_POST[$sfield];
890
+ endif;
891
+ endif;
892
+ endforeach;
893
+
894
+ if (is_array($meta['cats'])) :
895
+ $meta['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $meta['cats']);
896
+ endif;
897
+
898
+ $notes = '';
899
+ foreach ($meta as $key => $value) :
900
+ $notes .= $key . ": ". addcslashes($value, "\0..\37") . "\n";
901
+ endforeach;
902
+ $alter[] = "link_notes = '".$wpdb->escape($notes)."'";
903
+
904
+ $alter_set = implode(", ", $alter);
905
+
906
+ // issue update query
907
+ $result = $wpdb->query("
908
+ UPDATE $wpdb->links
909
+ SET $alter_set
910
+ WHERE link_id='$link_id'
911
+ ");
912
+ $updated_link = true;
913
+
914
+ // reload link information from DB
915
+ $row = $wpdb->get_row("
916
+ SELECT * FROM $wpdb->links WHERE link_id = $link_id
917
+ ");
918
+ else :
919
+ $updated_link = false;
920
+ endif;
921
+
922
+ $link_url = wp_specialchars($row->link_url, 1);
923
+ $link_name = wp_specialchars($row->link_name, 1);
924
+ $link_image = $row->link_image;
925
+ $link_target = $row->link_target;
926
+ $link_category = $row->link_category;
927
+ $link_description = wp_specialchars($row->link_description);
928
+ $link_visible = $row->link_visible;
929
+ $link_rating = $row->link_rating;
930
+ $link_rel = $row->link_rel;
931
+ $link_notes = wp_specialchars($row->link_notes);
932
+ $link_rss_uri = wp_specialchars($row->link_rss);
933
+
934
+ $meta = FeedWordPress::notes_to_settings($row->link_notes);
935
+
936
+ $status['post'] = array('publish' => '', 'private' => '', 'draft' => '', 'site-default' => '');
937
+ $status['comment'] = array('open' => '', 'closed' => '', 'site-default' => '');
938
+ $status['ping'] = array('open' => '', 'closed' => '', 'site-default' => '');
939
+
940
+ foreach (array('post', 'comment', 'ping') as $what) :
941
+ if (isset($meta["{$what} status"])) :
942
+ $status[$what][$meta["{$what} status"]] = ' checked="checked"';
943
+ else :
944
+ $status[$what]['site-default'] = ' checked="checked"';
945
+ endif;
946
+ endforeach;
947
+
948
+ $unfamiliar['author'] = array ('create' => '','default' => '','filter' => '');
949
+ $unfamiliar['category'] = array ('create'=>'','default'=>'','filter'=>'');
950
+
951
+ foreach (array('author', 'category') as $what) :
952
+ if (is_string($meta["unfamiliar {$what}"]) and
953
+ array_key_exists($meta["unfamiliar {$what}"], $unfamiliar[$what])) :
954
+ $key = $meta["unfamiliar {$what}"];
955
+ else:
956
+ $key = 'site-default';
957
+ endif;
958
+ $unfamiliar[$what][$key] = ' checked="checked"';
959
+ endforeach;
960
+
961
+ $dogs = get_nested_categories(-1, 0);
962
+ $cats = array_map('strtolower',
963
+ array_map('trim',
964
+ preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $meta['cats'])
965
+ ));
966
+
967
+ foreach ($dogs as $tag => $dog) :
968
+ if (in_array(strtolower(trim($dog['cat_name'])), $cats)) :
969
+ $dogs[$tag]['checked'] = true;
970
+ endif;
971
+ endforeach;
972
+ else :
973
+ die( __('Link not found.') );
974
+ endif;
975
+
976
+ ?>
977
+ <script type="text/javascript">
978
+ function flip_hardcode (item) {
979
+ ed=document.getElementById('basics-'+item+'-edit');
980
+ view=document.getElementById('basics-'+item+'-view');
981
+
982
+ o = document.getElementById('basics-hardcode-'+item);
983
+ if (o.value=='yes') { ed.style.display='inline'; view.style.display='none'; }
984
+ else { ed.style.display='none'; view.style.display='inline'; }
985
+ }
986
+ </script>
987
+
988
+ <?php if ($updated_link) : ?>
989
+ <div class="updated"><p>Syndicated feed settings updated.</p></div>
990
+ <?php endif; ?>
991
+
992
+ <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
993
+ <div class="wrap">
994
+ <input type="hidden" name="link_id" value="<?=$link_id?>" />
995
+ <input type="hidden" name="action" value="linkedit" />
996
+ <input type="hidden" name="save" value="link" />
997
+
998
+ <h2>Edit a syndicated feed:</h2>
999
+ <fieldset><legend>Basics</legend>
1000
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1001
+ <tr>
1002
+ <th scope="row" width="20%"><?php _e('Feed URI:') ?></th>
1003
+ <td width="60%"><a href="<?=wp_specialchars($link_rss_uri)?>"><?=$link_rss_uri?></a>
1004
+ <a href="<?=FEEDVALIDATOR_URI?>?url=<?=urlencode($link_rss_uri)?>"
1005
+ title="Check feed &lt;<?=wp_specialchars($link_rss_uri)?>&gt; for validity"><img src="../wp-images/smilies/icon_arrow.gif" alt="&rarr;" /></a>
1006
+ </td>
1007
+ <td width="20%"><input type="submit" name="feedfinder" value="switch &rarr;" style="font-size:smaller" /></td>
1008
+ </tr>
1009
+ <tr>
1010
+ <th scope="row" width="20%"><?php _e('Link Name:') ?></th>
1011
+ <td width="60%"><input type="text" id="basics-name-edit" name="name"
1012
+ value="<?php echo $link_name; ?>" style="width: 95%" />
1013
+ <span id="basics-name-view"><strong><?=$link_name?></strong></span>
1014
+ </td>
1015
+ <td>
1016
+ <select id="basics-hardcode-name" onchange="flip_hardcode('name')" name="hardcode_name">
1017
+ <option value="no" <?=FeedWordPress::hardcode('name', $meta)?'':'selected="selected"'?>>update automatically</option>
1018
+ <option value="yes" <?=FeedWordPress::hardcode('name', $meta)?'selected="selected"':''?>>edit manually</option>
1019
+ </select>
1020
+ </td>
1021
+ </tr>
1022
+ <tr>
1023
+ <th scope="row" width="20%"><?php _e('Short description:') ?></th>
1024
+ <td width="60%">
1025
+ <input id="basics-description-edit" type="text" name="description" value="<?php echo $link_description; ?>" style="width: 95%" />
1026
+ <span id="basics-description-view"><strong><?=$link_description?></strong></span>
1027
+ </td>
1028
+ <td>
1029
+ <select id="basics-hardcode-description" onchange="flip_hardcode('description')"
1030
+ name="hardcode_description">
1031
+ <option value="no" <?=FeedWordPress::hardcode('description', $meta)?'':'selected="selected"'?>>update automatically</option>
1032
+ <option value="yes" <?=FeedWordPress::hardcode('description', $meta)?'selected="selected"':''?>>edit manually</option>
1033
+ </select></td>
1034
+ </tr>
1035
+ <tr>
1036
+ <th width="20%" scope="row"><?php _e('Homepage:') ?></th>
1037
+ <td width="60%">
1038
+ <input id="basics-url-edit" type="text" name="linkurl" value="<?php echo $link_url; ?>" style="width: 95%;" />
1039
+ <a id="basics-url-view" href="<?=$link_url?>"><?=$link_url?></a></td>
1040
+ <td>
1041
+ <select id="basics-hardcode-url" onchange="flip_hardcode('url')" name="hardcode_url">
1042
+ <option value="no"<?=FeedWordPress::hardcode('url', $meta)?'':' selected="selected"'?>>update live from feed</option>
1043
+ <option value="yes"<?=FeedWordPress::hardcode('url', $meta)?' selected="selected"':''?>>edit manually</option>
1044
+ </select></td></tr>
1045
+
1046
+ <tr>
1047
+ <th width="20%"><?php _e('Last update') ?>:</th>
1048
+ <td colspan="2"><?php
1049
+ if (isset($meta['update/last'])) :
1050
+ echo strftime('%x %X', $meta['update/last'])." ";
1051
+ else :
1052
+ echo " none yet";
1053
+ endif;
1054
+ ?></td></tr>
1055
+ <tr><th width="20%">Next update:</th>
1056
+ <td colspan="2"><?php
1057
+ $holdem = (isset($meta['update/hold']) ? $meta['update/hold'] : 'scheduled');
1058
+ ?>
1059
+ <select name="update_schedule">
1060
+ <option value="scheduled"<?=($holdem=='scheduled')?' selected="selected"':''?>>update on schedule <?php
1061
+ echo " (";
1062
+ if (isset($meta['update/ttl']) and is_numeric($meta['update/ttl'])) :
1063
+ if (isset($meta['update/timed']) and $meta['update/timed']=='automatically') :
1064
+ echo 'next: ';
1065
+ $next = $meta['update/last'] + ((int) $meta['update/ttl'] * 60);
1066
+ if (strftime('%x', time()) != strftime('%x', $next)) :
1067
+ echo strftime('%x', $next)." ";
1068
+ endif;
1069
+ echo strftime('%X', $meta['update/last']+((int) $meta['update/ttl']*60));
1070
+ else :
1071
+ echo "every ".$meta['update/ttl']." minute".(($meta['update/ttl']!=1)?"s":"");
1072
+ endif;
1073
+ else:
1074
+ echo "next scheduled update";
1075
+ endif;
1076
+ echo ")";
1077
+ ?></option>
1078
+ <option value="next"<?=($holdem=='next')?' selected="selected"':''?>>update ASAP</option>
1079
+ <option value="ping"<?=($holdem=='ping')?' selected="selected"':''?>>update only when pinged</option>
1080
+ </select></tr>
1081
+ </table>
1082
+ </fieldset>
1083
+
1084
+ <script type="text/javascript">
1085
+ flip_hardcode('name');
1086
+ flip_hardcode('description');
1087
+ flip_hardcode('url');
1088
+ </script>
1089
+
1090
+ <p class="submit">
1091
+ <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1092
+ </p>
1093
+
1094
+ <fieldset>
1095
+ <legend>Syndicated Posts</legend>
1096
+
1097
+ <fieldset id="categorydiv" style="width: 20%; margin-right: 2em">
1098
+ <legend>Categories</legend>
1099
+ <p style="font-size:smaller;font-style:bold;margin:0">Place all syndicated posts from this feed
1100
+ under...</p>
1101
+ <div style="height: 16em"><?php write_nested_categories($dogs); ?></div>
1102
+ </fieldset>
1103
+
1104
+ <table class="editform" width="80%" cellspacing="2" cellpadding="5">
1105
+ <tr><th width="20%" scope="row" style="vertical-align:top">Publication:</th>
1106
+ <td width="80%" style="vertical-align:top"><ul style="margin:0; list-style:none">
1107
+ <li><label><input type="radio" name="feed_post_status" value="site-default"
1108
+ <?=$status['post']['site-default']?> /> Use site-wide setting from <a href="options-general.php?page=<?=basename(__FILE__)?>">Syndication Options</a>
1109
+ (currently: <strong><?=FeedWordPress::syndicated_status('post', array(), 'publish')?></strong>)</label></li>
1110
+ <li><label><input type="radio" name="feed_post_status" value="publish"
1111
+ <?=$status['post']['publish']?> /> Publish posts from this feed immediately</label></li>
1112
+ <li><label><input type="radio" name="feed_post_status" value="private"
1113
+ <?=$status['post']['private']?> /> Hold posts from this feed as private posts</label></li>
1114
+ <li><label><input type="radio" name="feed_post_status" value="draft"
1115
+ <?=$status['post']['draft']?> /> Hold posts from this feed as drafts</label></li>
1116
+ </ul></td>
1117
+ </tr>
1118
+
1119
+ <tr><th width="20%" scope="row" style="vertical-align:top">Comments:</th>
1120
+ <td width="80%"><ul style="margin:0; list-style:none">
1121
+ <li><label><input type="radio" name="feed_comment_status" value="site-default"
1122
+ <?=$status['comment']['site-default']?> /> Use site-wide setting from <a href="options-general.php?page=<?=basename(__FILE__)?>">Syndication Options</a>
1123
+ (currently: <strong><?=FeedWordPress::syndicated_status('comment', array(), 'closed')?>)</strong></label></li>
1124
+ <li><label><input type="radio" name="feed_comment_status" value="open"
1125
+ <?=$status['comment']['open']?> /> Allow comments on syndicated posts from this feed</label></li>
1126
+ <li><label><input type="radio" name="feed_comment_status" value="closed"
1127
+ <?=$status['comment']['closed']?> /> Don't allow comments on syndicated posts from this feed</label></li>
1128
+ </ul></td>
1129
+ </tr>
1130
+
1131
+ <tr><th width="20%" scope="row" style="vertical-align:top">Trackback and Pingback:</th>
1132
+ <td width="80%"><ul style="margin:0; list-style:none">
1133
+ <li><label><input type="radio" name="feed_ping_status" value="site-default"
1134
+ <?=$status['ping']['site-default']?> /> Use site-wide setting from <a href="options-general.php?page=<?=basename(__FILE__)?>">Syndication Options</a>
1135
+ (currently: <strong><?=FeedWordPress::syndicated_status('ping', array(), 'closed')?>)</strong></label></li>
1136
+ <li><label><input type="radio" name="feed_ping_status" value="open"
1137
+ <?=$status['ping']['open']?> /> Accept pings on syndicated posts from this feed</label></li>
1138
+ <li><label><input type="radio" name="feed_ping_status" value="closed"
1139
+ <?=$status['ping']['closed']?> /> Don't accept pings on syndicated posts from this feed</label></li>
1140
+ </ul></td>
1141
+ </tr>
1142
+ </table>
1143
+ </fieldset>
1144
+
1145
+ <p class="submit">
1146
+ <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1147
+ </p>
1148
+
1149
+ <fieldset>
1150
+ <legend>Advanced Feed Options</legend>
1151
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1152
+ <tr>
1153
+ <th width="20%" scope="row" style="vertical-align:top">Unfamiliar authors:</th>
1154
+ <td width="80%"><ul style="margin: 0; list-style:none">
1155
+ <li><label><input type="radio" name="unfamiliar_author" value="site-default"<?=$unfamiliar['author']['site-default']?> /> use site-wide setting from <a href="options-general.php?page=<?=basename(__FILE__)?>">Syndication Options</a>
1156
+ (currently <strong><?=FeedWordPress::on_unfamiliar('author');?></strong>)</label></li>
1157
+ <li><label><input type="radio" name="unfamiliar_author" value="create"<?=$unfamiliar['author']['create']?>/> create a new author account</label></li>
1158
+ <li><label><input type="radio" name="unfamiliar_author" value="default"<?=$unfamiliar['author']['default']?> /> attribute the post to the default author</label></li>
1159
+ <li><label><input type="radio" name="unfamiliar_author" value="filter"<?=$unfamiliar['author']['filter']?> /> don't syndicate the post</label></li>
1160
+ </ul></td>
1161
+ </tr>
1162
+
1163
+ <tr>
1164
+ <th width="20%" scope="row" style="vertical-align:top">Unfamiliar categories:</th>
1165
+ <td width="80%"><ul style="margin: 0; list-style:none">
1166
+ <li><label><input type="radio" name="unfamiliar_category" value="site-default"<?=$unfamiliar['category']['site-default']?> /> use site-wide setting from <a href="options-general.php?page=<?=basename(__FILE__)?>">Syndication Options</a>
1167
+ (currently <strong><?=FeedWordPress::on_unfamiliar('category');?></strong>)</label></li>
1168
+ <li><label><input type="radio" name="unfamiliar_category" value="create"<?=$unfamiliar['category']['create']?> /> create any categories the post is in</label></li>
1169
+ <li><label><input type="radio" name="unfamiliar_category" value="default"<?=$unfamiliar['category']['default']?> /> don't create new categories</label></li>
1170
+ <li><label><input type="radio" name="unfamiliar_category" value="filter"<?=$unfamiliar['category']['filter']?> /> don't create new categories and don't syndicate posts unless they match at least one familiar category</label></li>
1171
+ </ul></td>
1172
+ </tr></table>
1173
+ </fieldset>
1174
+
1175
+ <p class="submit">
1176
+ <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1177
+ </p>
1178
+
1179
+ <fieldset id="postcustom">
1180
+ <legend>Custom Settings (for use in templates)</legend>
1181
+ <div id="postcustomstuff">
1182
+ <table id="meta-list" cellpadding="3">
1183
+ <tr>
1184
+ <th>Key</th>
1185
+ <th>Value</th>
1186
+ <th>Action</th>
1187
+ </tr>
1188
+
1189
+ <?php
1190
+ $i = 0;
1191
+ foreach ($meta as $key => $value) :
1192
+ if (!preg_match("\007^((".implode(')|(', $special_settings)."))$\007i", $key)) :
1193
+ ?>
1194
+ <tr style="vertical-align:top">
1195
+ <th width="30%" scope="row"><input type="hidden" name="notes[<?=$i?>][key0]" value="<?=wp_specialchars($key)?>" />
1196
+ <input id="notes-<?=$i?>-key" name="notes[<?=$i?>][key1]" value="<?=wp_specialchars($key)?>" /></th>
1197
+ <td width="60%"><textarea rows="2" cols="40" id="notes-<?=$i?>-value" name="notes[<?=$i?>][value]"><?=wp_specialchars($value)?></textarea></td>
1198
+ <td width="10%"><select name="notes[<?=$i?>][action]">
1199
+ <option value="update">save changes</option>
1200
+ <option value="delete">delete this setting</option>
1201
+ </select></td>
1202
+ </tr>
1203
+ <?php
1204
+ $i++;
1205
+ endif;
1206
+ endforeach;
1207
+ ?>
1208
+ <tr>
1209
+ <th scope="row"><input type="text" size="10" name="notes[<?=$i?>][key1]" value="" /></th>
1210
+ <td><textarea name="notes[<?=$i?>][value]" rows="2" cols="40"></textarea></td>
1211
+ <td><em>add new setting...</em><input type="hidden" name="notes[<?=$i?>][action]" value="update" /></td>
1212
+ </tr>
1213
+ </table>
1214
+ </fieldset>
1215
+
1216
+ <p class="submit">
1217
+ <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1218
+ </p>
1219
+
1220
+ </div>
1221
+ <?php
1222
+ endif;
1223
+ return false; // Don't continue
1224
+ }
1225
+
1226
  function fwp_multidelete_page () {
1227
  global $wpdb, $user_level;
1228
+
1229
+ check_admin_referer(); // Make sure the referers are kosher
1230
+
1231
+ $link_ids = (isset($_REQUEST['link_ids']) ? $_REQUEST['link_ids'] : array());
1232
+ if (isset($_REQUEST['link_id'])) : array_push($link_ids, $_REQUEST['link_id']); endif;
1233
+
1234
  if ($user_level < 5):
1235
  die (__("Cheatin' uh ?"));
1236
+ elseif (isset($_POST['confirm']) and $_POST['confirm']=='Delete'):
1237
+ foreach ($_POST['link_action'] as $link_id => $what) :
1238
+ $do_it[$what][] = $link_id;
1239
+ endforeach;
 
 
1240
 
1241
+ $alter = array();
1242
+ if (count($do_it['hide']) > 0) :
1243
+ $hidem = "(".implode(', ', $do_it['hide']).")";
1244
+ $alter[] = "
1245
+ UPDATE $wpdb->links
1246
+ SET link_visible = 'N'
1247
+ WHERE link_id IN {$hidem}
1248
+ ";
1249
  endif;
1250
+
1251
+ if (count($do_it['nuke']) > 0) :
1252
+ $nukem = "(".implode(', ', $do_it['nuke']).")";
1253
+
1254
+ // Make a list of the items syndicated from this feed...
1255
+ $post_ids = $wpdb->get_col("
1256
+ SELECT post_id FROM $wpdb->postmeta
1257
+ WHERE meta_key = 'syndication_feed_id'
1258
+ AND meta_value IN {$nukem}
1259
+ ");
1260
+
1261
+ // ... and kill them all
1262
+ if (count($post_ids) > 0) :
1263
+ foreach ($post_ids as $post_id) :
1264
+ wp_delete_post($post_id);
1265
+ endforeach;
1266
+ endif;
1267
+
1268
+ $alter[] = "
1269
+ DELETE FROM $wpdb->links
1270
+ WHERE link_id IN {$nukem}
1271
+ ";
1272
+ endif;
1273
+
1274
+ if (count($do_it['delete']) > 0) :
1275
+ $deletem = "(".implode(', ', $do_it['delete']).")";
1276
+
1277
+ // Make the items syndicated from this feed appear to be locally-authored
1278
+ $alter[] = "
1279
+ DELETE FROM $wpdb->postmeta
1280
+ WHERE meta_key = 'syndication_feed_id'
1281
+ AND meta_value IN {$deletem}
1282
+ ";
1283
+
1284
+ // ... and delete the links themselves.
1285
+ $alter[] = "
1286
+ DELETE FROM $wpdb->links
1287
+ WHERE link_id IN {$deletem}
1288
+ ";
1289
+ endif;
1290
+
1291
+ $errs = array(); $success = array ();
1292
+ foreach ($alter as $sql) :
1293
+ $result = $wpdb->query($sql);
1294
+ if (!$result):
1295
+ $errs[] = mysql_error();
1296
+ endif;
1297
+ endforeach;
1298
+
1299
+ if (count($alter) > 0) :
1300
+ echo "<div class=\"updated\">\n";
1301
+ if (count($errs) > 0) :
1302
+ echo "There were some problems processing your ";
1303
+ echo "unsubscribe request. [SQL: ".implode('; ', $errs)."]";
1304
+ else :
1305
+ echo "Your unsubscribe request(s) have been processed.";
1306
+ endif;
1307
+ echo "</div>\n";
1308
+ endif;
1309
+
1310
+ return true; // Continue on to Syndicated Sites listing
1311
+ else :
1312
+ $targets = $wpdb->get_results("
1313
+ SELECT * FROM $wpdb->links
1314
+ WHERE link_id IN (".implode(",",$link_ids).")
1315
+ ");
1316
+ ?>
1317
+ <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
1318
+ <div class="wrap">
1319
+ <input type="hidden" name="action" value="Unsubscribe" />
1320
+ <input type="hidden" name="confirm" value="Delete" />
1321
+
1322
+ <h2>Unsubscribe from Syndicated Links:</h2>
1323
+ <?php foreach ($targets as $link) :
1324
+ $link_url = wp_specialchars($link->link_url, 1);
1325
+ $link_name = wp_specialchars($link->link_name, 1);
1326
+ $link_description = wp_specialchars($link->link_description);
1327
+ $link_rss = wp_specialchars($link->link_rss);
1328
+ $meta = FeedWordPress::notes_to_settings($link->link_notes);
1329
+ ?>
1330
+ <fieldset>
1331
+ <legend><?=$link_name?></legend>
1332
+ <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1333
+ <tr><th scope="row" width="20%"><?php _e('Feed URI:') ?></th>
1334
+ <td width="80%"><a href="<?=$link_rss?>"><?=$link_rss?></a></td></tr>
1335
+ <tr><th scope="row" width="20%"><?php _e('Short description:') ?></th>
1336
+ <td width="80%"><?=$link_description?></span></td></tr>
1337
+ <tr><th width="20%" scope="row"><?php _e('Homepage:') ?></th>
1338
+ <td width="80%"><a href="<?=$link_url?>"><?=$link_url?></a></td></tr>
1339
+ <tr style="vertical-align:top"><th width="20%" scope="row">Subscription <?php _e('Options') ?>:</th>
1340
+ <td width="80%"><ul style="margin:0; padding: 0; list-style: none">
1341
+ <li><input type="radio" id="hide-<?=$link->link_id?>"
1342
+ name="link_action[<?=$link->link_id?>]" value="hide" />
1343
+ <label for="hide-<?=$link->link_id?>">Turn off the subscription for this
1344
+ syndicated link<br/><span style="font-size:smaller">(Keep the feed information
1345
+ and all the posts from this feed in the database, but don't syndicate any
1346
+ new posts from the feed.)</span></label></li>
1347
+ <li><input type="radio" id="nuke-<?=$link->link_id?>"
1348
+ name="link_action[<?=$link->link_id?>]" value="nuke" />
1349
+ <label for="nuke-<?=$link->link_id?>">Delete this syndicated link and all the
1350
+ posts that were syndicated from it</label></li>
1351
+ <li><input type="radio" id="delete-<?=$link->link_id?>"
1352
+ name="link_action[<?=$link->link_id?>]" value="delete" />
1353
+ <label for="delete-<?=$link->link_id?>">Delete this syndicated link, but
1354
+ <em>keep</em> posts that were syndicated from it (as if they were authored
1355
+ locally).</label></li>
1356
+ <li><input type="radio" id="nothing-<?=$link->link_id?>"
1357
+ name="link_action[<?=$link->link_id?>]" value="nothing" />
1358
+ <label for="nothing-<?=$link->link_id?>">Keep this feed as it is. I changed
1359
+ my mind.</label></li>
1360
+ </ul>
1361
+ </table>
1362
+ </fieldset>
1363
+ <?php endforeach; ?>
1364
+
1365
+ <div class="submit">
1366
+ <input class="delete" type="submit" name="submit" value="<?php _e('Unsubscribe from selected feeds &raquo;') ?>" />
1367
+ </div>
1368
+ </div>
1369
+ <?php
1370
+ return false; // Don't continue on to Syndicated Sites listing
1371
  endif;
 
1372
  }
1373
 
1374
+ ################################################################################
1375
+ ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
1376
+ ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
1377
+ ################################################################################
1378
+
1379
  $fwp_held_ping = NULL; // NULL: not holding pings yet
1380
 
1381
  function fwp_hold_pings () {
1402
  endif;
1403
  }
1404
 
1405
+ ################################################################################
1406
+ ## class FeedWordPress #########################################################
1407
+ ################################################################################
1408
+
1409
+ // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
1410
  class FeedWordPress {
1411
  var $strip_attrs = array (
1412
  array('[a-z]+', 'style'),
1456
  # like so:
1457
  #
1458
  # key: value
1459
+ # cats: computers\nweb
1460
  # feed/key: value
1461
  #
1462
  # Keys that start with "feed/" are gleaned from the data supplied
1463
  # by the feed itself, and will be overwritten with each update.
1464
  #
1465
+ # Values have linebreak characters escaped with C-style
1466
+ # backslashes (so, for example, a newline becomes "\n").
1467
+ #
1468
+ # The value of `cats` is used as a newline-separated list of
1469
  # default categories for any post coming from a particular feed.
1470
  # (In the example above, any posts from this feed will be placed
1471
  # in the "computers" and "web" categories--*in addition to* any
1481
  if ($result): foreach ($result as $link):
1482
  if (strlen($link->link_rss) > 0):
1483
  $sec = FeedWordPress::notes_to_settings($link->link_notes);
1484
+ $sec['link/uri'] = $link->link_rss;
1485
+ $sec['link/name'] = $link->link_name;
1486
+ $sec['link/id'] = $link->link_id;
1487
 
1488
  // `hardcode categories` is deprecated in favor
1489
  // of `unfamiliar categories`
1495
  endif;
1496
 
1497
  if (isset($sec['cats'])):
1498
+ $sec['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $sec['cats']);
1499
  endif;
 
1500
 
1501
  $feeds[] = $sec;
1502
  endif;
1505
  $this->feeds = $feeds;
1506
  } // FeedWordPress::FeedWordPress ()
1507
 
1508
+ # function notes_to_settings (): Convert WordPress Link Notes to array
1509
+ # of feed-level settings
1510
+ #
1511
+ # Arguments:
1512
+ # ----------
1513
+ # * $link_notes (string): the text from the Link Notes section of a link
1514
+ #
1515
+ # Returns:
1516
+ # --------
1517
+ # An associative array of settings stored in the Link Notes field. (For
1518
+ # the `unfamiliar authors` setting, for example, simply look up the
1519
+ # value of $meta['unfamiliar authors'], if $meta contains the value
1520
+ # returned by `notes_to_settings()`.
1521
+ #
1522
+ # Values in FeedWordPress feed settings are escaped using C-style
1523
+ # slashes. The escaped characters will already have been processed and
1524
+ # converted in the returned array.
1525
  function notes_to_settings ($link_notes) {
1526
  $notes = explode("\n", $link_notes);
1527
 
1539
  return $sec;
1540
  } // FeedWordPress::notes_to_settings ()
1541
 
1542
+ # function update (): polls for updates on one or more Contributor feeds
1543
+ #
1544
+ # Arguments:
1545
+ # ----------
1546
+ # * $uri (string): either the URI of the feed to poll, the URI of the
1547
+ # website (human-readable link) whose feed you want to poll, or a
1548
+ # "magic" tag: URI composed of the URI in the constant `RPC_MAGIC`
1549
+ # and a "secret word" set in the FeedWordPress Options.
1550
+ #
1551
+ # If the "magic" URI is used, then FeedWordPress will poll any
1552
+ # feeds that are ready for polling. It will not poll feeds that are
1553
+ # marked as "Invisible" Links (signifying that the subscription has
1554
+ # been de-activated), or feeds that are not yet stale according to
1555
+ # their TTL setting (which is either set in the feed, or else
1556
+ # set randomly within a window of 30 minutes - 2 hours).
1557
+ #
1558
+ # Returns:
1559
+ # --------
1560
+ # * Normally returns an associative array, with 'new' => the number
1561
+ # of new posts added during the update, and 'updated' => the number
1562
+ # of old posts that were updated during the update. If both numbers
1563
+ # are zero, there was no change since the last poll on that URI.
1564
+ #
1565
+ # * Returns NULL if URI it was passed was not a URI that this
1566
+ # installation of FeedWordPress syndicates (the most common cause
1567
+ # of this error is attempts to poll all feeds, lacking, or using
1568
+ # an incorrect, "secret word."
1569
+ #
1570
+ # Effects:
1571
+ # --------
1572
+ # * One or more feeds are polled for updates
1573
+ #
1574
+ # * If the feed Link does not have a hardcoded name set, its Link
1575
+ # Name is synchronized with the feed's title element
1576
+ #
1577
+ # * If the feed Link does not have a hardcoded URI set, its Link URI
1578
+ # is synchronized with the feed's human-readable link element
1579
+ #
1580
+ # * If the feed Link does not have a hardcoded description set, its
1581
+ # Link Description is synchronized with the feed's description,
1582
+ # tagline, or subtitle element.
1583
+ #
1584
+ # * The time of polling is recorded in the feed's settings, and the
1585
+ # TTL (time until the feed is next available for polling) is set
1586
+ # either from the feed (if it is supplied in the ttl or syndication
1587
+ # module elements) or else from a randomly-generated time window
1588
+ # (between 30 minutes and 2 hours).
1589
+ #
1590
+ # * New posts from the polled feed are added to the WordPress store.
1591
+ #
1592
+ # * Updates to existing posts since the last poll are mirrored in the
1593
+ # WordPress store.
1594
+ #
1595
  function update ($uri) {
1596
  global $wpdb;
1597
+
1598
+ $uri = trim($uri);
1599
+
1600
+ if ($GLOBALS['feedwordpress_needs_upgrade']) : // Will make duplicate posts if we don't hold off
1601
+ return NULL;
1602
+ endif;
1603
+
1604
  do_action('feedwordpress_update', $uri);
1605
 
1606
  // Secret voodoo tag: URI for updating *everything*.
1607
  $secret = RPC_MAGIC.FeedWordPress::rpc_secret();
1608
 
1609
+ fwp_hold_pings(); // Only send out one ping for the whole to-do
1610
 
1611
  // Loop through and check for new posts
1612
  $delta = NULL;
1613
+ foreach ($this->feeds as $feed) :
1614
+ $pinged_that = in_array($uri, array($secret, $feed['link/uri'], $feed['feed/link']));
1615
+
1616
+ if ($uri != $secret) : // A site-specific ping always updates
1617
+ $timely = true;
1618
+ elseif (isset($feed['update/hold']) and ($feed['update/hold']=='ping')) :
1619
+ $timely = false;
1620
+ elseif (isset($feed['update/hold']) and ($feed['update/hold']=='next')) :
1621
+ $timely = true;
1622
+ elseif (!isset($feed['update/ttl']) or !isset($feed['update/last'])) :
1623
+ $timely = true;
1624
+ else :
1625
+ $after = ((int) $feed['update/last'])
1626
+ +((int) $feed['update/ttl'] * 60);
1627
+ $timely = (time() >= $after);
1628
+ endif;
1629
+
1630
+ if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
1631
+ $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
1632
+ endif;
1633
+
1634
+ if ($pinged_that and $timely) :
1635
  do_action('feedwordpress_check_feed', array($feed));
1636
  $added = $this->feed2wp($wpdb, $feed);
1637
+ if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1638
+ if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1639
+ endif;
1640
+ endforeach;
1641
+
1642
  do_action('feedwordpress_update_complete', array($delta));
1643
+ fwp_release_pings(); // Now that we're done, send the one ping
1644
 
1645
  return $delta;
1646
  }
1647
 
1648
  function feed2wp ($wpdb, $f) {
1649
+ $feed = fetch_rss($f['link/uri']);
1650
  $new_count = array('new' => 0, 'updated' => 0);
1651
 
1652
  $this->update_feed($wpdb, $feed->channel, $f);
1653
 
1654
  if (is_array($feed->items)) :
1655
  foreach ($feed->items as $item) :
1656
+ $post = $this->item_to_post($wpdb, $item, $feed, $f);
1657
  if (!is_null($post)) :
1658
  $new = $this->add_post($wpdb, $post);
1659
  if ( $new !== false ) $new_count[$new]++;
1728
  return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
1729
  }
1730
 
1731
+ function feed_ttl ($channel) {
1732
+ if (isset($channel['ttl'])) :
1733
+ // "ttl stands for time to live. It's a number of
1734
+ // minutes that indicates how long a channel can be
1735
+ // cached before refreshing from the source."
1736
+ // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
1737
+ $ret = $channel['ttl'];
1738
+ elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
1739
+ $period_minutes = array (
1740
+ 'hourly' => 60, /* minutes in an hour */
1741
+ 'daily' => 1440, /* minutes in a day */
1742
+ 'weekly' => 10080, /* minutes in a week */
1743
+ 'monthly' => 43200, /* minutes in a month */
1744
+ 'yearly' => 525600, /* minutes in a year */
1745
+ );
1746
+
1747
+ // "sy:updatePeriod: Describes the period over which the
1748
+ // channel format is updated. Acceptable values are:
1749
+ // hourly, daily, weekly, monthly, yearly. If omitted,
1750
+ // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
1751
+ if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
1752
+ else : $period = 'daily';
1753
+ endif;
1754
+
1755
+ // "sy:updateFrequency: Used to describe the frequency
1756
+ // of updates in relation to the update period. A
1757
+ // positive integer indicates how many times in that
1758
+ // period the channel is updated. ... If omitted a value
1759
+ // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
1760
+ if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
1761
+ else : $freq = 1;
1762
+ endif;
1763
+
1764
+ $ret = (int) ($period_minutes[$period] / $freq);
1765
+ else :
1766
+ $ret = NULL;
1767
+ endif;
1768
+ return $ret;
1769
+ }
1770
+
1771
  function update_feed ($wpdb, $channel, $f) {
1772
+ $link_id = $f['link/id'];
1773
 
1774
  if (!isset($channel['id'])) :
1775
+ $channel['id'] = $f['link/uri'];
1776
  endif;
1777
 
1778
  $update = array();
1793
  endif;
1794
 
1795
  if (is_array($f['cats'])) :
1796
+ $f['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $f['cats']);
1797
  endif;
1798
 
1799
  $f = array_merge($f, $this->flatten_array($channel));
1800
+
1801
+ $f['update/last'] = time();
1802
+ $ttl = $this->feed_ttl($channel);
1803
+ if (!is_null($ttl)) :
1804
+ $f['update/ttl'] = $ttl;
1805
+ $f['update/timed'] = 'feed';
1806
+ else :
1807
+ $f['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates
1808
+ $f['update/timed'] = 'automatically';
1809
+ endif;
1810
+
1811
+ if (!isset($f['update/hold']) or $f['update/hold']!='ping') :
1812
+ $f['update/hold'] = 'scheduled';
1813
+ endif;
1814
 
1815
  # -- A few things we don't want to save in the notes
1816
+ unset($f['link/id']); unset($f['link/uri']);
1817
+ unset($f['link/name']);
1818
  unset($f['hardcode categories']); // Deprecated
1819
 
1820
  $notes = '';
1832
  WHERE link_id='$link_id'
1833
  ");
1834
  } // function FeedWordPress::update_feed ()
1835
+
1836
+ function date_created ($item) {
1837
+ if (isset($item['dc']['created'])) :
1838
+ $epoch = @parse_w3cdtf($item['dc']['created']);
1839
+ elseif (isset($item['dcterms']['created'])) :
1840
+ $epoch = @parse_w3cdtf($item['dcterms']['created']);
1841
+ elseif (isset($item['created'])): // Atom 0.3
1842
+ $epoch = @parse_w3cdtf($item['created']);
1843
+ endif;
1844
+ return $epoch;
1845
+ }
1846
+
1847
+ function guid ($item, $feed) {
1848
+ if (isset($item['id'])): // Atom 0.3 / 1.0
1849
+ $guid = $item['id'];
1850
+ elseif (isset($item['atom']['id'])) : // Namespaced Atom
1851
+ $guid = $item['atom']['id'];
1852
+ elseif (isset($item['guid'])) : // RSS 2.0
1853
+ $guid = $item['guid'];
1854
+ elseif (isset($item['dc']['identifier'])) : // yeah, right
1855
+ $guid = $item['dc']['identifier'];
1856
+ else :
1857
+ // The feed does not seem to have provided us with a
1858
+ // unique identifier, so we'll have to cobble together
1859
+ // a tag: URI that might work for us. The base of the
1860
+ // URI will be the host name of the feed source ...
1861
+ $bits = parse_url($feed['link/uri']);
1862
+ $guid = 'tag:'.$bits['host'];
1863
+
1864
+ // If we have a date of creation, then we can use that
1865
+ // to uniquely identify the item. (On the other hand, if
1866
+ // the feed producer was consicentious enough to
1867
+ // generate dates of creation, she probably also was
1868
+ // conscientious enough to generate unique identifiers.)
1869
+ if (!is_null(FeedWordPress::date_created($item))) :
1870
+ $guid .= '://post.'.date('YmdHis', FeedWordPress::date_created($item));
1871
+
1872
+ // Otherwise, use both the URI of the item, *and* the
1873
+ // item's title. We have to use both because titles are
1874
+ // often not unique, and sometimes links aren't unique
1875
+ // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
1876
+ // some podcasts). But it's rare to have *both* the same
1877
+ // title *and* the same link for two different items. So
1878
+ // this is about the best we can do.
1879
+ else :
1880
+ $guid .= '://'.md5($item['link'].'/'.$item['title']);
1881
+ endif;
1882
+ endif;
1883
+ return $guid;
1884
+ }
1885
+
1886
  // item_to_post(): convert information from a single item from an
1887
  // Atom/RSS feed to a post for WordPress's database.
1888
  //
1897
  // add_post()). If you want plugins that have side effects on the posts
1898
  // database, you should probably hook into the action
1899
  // post_syndicated_item
1900
+ function item_to_post($wpdb, $item, $rss, $f) {
1901
+ $channel = $rss->channel;
1902
+
1903
  $post = array();
1904
 
1905
  // This is ugly as all hell. I'd like to use apply_filters()'s
1921
  $post['post_title'] = $wpdb->escape($item['title']);
1922
 
1923
  $post['named']['author'] = array ();
1924
+
1925
+ if (isset($item['author_name'])):
1926
+ $post['named']['author']['name'] = $item['author_name'];
1927
  elseif (isset($item['dc']['creator'])):
1928
+ $post['named']['author']['name'] = $item['dc']['creator'];
1929
+ elseif (isset($item['dc']['contributor'])):
1930
  $post['named']['author']['name'] = $item['dc']['contributor'];
1931
+ elseif (isset($channel['dc']['creator'])) :
1932
+ $post['named']['author']['name'] = $channel['dc']['creator'];
1933
+ elseif (isset($channel['dc']['contributor'])) :
1934
+ $post['named']['author']['name'] = $channel['dc']['contributor'];
1935
+ elseif (isset($channel['author_name'])) :
1936
+ $post['named']['author']['name'] = $channel['author_name'];
1937
+ elseif ($rss->is_rss() and isset($item['author'])) :
1938
+ // The author element in RSS is allegedly an
1939
+ // e-mail address, but lots of people don't use
1940
+ // it that way. So let's make of it what we can.
1941
+ $post['named']['author'] = parse_email_with_realname($item['author']);
1942
+
1943
+ if (!isset($post['named']['author']['name'])) :
1944
+ if (isset($post['named']['author']['email'])) :
1945
+ $post['named']['author']['name'] = $post['named']['author']['email'];
1946
+ else :
1947
+ $post['named']['author']['name'] = $channel['title'];
1948
+ endif;
1949
+ endif;
1950
+ else :
1951
  $post['named']['author']['name'] = $channel['title'];
1952
  endif;
1953
 
1954
  if (isset($item['author_email'])):
1955
  $post['named']['author']['email'] = $item['author_email'];
1956
+ elseif (isset($channel['author_email'])) :
1957
+ $post['named']['author']['email'] = $channel['author_email'];
1958
  endif;
1959
 
1960
  if (isset($item['author_url'])):
1961
  $post['named']['author']['uri'] = $item['author_url'];
1962
+ elseif (isset($channel['author_url'])) :
1963
+ $post['named']['author']['uri'] = $item['author_url'];
1964
  else:
1965
  $post['named']['author']['uri'] = $channel['link'];
1966
  endif;
1972
 
1973
  # Identify content and sanitize it.
1974
  # ---------------------------------
1975
+ if (isset($item['xhtml']['body'])) :
1976
+ $content = $item['xhtml']['body'];
1977
+ elseif (isset($item['xhtml']['div'])) :
1978
+ $content = $item['xhtml']['div'];
1979
+ elseif (isset($item['content']['encoded']) and $item['content']['encoded']):
1980
  $content = $item['content']['encoded'];
1981
  else:
1982
  $content = $item['description'];
2038
  # current time.
2039
  if (isset($item['dc']['date'])):
2040
  $post['epoch']['issued'] = parse_w3cdtf($item['dc']['date']);
2041
+ elseif (isset($item['dcterms']['issued'])) :
2042
+ $post['epoch']['issued'] = parse_w3cdtf($item['dcterms']['issued']);
2043
+ elseif (isset($item['published'])) : // Atom 1.0
2044
+ $post['epoch']['issued'] = parse_w3cdtf($item['published']);
2045
+ elseif (isset($item['issued'])): // Atom 0.3
2046
  $post['epoch']['issued'] = parse_w3cdtf($item['issued']);
2047
+ elseif (isset($item['pubdate'])): // RSS 2.0
2048
  $post['epoch']['issued'] = strtotime($item['pubdate']);
2049
  else:
2050
  $post['epoch']['issued'] = time();
2051
  endif;
2052
 
2053
+ # And again, for the created date
2054
+ $post['epoch']['created'] = FeedWordPress::date_created($item);
2055
+
2056
  # As far as I know, only atom currently has a reliable way to
2057
  # specify when something was *modified* last
2058
+ if (isset($item['dc']['modified'])) : // Not really correct
2059
+ $post['epoch']['modified'] = @parse_w3cdtf($item['dc']['modified']);
2060
+ elseif (isset($item['dcterms']['modified'])) : // Dublin Core extensions
2061
+ $post['epoch']['modified'] = @parse_w3cdtf($item['dcterms']['modified']);
2062
+ elseif (isset($item['modified'])): // Atom 0.3
2063
+ $post['epoch']['modified'] = @parse_w3cdtf($item['modified']);
2064
+ elseif (isset($item['updated'])): // Atom 1.0
2065
+ $post['epoch']['modified'] = @parse_w3cdtf($item['updated']);
2066
+ else : // Fall back to issued / dc:date
2067
  $post['epoch']['modified'] = $post['epoch']['issued'];
2068
  endif;
2069
 
2078
  $post['ping_status'] = FeedWordPress::syndicated_status('ping', $f, 'closed');
2079
 
2080
  // Unique ID (hopefully a unique tag: URI); failing that, the permalink
2081
+ $post['guid'] = $wpdb->escape(FeedWordPress::guid($item, $f));
 
 
 
 
2082
 
2083
+ // RSS 2.0 / Atom 1.0 enclosure support
2084
+ if ( isset($item['enclosure#']) ) :
2085
+ for ($i = 1; $i <= $item['enclosure#']; $i++) :
2086
+ $eid = (($i > 1) ? "#{$id}" : "");
2087
  $post['meta']['enclosure'][] =
2088
+ $item["enclosure{$eid}@url"]."\n".
2089
+ $item["enclosure{$eid}@length"]."\n".
2090
+ $item["enclosure{$eid}@type"];
2091
+ endfor;
2092
  endif;
2093
 
2094
  // In case you want to point back to the blog this was syndicated from
2095
  if (isset($channel['title'])) $post['meta']['syndication_source'] = $channel['title'];
2096
  if (isset($channel['link'])) $post['meta']['syndication_source_uri'] = $channel['link'];
2097
+
2098
+ // Store information on human-readable and machine-readable comment URIs
2099
+ if (isset($item['comments'])) : $post['meta']['rss:comments'] = $item['comments']; endif;
2100
+ if (isset($item['wfw']['commentrss'])) : $post['meta']['wfw:commentRSS'] = $item['wfw']['commentrss']; endif;
2101
+
2102
+ // Store information to identify the feed that this came from
2103
+ $post['meta']['syndication_feed'] = $f['link/uri'];
2104
+ $post['meta']['syndication_feed_id'] = $f['link/id'];
2105
 
2106
  // In case you want to know the external permalink...
2107
  $post['meta']['syndication_permalink'] = $item['link'];
2111
  $post['named']['unfamiliar']['category'] = $f['unfamiliar categories'];
2112
 
2113
  // Categories: start with default categories
2114
+ $fc = get_settings("feedwordpress_syndication_cats");
2115
+ if ($fc) : $post['named']['preset/category'] = explode("\n", $fc);
2116
+ else : $post['named']['preset/category'] = array();
2117
+ endif;
2118
+ $post['named']['preset/category'] = array_merge($post['named']['preset/category'], $f['cats']);
2119
 
2120
  // Now add categories from the post, if we have 'em
2121
+ $post['named']['category'] = array();
2122
+ if ( isset($item['category#']) ) :
2123
+ for ($i = 1; $i <= $item['category#']; $i++) :
2124
+ $cat_idx = (($i > 1) ? "#{$i}" : "");
2125
+ $cat = $item["category{$cat_idx}"];
2126
+
2127
+ if ( strpos($f['link/uri'], 'del.icio.us') !== false ):
2128
  $post['named']['category'] = array_merge($post['named']['category'], explode(' ', $cat));
2129
  else:
2130
  $post['named']['category'][] = $cat;
2131
  endif;
2132
+ endfor;
2133
  endif;
2134
  endif;
2135
  return $post;
2169
  $post['named']['category'],
2170
  FeedWordPress::on_unfamiliar('category', $post['named']['unfamiliar']['category'])
2171
  );
2172
+
2173
+ if (is_null($post['post_category'])) : // filter mode on, no matching categories; drop the post
2174
  $freshness = 0;
2175
+ else : // filter mode off or at least one match; now add on the feed and global presets
2176
+ $post['post_category'] = array_merge (
2177
+ $post['post_category'],
2178
+ $this->lookup_categories (
2179
+ $wpdb,
2180
+ $post['named']['preset/category'],
2181
+ 'default'
2182
+ )
2183
+ );
2184
  endif;
2185
  endif;
2186
 
2272
  do_action('edit_post', $postId);
2273
 
2274
  $this->add_rss_meta($wpdb, $postId, $post);
2275
+
2276
  do_action('update_syndicated_item', $postId);
2277
 
2278
  $ret = 'updated';
2352
  function author_to_id ($wpdb, $author, $email, $url, $unfamiliar_author = 'create') {
2353
  // Never can be too careful...
2354
  $nice_author = sanitize_title($author);
2355
+ $reg_author = $wpdb->escape(preg_quote($author));
2356
  $author = $wpdb->escape($author);
2357
  $email = $wpdb->escape($email);
2358
  $url = $wpdb->escape($url);
2359
+
2360
  $id = $wpdb->get_var(
2361
  "SELECT ID from $wpdb->users
2362
  WHERE
2363
+ TRIM(LCASE(user_login)) = TRIM(LCASE('$author')) OR
2364
+ TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
2365
+ TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
2366
+ TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
2367
+ TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
2368
+ (
2369
+ LOWER(user_description)
2370
+ RLIKE CONCAT(
2371
+ '(^|\\n)a.k.a.( |\\t)*:?( |\\t)*',
2372
+ LCASE('$reg_author'),
2373
+ '( |\\t|\\r)*(\\n|\$)'
2374
+ )
2375
+ )
2376
  ");
2377
+
2378
  if (is_null($id)) :
2379
  if ($unfamiliar_author === 'create') :
2380
  $wpdb->query (
2415
  $cat_str = array ();
2416
  $cat_aka = array ();
2417
  foreach ( $cats as $c ) :
2418
+ $resc = $wpdb->escape(preg_quote($c));
2419
  $esc = $wpdb->escape($c);
2420
  $cat_str[] = "'$esc'";
2421
+
2422
+ $cat_aka[] = "(LOWER(category_description)
2423
+ RLIKE CONCAT('(^|\n)a.k.a.( |\t)*:?( |\t)*', LOWER('{$resc}'), '( |\t|\r)*(\n|\$)'))";
2424
  endforeach;
2425
 
2426
  $match_cat_name = 'cat_name IN ('.join(',', $cat_str).')';
2427
  $match_cat_alias = join(' OR ', $cat_aka);
2428
 
 
 
 
 
 
2429
  $results = $wpdb->get_results(
2430
  "SELECT
2431
  cat_ID,
2432
+ cat_name,
2433
+ category_description
2434
  FROM $wpdb->categories
2435
  WHERE ($match_cat_name) OR ($match_cat_alias)"
2436
  );
2445
  $cat_ids[] = $row->cat_ID;
2446
 
2447
  // Add name to list of categories not to
2448
+ // create afresh. Normalizing case with
2449
+ // strtolower() avoids mismatches in
2450
+ // VARCHAR comparison between PHP (which
2451
+ // has case-sensitive comparisons) and
2452
+ // MySQL (which has case-insensitive
2453
+ // comparisons for the field types used
2454
+ // by WordPress)
2455
+ $found[] = strtolower(trim($row->cat_name));
2456
 
2457
  // Add name of any aliases to list of
2458
  // categories not to create afresh.
2459
  if (preg_match_all('/^a.k.a. \s* :? \s* (.*\S) \s*$/mx',
2460
  $row->category_description, $aka,
2461
  PREG_PATTERN_ORDER)) :
2462
+ $found = array_merge (
2463
+ $found,
2464
+ array_map('strtolower',
2465
+ array_map('trim',
2466
+ $aka[1]
2467
+ ))
2468
+ );
2469
  endif;
2470
  endforeach;
2471
  endif;
2556
  ");
2557
  return $cat_name;
2558
  }
2559
+
2560
+ function upgrade_database ($from = NULL) {
2561
+ global $wpdb;
2562
+
2563
+ if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
2564
+
2565
+ switch ($from) :
2566
+ case 0.96: // account for changes to syndication custom values and guid
2567
+ echo "<p>Upgrading database from {$from} to 0.97...</p>\n";
2568
+
2569
+ $cat_id = FeedWordPress::link_category_id();
2570
+
2571
+ // Avoid duplicates
2572
+ $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'");
2573
+
2574
+ // Look up all the link IDs
2575
+ $wpdb->query("
2576
+ CREATE TEMPORARY TABLE tmp_custom_values
2577
+ SELECT
2578
+ NULL AS meta_id,
2579
+ post_id,
2580
+ 'syndication_feed_id' AS meta_key,
2581
+ link_id AS meta_value
2582
+ FROM `{$wpdb->postmeta}`, `{$wpdb->links}`
2583
+ WHERE
2584
+ meta_key='syndication_feed'
2585
+ AND meta_value=link_rss
2586
+ AND link_category = {$cat_id}
2587
+ ");
2588
+
2589
+ // Now attach them to their posts
2590
+ $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values");
2591
+
2592
+ // And clean up after ourselves.
2593
+ $wpdb->query("DROP TABLE tmp_custom_values");
2594
+
2595
+ // Now fix the guids to avoid duplicate posts
2596
+ echo "<ul>";
2597
+ foreach ($this->feeds as $feed) :
2598
+ echo "<li>Fixing post meta-data for <cite>".$feed['link/name']."</cite> &#8230; "; flush();
2599
+ $rss = @fetch_rss($feed['link/uri']);
2600
+ if (is_array($rss->items)) :
2601
+ foreach ($rss->items as $item) :
2602
+ $guid = $wpdb->escape(FeedWordPress::guid($item, $feed)); // new GUID algorithm
2603
+ $link = $wpdb->escape($item['link']);
2604
+
2605
+ $wpdb->query("
2606
+ UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}'
2607
+ ");
2608
+ endforeach;
2609
+ endif;
2610
+ echo "<strong>complete.</strong></li>\n";
2611
+ endforeach;
2612
+ echo "</ul>\n";
2613
+
2614
+ // Mark the upgrade as successful.
2615
+ update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2616
+ endswitch;
2617
+ echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
2618
+ } /* FeedWordPress::upgrade_database() */
2619
+
2620
  } // class FeedWordPress
2621
 
2622
+ ################################################################################
2623
+ ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
2624
+ ################################################################################
2625
+
2626
  function feedwordpress_xmlrpc_hook ($args = array ()) {
2627
  $args['weblogUpdates.ping'] = 'feedwordpress_pong';
2628
  return $args;
2642
  endif;
2643
  }
2644
 
2645
+ ################################################################################
2646
+ ## class FeedFinder: find likely feeds using autodetection and/or guesswork ####
2647
+ ################################################################################
2648
+
2649
  class FeedFinder {
2650
  var $uri = NULL;
2651
  var $_cache_uri = NULL;
2976
  return preg_replace('/[^\x21-\x7e]/', '', $encoded);
2977
  }
2978
  }
2979
+
2980
+ // take your best guess at the realname and e-mail, given a string
2981
+ define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2982
+ define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
2983
+ define('FWP_REGEX_EMAIL_POSTFIX_NAME', "/^\s*".FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME."\)\s*$/");
2984
+ define('FWP_REGEX_EMAIL_PREFIX_NAME', "/^\s*".FWP_REGEX_EMAIL_NAME."\s*<".FWP_REGEX_EMAIL_ADDY.">\s*$/");
2985
+ define('FWP_REGEX_EMAIL_JUST_ADDY', "/^\s*".FWP_REGEX_EMAIL_ADDY."\s*$/");
2986
+ define('FWP_REGEX_EMAIL_JUST_NAME', "/^\s*".FWP_REGEX_EMAIL_NAME."\s*$/");
2987
+
2988
+ function parse_email_with_realname ($email) {
2989
+ if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
2990
+ ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
2991
+ $ret['email'] = $matches[1];
2992
+ elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
2993
+ ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2994
+ $ret['email'] = $matches[4];
2995
+ elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
2996
+ $ret['name'] = NULL; $ret['email'] = $matches[1];
2997
+ elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
2998
+ $ret['email'] = NULL;
2999
+ ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
3000
+ else :
3001
+ $ret['name'] = NULL; $ret['email'] = NULL;
3002
+ endif;
3003
+ return $ret;
3004
+ }
3005
+
3006
  ?>
wp-content/update-feeds.php CHANGED
@@ -5,7 +5,7 @@
5
  # URI: <http://projects.radgeek.com/feedwordpress>
6
  # Author: Charles Johnson <technophilia@radgeek.com>
7
  # License: GPL
8
- # Version: 2005.04.09
9
  #
10
  # USAGE
11
  # -----
@@ -18,90 +18,188 @@
18
  # <http://projects.radgeek.com/feedwordpress/install> if you need a guide
19
  # for the perplexed.)
20
  #
21
- # 2. Set up a cron job to run update-feeds.php locally:
 
 
 
 
22
  #
23
- # cd <your-wordpress>/wp-content ; php update-feeds.php
 
24
  #
25
- # or to send an HTTP POST request to the appropriate URI:
26
  #
27
- # curl http://xyz.com/wp-content/update-feeds.php -d shibboleth=foo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  #
29
  # 4. If you want to update *one* of the feeds rather than *all* of them, then
30
  # pass the URI and title as command-line arguments:
31
  #
32
- # $ php update-feeds.php http://www.radgeek.com "Geekery Today"
33
  #
34
  # or in the POST request:
35
  #
36
- # $ curl http://www.xyz.com/wp-content/update-feeds.php -d uri=http://www.radgeek.com\&title=Geekery+Today\&shibboleth=foo
37
  #
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- require_once ('../wp-config.php');
40
- require_once (ABSPATH . WPINC . '/class-IXR.php');
 
 
 
 
 
 
 
 
 
41
 
42
  # -- Don't change these unless you know what you're doing...
43
- define ('RPC_URI', NULL); // Change this setting to ping a URI of your own devising
44
  define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/'); // update all
45
 
46
- if (is_null(RPC_URI)):
47
- $rpc_uri = get_settings('siteurl');
48
- if (substr($rpc_uri,-1)!='/') $rpc_uri .= '/';
49
- $rpc_uri .= 'xmlrpc.php';
50
- else:
51
- $rpc_uri = RPC_URI;
52
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- # -- Are we running from an HTTP GET or from the command line?
55
- if (isset($_SERVER['REQUEST_URI'])) {
56
- $rpc_secret = (isset($_REQUEST['shibboleth'])?$_REQUEST['shibboleth']:'');
57
  $uri = (isset($_REQUEST['uri']) ? $_REQUEST['uri'] : RPC_MAGIC.$rpc_secret);
58
- $blog = (isset($_REQUEST['title']) ? $_REQUEST['title'] : 'Refresh');
59
 
60
- echo <<< EOHTML
 
 
61
  <html>
62
  <head>
63
  <title>update-feeds :: FeedWordPress</title>
64
  </head>
65
 
66
  <body>
67
- <h1>update-feeds: instruct FeedWordPress to look for new syndicated content</h1>
68
 
69
- <p>Sending ping to &lt;$rpc_uri&gt;...</p>
70
-
71
- <p>
72
  EOHTML;
73
- } else {
74
- // Query secret word from database
75
- $rpc_secret = get_settings('feedwordpress_rpc_secret');
76
-
 
 
77
  $uri = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : RPC_MAGIC.$rpc_secret);
78
- $blog = (isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 'Refresh');
79
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- $client =& new IXR_Client($rpc_uri);
82
- $ret = $client->query('weblogUpdates.ping', $blog, $uri);
83
-
84
- if (!$ret):
85
- if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
86
- echo "The XML-RPC ping failed (local): ".wp_specialchars($client->getErrorMessage())."\n";
87
- else:
88
- $response = $client->getResponse();
89
- if ($response['flerror']):
90
- if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
91
- echo "The XML-RPC ping failed (remote): ".wp_specialchars($response['message'])."\n";
92
- elseif (isset($_SERVER['REQUEST_URI'])):
93
- if (!isset($_SERVER['REQUEST_URI'])) echo "[".date('Y-m-d H:i:s')."][update-feeds] ";
94
- echo "The XML-RPC ping succeeded: ".wp_specialchars($response['message'])."\n";
 
 
 
 
 
 
 
 
95
  endif;
96
  endif;
97
 
98
- if (isset($_SERVER['REQUEST_URI'])) {
99
  echo <<<EOHTML
100
- </p>
101
 
102
  <p><a href="../wp-admin">&larr; Return to WordPress Dashboard</a></p>
103
  </body>
104
  </html>
105
  EOHTML;
106
- }
107
  ?>
5
  # URI: <http://projects.radgeek.com/feedwordpress>
6
  # Author: Charles Johnson <technophilia@radgeek.com>
7
  # License: GPL
8
+ # Version: 2005.11.06
9
  #
10
  # USAGE
11
  # -----
18
  # <http://projects.radgeek.com/feedwordpress/install> if you need a guide
19
  # for the perplexed.)
20
  #
21
+ # 2. If you want to manually update one or more of your feeds, you can do
22
+ # so by pointing your web browser to the URI
23
+ # <http://xyz.com/wp-content/update-feeds.php>, where `http://xyz.com/` is
24
+ # replaced by the URI to your installation of WordPress. Log in as any
25
+ # user in your WordPress database and use the form to update.
26
  #
27
+ # 3. To keep content up-to-date automatically, set up a cron job to run
28
+ # update-feeds.php locally:
29
  #
30
+ # cd /path/to/wordpress/wp-content ; php update-feeds.php
31
  #
32
+ # where `/path/to/wordpress` is replaced by the filesystem path to your
33
+ # installation of WordPress; or, if you don't have, or don't want to use,
34
+ # command-line PHP, you can send an HTTP POST request to the appropriate
35
+ # URI:
36
+ #
37
+ # curl --user user:pass http://xyz.com/wp-content/update-feeds.php -d update=quiet
38
+ #
39
+ # `user` and `pass` should be replaced by the username and password of
40
+ # a user in your WordPress database (you can create a dummy user for
41
+ # updates if you want; that's what I do). `http://xyz.com/` should be
42
+ # replaced by the URI to your installation of WordPress.
43
+ #
44
+ # Don't be afraid to run this cron job frequently. FeedWordPress staggers
45
+ # updates over time rather than checking all of the feeds every time the
46
+ # cron job runs, so even if the cron job runs every 10 minutes, each feed
47
+ # will, on average only be polled for updates once an hour or so (or less
48
+ # frequently if the feed author requests less frequent updates using
49
+ # the RSS <ttl> element or the syndication module elements).
50
  #
51
  # 4. If you want to update *one* of the feeds rather than *all* of them, then
52
  # pass the URI and title as command-line arguments:
53
  #
54
+ # $ php update-feeds.php http://radgeek.com
55
  #
56
  # or in the POST request:
57
  #
58
+ # $ curl --user login:password http://xyz.com/wp-content/update-feeds.php -d uri=http://www.radgeek.com\&update=quiet
59
  #
60
+ // Help us to pick out errors, if any.
61
+ ini_set('error_reporting', E_ALL & ~E_NOTICE);
62
+ ini_set('display_errors', true);
63
+ define('MAGPIE_DEBUG', true);
64
+
65
+ // Are we running from a web request or from the command line?
66
+ if (!isset($_SERVER['REQUEST_URI'])) :
67
+ $update_feeds_display = 'text/plain';
68
+ $update_feeds_invoke = 'cmd';
69
+ elseif (isset($_POST['update'])) :
70
+ if ($_POST['update'] == 'quiet') :
71
+ $update_feeds_display = 'text/plain';
72
+ $update_feeds_invoke = 'post';
73
+ $update_feeds_verbose = false;
74
+ elseif ($_POST['update'] == 'verbose') :
75
+ $update_feeds_display = 'text/plain';
76
+ $update_feeds_invoke = 'post';
77
+ $update_feeds_verbose = true;
78
+ else :
79
+ $update_feeds_display = 'text/html';
80
+ $update_feeds_invoke = 'post';
81
+ endif;
82
+ else :
83
+ $update_feeds_display = 'text/html';
84
+ $update_feeds_invoke = 'get';
85
+ endif;
86
+
87
+ require_once ('../wp-blog-header.php');
88
 
89
+ function update_feeds_mention ($feed) {
90
+ global $update_feeds_display;
91
+
92
+ if ($update_feeds_display=='text/html') :
93
+ echo "<li>Updating <cite>".$feed['link/name']."</cite> from &lt;<a href=\""
94
+ .$feed['link/uri']."\">".$feed['link/uri']."</a>&gt; ...</li>\n";
95
+ else :
96
+ echo "* Updating ".$feed['link/name']." from <".$feed['link/uri']."> ...\n";
97
+ endif;
98
+ flush();
99
+ }
100
 
101
  # -- Don't change these unless you know what you're doing...
 
102
  define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/'); // update all
103
 
104
+ // Query secret word from database
105
+ $rpc_secret = get_settings('feedwordpress_rpc_secret');
106
+
107
+ header("Content-Type: {$update_feeds_display}; charset=utf-8");
108
+
109
+ # -- Are we running from an HTTP GET, HTTP POST, or from the command line?
110
+ if ($update_feeds_invoke != 'cmd') : // We're acessing this from HTTP GET or HTTP POST
111
+ // Authenticate the user, if possible ...
112
+ if (isset($_SERVER['PHP_AUTH_USER']) and isset($_SERVER['PHP_AUTH_PW'])) : // try HTTP authentication
113
+ $login = $_SERVER['PHP_AUTH_USER']; $pass = $_SERVER['PHP_AUTH_PW'];
114
+ elseif (isset($_POST['log']) and isset($_POST['pwd'])) : // try POST data
115
+ $login = $_REQUEST['log']; $pass = $_REQUEST['pwd'];
116
+ endif;
117
+
118
+ if (empty($login) or empty($pass) or !wp_login($login, $pass)) :
119
+ if ($update_feeds_display=='text/html') :
120
+ auth_redirect(); // try authentication cookies; if all else fails, redirect to wp-login.php
121
+ else :
122
+ echo "update-feeds (".date('Y-m-d H:i:s')."): ERROR: Could not log in as '$login'/pass='$pass']\n";
123
+ die;
124
+ endif;
125
+ endif;
126
 
127
+ // Henceforward, we can proceed on the assumption that we have an authenticated user
 
 
128
  $uri = (isset($_REQUEST['uri']) ? $_REQUEST['uri'] : RPC_MAGIC.$rpc_secret);
 
129
 
130
+ if ($update_feeds_display=='text/html') :
131
+ echo <<<EOHTML
132
+ <?xml version="1.0" encoding="utf-8"?>
133
  <html>
134
  <head>
135
  <title>update-feeds :: FeedWordPress</title>
136
  </head>
137
 
138
  <body>
139
+ <h1>update-feeds: make FeedWordPress check for new syndicated content</h1>
140
 
 
 
 
141
  EOHTML;
142
+ endif;
143
+ else :
144
+ // update-feeds has been invoked from the command line; no further
145
+ // authentication is necessary. (If PAM hasn't already done the
146
+ // necessary screening for you, you have bigger problems than their
147
+ // access to FeedWordPress...)
148
  $uri = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : RPC_MAGIC.$rpc_secret);
149
+ endif;
150
+
151
+ $feedwordpress =& new FeedWordPress;
152
+
153
+ if ($update_feeds_display=='text/html' or $update_feeds_verbose) :
154
+ add_action('feedwordpress_check_feed', 'update_feeds_mention');
155
+ endif;
156
+
157
+ if ($update_feeds_display=='text/html') : // HTTP GET or HTTP POST: add some web niceties
158
+
159
+ echo "<form action=\"\" method=\"POST\">\n";
160
+ echo "<select name=\"uri\">\n";
161
+ echo "<option value=\"".RPC_MAGIC.$rpc_secret."\">All feeds</option>\n";
162
+ foreach ($feedwordpress->feeds as $feed) :
163
+ echo "<option value=\"{$feed['link/uri']}\"";
164
+ if ($feed['link/uri']==$_REQUEST['uri']) : echo ' selected="selected"'; endif;
165
+ echo ">{$feed['link/name']}</option>\n";
166
+ endforeach;
167
+ echo "</select> ";
168
+ echo "<input type=\"submit\" name=\"update\" value=\"Update\" />\n";
169
+ echo "</form>\n";
170
+ endif;
171
 
172
+ if ($update_feeds_invoke != 'get') : // Only do things with side-effects for HTTP POST or command line
173
+ if ($update_feeds_display == 'text/html') : echo "<ul>\n"; endif;
174
+ $delta = @$feedwordpress->update($uri);
175
+ if ($update_feeds_display == 'text/html') : echo "</ul>\n"; endif;
176
+
177
+ if (is_null($delta)) :
178
+ if ($update_feeds_invoke == 'cmd') :
179
+ $stderr = fopen('php://stderr', 'w');
180
+ fputs($stderr, "update-feeds (".date('Y-m-d H:i:s')."): ERROR: I don't syndicate <$uri>\n");
181
+ elseif ($update_feeds_display == 'text/plain') :
182
+ echo "update-feeds (".date('Y-m-d H:i:s')."): ERROR: I don't syndicate <$uri>\n";
183
+ else :
184
+ echo "<p><strong>Error:</strong> I don't syndicate <a href=\"$uri\">$uri</a></p>\n";
185
+ endif;
186
+ elseif ($update_feeds_display=='text/html' or $update_feeds_verbose) :
187
+ $mesg = array();
188
+ if (isset($delta['new'])) : $mesg[] = ' '.$delta['new'].' new posts were syndicated'; endif;
189
+ if (isset($delta['updated'])) : $mesg[] = ' '.$delta['updated'].' existing posts were updated'; endif;
190
+ if ($update_feeds_display=='text/html') : echo "<p>"; endif;
191
+ echo "Update complete.".implode(' and', $mesg);
192
+ if ($update_feeds_display=='text/html') : echo "</p>"; endif;
193
+ echo "\n"; flush();
194
  endif;
195
  endif;
196
 
197
+ if ($update_feeds_display=='text/html') : // HTTP GET or HTTP POST: close off web niceties
198
  echo <<<EOHTML
 
199
 
200
  <p><a href="../wp-admin">&larr; Return to WordPress Dashboard</a></p>
201
  </body>
202
  </html>
203
  EOHTML;
204
+ endif;
205
  ?>