PluginProbe
FeedWordPress / 0.96
FeedWordPress v0.96
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
← All changes | wp-content/plugins/feedwordpress.php +728 -344 0.80.96 View file →
@@ -2,17 +2,15 @@
2 2 /*
3 3 Plugin Name: FeedWordPress
4 4 Plugin URI: http://projects.radgeek.com/feedwordpress
5 5 Description: simple and flexible Atom/RSS syndication for WordPress
6 -Version: 0.8
6 +Version: 0.96
7 7 Author: Charles Johnson
8 8 Author URI: http://www.radgeek.com/
9 +License: GPL
10 +Last modified: 2005-05-08 1:54pm EDT
9 11 */
10 12
11 -# Author: Charles Johnson <technophilia@radgeek.com>
12 -# License: GPL
13 -# Last modified: 2005-03-21
14 -#
15 13 # This uses code derived from:
16 14 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
17 15 # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
18 16 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
@@ -26,14 +24,11 @@
26 24 # contributors to put your XML-RPC URI (if WordPress is installed at
27 25 # <http://www.zyx.com/blog>, XML-RPC requests should be sent to
28 26 # <http://www.zyx.com/blog/xmlrpc.php>), or see `update-feeds.php`
29 27
30 -# -- Change these as you please
31 -define ('FEEDWORDPRESS_LOG_UPDATES', true); // Make false if you hate status updates sent to error_log()
32 -
33 28 # -- Don't change these unless you know what you're doing...
34 29 define ('RPC_MAGIC', 'tag:radgeek.com/projects/feedwordpress/');
35 -define ('FEEDWORDPRESS_VERSION', '0.8');
30 +define ('FEEDWORDPRESS_VERSION', '0.96');
36 31 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
37 32
38 33 // Note that the rss-functions.php that comes prepackaged with WordPress is
39 34 // old & busted. For the new hotness, drop a copy of rss-functions.php from
@@ -39,32 +34,27 @@
39 34 // old & busted. For the new hotness, drop a copy of rss-functions.php from
40 35 // this archive into wp-includes/rss-functions.php
41 36 require_once (ABSPATH . WPINC . '/rss-functions.php');
42 37
43 -// Is this being loaded from within WordPress?
44 -if (!isset($wp_version)):
45 - echo "FeedWordPress/".FEEDWORDPRESS_VERSION.": an Atom/RSS aggregator plugin for WordPress 1.5\n";
46 - exit;
47 -endif;
38 +// Is this being loaded from within WordPress 1.5 or later?
39 +if (isset($wp_version) and $wp_version >= 1.5):
48 40
49 - # Remove default WordPress auto-paragraph filter.
50 - remove_filter('the_content', 'wpautop');
51 - remove_filter('the_excerpt', 'wpautop');
52 - remove_filter('comment_text', 'wpautop');
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);
53 55
54 - # What really should happen here is that we create our own ueber-filter,
55 - # to run with the highest possible priority, which would intercept
56 - # and check whether or not the post comes from off the wire, and
57 - # pre-empty any further formatting filters if it does. Then we could
58 - # leave wpautop in peace and not worry about Markdown, Textile, etc.
59 - # Sadly, WordPress 1.5 gives you no way to pre-empt downstream filters
60 - # (and no way for the furthest downstream filter to recover the original
61 - # content, either.)
62 -
63 - # add_filter('the_content', 'feedwordpress_preempt', 10);
64 - # add_filter('the_excerpt', 'feedwordpress_preempt', 10);
65 - # add_filter('comment_text', 'feedwordpress_preempt', 30);
66 -
56 + # Filter in original permalinks if the user wants that
67 57 add_filter('post_link', 'syndication_permalink', 1);
68 58
69 59 # Admin menu
70 60 add_action('admin_menu', 'fwp_add_pages');
@@ -75,8 +65,54 @@
75 65 # Outbound XML-RPC reform
76 66 remove_action('publish_post', 'generic_ping');
77 67 add_action('publish_post', 'fwp_catch_ping');
78 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 +
79 115 # -- Template functions for syndication sites
80 116 function is_syndicated () { return (strlen(get_syndication_feed()) > 0); }
81 117
82 118 function the_syndication_source_link () { echo get_syndication_source_link(); }
@@ -97,14 +133,10 @@
97 133 $result = $wpdb->get_var("
98 134 SELECT link_notes FROM $wpdb->links
99 135 WHERE link_rss = '".$wpdb->escape($feed)."'"
100 136 );
101 -
102 - $notes = explode("\n", $result);
103 - foreach ($notes as $note):
104 - list($k, $v) = explode(': ', $note, 2);
105 - $meta[$k] = $v;
106 - endforeach;
137 +
138 + $meta = FeedWordPress::notes_to_settings($result);
107 139 $ret = $meta[$key];
108 140 endif; /* if */
109 141 return $ret;
110 142 }
@@ -116,8 +148,31 @@
116 148 echo get_syndication_permalink();
117 149 }
118 150
119 151 # -- Filters for templates and feeds
152 +$feedwordpress_the_syndicated_content = NULL;
153 +
154 +function feedwordpress_preserve_syndicated_content ($text) {
155 + global $feedwordpress_the_syndicated_content;
156 +
157 + if ( is_syndicated() ) :
158 + $feedwordpress_the_syndicated_content = $text;
159 + else :
160 + $feedwordpress_the_syndicated_content = NULL;
161 + endif;
162 + return $text;
163 +}
164 +
165 +function feedwordpress_restore_syndicated_content ($text) {
166 + global $feedwordpress_the_syndicated_content;
167 +
168 + if ( !is_null($feedwordpress_the_syndicated_content) ) :
169 + $text = $feedwordpress_the_syndicated_content;
170 + endif;
171 +
172 + return $text;
173 +}
174 +
120 175 function syndication_permalink ($permalink = '') {
121 176 if (get_settings('feedwordpress_munge_permalink') != 'no'):
122 177 $uri = get_syndication_permalink();
123 178 return ((strlen($uri) > 0) ? $uri : $permalink);
@@ -127,10 +182,10 @@
127 182 } // function syndication_permalink ()
128 183
129 184 # -- Admin menu add-ons
130 185 function fwp_add_pages () {
131 - add_submenu_page('link-manager.php', 'Syndicated Sites', 'Syndicated', 5, __FILE__, 'fwp_syndication_manage_page');
132 - add_options_page('Syndication', 'Syndication', 5, __FILE__, 'fwp_syndication_options_page');
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');
133 188 } // function fwp_add_pages () */
134 189
135 190 function fwp_syndication_options_page () {
136 191 global $wpdb, $user_level;
@@ -138,14 +193,48 @@
138 193 $caption = 'Save Changes';
139 194 if (isset($_REQUEST['action']) and $_REQUEST['action']=$caption):
140 195 check_admin_referer();
141 196
142 - if ($user_level < 5):
197 + if ($user_level < 6):
143 198 die (__("Cheatin' uh ?"));
144 199 else:
145 200 update_option('feedwordpress_rpc_secret', $_REQUEST['rpc_secret']);
146 201 update_option('feedwordpress_cat_id', $_REQUEST['syndication_category']);
147 202 update_option('feedwordpress_munge_permalink', $_REQUEST['munge_permalink']);
203 + update_option('feedwordpress_update_logging', $_REQUEST['update_logging']);
204 + update_option('feedwordpress_unfamiliar_author', $_REQUEST['unfamiliar_author']);
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 :
211 + update_option('feedwordpress_syndicated_comment_status', 'closed');
212 + endif;
213 +
214 + if (isset($_REQUEST['ping_status']) and ($_REQUEST['ping_status'] == 'open')) :
215 + update_option('feedwordpress_syndicated_ping_status', 'open');
216 + else :
217 + update_option('feedwordpress_syndicated_ping_status', 'closed');
218 + endif;
219 +
220 + if (isset($_REQUEST['hardcode_name']) and ($_REQUEST['hardcode_name'] == 'no')) :
221 + update_option('feedwordpress_hardcode_name', 'no');
222 + else :
223 + update_option('feedwordpress_hardcode_name', 'yes');
224 + endif;
225 +
226 + if (isset($_REQUEST['hardcode_description']) and ($_REQUEST['hardcode_description'] == 'no')) :
227 + update_option('feedwordpress_hardcode_description', 'no');
228 + else :
229 + update_option('feedwordpress_hardcode_description', 'yes');
230 + endif;
231 +
232 + if (isset($_REQUEST['hardcode_url']) and ($_REQUEST['hardcode_url'] == 'no')) :
233 + update_option('feedwordpress_hardcode_url', 'no');
234 + else :
235 + update_option('feedwordpress_hardcode_url', 'yes');
236 + endif;
148 237 ?>
149 238 <div class="updated">
150 239 <p><?php _e('Options saved.')?></p>
151 240 </div>
@@ -155,8 +244,28 @@
155 244
156 245 $cat_id = FeedWordPress::link_category_id();
157 246 $rpc_secret = FeedWordPress::rpc_secret();
158 247 $munge_permalink = get_settings('feedwordpress_munge_permalink');
248 + $update_logging = get_settings('feedwordpress_update_logging');
249 +
250 + $hardcode_name = get_settings('feedwordpress_hardcode_name');
251 + $hardcode_description = get_settings('feedwordpress_hardcode_description');
252 + $hardcode_url = get_settings('feedwordpress_hardcode_url');
253 +
254 + $post_status = FeedWordPress::syndicated_status('post', array(), 'publish');
255 + $comment_status = FeedWordPress::syndicated_status('comment', array(), 'closed');
256 + $ping_status = FeedWordPress::syndicated_status('ping', array(), 'closed');
257 +
258 + $unfamiliar_author = array ('create' => '','default' => '','filter' => '');
259 + $ua = FeedWordPress::on_unfamiliar('author');
260 + if (is_string($ua) and array_key_exists($ua, $unfamiliar_author)) :
261 + $unfamiliar_author[$ua] = ' checked="checked"';
262 + endif;
263 + $unfamiliar_category = array ('create'=>'','default'=>'','filter'=>'');
264 + $uc = FeedWordPress::on_unfamiliar('category');
265 + if (is_string($uc) and array_key_exists($uc, $unfamiliar_category)) :
266 + $unfamiliar_category[$uc] = ' checked="checked"';
267 + endif;
159 268 $results = $wpdb->get_results("SELECT cat_id, cat_name, auto_toggle FROM $wpdb->linkcategories ORDER BY cat_id");
160 269 ?>
161 270 <div class="wrap">
162 271 <h2>Syndication Options</h2>
@@ -161,25 +270,11 @@
161 270 <div class="wrap">
162 271 <h2>Syndication Options</h2>
163 272 <form action="" method="post">
164 273 <fieldset class="options">
165 -<legend>Template Options</legend>
274 +<legend>Syndicated Feeds</legend>
166 275 <table class="editform" width="100%" cellspacing="2" cellpadding="5">
167 276 <tr>
168 -<th width="33%" scope="row">Permalinks for syndicated posts point to:</th>
169 -<td width="67%"><select name="munge_permalink" size="1">
170 -<option value="yes"<?=($munge_permalink=='yes')?' selected="selected"':''?>>source website</option>
171 -<option value="no"<?=($munge_permalink=='no')?' selected="selected"':''?>>this website</option>
172 -</select></td>
173 -</tr>
174 -</table>
175 -<div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
176 -</fieldset>
177 -
178 -<fieldset class="options">
179 -<legend>Syndication Options</legend>
180 -<table class="editform" width="100%" cellspacing="2" cellpadding="5">
181 -<tr>
182 277 <th width="33%" scope="row">Syndicate links in category:</th>
183 278 <td width="67%"><?php
184 279 echo "\n<select name=\"syndication_category\" size=\"1\">";
185 280 foreach ($results as $row) {
@@ -193,9 +288,59 @@
193 288 }
194 289 echo "\n</select>\n";
195 290 ?></td>
196 291 </tr>
292 +
293 +<tr><th width="33%" scope="row" style="vertical-align:top">Update live from feed:</th>
294 +<td width="67%"><ul style="margin:0;list-style:none">
295 +<li><input type="checkbox" name="hardcode_name" value="no"<?=(($hardcode_name=='yes')?'':' checked="checked"')?>/> Contributor name (feed title)</li>
296 +<li><input type="checkbox" name="hardcode_description" value="no"<?=(($hardcode_description=='yes')?'':' checked="checked"')?>/> Contributor description (feed tagline)</li>
297 +<li><input type="checkbox" name="hardcode_url" value="no"<?=(($hardcode_url=='yes')?'':' checked="checked"')?>/> Homepage (feed link)</li>
298 +</ul></td></tr>
197 299 </table>
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>
198 343 <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
199 344 </fieldset>
200 345
201 346 <fieldset class="options">
@@ -204,8 +349,16 @@
204 349 <tr>
205 350 <th width="33%" scope="row">XML-RPC update secret word:</th>
206 351 <td width="67%"><input id="rpc_secret" name="rpc_secret" value="<?=$rpc_secret?>" />
207 352 </td>
353 +</tr>
354 +<tr>
355 +<th scope="row">Write update notices to PHP logs:</th>
356 +<td><select name="update_logging" size="1">
357 +<option value="yes"<?=(($update_logging=='yes')?' selected="selected"':'')?>>yes</option>
358 +<option value="no"<?=(($update_logging!='yes')?' selected="selected"':'')?>>no</option>
359 +</select></td>
360 +</tr>
208 361 </table>
209 362 <div class="submit"><input type="submit" name="action" value="<?=$caption?>" /></div>
210 363 </fieldset>
211 364 </form>
@@ -218,11 +371,11 @@
218 371 ?>
219 372 <?php $cont = true;
220 373 if (isset($_REQUEST['action'])):
221 374 //die("ACTION: '".$_REQUEST['action']."'");
222 - if ($_REQUEST['action'] == 'feedfinder'): $cont = fwp_feedfinder_page();
223 - elseif ($_REQUEST['action'] == 'switchfeed'): $cont = fwp_switchfeed_page();
224 - elseif ($_REQUEST['action'] == 'Delete Checked'): $cont = fwp_multidelete_page();
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();
225 378 endif;
226 379 endif;
227 380
228 381 if ($cont):
@@ -230,9 +383,9 @@
230 383 <?php
231 384 $links = get_linkobjects(FeedWordPress::link_category_id());
232 385 ?>
233 386 <div class="wrap">
234 - <form action="link-manager.php?page=feedwordpress.php" method="post">
387 + <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
235 388 <h2>Syndicate a new site:</h2>
236 389 <div>
237 390 <label for="add-uri">Website or newsfeed:</label>
238 391 <input type="text" name="lookup" id="add-uri" value="URI" size="64" />
@@ -241,9 +394,9 @@
241 394 <div class="submit"><input type="submit" value="Syndicate &raquo;" /></div>
242 395 </form>
243 396 </div>
244 397
245 - <form action="link-manager.php?page=feedwordpress.php" method="post">
398 + <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
246 399 <div class="wrap">
247 400 <h2>Syndicated Sites</h2>
248 401 <?php $alt_row = true;
249 402 if ($links): ?>
@@ -261,9 +414,9 @@
261 414 <td><a href="<?=wp_specialchars($link->link_url)?>"><?=wp_specialchars($link->link_name)?></a></td>
262 415 <?php if (strlen($link->link_rss) > 0): $caption='Switch Feed'; ?>
263 416 <td style="font-size:smaller;text-align:center">
264 417 <strong><a href="<?=$link->link_rss?>"><?=wp_specialchars($link->link_rss)?></a></strong>
265 -<br/><em>check validity</em> <a style="vertical-align:middle"
418 +<em>check validity</em> <a style="vertical-align:middle"
266 419 title="Check feed &lt;<?=wp_specialchars($link->link_rss)?>&gt; for validity"
267 420 href="http://feedvalidator.org/check.cgi?url=<?=urlencode($link->link_rss)?>"><img
268 421 src="../wp-images/smilies/icon_arrow.gif" alt="&rarr;" /></a></td>
269 422 <?php else: $caption='Find Feed'; ?>
@@ -270,9 +423,9 @@
270 423 <td style="background-color:#FFFFD0"><p><strong>no
271 424 feed assigned</strong></p></td>
272 425 <? endif; ?>
273 426 <?php if (($link->user_level <= $user_level)): ?>
274 - <td><a href="link-manager.php?page=feedwordpress.php&amp;link_id=<?=$link->link_id?>&amp;action=feedfinder" class="edit"><?=$caption?></a></div></td>
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>
275 428 <td><a href="link-manager.php?link_id=<?=$link->link_id?>&amp;action=linkedit" class="edit"><?php _e('Edit')?></a></td>
276 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>
277 430 <td><input type="checkbox" name="linkcheck[]" value="<?=$link->link_id?>" /></td>
278 431 <?php else:
@@ -331,9 +484,9 @@
331 484 if ($rss):
332 485 $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link'];
333 486 $feed_link = isset($rss->channel['link'])?$rss->channel['link']:'';
334 487 ?>
335 - <form action="link-manager.php?page=feedwordpress.php" method="post">
488 + <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
336 489 <fieldset style="clear: both">
337 490 <legend><?=$rss->feed_type?> <?=$rss->feed_version?> feed</legend>
338 491
339 492 <?php if ($link_id===0): ?>
@@ -384,9 +537,9 @@
384 537 endif;
385 538 ?>
386 539 </div>
387 540
388 - <form action="link-manager.php?page=feedwordpress.php" method="post">
541 + <form action="link-manager.php?page=<?=basename(__FILE__)?>" method="post">
389 542 <div class="wrap">
390 543 <h2>Use another feed</h2>
391 544 <div><label>Feed:</label>
392 545 <input type="text" name="lookup" value="URI" />
@@ -569,19 +722,21 @@
569 722 $result = get_linkobjects(FeedWordPress::link_category_id());
570 723
571 724 $feeds = array ();
572 725 if ($result): foreach ($result as $link):
573 - $sec = array ();
574 -
575 726 if (strlen($link->link_rss) > 0):
576 - $notes = explode("\n", $link->link_notes);
577 - foreach ($notes as $note):
578 - list($key, $value) = explode(": ", $note, 2);
579 - if (strlen($key) > 0) $sec[$key] = $value;
580 - endforeach;
727 + $sec = FeedWordPress::notes_to_settings($link->link_notes);
728 + $sec['uri'] = $link->link_rss;
729 + $sec['name'] = $link->link_name;
581 730
582 - $sec['url'] = $link->link_rss;
583 - $sec['name'] = $link->link_name;
731 + // `hardcode categories` is deprecated in favor
732 + // of `unfamiliar categories`
733 + if (
734 + FeedWordPress::affirmative($sec, 'hardcode categories')
735 + and !isset($sec['unfamiliar categories'])
736 + ) :
737 + $sec['unfamiliar categories'] = 'default';
738 + endif;
584 739
585 740 if (isset($sec['cats'])):
586 741 $sec['cats'] = explode(':',$sec['cats']);
587 742 endif;
@@ -591,14 +746,31 @@
591 746 endif;
592 747 endforeach; endif;
593 748
594 749 $this->feeds = $feeds;
595 - } // function acquire_feeds ()
750 + } // FeedWordPress::FeedWordPress ()
596 751
752 + function notes_to_settings ($link_notes) {
753 + $notes = explode("\n", $link_notes);
754 +
755 + $sec = array ();
756 + foreach ($notes as $note):
757 + list($key, $value) = explode(": ", $note, 2);
758 +
759 + if (strlen($key) > 0) :
760 + // Unescape and trim() off the whitespace.
761 + // Thanks to Ray Lischner for pointing out the
762 + // need to trim off whitespace.
763 + $sec[$key] = stripcslashes (trim($value));
764 + endif;
765 + endforeach;
766 + return $sec;
767 + } // FeedWordPress::notes_to_settings ()
768 +
597 769 function update ($uri) {
598 - if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
599 -
600 770 global $wpdb;
771 +
772 + do_action('feedwordpress_update', $uri);
601 773
602 774 // Secret voodoo tag: URI for updating *everything*.
603 775 $secret = RPC_MAGIC.FeedWordPress::rpc_secret();
604 776
@@ -607,12 +779,12 @@
607 779 // Loop through and check for new posts
608 780 $delta = NULL;
609 781 foreach ($this->feeds as $feed) {
610 782 if (($uri === $secret)
611 - or ($uri === $feed['url'])
783 + or ($uri === $feed['uri'])
612 784 or ($uri === $feed['feed/link'])) {
613 785 if (is_null($delta)) $delta = array('new' => 0, 'updated' => 0);
614 - if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $feed[name] <$feed[url]>");
786 + do_action('feedwordpress_check_feed', array($feed));
615 787 $added = $this->feed2wp($wpdb, $feed);
616 788 if (isset($added['new'])) $delta['new'] += $added['new'];
617 789 if (isset($added['updated'])) $delta['updated'] += $added['updated'];
618 790 } /* if */
@@ -617,35 +789,29 @@
617 789 if (isset($added['updated'])) $delta['updated'] += $added['updated'];
618 790 } /* if */
619 791 } /* foreach */
620 792
793 + do_action('feedwordpress_update_complete', array($delta));
621 794 fwp_release_pings();
622 - if (FEEDWORDPRESS_LOG_UPDATES):
623 - $mesg = array();
624 - if (isset($delta['new'])) { $mesg[] = 'added '.$delta['new'].' new posts'; }
625 - if (isset($delta['updated'])) { $mesg[] = 'updated '.$delta['updated'].' existing posts'; }
626 - if (empty($mesg)) { $mesg[] = 'nothing changed'; }
627 795
628 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
629 - .(is_null($delta) ? "I don't syndicate <$uri>"
630 - : implode(' and ', $mesg)));
631 - endif;
632 796 return $delta;
633 797 }
634 798
635 799 function feed2wp ($wpdb, $f) {
636 - $feed = fetch_rss($f['url']);
800 + $feed = fetch_rss($f['uri']);
637 801 $new_count = array('new' => 0, 'updated' => 0);
638 802
639 803 $this->update_feed($wpdb, $feed->channel, $f);
640 804
641 - if (is_array($feed->items)) {
642 - foreach ($feed->items as $item) {
805 + if (is_array($feed->items)) :
806 + foreach ($feed->items as $item) :
643 807 $post = $this->item_to_post($wpdb, $item, $feed->channel, $f);
644 - $new = $this->add_post($wpdb, $post);
645 - if ( $new !== false ) { $new_count[$new]++; }
646 - } // foreach
647 - } // if
808 + if (!is_null($post)) :
809 + $new = $this->add_post($wpdb, $post);
810 + if ( $new !== false ) $new_count[$new]++;
811 + endif;
812 + endforeach;
813 + endif;
648 814 return $new_count;
649 815 } // function feed2wp ()
650 816
651 817 // FeedWordPress::flatten_array (): flatten an array. Useful for
@@ -658,16 +824,16 @@
658 824 // returned array for FeedWordPress::flatten_array($a) will contain a key
659 825 // $a['feed/b/c/d'] with value 'e'.
660 826 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
661 827 $ret = array ();
662 - if (is_array($arr)):
663 - foreach ($arr as $key => $value) {
664 - if (is_scalar($value)) {
828 + if (is_array($arr)) :
829 + foreach ($arr as $key => $value) :
830 + if (is_scalar($value)) :
665 831 $ret[$prefix.$key] = $value;
666 - } else {
832 + else :
667 833 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
668 - } /* if */
669 - } /* foreach */
834 + endif;
835 + endforeach;
670 836 endif;
671 837 return $ret;
672 838 } // function FeedWordPress::flatten_array ()
673 839
@@ -674,50 +840,89 @@
674 840 function resolve_relative_uri ($matches) {
675 841 return $matches[1].Relative_URI::resolve($matches[2], $this->_base).$matches[3];
676 842 } // function FeedWordPress::resolve_relative_uri ()
677 843
678 - function update_feed ($wpdb, $channel, $f) {
844 + function hardcode ($what, $f) {
845 + $default = get_settings("feedwordpress_hardcode_$what");
846 + if ( $default === 'yes' ) :
847 + // If the default is to hardcode, then we want the
848 + // negation of negative(): TRUE by default and FALSE if
849 + // the setting is explicitly "no"
850 + $ret = !FeedWordPress::negative($f, "hardcode $what");
851 + else :
852 + // If the default is NOT to hardcode, then we want
853 + // affirmative(): FALSE by default and TRUE if the
854 + // setting is explicitly "yes"
855 + $ret = FeedWordPress::affirmative($f, "hardcode $what");
856 + endif;
857 + return $ret;
858 + }
859 +
860 + function syndicated_status ($what, $f, $default) {
861 + global $wpdb;
862 +
863 + $ret = get_settings("feedwordpress_syndicated_{$what}_status");
864 + if ( isset($f["$what status"]) ) :
865 + $ret = $f["$what status"];
866 + elseif (!$ret) :
867 + $ret = $default;
868 + endif;
869 + return $wpdb->escape(trim(strtolower($ret)));
870 + }
871 +
872 + function negative ($f, $setting) {
873 + $nego = array ('n', 'no', 'f', 'false');
874 + return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
875 + }
876 +
877 + function affirmative ($f, $setting) {
679 878 $affirmo = array ('y', 'yes', 't', 'true', 1);
879 + return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
880 + }
680 881
882 + function update_feed ($wpdb, $channel, $f) {
681 883 $link_id = $f['link_id'];
682 884
683 - if (!isset($channel['id'])) {
684 - $channel['id'] = $f['url'];
685 - }
885 + if (!isset($channel['id'])) :
886 + $channel['id'] = $f['uri'];
887 + endif;
686 888
687 889 $update = array();
688 - if (isset($channel['link'])) {
890 + if (!FeedWordPress::hardcode('url', $f) and isset($channel['link'])) :
689 891 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
690 - }
691 - if (isset($channel['title']) and (!isset($f['hardcode name'])
692 - or in_array(trim(strtolower($f['hardcode name'])), $affirmo))) {
892 + endif;
893 +
894 + if (!FeedWordPress::hardcode('name', $f) and isset($channel['title'])) :
693 895 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
694 - }
896 + endif;
695 897
696 - if (isset($channel['tagline'])) {
697 - $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
698 - } elseif (isset($channel['description'])) {
699 - $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
700 - }
898 + if (!FeedWordPress::hardcode('description', $f)) :
899 + if (isset($channel['tagline'])) :
900 + $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
901 + elseif (isset($channel['description'])) :
902 + $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
903 + endif;
904 + endif;
701 905
702 - if (is_array($f['cats'])) {
703 - $f['cats'] = implode(':',$f['cats']);
704 - } /* if */
906 + if (is_array($f['cats'])) :
907 + $f['cats'] = implode(':',$f['cats']);
908 + endif;
705 909
706 910 $f = array_merge($f, $this->flatten_array($channel));
707 911
708 912 # -- A few things we don't want to save in the notes
709 - unset($f['link_id']); unset($f['uri']); unset($f['url']);
913 + unset($f['link_id']); unset($f['uri']); unset($f['name']);
914 + unset($f['hardcode categories']); // Deprecated
710 915
711 916 $notes = '';
712 - foreach ($f as $key => $value) {
713 - $notes .= "${key}: $value\n";
714 - }
917 + foreach ($f as $key => $value) :
918 + $notes .= $key . ": ". addcslashes($value, "\0..\37") . "\n";
919 + endforeach;
715 920 $update[] = "link_notes = '".$wpdb->escape($notes)."'";
716 921
717 922 $update_set = implode(',', $update);
718 923
719 - // if we've already have this feed, update
924 + // Update the properties of the link from the feed information
720 925 $result = $wpdb->query("
721 926 UPDATE $wpdb->links
722 927 SET $update_set
723 928 WHERE link_id='$link_id'
@@ -723,136 +928,203 @@
723 928 WHERE link_id='$link_id'
724 929 ");
725 930 } // function FeedWordPress::update_feed ()
726 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 + //
935 + // item_to_post() invokes the syndicated_item filter on each item it
936 + // receives. Filters should return either (a) the item unmodified,
937 + // (b) the item modified according to the rules of the filter, or
938 + // (c) NULL. A NULL item will not be posted into the database.
939 + //
940 + // N.B.: item_to_post and the syndicate_item filter really ought to have
941 + // *no* side effects on the WordPress database (that's why, for example,
942 + // we handle lookup/creation of numeric author and category IDs in
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 + //
727 947 function item_to_post($wpdb, $item, $channel, $f) {
728 948 $post = array();
729 - $post['post_title'] = $wpdb->escape($item['title']);
730 -
731 - $author = array ();
732 - if (isset($item['dc']['creator'])):
733 - $author['name'] = $item['dc']['creator'];
734 - elseif (isset($item['dc']['creator'])):
735 - $author['name'] = $item['dc']['contributor'];
736 - elseif (isset($item['author_name'])):
737 - $author['name'] = $item['author_name'];
738 - else:
739 - $author['name'] = $channel['title'];
740 - endif;
741 949
742 - if (isset($item['author_email'])):
743 - $author['email'] = $item['author_email'];
744 - endif;
950 + // This is ugly as all hell. I'd like to use apply_filters()'s
951 + // alleged support for a variable argument count, but this seems
952 + // to have been broken in WordPress 1.5. It'll be fixed somehow
953 + // in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
954 + // the board here.
955 + //
956 + // Cf.: <http://mosquito.wordpress.org/view.php?id=901>
957 + global $fwp_channel, $fwp_feedmeta;
958 + $fwp_channel = $channel; $fwp_feedmeta = $f;
745 959
746 - if (isset($item['author_url'])):
747 - $author['url'] = $item['author_url'];
748 - else:
749 - $author['url'] = $channel['link'];
750 - endif;
751 -
752 - $post['post_author'] = $this->author_to_id($wpdb, $author['name'], $author['email'], $author['url']);
753 -
754 - # Identify content and sanitize it.
755 - # ---------------------------------
756 - if (isset($item['content']['encoded']) and $item['content']['encoded']):
757 - $content = $item['content']['encoded'];
758 - else:
759 - $content = $item['description'];
760 - endif;
761 -
762 - # Resolve relative URIs in post content
763 - #
764 - # N.B.: We *might* get screwed over by xml:base. But I don't see
765 - # any way to get that information out of MagpieRSS if it's
766 - # in the feed, and if it's in the content itself we'd have
767 - # to do yet more XML parsing to do things right. For now
768 - # this will have to do.
960 + $item = apply_filters('syndicated_item', $item);
961 +
962 + // Filters can halt further processing by returning NULL
963 + if (is_null($item)) :
964 + $post = NULL;
965 + else :
966 + $post['post_title'] = $wpdb->escape($item['title']);
769 967
770 - $this->_base = $item['link']; // Reset the base for resolving relative URIs
771 - foreach ($this->uri_attrs as $pair):
772 - list($tag,$attr) = $pair;
773 - $content = preg_replace_callback (
774 - ":(<$tag [^>]*$attr=\")([^\">]*)(\"[^>]*>):i",
775 - array(&$this,'resolve_relative_uri'),
776 - $content
777 - );
778 - endforeach;
779 -
780 - # Sanitize problematic attributes
781 - foreach ($this->strip_attrs as $pair):
782 - list($tag,$attr) = $pair;
783 - $content = preg_replace (
784 - ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i",
785 - "\\1\\4",
786 - $content
787 - );
788 - endforeach;
789 -
790 - $post['post_content'] = $wpdb->escape($content);
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;
791 978
792 - $post['post_name'] = sanitize_title($post['post_title']);
793 -
794 - # RSS is a fucking mess. Figure out whether we have a date in
795 - # dc:date, <issued>, <pubDate>, etc., and get it into Unix epoch
796 - # format for reformatting. If you can't find anything, use the
797 - # current time.
798 - if (isset($item['dc']['date'])):
799 - $post['epoch']['issued'] = parse_w3cdtf($item['dc']['date']);
800 - elseif (isset($item['issued'])):
801 - $post['epoch']['issued'] = parse_w3cdtf($item['issued']);
802 - elseif (isset($item['pubdate'])):
803 - $post['epoch']['issued'] = strtotime($item['pubdate']);
804 - else:
805 - $post['epoch']['issued'] = time();
806 - endif;
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;
807 988
808 - # As far as I know, only atom currently has a reliable way to
809 - # specify when something was *modified* last
810 - if (isset($item['modified'])):
811 - $post['epoch']['modified'] = parse_w3cdtf($item['modified']);
812 - else:
813 - $post['epoch']['modified'] = $post['epoch']['issued'];
814 - endif;
989 + // ... So far we just have an alphanumeric
990 + // representation of the author. We will look up (or
991 + // create) the numeric ID for the author in
992 + // FeedWordPress::add_post()
815 993
816 - $post['post_date'] = date('Y-m-d H:i:s', $post['epoch']['issued']);
817 - $post['post_modified'] = date('Y-m-d H:i:s', $post['epoch']['modified']);
818 - $post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['issued']);
819 - $post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['modified']);
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'];
1000 + endif;
820 1001
821 - # Use feed-level preferences or a sensible default.
822 - $post['post_status'] = (isset($f['post status']) ? $wpdb->escape(trim(strtolower($f['post status']))) : 'publish');
823 - $post['comment_status'] = (isset($f['comment status']) ? $wpdb->escape(trim(strtolower($f['comment status']))) : 'closed');
824 - $post['ping_status'] = (isset($f['ping status']) ? $wpdb->escape(trim(strtolower($f['ping status']))) : 'closed');
1002 + # Resolve relative URIs in post content
1003 + #
1004 + # N.B.: We *might* get screwed over by xml:base. But I don't see
1005 + # any way to get that information out of MagpieRSS if it's
1006 + # in the feed, and if it's in the content itself we'd have
1007 + # to do yet more XML parsing to do things right. For now
1008 + # this will have to do.
825 1009
826 - // Unique ID (hopefully a unique tag: URI); failing that, the permalink
827 - if (isset($item['id'])):
828 - $post['guid'] = $wpdb->escape($item['id']);
829 - else:
830 - $post['guid'] = $wpdb->escape($item['link']);
831 - endif;
1010 + $this->_base = $item['link']; // Reset the base for resolving relative URIs
1011 + foreach ($this->uri_attrs as $pair):
1012 + list($tag,$attr) = $pair;
1013 + $content = preg_replace_callback (
1014 + ":(<$tag [^>]*$attr=\")([^\">]*)(\"[^>]*>):i",
1015 + array(&$this,'resolve_relative_uri'),
1016 + $content
1017 + );
1018 + endforeach;
832 1019
833 - if (isset($channel['title'])) $post['syndication_source'] = $channel['title'];
834 - if (isset($channel['link'])) $post['syndication_source_uri'] = $channel['link'];
835 - $post['syndication_feed'] = $f['url'];
1020 + # Sanitize problematic attributes
1021 + foreach ($this->strip_attrs as $pair):
1022 + list($tag,$attr) = $pair;
1023 + $content = preg_replace (
1024 + ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i",
1025 + "\\1\\4",
1026 + $content
1027 + );
1028 + endforeach;
836 1029
837 - // In case you want to know the external permalink...
838 - $post['syndication_permalink'] = $item['link'];
1030 + # Identify and sanitize excerpt
1031 + $excerpt = NULL;
1032 + if ( isset($item['description']) and $item['description'] ) :
1033 + $excerpt = $item['description'];
1034 + elseif ( isset($content) and $content ) :
1035 + $excerpt = strip_tags($content);
1036 + if (strlen($excerpt) > 255) :
1037 + $excerpt = substr($excerpt,0,252).'...';
1038 + endif;
1039 + endif;
839 1040
840 - // Categories: start with default categories
841 - $item_cats = $f['cats'];
1041 + $post['post_content'] = $wpdb->escape($content);
1042 +
1043 + if (!is_null($excerpt)):
1044 + $post['post_excerpt'] = $wpdb->escape($excerpt);
1045 + endif;
1046 +
1047 + # This is unneeded if wp_insert_post can be used.
1048 + # --- cut here ---
1049 + $post['post_name'] = sanitize_title($post['post_title']);
1050 + # --- cut here ---
842 1051
843 - // Now add categories from the post, if we have 'em
844 - if (is_array($item['categories'])):
845 - foreach ($item['categories'] as $cat):
846 - if ( strpos($f['url'], 'del.icio.us') !== false ):
847 - $item_cats = array_merge($item_cats, explode(' ', $cat));
848 - else:
849 - $item_cats[] = $cat;
850 - endif;
851 - endforeach;
1052 + # RSS is a fucking mess. Figure out whether we have a date in
1053 + # dc:date, <issued>, <pubDate>, etc., and get it into Unix epoch
1054 + # format for reformatting. If you can't find anything, use the
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 +
1074 + $post['post_date'] = date('Y-m-d H:i:s', $post['epoch']['issued']);
1075 + $post['post_modified'] = date('Y-m-d H:i:s', $post['epoch']['modified']);
1076 + $post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['issued']);
1077 + $post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $post['epoch']['modified']);
1078 +
1079 + # Use feed-level preferences or the global default.
1080 + $post['post_status'] = FeedWordPress::syndicated_status('post', $f, 'publish');
1081 + $post['comment_status'] = FeedWordPress::syndicated_status('comment', $f, 'closed');
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'];
1108 +
1109 + // Feed-by-feed options for author and category creation
1110 + $post['named']['unfamiliar']['author'] = $f['unfamiliar author'];
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;
852 1126 endif;
853 - $post['post_category'] = $this->lookup_categories($wpdb, $item_cats);
854 -
855 1127 return $post;
856 1128 } // function FeedWordPress::item_to_post ()
857 1129
858 1130 function add_post ($wpdb, $post) {
@@ -861,10 +1133,51 @@
861 1133 SELECT id, guid, UNIX_TIMESTAMP(post_modified) AS modified
862 1134 FROM $wpdb->posts WHERE guid='$guid'
863 1135 ");
864 1136
865 - if (!$result):
866 - // The item has not yet been added.
1137 + if (!$result) :
1138 + $freshness = 2; // New content
1139 + elseif ($post['epoch']['modified'] > $result->modified) :
1140 + $freshness = 1; // Updated content
1141 + else :
1142 + $freshness = 0;
1143 + endif;
1144 +
1145 + if ($freshness > 0) :
1146 + # -- Look up, or create, numeric ID for author
1147 + $post['post_author'] = $this->author_to_id (
1148 + $wpdb,
1149 + $post['named']['author']['name'],
1150 + $post['named']['author']['email'],
1151 + $post['named']['author']['uri'],
1152 + FeedWordPress::on_unfamiliar('author', $post['named']['unfamiliar']['author'])
1153 + );
1154 +
1155 + if (is_null($post['post_author'])) :
1156 + $freshness = 0;
1157 + else :
1158 + # -- Look up, or create, numeric ID for categories
1159 + $post['post_category'] = $this->lookup_categories (
1160 + $wpdb,
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 +
1169 + unset($post['named']);
1170 + endif;
1171 +
1172 + if ($freshness > 0) :
1173 + $post = apply_filters('syndicated_post', $post);
1174 + if (is_null($post)) $freshness = 0;
1175 + endif;
1176 +
1177 + if ($freshness == 2) :
1178 + // The item has not yet been added. So let's add it.
1179 +
867 1180 # The right way to do this would be to use:
868 1181 #
869 1182 # $postId = wp_insert_post($post);
870 1183 # $result = $wpdb->query("
@@ -888,9 +1201,10 @@
888 1201 guid = '$guid',
889 1202 post_author = '".$post['post_author']."',
890 1203 post_date = '".$post['post_date']."',
891 1204 post_date_gmt = '".$post['post_date_gmt']."',
892 - post_content = '".$post['post_content']."',
1205 + post_content = '".$post['post_content']."',"
1206 + .(isset($post['post_excerpt']) ? "post_excerpt = '".$post['post_excerpt']."'," : "")."
893 1207 post_title = '".$post['post_title']."',
894 1208 post_name = '".$post['post_name']."',
895 1209 post_modified = '".$post['post_modified']."',
896 1210 post_modified_gmt = '".$post['post_modified_gmt']."',
@@ -908,13 +1222,15 @@
908 1222 // that a large aggregator website is going to *want* to be
909 1223 // able to use).
910 1224 do_action('publish_post', $postId);
911 1225 # --- cut here ---
912 -
913 - if (FEEDWORDPRESS_LOG_UPDATES) error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted '".$post['post_title']."' (".$post['post_date'].") from '".$post['syndication_source']."'");
1226 +
914 1227 $this->add_rss_meta($wpdb, $postId, $post);
1228 +
1229 + do_action('post_syndicated_item', $postId);
1230 +
915 1231 $ret = 'new';
916 - elseif ($post['epoch']['modified'] > $result->modified):
1232 + elseif ($freshness == 1) :
917 1233 $postId = $result->id; $modified = $result->modified;
918 1234
919 1235 $result = $wpdb->query("
920 1236 UPDATE $wpdb->posts
@@ -936,22 +1252,14 @@
936 1252 // that a large aggregator website is going to *want* to be
937 1253 // able to use).
938 1254 do_action('edit_post', $postId);
939 1255
940 - if (FEEDWORDPRESS_LOG_UPDATES):
941 - error_log("[".date('Y-m-d H:i:s')
942 - ."][feedwordpress] updated '"
943 - .$post['post_title']
944 - ."' (".$post['post_date']
945 - .") from '".$post['syndication_source']
946 - ."' (".date('Y-m-d H:i:s', $modified)
947 - ." ==> "
948 - .date('Y-m-d H:i:s', $post['epoch']['modified'])
949 - .')');
950 - endif;
951 1256 $this->add_rss_meta($wpdb, $postId, $post);
1257 +
1258 + do_action('update_syndicated_item', $postId);
1259 +
952 1260 $ret = 'updated';
953 - else:
1261 + else :
954 1262 $ret = false;
955 1263 endif;
956 1264
957 1265 return $ret;
@@ -982,38 +1290,49 @@
982 1290 endforeach;
983 1291 } // function FeedWordPress::add_to_category ()
984 1292 # --- cut here ---
985 1293
986 - // FeedWordPress::add_rss_meta: adds feed meta-data to user-defined keys
987 - // for each entry. Interesting feed meta-data is tagged in the $post
988 - // array using the prefix 'syndication_'. This should be used for
989 - // anything that the WordPress user might want to access about a post's
990 - // original source that isn't provided for by standard WP meta-data
991 - // (i.e., beyond author, title, timestamp, and categories)
1294 + // FeedWordPress::add_rss_meta: adds interesting meta-data to each entry
1295 + // using the space for custom keys. The set of keys and values to add is
1296 + // specified by the keys and values of $post['meta']. This is used to
1297 + // store anything that the WordPress user might want to access from a
1298 + // template concerning the post's original source that isn't provided
1299 + // for by standard WP meta-data (i.e., any interesting data about the
1300 + // syndicated post other than author, title, timestamp, categories, and
1301 + // guid). It's also used to hook into WordPress's support for
1302 + // enclosures.
992 1303 function add_rss_meta ($wpdb, $postId, $post) {
993 - foreach ($post as $key => $value):
994 - if (strpos($key, "syndication_") === 0):
995 - $value = $wpdb->escape($value);
1304 + if ( is_array($post) and isset($post['meta']) and is_array($post['meta']) ) :
1305 + foreach ( $post['meta'] as $key => $values ) :
996 1306
1307 + $key = $wpdb->escape($key);
1308 +
1309 + // If this is an update, clear out the old
1310 + // values to avoid duplication.
997 1311 $result = $wpdb->query("
998 1312 DELETE FROM $wpdb->postmeta
999 1313 WHERE post_id='$postId' AND meta_key='$key'
1000 1314 ");
1001 1315
1002 - $result = $wpdb->query("
1003 - INSERT INTO $wpdb->postmeta
1004 - SET
1005 - post_id='$postId',
1006 - meta_key='$key',
1007 - meta_value='$value'
1008 - ");
1009 - endif;
1010 - endforeach;
1316 + // Allow for either a single value or an array
1317 + if (!is_array($values)) $values = array($values);
1318 + foreach ( $values as $value ) :
1319 + $value = $wpdb->escape($value);
1320 + $result = $wpdb->query("
1321 + INSERT INTO $wpdb->postmeta
1322 + SET
1323 + post_id='$postId',
1324 + meta_key='$key',
1325 + meta_value='$value'
1326 + ");
1327 + endforeach;
1328 + endforeach;
1329 + endif;
1011 1330 } /* FeedWordPress::add_rss_meta () */
1012 1331
1013 1332 // FeedWordPress::author_to_id (): get the ID for an author name from
1014 1333 // the feed. Create the author if necessary.
1015 - function author_to_id ($wpdb, $author, $email, $url) {
1334 + function author_to_id ($wpdb, $author, $email, $url, $unfamiliar_author = 'create') {
1016 1335 // Never can be too careful...
1017 1336 $nice_author = sanitize_title($author);
1018 1337 $author = $wpdb->escape($author);
1019 1338 $email = $wpdb->escape($email);
@@ -1019,69 +1338,121 @@
1019 1338 $email = $wpdb->escape($email);
1020 1339 $url = $wpdb->escape($url);
1021 1340
1022 1341 $id = $wpdb->get_var(
1023 - "SELECT ID from $wpdb->users
1024 - WHERE
1025 - user_login = '$author' OR
1026 - user_firstname = '$author' OR
1027 - user_nickname = '$author' OR
1028 - user_description = '$author' OR
1029 - user_nicename = '$nice_author'");
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 + ");
1030 1352
1031 - if (is_null($id)):
1032 - $wpdb->query (
1033 - "INSERT INTO $wpdb->users
1034 - SET
1035 - ID='0',
1036 - user_login='$author',
1037 - user_firstname='$author',
1038 - user_nickname='$author',
1039 - user_nicename='$nice_author',
1040 - user_description='$author',
1041 - user_email='$email',
1042 - user_url='$url'");
1043 - $id = $wpdb->insert_id;
1353 + if (is_null($id)) :
1354 + if ($unfamiliar_author === 'create') :
1355 + $wpdb->query (
1356 + "INSERT INTO $wpdb->users
1357 + SET
1358 + ID='0',
1359 + user_login='$author',
1360 + user_firstname='$author',
1361 + user_nickname='$author',
1362 + user_nicename='$nice_author',
1363 + user_description='$author',
1364 + user_email='$email',
1365 + user_url='$url'");
1366 + $id = $wpdb->insert_id;
1367 + elseif ($unfamiliar_author === 'default') :
1368 + $id = 1;
1369 + endif;
1044 1370 endif;
1045 1371 return $id;
1046 1372 } // function FeedWordPress::author_to_id ()
1047 -
1373 +
1048 1374 // look up (and create) category ids from a list of categories
1049 - function lookup_categories ($wpdb, $cats) {
1050 - if ( !count($cats) ) return array();
1051 -
1052 - # i'd kill for a decent map function in PHP
1053 - # but that would require functiosn to be first class object, or at least
1054 - # coderef support
1055 - $cat_strs = array();
1056 - foreach ( $cats as $c ) {
1057 - $c = $wpdb->escape($c); $c = "'$c'";
1058 - $cat_strs[] = $c;
1059 - }
1060 -
1061 - $cat_sql = join(',', $cat_strs);
1062 - $sql = "SELECT cat_ID,cat_name from $wpdb->categories WHERE cat_name IN ($cat_sql)";
1063 - $results = $wpdb->get_results($sql);
1064 -
1065 - $cat_ids = array();
1066 - $cat_found = array();
1067 -
1068 - if (!is_null($results)):
1069 - foreach ( $results as $row ) {
1070 - $cat_ids[] = $row->cat_ID;
1071 - $cat_found[] = strtolower($row->cat_name); // Normalize to avoid case problems
1072 - }
1375 + function lookup_categories ($wpdb, $cats, $unfamiliar_category = 'create') {
1376 + // Normalize whitespace because (1) trailing whitespace can
1377 + // cause PHP and MySQL not to see eye to eye on VARCHAR
1378 + // comparisons for some versions of MySQL (cf.
1379 + // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1380 + // because I doubt most people want to make a semantic
1381 + // distinction between 'Computers' and 'Computers '
1382 + $cats = array_map('trim', $cats);
1383 +
1384 + $cat_ids = array ();
1385 +
1386 + if ( count($cats) > 0 ) :
1387 + # i'd kill for a decent map function in PHP
1388 + # but that would require functions to be first class object,
1389 + # or at least coderef support
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 + );
1414 +
1415 + $cat_ids = array();
1416 + $found = array();
1417 +
1418 + if (!is_null($results)):
1419 + foreach ( $results as $row ) :
1420 + // Add existing ID to list of numerical
1421 + // IDs to eventually place post in
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;
1437 +
1438 + foreach ($cats as $new_cat) :
1439 + if (($unfamiliar_category==='create') and !in_array(strtolower($new_cat), $found)) :
1440 + $nice_cat = sanitize_title($new_cat);
1441 + $wpdb->query(sprintf("
1442 + INSERT INTO $wpdb->categories
1443 + SET
1444 + cat_name='%s',
1445 + category_nicename='%s'
1446 + ", $wpdb->escape($new_cat), $nice_cat));
1447 + $cat_ids[] = $wpdb->insert_id;
1448 + endif;
1449 + endforeach;
1450 +
1451 + if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
1452 + $cat_ids = NULL; // Drop the post
1453 + endif;
1073 1454 endif;
1074 -
1075 - foreach ($cats as $new_cat):
1076 - $sql = "INSERT INTO $wpdb->categories (cat_name, category_nicename)
1077 - VALUES ('%s', '%s')";
1078 - if (!in_array(strtolower($new_cat), $cat_found)):
1079 - $nice_cat = sanitize_title($new_cat);
1080 - $wpdb->query(sprintf($sql, $wpdb->escape($new_cat), $nice_cat));
1081 - $cat_ids[] = $wpdb->insert_id;
1082 - endif;
1083 - endforeach;
1084 1455 return $cat_ids;
1085 1456 } // function FeedWordPress::lookup_categories ()
1086 1457
1087 1458 function rpc_secret () {
@@ -1087,8 +1458,22 @@
1087 1458 function rpc_secret () {
1088 1459 return get_settings('feedwordpress_rpc_secret');
1089 1460 } // function FeedWordPress::rpc_secret ()
1090 1461
1462 + function on_unfamiliar ($what = 'author', $override = NULL) {
1463 + $set = array('create', 'default', 'filter');
1464 +
1465 + $ret = strtolower($override);
1466 + if (!in_array($ret, $set)) :
1467 + $ret = get_settings('feedwordpress_unfamiliar_'.$what);
1468 + if (!in_array($ret, $set)) :
1469 + $ret = 'create';
1470 + endif;
1471 + endif;
1472 +
1473 + return $ret;
1474 + } // function FeedWordPress::on_unfamiliar()
1475 +
1091 1476 function link_category_id () {
1092 1477 global $wpdb;
1093 1478
1094 1479 $cat_id = get_settings('feedwordpress_cat_id');
@@ -1239,26 +1624,25 @@
1239 1624 function _get ($uri = NULL) {
1240 1625 if ($uri) $this->uri = $uri;
1241 1626
1242 1627 // Is the result not yet cached?
1243 - if ($this->_cache_uri !== $this->uri) {
1244 - // Retrieve, with headers, using cURL
1245 - $ch = curl_init($this->uri);
1246 - curl_setopt($ch, CURLOPT_HEADER, false);
1247 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
1248 - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: close'));
1249 - curl_setopt($ch, CURLOPT_HTTPHEADER, array('User-Agent: feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress'));
1250 - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1251 - curl_setopt($ch, CURLOPT_TIMEOUT, 15);
1252 - $response = curl_exec($ch);
1253 - curl_close($ch);
1628 + if ($this->_cache_uri !== $this->uri) :
1629 + // Snoopy is an HTTP client in PHP
1630 + $client = new Snoopy();
1631 +
1632 + // Prepare headers and internal settings
1633 + $client->rawheaders['Connection'] = 'close';
1634 + $client->accept = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*';
1635 + $client->agent = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress';
1636 + $client->read_timeout = 5;
1637 +
1638 + // Fetch the HTML or feed
1639 + @$client->fetch($this->uri);
1640 + $this->_data = $client->results;
1254 1641
1255 - // Split into headers and content
1256 - $this->_data = $response;
1257 -
1258 - // Kilroy was here
1259 - $this->_cache_uri = $this->uri;
1260 - } /* if */
1642 + // Kilroy was here
1643 + $this->_cache_uri = $this->uri;
1644 + endif;
1261 1645 } /* FeedFinder::_get () */
1262 1646
1263 1647 function _link_rel_feeds () {
1264 1648 $links = $this->_tags('link');