PluginProbe
FeedWordPress / 2009.0613
FeedWordPress v2009.0613
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
feedwordpress / feedwordpress.php

feedwordpress.php in FeedWordPress 2009.0613, at feedwordpress.php

2,563 lines 90.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: FeedWordPress
4 Plugin URI: http://projects.radgeek.com/feedwordpress
5 Description: simple and flexible Atom/RSS syndication for WordPress
6 Version: 2009.0613
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 */
11
12 # This uses code derived from:
13 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
14 # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
15 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
16 # according to the terms of the GNU General Public License.
17 #
18 # INSTALLATION: see readme.txt or <http://projects.radgeek.com/install>
19 #
20 # USAGE: once FeedWordPress is installed, you manage just about everything from
21 # the WordPress Dashboard, under the Syndication menu. To ensure that fresh
22 # content is added as it becomes available, you can convince your contributors
23 # to put your XML-RPC URI (if WordPress is installed at
24 # <http://www.zyx.com/blog>, XML-RPC requests should be sent to
25 # <http://www.zyx.com/blog/xmlrpc.php>), or update manually under the
26 # Syndication menu, or set up automatic updates under Syndication --> Settings,
27 # or use a cron job.
28
29 # -- Don't change these unless you know what you're doing...
30
31 define ('FEEDWORDPRESS_VERSION', '2009.0612');
32 define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
33 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
34
35 $feedwordpress_debug = get_option('feedwordpress_debug');
36 if (is_string($feedwordpress_debug)) :
37 $feedwordpress_debug = ($feedwordpress_debug == 'yes');
38 endif;
39 define ('FEEDWORDPRESS_DEBUG', $feedwordpress_debug);
40
41 define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
42 define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
43
44 define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi');
45
46 define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes
47
48 define ('FWP_SCHEMA_HAS_USERMETA', 2966);
49 define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0
50 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1
51 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
52 define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5
53 define ('FWP_SCHEMA_26', 8201); // Database schema # for WP 2.6
54 define ('FWP_SCHEMA_27', 9872); // Database schema # for WP 2.7
55
56 if (FEEDWORDPRESS_DEBUG) :
57 // Help us to pick out errors, if any.
58 ini_set('error_reporting', E_ALL & ~E_NOTICE);
59 ini_set('display_errors', true);
60 define('MAGPIE_DEBUG', true);
61
62 // When testing we don't want cache issues to interfere. But this is
63 // a VERY BAD SETTING for a production server. Webmasters will eat your
64 // face for breakfast if you use it, and the baby Jesus will cry. So
65 // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
66 // used for more than testing purposes!
67 define('MAGPIE_CACHE_AGE', 1);
68 else :
69 define('MAGPIE_DEBUG', false);
70 endif;
71
72 // Note that the rss-functions.php that comes prepackaged with WordPress is
73 // old & busted. For the new hotness, drop a copy of rss.php from
74 // this archive into wp-includes/rss.php
75
76 if (is_readable(ABSPATH . WPINC . '/rss.php')) :
77 require_once (ABSPATH . WPINC . '/rss.php');
78 else :
79 require_once (ABSPATH . WPINC . '/rss-functions.php');
80 endif;
81
82 if (isset($wp_db_version)) :
83 if ($wp_db_version >= FWP_SCHEMA_23) :
84 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
85 elseif ($wp_db_version >= FWP_SCHEMA_21) : // WordPress 2.1 and 2.2, but not 2.3
86 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
87 require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
88 elseif ($wp_db_version >= FWP_SCHEMA_20) : // WordPress 2.0
89 require_once (ABSPATH . WPINC . '/registration-functions.php'); // for wp_insert_user
90 require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
91 endif;
92 endif;
93
94 require_once(dirname(__FILE__) . '/compatability.php'); // LEGACY API: Replicate or mock up functions for legacy support purposes
95 require_once(dirname(__FILE__) . '/feedwordpresshtml.class.php');
96
97 // Magic quotes are just about the stupidest thing ever.
98 if (is_array($_POST)) :
99 $fwp_post = stripslashes_deep($_POST);
100 endif;
101
102 // Get the path relative to the plugins directory in which FWP is stored
103 preg_match (
104 '|/wp-content/plugins/(.+)$|',
105 dirname(__FILE__),
106 $ref
107 );
108
109 if (isset($ref[1])) :
110 $fwp_path = $ref[1];
111 else : // Something went wrong. Let's just guess.
112 $fwp_path = 'feedwordpress';
113 endif;
114
115 // If this is a FeedWordPress admin page, queue up scripts for AJAX functions that FWP uses
116 // If it is a display page or a non-FeedWordPress admin page, don't.
117 if (is_admin() and isset($_REQUEST['page']) and preg_match("|^{$fwp_path}/|", $_REQUEST['page'])) :
118 if (function_exists('wp_enqueue_script')) :
119 if (isset($wp_db_version) and $wp_db_version >= FWP_SCHEMA_25) :
120 wp_enqueue_script('post'); // for magic tag and category boxes
121 wp_enqueue_script('thickbox'); // for fold-up boxes
122 wp_enqueue_script('admin-forms'); // for checkbox selection
123 else :
124 wp_enqueue_script( 'ajaxcat' ); // Provides the handy-dandy new category text box
125 endif;
126 endif;
127 if (function_exists('wp_enqueue_style')) :
128 if (fwp_test_wp_version(FWP_SCHEMA_25)) :
129 wp_enqueue_style('dashboard');
130 endif;
131 endif;
132 if (function_exists('wp_admin_css')) :
133 if (fwp_test_wp_version(FWP_SCHEMA_25)) :
134 wp_admin_css('css/dashboard');
135 endif;
136 endif;
137 endif;
138
139 if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
140
141 # Syndicated items are generally received in output-ready (X)HTML and
142 # should not be folded, crumpled, mutilated, or spindled by WordPress
143 # formatting filters. But we don't want to interfere with filters for
144 # any locally-authored posts, either.
145 #
146 # What WordPress should really have is a way for upstream filters to
147 # stop downstream filters from running at all. Since it doesn't, and
148 # since a downstream filter can't access the original copy of the text
149 # that is being filtered, what we will do here is (1) save a copy of the
150 # original text upstream, before any other filters run, and then (2)
151 # retrieve that copy downstream, after all the other filters run, *if*
152 # this is a syndicated post
153
154 add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
155 add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
156
157 add_action('atom_entry', 'feedwordpress_item_feed_data');
158
159 # Filter in original permalinks if the user wants that
160 add_filter('post_link', 'syndication_permalink', 1);
161
162 # WTF? By default, wp_insert_link runs incoming link_url and link_rss
163 # URIs through default filters that include `wp_kses()`. But `wp_kses()`
164 # just happens to escape any occurrence of & to &amp; -- which just
165 # happens to fuck up any URI with a & to separate GET parameters.
166 remove_filter('pre_link_rss', 'wp_filter_kses');
167 remove_filter('pre_link_url', 'wp_filter_kses');
168
169 # Admin menu
170 add_action('admin_menu', 'fwp_add_pages');
171 add_action('admin_notices', 'fwp_check_debug');
172 add_action('admin_notices', 'fwp_check_magpie');
173
174 # Inbound XML-RPC update methods
175 add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
176
177 # Outbound XML-RPC ping reform
178 remove_action('publish_post', 'generic_ping'); // WP 1.5.x
179 remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2
180 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3
181
182 add_action('publish_post', 'fwp_publish_post_hook', 5, 1);
183 add_action('do_pings', 'fwp_do_pings', 10, 1);
184 add_action('feedwordpress_update', 'fwp_hold_pings');
185 add_action('feedwordpress_update_complete', 'fwp_release_pings');
186
187 # Hook in logging functions only if the logging option is ON
188 $update_logging = get_option('feedwordpress_update_logging');
189 if ($update_logging == 'yes') :
190 add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
191 add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
192 add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
193 add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
194 add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
195 endif;
196
197 # Cron-less auto-update. Hooray!
198 add_action('init', 'feedwordpress_auto_update');
199 add_action('init', 'feedwordpress_check_for_magpie_fix');
200
201 # Default sanitizers
202 add_filter('syndicated_item_content', array('SyndicatedPost', 'resolve_relative_uris'), 0, 2);
203 add_filter('syndicated_item_content', array('SyndicatedPost', 'sanitize_content'), 0, 2);
204
205 else :
206 # Hook in the menus, which will just point to the upgrade interface
207 add_action('admin_menu', 'fwp_add_pages');
208 endif; // if (!FeedWordPress::needs_upgrade())
209
210 function feedwordpress_check_for_magpie_fix () {
211 if (isset($_POST['action']) and $_POST['action']=='fix_magpie_version') :
212 FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_fix_magpie', /*capability=*/ 'edit_files');
213
214 $back_to = $_SERVER['REQUEST_URI'];
215 if (isset($_POST['ignore'])) :
216 // kill error message by telling it we ignored the upgrade request for this version
217 update_option('feedwordpress_magpie_ignored_upgrade_to', EXPECTED_MAGPIE_VERSION);
218 $ret = 'ignored';
219 elseif (isset($_POST['upgrade'])) :
220 $source = dirname(__FILE__)."/MagpieRSS-upgrade/rss.php";
221 $destination = ABSPATH . WPINC . '/rss.php';
222 $success = @copy($source, $destination);
223 $ret = (int) $success;
224 endif;
225
226 if (strpos($back_to, '?')===false) : $sep = '?';
227 else : $sep = '&';
228 endif;
229
230 header("Location: {$back_to}{$sep}feedwordpress_magpie_fix=".$ret);
231 exit;
232 endif;
233 } /* feedwordpress_check_for_magpie_fix() */
234
235 function feedwordpress_auto_update () {
236 if (FeedWordPress::stale()) :
237 $feedwordpress =& new FeedWordPress;
238 $feedwordpress->update();
239 endif;
240
241 if (FeedWordPress::update_requested()) :
242 exit;
243 endif;
244 } /* feedwordpress_auto_update () */
245
246 ################################################################################
247 ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
248 ################################################################################
249
250 function log_feedwordpress_post ($id) {
251 $post = wp_get_single_post($id);
252 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
253 ."'{$post->post_title}' ({$post->post_date})");
254 }
255
256 function log_feedwordpress_update_post ($id) {
257 $post = wp_get_single_post($id);
258 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
259 ."'{$post->post_title}' ({$post->post_date})"
260 ." (as of {$post->post_modified})");
261 }
262
263 function log_feedwordpress_update_feeds ($uri) {
264 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
265 }
266
267 function log_feedwordpress_check_feed ($feed) {
268 $uri = $feed['link/uri']; $name = $feed['link/name'];
269 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
270 }
271
272 function log_feedwordpress_update_complete ($delta) {
273 $mesg = array();
274 if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
275 if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
276 if (empty($mesg)) $mesg[] = 'nothing changed';
277
278 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
279 .(is_null($delta) ? "Error: I don't syndicate that URI"
280 : implode(' and ', $mesg)));
281 }
282
283 ################################################################################
284 ## TEMPLATE API: functions to make your templates syndication-aware ############
285 ################################################################################
286
287 function is_syndicated () { return (strlen(get_syndication_feed_id()) > 0); }
288
289 function get_syndication_source_link ($original = NULL) {
290 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
291 endif;
292
293 if ($original) : $vals = get_post_custom_values('syndication_source_uri_original');
294 else : $vals = array();
295 endif;
296
297 if (count($vals) == 0) : $vals = get_post_custom_values('syndication_source_uri');
298 endif;
299
300 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
301
302 return $ret;
303 } /* function get_syndication_source_link() */
304
305 function the_syndication_source_link ($original = NULL) { echo get_syndication_source_link($original); }
306
307 function get_syndication_source ($original = NULL) {
308 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
309 endif;
310
311 if ($original) : $vals = get_post_custom_values('syndication_source_original');
312 else : $vals = array();
313 endif;
314
315 if (count($vals) == 0) : $vals = get_post_custom_values('syndication_source');
316 endif;
317
318 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
319
320 return $ret;
321 } /* function get_syndication_source() */
322
323 function the_syndication_source ($original = NULL) { echo get_syndication_source($original); }
324
325 function get_syndication_feed ($original = NULL) {
326 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
327 endif;
328
329 if ($original) : $vals = get_post_custom_values('syndication_feed_original');
330 else : $vals = array();
331 endif;
332
333 if (count($vals) == 0) : $vals = get_post_custom_values('syndication_feed');
334 endif;
335
336 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
337
338 return $ret;
339 } /* function get_syndication_feed() */
340
341 function the_syndication_feed ($original = NULL) { echo get_syndication_feed ($original); }
342
343 function get_syndication_feed_guid ($original = NULL) {
344 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
345 endif;
346
347 if ($original) : $vals = get_post_custom_values('syndication_source_id_original');
348 else : $vals = array();
349 endif;
350
351 if (count($vals) == 0) : $vals = array(get_feed_meta('feed/id'));
352 endif;
353
354 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
355
356 return $ret;
357 } /* function get_syndication_feed_guid () */
358
359 function the_syndication_feed_guid ($original = NULL) { echo get_syndication_feed_guid($original); }
360
361 function get_syndication_feed_id () { list($u) = get_post_custom_values('syndication_feed_id'); return $u; }
362 function the_syndication_feed_id () { echo get_syndication_feed_id(); }
363
364 $feedwordpress_linkcache = array (); // only load links from database once
365
366 function get_feed_meta ($key) {
367 global $wpdb, $feedwordpress_linkcache;
368 $feed_id = get_syndication_feed_id();
369
370 $ret = NULL;
371 if (strlen($feed_id) > 0):
372 if (isset($feedwordpress_linkcache[$feed_id])) :
373 $link = $feedwordpress_linkcache[$feed_id];
374 else :
375 $link =& new SyndicatedLink($feed_id);
376 $feedwordpress_linkcache[$feed_id] = $link;
377 endif;
378
379 $ret = $link->settings[$key];
380 endif;
381 return $ret;
382 } /* get_feed_meta() */
383
384 function get_syndication_permalink () {
385 list($u) = get_post_custom_values('syndication_permalink'); return $u;
386 }
387 function the_syndication_permalink () {
388 echo get_syndication_permalink();
389 }
390
391 ################################################################################
392 ## FILTERS: syndication-aware handling of post data for templates and feeds ####
393 ################################################################################
394
395 $feedwordpress_the_syndicated_content = NULL;
396
397 function feedwordpress_preserve_syndicated_content ($text) {
398 global $feedwordpress_the_syndicated_content;
399
400 $globalExpose = (get_option('feedwordpress_formatting_filters') == 'yes');
401 $localExpose = get_post_custom_values('_feedwordpress_formatting_filters');
402 $expose = ($globalExpose or ((count($localExpose) > 0) and $localExpose[0]));
403
404 if ( is_syndicated() and !$expose ) :
405 $feedwordpress_the_syndicated_content = $text;
406 else :
407 $feedwordpress_the_syndicated_content = NULL;
408 endif;
409 return $text;
410 }
411
412 function feedwordpress_restore_syndicated_content ($text) {
413 global $feedwordpress_the_syndicated_content;
414
415 if ( !is_null($feedwordpress_the_syndicated_content) ) :
416 $text = $feedwordpress_the_syndicated_content;
417 endif;
418
419 return $text;
420 }
421
422 function feedwordpress_item_feed_data () {
423 // In a post context....
424 if (is_syndicated()) :
425 ?>
426 <source>
427 <title><?php the_syndication_source(); ?></title>
428 <link rel="alternate" type="text/html" href="<?php the_syndication_source_link(); ?>" />
429 <link rel="self" href="<?php the_syndication_feed(); ?>" />
430 <?php
431 $id = get_syndication_feed_guid();
432 if (strlen($id) > 0) :
433 ?>
434 <id><?php print $id; ?></id>
435 <?php
436 endif;
437 $updated = get_feed_meta('feed/updated');
438 if (strlen($updated) > 0) : ?>
439 <updated><?php print $updated; ?></updated>
440 <?php
441 endif;
442 ?>
443 </source>
444 <?php
445 endif;
446 }
447
448 function syndication_permalink ($permalink = '') {
449 if (get_option('feedwordpress_munge_permalink') != 'no'):
450 $uri = get_syndication_permalink();
451 return ((strlen($uri) > 0) ? $uri : $permalink);
452 else:
453 return $permalink;
454 endif;
455 } // function syndication_permalink ()
456
457 ################################################################################
458 ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
459 ################################################################################
460
461 function fwp_add_pages () {
462 global $fwp_capability;
463 global $fwp_path;
464
465 $menu = array('Syndicated Sites', 'Syndication', $fwp_capability['manage_links'], $fwp_path.'/syndication.php', NULL);
466 if (fwp_test_wp_version(FWP_SCHEMA_27)) :
467 // add icon parameter
468 $menu[] = WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-tiny.png';
469 endif;
470
471 if (fwp_test_wp_version(FWP_SCHEMA_26)) :
472 $options = __('Settings');
473 $longoptions = __('Syndication Settings');
474 else :
475 $options = __('Options');
476 $longoptions = __('Syndication Options');
477 endif;
478
479 call_user_func_array('add_menu_page', $menu);
480 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Posts', 'Posts', $fwp_capability['manage_options'], $fwp_path.'/posts-page.php');
481 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Authors', 'Authors', $fwp_capability['manage_options'], $fwp_path.'/authors-page.php');
482 add_submenu_page($fwp_path.'/syndication.php', 'Categories & Tags', 'Categories & Tags', $fwp_capability['manage_options'], $fwp_path.'/categories-page.php');
483 add_submenu_page($fwp_path.'/syndication.php', $longoptions, $options, $fwp_capability['manage_options'], $fwp_path.'/syndication-options.php');
484
485 add_options_page($longoptions, 'Syndication', $fwp_capability['manage_options'], $fwp_path.'/syndication-options.php');
486 } /* function fwp_add_pages () */
487
488 function fwp_check_debug () {
489 // This is a horrible fucking kludge that I have to do because the
490 // admin notice code is triggered before the code that updates the
491 // setting.
492 if (isset($_POST['feedwordpress_debug'])) :
493 $feedwordpress_debug = $_POST['feedwordpress_debug'];
494 else :
495 $feedwordpress_debug = get_option('feedwordpress_debug');
496 endif;
497 if ($feedwordpress_debug==='yes') :
498 ?>
499 <div class="error">
500 <p><strong>FeedWordPress warning.</strong> Debugging mode is <strong>ON</strong>.
501 While it remains on, FeedWordPress displays many diagnostic error messages,
502 warnings, and notices that are ordinarily suppressed, and also turns off all
503 caching of feeds. Use with caution: this setting is absolutely inappropriate
504 for a production server.</p>
505 </div>
506 <?php
507 endif;
508 } /* function fwp_check_debug () */
509
510 define('EXPECTED_MAGPIE_VERSION', '2009.0613');
511 function fwp_check_magpie () {
512 if (isset($_REQUEST['feedwordpress_magpie_fix'])) :
513 if ($_REQUEST['feedwordpress_magpie_fix']=='ignored') :
514 ?>
515 <div class="updated fade">
516 <p>O.K., we'll ignore the problem for now. FeedWordPress will not display any
517 more error messages.</p>
518 </div>
519 <?php
520 elseif ((bool) $_REQUEST['feedwordpress_magpie_fix']) :
521 ?>
522 <div class="updated fade">
523 <p>Congratulations! Your MagpieRSS has been successfully upgraded to the version
524 shipped with FeedWordPress.</p>
525 </div>
526 <?php
527 else :
528 $source = dirname(__FILE__)."/MagpieRSS-upgrade/rss.php";
529 $destination = ABSPATH . WPINC . '/rss.php';
530 $cmd = "cp '".htmlspecialchars(addslashes($source))."' '".htmlspecialchars(addslashes($destination))."'";
531 $cmd = wordwrap($cmd, /*width=*/ 75, /*break=*/ " \\\n\t");
532
533 ?>
534 <div class="error">
535 <p><strong>FeedWordPress was unable to automatically upgrade your copy of MagpieRSS.</strong></p>
536 <p>It's likely that you need to change the file permissions on <code><?php print htmlspecialchars($source); ?></code>
537 to allow FeedWordPress to overwrite it.</p>
538 <p><strong>To perform the upgrade manually,</strong> you can
539 use a SFTP or FTP client to upload a copy of <code>rss.php</code> from the
540 <code>MagpieRSS-upgrades/</code> directory of your FeedWordPress archive so that
541 it overwrites <code><?php print htmlspecialchars($destination); ?></code>. Or,
542 if your web host provides shell access, you can issue the following command from
543 a command prompt to perform the upgrade:</p>
544 <pre>
545 <samp>$</samp> <kbd><?php print $cmd; ?></kbd>
546 </pre>
547 <p><strong>If you've fixed the file permissions,</strong> you can try the
548 automatic upgrade again.</p>
549
550 <?php feedwordpress_upgrade_old_and_busted_buttons(); ?>
551 </div>
552 <?php
553 endif;
554 else :
555 $magpie_version = FeedWordPress::magpie_version();
556
557 $ignored = get_option('feedwordpress_magpie_ignored_upgrade_to');
558 if (EXPECTED_MAGPIE_VERSION != $magpie_version and EXPECTED_MAGPIE_VERSION != $ignored) :
559 if (current_user_can('edit_files')) :
560 $youAre = 'you are';
561 $itIsRecommendedThatYou = 'It is <strong>strongly recommended</strong> that you';
562 else :
563 $youAre = 'this site is';
564 $itIsRecommendedThatYou = 'You may want to contact the administrator of the site; it is <strong>strongly recommended</strong> that they';
565 endif;
566 print '<div class="error">';
567 ?>
568 <p style="font-style: italic"><strong>FeedWordPress has detected that <?php print $youAre; ?> currently using a version of
569 MagpieRSS other than the upgraded version that ships with this version of FeedWordPress.</strong></p>
570 <ul>
571 <li><strong>Currently running:</strong> MagpieRSS <?php print $magpie_version; ?></li>
572 <li><strong>Version included with FeedWordPress <?php print FEEDWORDPRESS_VERSION; ?>:</strong> MagpieRSS <?php print EXPECTED_MAGPIE_VERSION; ?></li>
573 </ul>
574 <p><?php print $itIsRecommendedThatYou; ?> install the upgraded
575 version of MagpieRSS supplied with FeedWordPress. The version of
576 MagpieRSS that ships with WordPress is very old and buggy, and
577 encounters a number of errors when trying to parse modern Atom
578 and RSS feeds.</p>
579 <?php
580 feedwordpress_upgrade_old_and_busted_buttons();
581 print '</div>';
582 endif;
583 endif;
584 }
585
586 function feedwordpress_upgrade_old_and_busted_buttons() {
587 if (current_user_can('edit_files')) :
588 ?>
589 <form action="" method="post"><div>
590 <?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_fix_magpie'); ?>
591 <input type="hidden" name="action" value="fix_magpie_version" />
592 <input class="button-secondary" type="submit" name="ignore" value="<?php _e('Ignore this problem'); ?>" />
593 <input class="button-primary" type="submit" name="upgrade" value="<?php _e('Upgrade'); ?>" />
594 </div></form>
595 <?php
596 endif;
597 }
598
599 ################################################################################
600 ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
601 ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
602 ################################################################################
603
604 $fwp_held_ping = NULL; // NULL: not holding pings yet
605
606 function fwp_hold_pings () {
607 global $fwp_held_ping;
608 if (is_null($fwp_held_ping)):
609 $fwp_held_ping = 0; // 0: ready to hold pings; none yet received
610 endif;
611 }
612
613 function fwp_release_pings () {
614 global $fwp_held_ping;
615 if ($fwp_held_ping):
616 if (function_exists('wp_schedule_single_event')) :
617 wp_schedule_single_event(time(), 'do_pings');
618 else :
619 generic_ping($fwp_held_ping);
620 endif;
621 endif;
622 $fwp_held_ping = NULL; // NULL: not holding pings anymore
623 }
624
625 function fwp_do_pings () {
626 if (!is_null($fwp_held_ping) and $post_id) : // Defer until we're done updating
627 $fwp_held_ping = $post_id;
628 elseif (function_exists('do_all_pings')) :
629 do_all_pings();
630 else :
631 generic_ping($fwp_held_ping);
632 endif;
633 }
634
635 function fwp_publish_post_hook ($post_id) {
636 global $fwp_held_ping;
637
638 if (!is_null($fwp_held_ping)) : // Syndicated post. Don't mark with _pingme
639 if ( defined('XMLRPC_REQUEST') )
640 do_action('xmlrpc_publish_post', $post_id);
641 if ( defined('APP_REQUEST') )
642 do_action('app_publish_post', $post_id);
643
644 if ( defined('WP_IMPORTING') )
645 return;
646
647 // Defer sending out pings until we finish updating
648 $fwp_held_ping = $post_id;
649 else :
650 if (function_exists('_publish_post_hook')) : // WordPress 2.3
651 _publish_post_hook($post_id);
652 endif;
653 endif;
654 }
655
656 ################################################################################
657 ## class FeedWordPress #########################################################
658 ################################################################################
659
660 // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
661 class FeedWordPress {
662 var $strip_attrs = array (
663 array('[a-z]+', 'style'),
664 array('[a-z]+', 'target'),
665 );
666 var $uri_attrs = array (
667 array('a', 'href'),
668 array('applet', 'codebase'),
669 array('area', 'href'),
670 array('blockquote', 'cite'),
671 array('body', 'background'),
672 array('del', 'cite'),
673 array('form', 'action'),
674 array('frame', 'longdesc'),
675 array('frame', 'src'),
676 array('iframe', 'longdesc'),
677 array('iframe', 'src'),
678 array('head', 'profile'),
679 array('img', 'longdesc'),
680 array('img', 'src'),
681 array('img', 'usemap'),
682 array('input', 'src'),
683 array('input', 'usemap'),
684 array('ins', 'cite'),
685 array('link', 'href'),
686 array('object', 'classid'),
687 array('object', 'codebase'),
688 array('object', 'data'),
689 array('object', 'usemap'),
690 array('q', 'cite'),
691 array('script', 'src')
692 );
693
694 var $feeds = NULL;
695
696 # function FeedWordPress (): Contructor; retrieve a list of feeds
697 function FeedWordPress () {
698 $this->feeds = array ();
699 $links = FeedWordPress::syndicated_links();
700 if ($links): foreach ($links as $link):
701 $this->feeds[] =& new SyndicatedLink($link);
702 endforeach; endif;
703 } // FeedWordPress::FeedWordPress ()
704
705 # function update (): polls for updates on one or more Contributor feeds
706 #
707 # Arguments:
708 # ----------
709 # * $uri (string): either the URI of the feed to poll, the URI of the
710 # (human-readable) website whose feed you want to poll, or NULL.
711 #
712 # If $uri is NULL, then FeedWordPress will poll any feeds that are
713 # ready for polling. It will not poll feeds that are marked as
714 # "Invisible" Links (signifying that the subscription has been
715 # de-activated), or feeds that are not yet stale according to their
716 # TTL setting (which is either set in the feed, or else set
717 # randomly within a window of 30 minutes - 2 hours).
718 #
719 # Returns:
720 # --------
721 # * Normally returns an associative array, with 'new' => the number
722 # of new posts added during the update, and 'updated' => the number
723 # of old posts that were updated during the update. If both numbers
724 # are zero, there was no change since the last poll on that URI.
725 #
726 # * Returns NULL if URI it was passed was not a URI that this
727 # installation of FeedWordPress syndicates.
728 #
729 # Effects:
730 # --------
731 # * One or more feeds are polled for updates
732 #
733 # * If the feed Link does not have a hardcoded name set, its Link
734 # Name is synchronized with the feed's title element
735 #
736 # * If the feed Link does not have a hardcoded URI set, its Link URI
737 # is synchronized with the feed's human-readable link element
738 #
739 # * If the feed Link does not have a hardcoded description set, its
740 # Link Description is synchronized with the feed's description,
741 # tagline, or subtitle element.
742 #
743 # * The time of polling is recorded in the feed's settings, and the
744 # TTL (time until the feed is next available for polling) is set
745 # either from the feed (if it is supplied in the ttl or syndication
746 # module elements) or else from a randomly-generated time window
747 # (between 30 minutes and 2 hours).
748 #
749 # * New posts from the polled feed are added to the WordPress store.
750 #
751 # * Updates to existing posts since the last poll are mirrored in the
752 # WordPress store.
753 #
754 function update ($uri = null, $crash_ts = null) {
755 global $wpdb;
756
757 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
758 return NULL;
759 endif;
760
761 if (!is_null($uri)) :
762 $uri = trim($uri);
763 else : // Update all
764 update_option('feedwordpress_last_update_all', time());
765 endif;
766
767 do_action('feedwordpress_update', $uri);
768
769 if (is_null($crash_ts)) :
770 $crash_dt = (int) get_option('feedwordpress_update_time_limit');
771 if ($crash_dt > 0) :
772 $crash_ts = time() + $crash_dt;
773 else :
774 $crash_ts = NULL;
775 endif;
776 endif;
777
778 // Randomize order for load balancing purposes
779 $feed_set = $this->feeds;
780 shuffle($feed_set);
781
782 // Loop through and check for new posts
783 $delta = NULL;
784 foreach ($feed_set as $feed) :
785 if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit
786 break;
787 endif;
788
789 $pinged_that = (is_null($uri) or in_array($uri, array($feed->uri(), $feed->homepage())));
790
791 if (!is_null($uri)) : // A site-specific ping always updates
792 $timely = true;
793 else :
794 $timely = $feed->stale();
795 endif;
796
797 if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
798 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
799 endif;
800
801 if ($pinged_that and $timely) :
802 do_action('feedwordpress_check_feed', $feed->settings);
803 $start_ts = time();
804 $added = $feed->poll($crash_ts);
805 do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
806
807 if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
808 if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
809 endif;
810 endforeach;
811
812 do_action('feedwordpress_update_complete', $delta);
813
814 return $delta;
815 }
816
817 function stale () {
818 if (get_option('feedwordpress_automatic_updates')) :
819 $last = get_option('feedwordpress_last_update_all');
820
821 // If we haven't updated all yet, give it a time window
822 if (false === $last) :
823 $ret = false;
824 update_option('feedwordpress_last_update_all', time());
825
826 // Otherwise, check against freshness interval
827 elseif (is_numeric($last)) : // Expect a timestamp
828 $freshness = get_option('feedwordpress_freshness');
829 if (false === $freshness) : // Use default
830 $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
831 endif;
832 $ret = ( (time() - $last) > $freshness);
833
834 // This should never happen.
835 else :
836 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__);
837 endif;
838
839 // Explicit request for an update (e.g. from a cron job).
840 elseif (FeedWordPress::update_requested()) :
841 $ret = true;
842
843 else :
844 $ret = false;
845 endif;
846 return $ret;
847 } // FeedWordPress::stale()
848
849 function update_requested () {
850 return (isset($_REQUEST['update_feedwordpress']) and $_REQUEST['update_feedwordpress']);
851 } // FeedWordPress::update_requested()
852
853 function syndicate_link ($name, $uri, $rss) {
854 global $wpdb;
855
856 // Get the category ID#
857 $cat_id = FeedWordPress::link_category_id();
858
859 // WordPress gets cranky if there's no homepage URI
860 if (!isset($uri) or strlen($uri)<1) : $uri = $rss; endif;
861
862 if (function_exists('wp_insert_link')) { // WordPress 2.x
863 $link_id = wp_insert_link(array(
864 "link_name" => $name,
865 "link_url" => $uri,
866 "link_category" => (fwp_test_wp_version(0, FWP_SCHEMA_21) ? $cat_id : array($cat_id)),
867 "link_rss" => $rss
868 ));
869 } else { // WordPress 1.5.x
870 $result = $wpdb->query("
871 INSERT INTO $wpdb->links
872 SET
873 link_name = '".$wpdb->escape($name)."',
874 link_url = '".$wpdb->escape($uri)."',
875 link_category = '".$wpdb->escape($cat_id)."',
876 link_rss = '".$wpdb->escape($rss)."'
877 ");
878 $link_id = $wpdb->insert_id;
879 } // if
880 return $link_id;
881 } // function FeedWordPress::syndicate_link()
882
883 function on_unfamiliar ($what = 'author', $override = NULL) {
884 $set = array(
885 'author' => array('create', 'default', 'filter'),
886 'category' => array('create', 'tag', 'default', 'filter'),
887 );
888
889 if (is_string($override)) :
890 $ret = strtolower($override);
891 endif;
892
893 if (!is_numeric($override) and !in_array($ret, $set[$what])) :
894 $ret = get_option('feedwordpress_unfamiliar_'.$what);
895 if (!is_numeric($ret) and !in_array($ret, $set[$what])) :
896 $ret = 'create';
897 endif;
898 endif;
899
900 return $ret;
901 } // function FeedWordPress::on_unfamiliar()
902
903 function null_email_set () {
904 $base = get_option('feedwordpress_null_email_set');
905
906 if ($base===false) :
907 $ret = array('noreply@blogger.com'); // default
908 else :
909 $ret = array_map('strtolower',
910 array_map('trim', explode("\n", $base)));
911 endif;
912 $ret = apply_filters('syndicated_item_author_null_email_set', $ret);
913 return $ret;
914
915 } /* FeedWordPress::null_email_set () */
916
917 function is_null_email ($email) {
918 $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set());
919 $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email);
920 return $ret;
921 } /* FeedWordPress::is_null_email () */
922
923 function use_aggregator_source_data () {
924 $ret = get_option('feedwordpress_use_aggregator_source_data');
925 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
926 }
927
928 function syndicated_links () {
929 $contributors = FeedWordPress::link_category_id();
930 if (function_exists('get_bookmarks')) :
931 $links = get_bookmarks(array("category" => $contributors));
932 else:
933 $links = get_linkobjects($contributors); // deprecated as of WP 2.1
934 endif;
935 return $links;
936 } // function FeedWordPress::syndicated_links()
937
938 function link_category_id () {
939 global $wpdb, $wp_db_version;
940
941 $cat_id = get_option('feedwordpress_cat_id');
942
943 // If we don't yet *have* the category, we'll have to create it
944 if ($cat_id === false) :
945 $cat = $wpdb->escape(DEFAULT_SYNDICATION_CATEGORY);
946
947 // Look for something with the right name...
948 // -----------------------------------------
949
950 // WordPress 2.3 introduces a new taxonomy/term API
951 if (function_exists('is_term')) :
952 $cat_id = is_term($cat, 'link_category');
953 // WordPress 2.1 and 2.2 use a common table for both link and post categories
954 elseif (fwp_test_wp_version(FWP_SCHEMA_21, FWP_SCHEMA_23)) :
955 $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->categories} WHERE cat_name='$cat'");
956 // WordPress 1.5 and 2.0.x have a separate table for link categories
957 elseif (fwp_test_wp_version(0, FWP_SCHEMA_21)):
958 $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->linkcategories} WHERE cat_name='$cat'");
959 // This should never happen.
960 else :
961 FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
962 endif;
963
964 // If you still can't find anything, make it for yourself.
965 // -------------------------------------------------------
966 if (!$cat_id) :
967 // WordPress 2.3+ term/taxonomy API
968 if (function_exists('wp_insert_term')) :
969 $term = wp_insert_term($cat, 'link_category');
970 $cat_id = $term['term_id'];
971 // WordPress 2.1, 2.2 category API. By the way, why the fuck is this API function only available in a wp-admin module?
972 elseif (function_exists('wp_insert_category') and !fwp_test_wp_version(FWP_SCHEMA_20, FWP_SCHEMA_21)) :
973 $cat_id = wp_insert_category(array('cat_name' => $cat));
974 // WordPress 1.5 and 2.0.x
975 elseif (fwp_test_wp_version(0, FWP_SCHEMA_21)) :
976 $result = $wpdb->query("
977 INSERT INTO $wpdb->linkcategories
978 SET
979 cat_id = 0,
980 cat_name='$cat',
981 show_images='N',
982 show_description='N',
983 show_rating='N',
984 show_updated='N',
985 sort_order='name'
986 ");
987 $cat_id = $wpdb->insert_id;
988 // This should never happen.
989 else :
990 FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
991 endif;
992 endif;
993
994 update_option('feedwordpress_cat_id', $cat_id);
995 endif;
996 return $cat_id;
997 } // function FeedWordPress::link_category_id()
998
999 # Upgrades and maintenance...
1000 function needs_upgrade () {
1001 global $wpdb;
1002 $fwp_db_version = get_option('feedwordpress_version');
1003 $ret = false; // innocent until proven guilty
1004 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
1005 // This is an older version or a fresh install. Does it
1006 // require a database upgrade or database initialization?
1007 if ($fwp_db_version > 0.96) :
1008 // No. Just brand it with the new version.
1009 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1010 else :
1011 // Yes. Check to see whether this is a fresh install or an upgrade.
1012 $syn = $wpdb->get_col("
1013 SELECT post_id
1014 FROM $wpdb->postmeta
1015 WHERE meta_key = 'syndication_feed'
1016 ");
1017 if (count($syn) > 0) : // contains at least one syndicated post
1018 $ret = true;
1019 else : // fresh install; brand it as ours
1020 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1021 endif;
1022 endif;
1023 endif;
1024 return $ret;
1025 }
1026
1027 function upgrade_database ($from = NULL) {
1028 global $wpdb;
1029
1030 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1031
1032 switch ($from) :
1033 case 0.96: // account for changes to syndication custom values and guid
1034 echo "<p>Upgrading database from {$from} to ".FEEDWORDPRESS_VERSION."...</p>\n";
1035
1036 $cat_id = FeedWordPress::link_category_id();
1037
1038 // Avoid duplicates
1039 $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'");
1040
1041 // Look up all the link IDs
1042 $wpdb->query("
1043 CREATE TEMPORARY TABLE tmp_custom_values
1044 SELECT
1045 NULL AS meta_id,
1046 post_id,
1047 'syndication_feed_id' AS meta_key,
1048 link_id AS meta_value
1049 FROM `{$wpdb->postmeta}`, `{$wpdb->links}`
1050 WHERE
1051 meta_key='syndication_feed'
1052 AND meta_value=link_rss
1053 AND link_category = {$cat_id}
1054 ");
1055
1056 // Now attach them to their posts
1057 $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values");
1058
1059 // And clean up after ourselves.
1060 $wpdb->query("DROP TABLE tmp_custom_values");
1061
1062 // Now fix the guids to avoid duplicate posts
1063 echo "<ul>";
1064 foreach ($this->feeds as $feed) :
1065 echo "<li>Fixing post meta-data for <cite>".$feed['link/name']."</cite> &#8230; "; flush();
1066 $rss = @fetch_rss($feed['link/uri']);
1067 if (is_array($rss->items)) :
1068 foreach ($rss->items as $item) :
1069 $guid = $wpdb->escape(FeedWordPress::guid($item, $feed)); // new GUID algorithm
1070 $link = $wpdb->escape($item['link']);
1071
1072 $wpdb->query("
1073 UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}'
1074 ");
1075 endforeach;
1076 endif;
1077 echo "<strong>complete.</strong></li>\n";
1078 endforeach;
1079 echo "</ul>\n";
1080
1081 // Mark the upgrade as successful.
1082 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1083 endswitch;
1084 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1085 } /* FeedWordPress::upgrade_database() */
1086
1087 function create_guid_index () {
1088 global $wpdb;
1089
1090 $wpdb->query("
1091 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
1092 ");
1093 } /* FeedWordPress::create_guid_index () */
1094
1095 function magpie_version () {
1096 if (!defined('MAGPIE_VERSION')) : $magpie_version = $GLOBALS['wp_version'].'-default';
1097 else : $magpie_version = MAGPIE_VERSION;
1098 endif;
1099 return $magpie_version;
1100 }
1101
1102 # Utility functions for handling text settings
1103 function negative ($f, $setting) {
1104 $nego = array ('n', 'no', 'f', 'false');
1105 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
1106 }
1107
1108 function affirmative ($f, $setting) {
1109 $affirmo = array ('y', 'yes', 't', 'true', 1);
1110 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
1111 }
1112
1113
1114 # Internal debugging functions
1115 function critical_bug ($varname, $var, $line) {
1116 global $wp_version;
1117
1118 if (defined('MAGPIE_VERSION')) : $mv = MAGPIE_VERSION;
1119 else : $mv = 'WordPress '.$wp_version.' default.';
1120 endif;
1121
1122 echo '<p>There may be a bug in FeedWordPress. Please <a href="'.FEEDWORDPRESS_AUTHOR_CONTACT.'">contact the author</a> and paste the following information into your e-mail:</p>';
1123 echo "\n<plaintext>";
1124 echo "Triggered at line # ".$line."\n";
1125 echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
1126 echo "MagpieRSS version: {$mv}\n";
1127 echo "WordPress version: {$wp_version}\n";
1128 echo "PHP version: ".phpversion()."\n";
1129 echo "\n";
1130 echo $varname.": "; var_dump($var); echo "\n";
1131 die;
1132 }
1133
1134 function noncritical_bug ($varname, $var, $line) {
1135 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
1136 FeedWordPress::critical_bug($varname, $var, $line);
1137 endif;
1138 }
1139 } // class FeedWordPress
1140
1141 class SyndicatedPost {
1142 var $item = null;
1143
1144 var $link = null;
1145 var $feed = null;
1146 var $feedmeta = null;
1147
1148 var $post = array ();
1149
1150 var $_freshness = null;
1151 var $_wp_id = null;
1152
1153 function SyndicatedPost ($item, $link) {
1154 global $wpdb;
1155
1156 $this->link = $link;
1157 $feedmeta = $link->settings;
1158 $feed = $link->magpie;
1159
1160 # This is ugly as all hell. I'd like to use apply_filters()'s
1161 # alleged support for a variable argument count, but this seems
1162 # to have been broken in WordPress 1.5. It'll be fixed somehow
1163 # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
1164 # the board here.
1165 #
1166 # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
1167 global $fwp_channel, $fwp_feedmeta;
1168 $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
1169
1170 $this->feed = $feed;
1171 $this->feedmeta = $feedmeta;
1172
1173 $this->item = $item;
1174 $this->item = apply_filters('syndicated_item', $this->item, $this);
1175
1176 # Filters can halt further processing by returning NULL
1177 if (is_null($this->item)) :
1178 $this->post = NULL;
1179 else :
1180 # Note that nothing is run through $wpdb->escape() here.
1181 # That's deliberate. The escaping is done at the point
1182 # of insertion, not here, to avoid double-escaping and
1183 # to avoid screwing with syndicated_post filters
1184
1185 $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
1186
1187 // This just gives us an alphanumeric representation of
1188 // the author. We will look up (or create) the numeric
1189 // ID for the author in SyndicatedPost::add()
1190 $this->post['named']['author'] = apply_filters('syndicated_item_author', $this->author(), $this);
1191
1192 # Identify content and sanitize it.
1193 # ---------------------------------
1194 if (isset($this->item['atom_content'])) :
1195 $content = $this->item['atom_content'];
1196 elseif (isset($this->item['xhtml']['body'])) :
1197 $content = $this->item['xhtml']['body'];
1198 elseif (isset($this->item['xhtml']['div'])) :
1199 $content = $this->item['xhtml']['div'];
1200 elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
1201 $content = $this->item['content']['encoded'];
1202 else:
1203 $content = $this->item['description'];
1204 endif;
1205 $this->post['post_content'] = apply_filters('syndicated_item_content', $content, $this);
1206
1207 # Identify and sanitize excerpt
1208 $excerpt = NULL;
1209 if ( isset($this->item['description']) and $this->item['description'] ) :
1210 $excerpt = $this->item['description'];
1211 elseif ( isset($content) and $content ) :
1212 $excerpt = strip_tags($content);
1213 if (strlen($excerpt) > 255) :
1214 $excerpt = substr($excerpt,0,252).'...';
1215 endif;
1216 endif;
1217 $excerpt = apply_filters('syndicated_item_excerpt', $excerpt, $this);
1218
1219 if (!is_null($excerpt)):
1220 $this->post['post_excerpt'] = $excerpt;
1221 endif;
1222
1223 // This is unnecessary if we use wp_insert_post
1224 if (!$this->use_api('wp_insert_post')) :
1225 $this->post['post_name'] = sanitize_title($this->post['post_title']);
1226 endif;
1227
1228 $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
1229 $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
1230 $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
1231
1232 // Dealing with timestamps in WordPress is so fucking fucked.
1233 $offset = (int) get_option('gmt_offset') * 60 * 60;
1234 $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
1235 $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
1236 $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
1237 $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
1238
1239 // Use feed-level preferences or the global default.
1240 $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
1241 $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
1242 $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
1243
1244 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
1245 $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
1246
1247 // User-supplied custom settings to apply to each post. Do first so that FWP-generated custom settings will overwrite if necessary; thus preventing any munging
1248 $default_custom_settings = get_option('feedwordpress_custom_settings');
1249 if ($default_custom_settings) :
1250 $default_custom_settings = unserialize($default_custom_settings);
1251 endif;
1252 if (!is_array($default_custom_settings)) :
1253 $default_custom_settings = array();
1254 endif;
1255
1256 $custom_settings = $this->link->settings['postmeta'];
1257 if ($custom_settings) :
1258 $custom_settings = unserialize($custom_settings);
1259 endif;
1260 if (!is_array($custom_settings)) :
1261 $custom_settings = array();
1262 endif;
1263 $this->post['meta'] = array_merge($default_custom_settings, $custom_settings);
1264
1265 // RSS 2.0 / Atom 1.0 enclosure support
1266 if ( isset($this->item['enclosure#']) ) :
1267 for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
1268 $eid = (($i > 1) ? "#{$id}" : "");
1269 $this->post['meta']['enclosure'][] =
1270 apply_filters('syndicated_item_enclosure_url', $this->item["enclosure{$eid}@url"], $this)."\n".
1271 apply_filters('syndicated_item_enclosure_length', $this->item["enclosure{$eid}@length"], $this)."\n".
1272 apply_filters('syndicated_item_enclosure_type', $this->item["enclosure{$eid}@type"], $this);
1273 endfor;
1274 endif;
1275
1276 // In case you want to point back to the blog this was syndicated from
1277 if (isset($this->feed->channel['title'])) :
1278 $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
1279 endif;
1280
1281 if (isset($this->feed->channel['link'])) :
1282 $this->post['meta']['syndication_source_uri'] = apply_filters('syndicated_item_source_link', $this->feed->channel['link'], $this);
1283 endif;
1284
1285 // Make use of atom:source data, if present in an aggregated feed
1286 if (isset($this->item['source_title'])) :
1287 $this->post['meta']['syndication_source_original'] = $this->item['source_title'];
1288 endif;
1289
1290 if (isset($this->item['source_link'])) :
1291 $this->post['meta']['syndication_source_uri_original'] = $this->item['source_link'];
1292 endif;
1293
1294 if (isset($this->item['source_id'])) :
1295 $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
1296 endif;
1297
1298 // Store information on human-readable and machine-readable comment URIs
1299 if (isset($this->item['comments'])) :
1300 $this->post['meta']['rss:comments'] = apply_filters('syndicated_item_comments', $this->item['comments']);
1301 endif;
1302 if (isset($this->item['wfw']['commentrss'])) :
1303 $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
1304 endif;
1305
1306 // Store information to identify the feed that this came from
1307 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
1308 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
1309
1310 if (isset($this->item['source_link_self'])) :
1311 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
1312 endif;
1313
1314 // In case you want to know the external permalink...
1315 $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->item['link']);
1316
1317 // Store a hash of the post content for checking whether something needs to be updated
1318 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
1319
1320 // Feed-by-feed options for author and category creation
1321 $this->post['named']['unfamiliar']['author'] = $this->feedmeta['unfamiliar author'];
1322 $this->post['named']['unfamiliar']['category'] = $this->feedmeta['unfamiliar category'];
1323
1324 // Categories: start with default categories, if any
1325 $fc = get_option("feedwordpress_syndication_cats");
1326 if ($fc) :
1327 $this->post['named']['preset/category'] = explode("\n", $fc);
1328 else :
1329 $this->post['named']['preset/category'] = array();
1330 endif;
1331
1332 if (is_array($this->feedmeta['cats'])) :
1333 $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
1334 endif;
1335
1336 // Now add categories from the post, if we have 'em
1337 $this->post['named']['category'] = array();
1338 if ( isset($this->item['category#']) ) :
1339 for ($i = 1; $i <= $this->item['category#']; $i++) :
1340 $cat_idx = (($i > 1) ? "#{$i}" : "");
1341 $cat = $this->item["category{$cat_idx}"];
1342
1343 if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
1344 $pcre = "\007".$this->feedmeta['cat_split']."\007";
1345 $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
1346 else :
1347 $this->post['named']['category'][] = $cat;
1348 endif;
1349 endfor;
1350 endif;
1351 $this->post['named']['category'] = apply_filters('syndicated_item_categories', $this->post['named']['category'], $this);
1352
1353 // Tags: start with default tags, if any
1354 $ft = get_option("feedwordpress_syndication_tags");
1355 if ($ft) :
1356 $this->post['tags_input'] = explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft);
1357 else :
1358 $this->post['tags_input'] = array();
1359 endif;
1360
1361 if (is_array($this->feedmeta['tags'])) :
1362 $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
1363 endif;
1364
1365 endif;
1366 } // SyndicatedPost::SyndicatedPost()
1367
1368 function filtered () {
1369 return is_null($this->post);
1370 }
1371
1372 function freshness () {
1373 global $wpdb;
1374
1375 if ($this->filtered()) : // This should never happen.
1376 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1377 endif;
1378
1379 if (is_null($this->_freshness)) :
1380 $guid = $wpdb->escape($this->guid());
1381
1382 $result = $wpdb->get_row("
1383 SELECT id, guid, post_modified_gmt
1384 FROM $wpdb->posts WHERE guid='$guid'
1385 ");
1386
1387 if (!$result) :
1388 $this->_freshness = 2; // New content
1389 else:
1390 $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
1391 if (count($stored_update_hashes) > 0) :
1392 $stored_update_hash = $stored_update_hashes[0];
1393 $update_hash_changed = ($stored_update_hash != $this->update_hash());
1394 else :
1395 $update_hash_changed = false;
1396 endif;
1397
1398 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
1399
1400 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
1401 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1402 $updated = ((
1403 !is_null($updated_ts)
1404 and ($updated_ts > $last_rev_ts)
1405 ) or $update_hash_changed);
1406
1407 if ($updated) :
1408 $this->_freshness = 1; // Updated content
1409 $this->_wp_id = $result->id;
1410 else :
1411 $this->_freshness = 0; // Same old, same old
1412 $this->_wp_id = $result->id;
1413 endif;
1414 endif;
1415 endif;
1416 return $this->_freshness;
1417 }
1418
1419 function wp_id () {
1420 if ($this->filtered()) : // This should never happen.
1421 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1422 endif;
1423
1424 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1425 $fresh = $this->freshness(); // sets WP DB id in the process
1426 endif;
1427 return $this->_wp_id;
1428 }
1429
1430 function store () {
1431 global $wpdb;
1432
1433 if ($this->filtered()) : // This should never happen.
1434 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1435 endif;
1436
1437 $freshness = $this->freshness();
1438 if ($freshness > 0) :
1439 # -- Look up, or create, numeric ID for author
1440 $this->post['post_author'] = $this->author_id (
1441 FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
1442 );
1443
1444 if (is_null($this->post['post_author'])) :
1445 $this->post = NULL;
1446 endif;
1447 endif;
1448
1449 if (!$this->filtered() and $freshness > 0) :
1450 # -- Look up, or create, numeric ID for categories
1451 list($pcats, $ptags) = $this->category_ids (
1452 $this->post['named']['category'],
1453 FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
1454 /*tags_too=*/ true
1455 );
1456
1457 $this->post['post_category'] = $pcats;
1458 $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
1459
1460 if (is_null($this->post['post_category'])) :
1461 // filter mode on, no matching categories; drop the post
1462 $this->post = NULL;
1463 else :
1464 // filter mode off or at least one match; now add on the feed and global presets
1465 $this->post['post_category'] = array_merge (
1466 $this->post['post_category'],
1467 $this->category_ids (
1468 $this->post['named']['preset/category'],
1469 'default'
1470 )
1471 );
1472
1473 if (count($this->post['post_category']) < 1) :
1474 $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
1475 endif;
1476 endif;
1477 endif;
1478
1479 if (!$this->filtered() and $freshness > 0) :
1480 unset($this->post['named']);
1481 $this->post = apply_filters('syndicated_post', $this->post, $this);
1482 endif;
1483
1484 if (!$this->filtered() and $freshness == 2) :
1485 // The item has not yet been added. So let's add it.
1486 $this->insert_new();
1487 $this->add_rss_meta();
1488 do_action('post_syndicated_item', $this->wp_id());
1489
1490 $ret = 'new';
1491 elseif (!$this->filtered() and $freshness == 1) :
1492 $this->post['ID'] = $this->wp_id();
1493 $this->update_existing();
1494 $this->add_rss_meta();
1495 do_action('update_syndicated_item', $this->wp_id());
1496
1497 $ret = 'updated';
1498 else :
1499 $ret = false;
1500 endif;
1501
1502 return $ret;
1503 } // function SyndicatedPost::store ()
1504
1505 function insert_new () {
1506 global $wpdb, $wp_db_version;
1507
1508 $dbpost = $this->normalize_post(/*new=*/ true);
1509 if (!is_null($dbpost)) :
1510 if ($this->use_api('wp_insert_post')) :
1511 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1512
1513 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1514 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1515
1516 // Kludge to prevent kses filters from stripping the
1517 // content of posts when updating without a logged in
1518 // user who has `unfiltered_html` capability.
1519 add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1520
1521 $this->_wp_id = wp_insert_post($dbpost);
1522
1523 // Turn off ridiculous fucking kludges #1 and #2
1524 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1525 remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1526
1527 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1528
1529 // Unfortunately, as of WordPress 2.3, wp_insert_post()
1530 // *still* offers no way to use a guid of your choice,
1531 // and munges your post modified timestamp, too.
1532 $result = $wpdb->query("
1533 UPDATE $wpdb->posts
1534 SET
1535 guid='{$dbpost['guid']}',
1536 post_modified='{$dbpost['post_modified']}',
1537 post_modified_gmt='{$dbpost['post_modified_gmt']}'
1538 WHERE ID='{$this->_wp_id}'
1539 ");
1540 else :
1541 # The right way to do this is the above. But, alas,
1542 # in earlier versions of WordPress, wp_insert_post has
1543 # too much behavior (mainly related to pings) that can't
1544 # be overridden. In WordPress 1.5, it's enough of a
1545 # resource hog to make PHP segfault after inserting
1546 # 50-100 posts. This can get pretty annoying, especially
1547 # if you are trying to update your feeds for the first
1548 # time.
1549
1550 $result = $wpdb->query("
1551 INSERT INTO $wpdb->posts
1552 SET
1553 guid = '{$dbpost['guid']}',
1554 post_author = '{$dbpost['post_author']}',
1555 post_date = '{$dbpost['post_date']}',
1556 post_date_gmt = '{$dbpost['post_date_gmt']}',
1557 post_content = '{$dbpost['post_content']}',"
1558 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1559 post_title = '{$dbpost['post_title']}',
1560 post_name = '{$dbpost['post_name']}',
1561 post_modified = '{$dbpost['post_modified']}',
1562 post_modified_gmt = '{$dbpost['post_modified_gmt']}',
1563 comment_status = '{$dbpost['comment_status']}',
1564 ping_status = '{$dbpost['ping_status']}',
1565 post_status = '{$dbpost['post_status']}'
1566 ");
1567 $this->_wp_id = $wpdb->insert_id;
1568
1569 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1570
1571 // WordPress 1.5.x - 2.0.x
1572 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1573
1574 // Since we are not going through official channels, we need to
1575 // manually tell WordPress that we've published a new post.
1576 // We need to make sure to do this in order for FeedWordPress
1577 // to play well with the staticize-reloaded plugin (something
1578 // that a large aggregator website is going to *want* to be
1579 // able to use).
1580 do_action('publish_post', $this->_wp_id);
1581 endif;
1582 endif;
1583 } /* SyndicatedPost::insert_new() */
1584
1585 function update_existing () {
1586 global $wpdb;
1587
1588 // Why the fuck doesn't wp_insert_post already do this?
1589 $dbpost = $this->normalize_post(/*new=*/ false);
1590 if (!is_null($dbpost)) :
1591 if ($this->use_api('wp_insert_post')) :
1592 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1593
1594 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1595 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1596
1597 // Kludge to prevent kses filters from stripping the
1598 // content of posts when updating without a logged in
1599 // user who has `unfiltered_html` capability.
1600 add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1601
1602 // Don't munge status fields that the user may have reset manually
1603 if (function_exists('get_post_field')) :
1604 $doNotMunge = array('post_status', 'comment_status', 'ping_status');
1605 foreach ($doNotMunge as $field) :
1606 $dbpost[$field] = get_post_field($field, $this->wp_id());
1607 endforeach;
1608 endif;
1609
1610 $this->_wp_id = wp_insert_post($dbpost);
1611
1612 // Turn off ridiculous fucking kludges #1 and #2
1613 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1614 remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1615
1616 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1617
1618 // Unfortunately, as of WordPress 2.3, wp_insert_post()
1619 // munges your post modified timestamp.
1620 $result = $wpdb->query("
1621 UPDATE $wpdb->posts
1622 SET
1623 post_modified='{$dbpost['post_modified']}',
1624 post_modified_gmt='{$dbpost['post_modified_gmt']}'
1625 WHERE ID='{$this->_wp_id}'
1626 ");
1627 else :
1628
1629 $result = $wpdb->query("
1630 UPDATE $wpdb->posts
1631 SET
1632 post_author = '{$dbpost['post_author']}',
1633 post_content = '{$dbpost['post_content']}',"
1634 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1635 post_title = '{$dbpost['post_title']}',
1636 post_name = '{$dbpost['post_name']}',
1637 post_modified = '{$dbpost['post_modified']}',
1638 post_modified_gmt = '{$dbpost['post_modified_gmt']}'
1639 WHERE guid='{$dbpost['guid']}'
1640 ");
1641
1642 // WordPress 2.1.x and up
1643 if (function_exists('wp_set_post_categories')) :
1644 wp_set_post_categories($this->wp_id(), $this->post['post_category']);
1645 // WordPress 1.5.x - 2.0.x
1646 elseif (function_exists('wp_set_post_cats')) :
1647 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1648 // This should never happen.
1649 else :
1650 FeedWordPress::critical_bug(__CLASS__.'::'.__FUNCTION.'(): no post categorizing function', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1651 endif;
1652
1653 // Since we are not going through official channels, we need to
1654 // manually tell WordPress that we've published a new post.
1655 // We need to make sure to do this in order for FeedWordPress
1656 // to play well with the staticize-reloaded plugin (something
1657 // that a large aggregator website is going to *want* to be
1658 // able to use).
1659 do_action('edit_post', $this->post['ID']);
1660 endif;
1661 endif;
1662 } /* SyndicatedPost::update_existing() */
1663
1664 /**
1665 * SyndicatedPost::normalize_post()
1666 *
1667 * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
1668 * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1669 */
1670 function normalize_post ($new = true) {
1671 global $wpdb;
1672
1673 $out = array();
1674
1675 // Why the fuck doesn't wp_insert_post already do this?
1676 foreach ($this->post as $key => $value) :
1677 if (is_string($value)) :
1678 $out[$key] = $wpdb->escape($value);
1679 else :
1680 $out[$key] = $value;
1681 endif;
1682 endforeach;
1683
1684 if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
1685 // FIXME: Option for filtering out empty posts
1686 endif;
1687 if (strlen($out['post_title'])==0) :
1688 $offset = (int) get_option('gmt_offset') * 60 * 60;
1689 $out['post_title'] =
1690 $this->post['meta']['syndication_source']
1691 .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1692 // FIXME: Option for what to fill a blank title with...
1693 endif;
1694
1695 return $out;
1696 }
1697
1698 /**
1699 * SyndicatedPost::validate_post_id()
1700 *
1701 * @param array $dbpost An array representing the post we attempted to insert or update
1702 * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1703 */
1704 function validate_post_id ($dbpost, $ns) {
1705 if (is_array($ns)) : $ns = implode('::', $ns);
1706 else : $ns = (string) $ns; endif;
1707
1708 // This should never happen.
1709 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1710 FeedWordPress::critical_bug(
1711 /*name=*/ $ns.'::_wp_id',
1712 /*var =*/ array(
1713 "\$this->_wp_id" => $this->_wp_id,
1714 "\$dbpost" => $dbpost,
1715 "\$this" => $this
1716 ),
1717 /*line # =*/ __LINE__
1718 );
1719 endif;
1720 } /* SyndicatedPost::validate_post_id() */
1721
1722 /**
1723 * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
1724 * meta-data (authorship, etc.) when storing revisions of an updated
1725 * syndicated post.
1726 *
1727 * In their infinite wisdom, the WordPress coders have made it completely
1728 * impossible for a plugin that uses wp_insert_post() to set certain
1729 * meta-data (such as the author) when you store an old revision of an
1730 * updated post. Instead, it uses the WordPress defaults (= currently
1731 * active user ID if the process is running with a user logged in, or
1732 * = #0 if there is no user logged in). This results in bogus authorship
1733 * data for revisions that are syndicated from off the feed, unless we
1734 * use a ridiculous kludge like this to end-run the munging of meta-data
1735 * by _wp_put_post_revision.
1736 *
1737 * @param int $revision_id The revision ID to fix up meta-data
1738 */
1739 function fix_revision_meta ($revision_id) {
1740 global $wpdb;
1741
1742 $post_author = (int) $this->post['post_author'];
1743
1744 $revision_id = (int) $revision_id;
1745 $wpdb->query("
1746 UPDATE $wpdb->posts
1747 SET post_author={$this->post['post_author']}
1748 WHERE post_type = 'revision' AND ID='$revision_id'
1749 ");
1750 } /* SyndicatedPost::fix_revision_meta () */
1751
1752 /**
1753 * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
1754 * an automatic update, that generally means that wp_insert_post() is
1755 * being called under the user credentials of whoever is viewing the
1756 * blog at the time -- usually meaning no user at all. But if WordPress
1757 * gets a wp_insert_post() when current_user_can('unfiltered_html') is
1758 * false, it will run the content of the post through a kses function
1759 * that strips out lots of HTML tags -- notably <object> and some others.
1760 * This causes problems for syndicating (for example) feeds that contain
1761 * YouTube videos. It also produces an unexpected asymmetry between
1762 * automatically-initiated updates and updates initiated manually from
1763 * the WordPress Dashboard (which are usually initiated under the
1764 * credentials of a logged-in admin, and so don't get run through the
1765 * kses function). So, to avoid the whole mess, what we do here is
1766 * just forcibly disable the kses munging for a single syndicated post,
1767 * by restoring the contents of the `post_content` field.
1768 *
1769 * @param string $content The content of the post, after other filters have gotten to it
1770 * @return string The original content of the post, before other filters had a chance to munge it.
1771 */
1772 function avoid_kses_munge ($content) {
1773 global $wpdb;
1774 return $wpdb->escape($this->post['post_content']);
1775 }
1776
1777 // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
1778 // using the space for custom keys. The set of keys and values to add is
1779 // specified by the keys and values of $post['meta']. This is used to
1780 // store anything that the WordPress user might want to access from a
1781 // template concerning the post's original source that isn't provided
1782 // for by standard WP meta-data (i.e., any interesting data about the
1783 // syndicated post other than author, title, timestamp, categories, and
1784 // guid). It's also used to hook into WordPress's support for
1785 // enclosures.
1786 function add_rss_meta () {
1787 global $wpdb;
1788 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
1789 $postId = $this->wp_id();
1790
1791 // Aggregated posts should NOT send out pingbacks.
1792 // WordPress 2.1-2.2 claim you can tell them not to
1793 // using $post_pingback, but they don't listen, so we
1794 // make sure here.
1795 $result = $wpdb->query("
1796 DELETE FROM $wpdb->postmeta
1797 WHERE post_id='$postId' AND meta_key='_pingme'
1798 ");
1799
1800 foreach ( $this->post['meta'] as $key => $values ) :
1801
1802 $key = $wpdb->escape($key);
1803
1804 // If this is an update, clear out the old
1805 // values to avoid duplication.
1806 $result = $wpdb->query("
1807 DELETE FROM $wpdb->postmeta
1808 WHERE post_id='$postId' AND meta_key='$key'
1809 ");
1810
1811 // Allow for either a single value or an array
1812 if (!is_array($values)) $values = array($values);
1813 foreach ( $values as $value ) :
1814 $value = $wpdb->escape($value);
1815 $result = $wpdb->query("
1816 INSERT INTO $wpdb->postmeta
1817 SET
1818 post_id='$postId',
1819 meta_key='$key',
1820 meta_value='$value'
1821 ");
1822 endforeach;
1823 endforeach;
1824 endif;
1825 } /* SyndicatedPost::add_rss_meta () */
1826
1827 // SyndicatedPost::author_id (): get the ID for an author name from
1828 // the feed. Create the author if necessary.
1829 function author_id ($unfamiliar_author = 'create') {
1830 global $wpdb;
1831
1832 $a = $this->author();
1833 $author = $a['name'];
1834 $email = $a['email'];
1835 $url = $a['uri'];
1836
1837 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
1838 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
1839 $test_email = $email;
1840 else :
1841 $test_email = NULL;
1842 endif;
1843
1844 // Never can be too careful...
1845 $login = sanitize_user($author, /*strict=*/ true);
1846 $login = apply_filters('pre_user_login', $login);
1847
1848 $nice_author = sanitize_title($author);
1849 $nice_author = apply_filters('pre_user_nicename', $nice_author);
1850
1851 $reg_author = $wpdb->escape(preg_quote($author));
1852 $author = $wpdb->escape($author);
1853 $email = $wpdb->escape($email);
1854 $test_email = $wpdb->escape($test_email);
1855 $url = $wpdb->escape($url);
1856
1857 // Check for an existing author rule....
1858 if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
1859 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
1860 else :
1861 $author_rule = NULL;
1862 endif;
1863
1864 // User name is mapped to a particular author. If that author ID exists, use it.
1865 if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
1866 $id = (int) $author_rule;
1867
1868 // User name is filtered out
1869 elseif ('filter' == $author_rule) :
1870 $id = NULL;
1871
1872 else :
1873 // Check the database for an existing author record that might fit
1874
1875 #-- WordPress 2.0+
1876 if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
1877
1878 // First try the user core data table.
1879 $id = $wpdb->get_var(
1880 "SELECT ID FROM $wpdb->users
1881 WHERE
1882 TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1883 OR (
1884 LENGTH(TRIM(LCASE(user_email))) > 0
1885 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1886 )
1887 OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
1888 ");
1889
1890 // If that fails, look for aliases in the user meta data table
1891 if (is_null($id)) :
1892 $id = $wpdb->get_var(
1893 "SELECT user_id FROM $wpdb->usermeta
1894 WHERE
1895 (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
1896 OR (
1897 meta_key = 'description'
1898 AND TRIM(LCASE(meta_value))
1899 RLIKE CONCAT(
1900 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1901 TRIM(LCASE('$reg_author')),
1902 '( |\\t|\\r)*(\\n|\$)'
1903 )
1904 )
1905 ");
1906 endif;
1907
1908 #-- WordPress 1.5.x
1909 else :
1910 $id = $wpdb->get_var(
1911 "SELECT ID from $wpdb->users
1912 WHERE
1913 TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
1914 (
1915 LENGTH(TRIM(LCASE(user_email))) > 0
1916 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1917 ) OR
1918 TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
1919 TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
1920 TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
1921 TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
1922 (
1923 LOWER(user_description)
1924 RLIKE CONCAT(
1925 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1926 LCASE('$reg_author'),
1927 '( |\\t|\\r)*(\\n|\$)'
1928 )
1929 )
1930 ");
1931
1932 endif;
1933
1934 // ... if you don't find one, then do what you need to do
1935 if (is_null($id)) :
1936 if ($unfamiliar_author === 'create') :
1937 $userdata = array();
1938
1939 #-- user table data
1940 $userdata['ID'] = NULL; // new user
1941 $userdata['user_login'] = $login;
1942 $userdata['user_nicename'] = $nice_author;
1943 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
1944 $userdata['user_email'] = $email;
1945 $userdata['user_url'] = $url;
1946 $userdata['display_name'] = $author;
1947
1948 $id = wp_insert_user($userdata);
1949 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
1950 $id = (int) $unfamiliar_author;
1951 elseif ($unfamiliar_author === 'default') :
1952 $id = 1;
1953 endif;
1954 endif;
1955 endif;
1956
1957 if ($id) :
1958 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
1959 endif;
1960 return $id;
1961 } // function SyndicatedPost::author_id ()
1962
1963 // look up (and create) category ids from a list of categories
1964 function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
1965 global $wpdb;
1966
1967 // We need to normalize whitespace because (1) trailing
1968 // whitespace can cause PHP and MySQL not to see eye to eye on
1969 // VARCHAR comparisons for some versions of MySQL (cf.
1970 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1971 // because I doubt most people want to make a semantic
1972 // distinction between 'Computers' and 'Computers '
1973 $cats = array_map('trim', $cats);
1974
1975 $tags = array();
1976
1977 $cat_ids = array ();
1978 foreach ($cats as $cat_name) :
1979 if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
1980 $cat_id = (int) $backref[1];
1981 if (function_exists('is_term') and is_term($cat_id, 'category')) :
1982 $cat_ids[] = $cat_id;
1983 elseif (get_category($cat_id)) :
1984 $cat_ids[] = $cat_id;
1985 endif;
1986 elseif (strlen($cat_name) > 0) :
1987 $esc = $wpdb->escape($cat_name);
1988 $resc = $wpdb->escape(preg_quote($cat_name));
1989
1990 // WordPress 2.3+
1991 if (function_exists('is_term')) :
1992 $cat_id = is_term($cat_name, 'category');
1993 if ($cat_id) :
1994 $cat_ids[] = $cat_id['term_id'];
1995 // There must be a better way to do this...
1996 elseif ($results = $wpdb->get_results(
1997 "SELECT term_id
1998 FROM $wpdb->term_taxonomy
1999 WHERE
2000 LOWER(description) RLIKE
2001 CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
2002 )) :
2003 foreach ($results AS $term) :
2004 $cat_ids[] = (int) $term->term_id;
2005 endforeach;
2006 elseif ('tag'==$unfamiliar_category) :
2007 $tags[] = $cat_name;
2008 elseif ('create'===$unfamiliar_category) :
2009 $term = wp_insert_term($cat_name, 'category');
2010 if (is_wp_error($term)) :
2011 FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
2012 else :
2013 $cat_ids[] = $term['term_id'];
2014 endif;
2015 endif;
2016
2017 // WordPress 1.5.x - 2.2.x
2018 else :
2019 $results = $wpdb->get_results(
2020 "SELECT cat_ID
2021 FROM $wpdb->categories
2022 WHERE
2023 (LOWER(cat_name) = LOWER('$esc'))
2024 OR (LOWER(category_description)
2025 RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
2026 ");
2027 if ($results) :
2028 foreach ($results as $term) :
2029 $cat_ids[] = (int) $term->cat_ID;
2030 endforeach;
2031 elseif ('create'===$unfamiliar_category) :
2032 if (function_exists('wp_insert_category')) :
2033 $cat_id = wp_insert_category(array('cat_name' => $cat_name));
2034 // And into the database we go.
2035 else :
2036 $nice_kitty = sanitize_title($cat_name);
2037 $wpdb->query(sprintf("
2038 INSERT INTO $wpdb->categories
2039 SET
2040 cat_name='%s',
2041 category_nicename='%s'
2042 ", $wpdb->escape($cat_name), $nice_kitty
2043 ));
2044 $cat_id = $wpdb->insert_id;
2045 endif;
2046 $cat_ids[] = $cat_id;
2047 endif;
2048 endif;
2049 endif;
2050 endforeach;
2051
2052 if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
2053 $cat_ids = NULL; // Drop the post
2054 else :
2055 $cat_ids = array_unique($cat_ids);
2056 endif;
2057
2058 if ($tags_too) : $ret = array($cat_ids, $tags);
2059 else : $ret = $cat_ids;
2060 endif;
2061
2062 return $ret;
2063 } // function SyndicatedPost::category_ids ()
2064
2065 function use_api ($tag) {
2066 global $wp_db_version;
2067 switch ($tag) :
2068 case 'wp_insert_post':
2069 // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
2070 // In 1.5 it was such a resource hog it would make PHP segfault on big updates
2071 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
2072 break;
2073 case 'post_status_pending':
2074 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
2075 break;
2076 endswitch;
2077 return $ret;
2078 } // function SyndicatedPost::use_api ()
2079
2080 #### EXTRACT DATA FROM FEED ITEM ####
2081
2082 function created () {
2083 $epoch = null;
2084 if (isset($this->item['dc']['created'])) :
2085 $epoch = @parse_w3cdtf($this->item['dc']['created']);
2086 elseif (isset($this->item['dcterms']['created'])) :
2087 $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
2088 elseif (isset($this->item['created'])): // Atom 0.3
2089 $epoch = @parse_w3cdtf($this->item['created']);
2090 endif;
2091 return $epoch;
2092 }
2093 function published ($fallback = true) {
2094 $epoch = null;
2095
2096 # RSS is a fucking mess. Figure out whether we have a date in
2097 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
2098 # epoch format for reformatting. If we can't find anything,
2099 # we'll use the last-updated time.
2100 if (isset($this->item['dc']['date'])): // Dublin Core
2101 $epoch = @parse_w3cdtf($this->item['dc']['date']);
2102 elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
2103 $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
2104 elseif (isset($this->item['published'])) : // Atom 1.0
2105 $epoch = @parse_w3cdtf($this->item['published']);
2106 elseif (isset($this->item['issued'])): // Atom 0.3
2107 $epoch = @parse_w3cdtf($this->item['issued']);
2108 elseif (isset($this->item['pubdate'])): // RSS 2.0
2109 $epoch = strtotime($this->item['pubdate']);
2110 elseif ($fallback) : // Fall back to <updated> / <modified> if present
2111 $epoch = $this->updated(/*fallback=*/ false);
2112 endif;
2113
2114 # If everything failed, then default to the current time.
2115 if (is_null($epoch)) :
2116 if (-1 == $default) :
2117 $epoch = time();
2118 else :
2119 $epoch = $default;
2120 endif;
2121 endif;
2122
2123 return $epoch;
2124 }
2125 function updated ($fallback = true, $default = -1) {
2126 $epoch = null;
2127
2128 # As far as I know, only dcterms and Atom have reliable ways to
2129 # specify when something was *modified* last. If neither is
2130 # available, then we'll try to get the time of publication.
2131 if (isset($this->item['dc']['modified'])) : // Not really correct
2132 $epoch = @parse_w3cdtf($this->item['dc']['modified']);
2133 elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
2134 $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
2135 elseif (isset($this->item['modified'])): // Atom 0.3
2136 $epoch = @parse_w3cdtf($this->item['modified']);
2137 elseif (isset($this->item['updated'])): // Atom 1.0
2138 $epoch = @parse_w3cdtf($this->item['updated']);
2139 elseif ($fallback) : // Fall back to issued / dc:date
2140 $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
2141 endif;
2142
2143 # If everything failed, then default to the current time.
2144 if (is_null($epoch)) :
2145 if (-1 == $default) :
2146 $epoch = time();
2147 else :
2148 $epoch = $default;
2149 endif;
2150 endif;
2151
2152 return $epoch;
2153 }
2154
2155 function update_hash () {
2156 return md5(serialize($this->item));
2157 }
2158
2159 function guid () {
2160 $guid = null;
2161 if (isset($this->item['id'])): // Atom 0.3 / 1.0
2162 $guid = $this->item['id'];
2163 elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
2164 $guid = $this->item['atom']['id'];
2165 elseif (isset($this->item['guid'])) : // RSS 2.0
2166 $guid = $this->item['guid'];
2167 elseif (isset($this->item['dc']['identifier'])) :// yeah, right
2168 $guid = $this->item['dc']['identifier'];
2169 else :
2170 // The feed does not seem to have provided us with a
2171 // unique identifier, so we'll have to cobble together
2172 // a tag: URI that might work for us. The base of the
2173 // URI will be the host name of the feed source ...
2174 $bits = parse_url($this->feedmeta['link/uri']);
2175 $guid = 'tag:'.$bits['host'];
2176
2177 // If we have a date of creation, then we can use that
2178 // to uniquely identify the item. (On the other hand, if
2179 // the feed producer was consicentious enough to
2180 // generate dates of creation, she probably also was
2181 // conscientious enough to generate unique identifiers.)
2182 if (!is_null($this->created())) :
2183 $guid .= '://post.'.date('YmdHis', $this->created());
2184
2185 // Otherwise, use both the URI of the item, *and* the
2186 // item's title. We have to use both because titles are
2187 // often not unique, and sometimes links aren't unique
2188 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
2189 // some podcasts). But it's rare to have *both* the same
2190 // title *and* the same link for two different items. So
2191 // this is about the best we can do.
2192 else :
2193 $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
2194 endif;
2195 endif;
2196 return $guid;
2197 }
2198
2199 function author () {
2200 $author = array ();
2201
2202 if (isset($this->item['author_name'])):
2203 $author['name'] = $this->item['author_name'];
2204 elseif (isset($this->item['dc']['creator'])):
2205 $author['name'] = $this->item['dc']['creator'];
2206 elseif (isset($this->item['dc']['contributor'])):
2207 $author['name'] = $this->item['dc']['contributor'];
2208 elseif (isset($this->feed->channel['dc']['creator'])) :
2209 $author['name'] = $this->feed->channel['dc']['creator'];
2210 elseif (isset($this->feed->channel['dc']['contributor'])) :
2211 $author['name'] = $this->feed->channel['dc']['contributor'];
2212 elseif (isset($this->feed->channel['author_name'])) :
2213 $author['name'] = $this->feed->channel['author_name'];
2214 elseif ($this->feed->is_rss() and isset($this->item['author'])) :
2215 // The author element in RSS is allegedly an
2216 // e-mail address, but lots of people don't use
2217 // it that way. So let's make of it what we can.
2218 $author = parse_email_with_realname($this->item['author']);
2219
2220 if (!isset($author['name'])) :
2221 if (isset($author['email'])) :
2222 $author['name'] = $author['email'];
2223 else :
2224 $author['name'] = $this->feed->channel['title'];
2225 endif;
2226 endif;
2227 else :
2228 $author['name'] = $this->feed->channel['title'];
2229 endif;
2230
2231 if (isset($this->item['author_email'])):
2232 $author['email'] = $this->item['author_email'];
2233 elseif (isset($this->feed->channel['author_email'])) :
2234 $author['email'] = $this->feed->channel['author_email'];
2235 endif;
2236
2237 if (isset($this->item['author_url'])):
2238 $author['uri'] = $this->item['author_url'];
2239 elseif (isset($this->feed->channel['author_url'])) :
2240 $author['uri'] = $this->item['author_url'];
2241 else:
2242 $author['uri'] = $this->feed->channel['link'];
2243 endif;
2244
2245 return $author;
2246 } // SyndicatedPost::author()
2247
2248 var $uri_attrs = array (
2249 array('a', 'href'),
2250 array('applet', 'codebase'),
2251 array('area', 'href'),
2252 array('blockquote', 'cite'),
2253 array('body', 'background'),
2254 array('del', 'cite'),
2255 array('form', 'action'),
2256 array('frame', 'longdesc'),
2257 array('frame', 'src'),
2258 array('iframe', 'longdesc'),
2259 array('iframe', 'src'),
2260 array('head', 'profile'),
2261 array('img', 'longdesc'),
2262 array('img', 'src'),
2263 array('img', 'usemap'),
2264 array('input', 'src'),
2265 array('input', 'usemap'),
2266 array('ins', 'cite'),
2267 array('link', 'href'),
2268 array('object', 'classid'),
2269 array('object', 'codebase'),
2270 array('object', 'data'),
2271 array('object', 'usemap'),
2272 array('q', 'cite'),
2273 array('script', 'src')
2274 ); /* var SyndicatedPost::$uri_attrs */
2275
2276 var $_base = null;
2277
2278 function resolve_single_relative_uri ($refs) {
2279 $tag = FeedWordPressHTML::attributeMatch($refs);
2280 $url = Relative_URI::resolve($tag['value'], $this->_base);
2281 return $tag['prefix'] . $url . $tag['suffix'];
2282 } /* function SyndicatedPost::resolve_single_relative_uri() */
2283
2284 function resolve_relative_uris ($content, $obj) {
2285 # The MagpieRSS upgrade has some `xml:base` support baked in.
2286 # However, sometimes people do silly things, like putting
2287 # relative URIs out on a production RSS 2.0 feed or other feeds
2288 # with no good support for `xml:base`. So we'll do our best to
2289 # try to catch any remaining relative URIs and resolve them as
2290 # best we can.
2291 $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
2292
2293 foreach ($obj->uri_attrs as $pair) :
2294 list($tag, $attr) = $pair;
2295 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
2296 $content = preg_replace_callback (
2297 $pattern,
2298 array(&$obj, 'resolve_single_relative_uri'),
2299 $content
2300 );
2301 endforeach;
2302
2303 return $content;
2304 } /* function SyndicatedPost::resolve_relative_uris () */
2305
2306 var $strip_attrs = array (
2307 array('[a-z]+', 'target'),
2308 // array('[a-z]+', 'style'),
2309 // array('[a-z]+', 'on[a-z]+'),
2310 );
2311
2312 function strip_attribute_from_tag ($refs) {
2313 $tag = FeedWordPressHTML::attributeMatch($refs);
2314 return $tag['before_attribute'].$tag['after_attribute'];
2315 }
2316
2317 function sanitize_content ($content, $obj) {
2318 # This kind of sucks. I intend to replace it with
2319 # lib_filter sometime soon.
2320 foreach ($obj->strip_attrs as $pair):
2321 list($tag,$attr) = $pair;
2322 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
2323
2324 $content = preg_replace_callback (
2325 $pattern,
2326 array(&$obj, 'strip_attribute_from_tag'),
2327 $content
2328 );
2329 endforeach;
2330 return $content;
2331 }
2332 } // class SyndicatedPost
2333
2334 require_once(dirname(__FILE__) . '/syndicatedlink.class.php');
2335
2336 ################################################################################
2337 ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
2338 ################################################################################
2339
2340 function feedwordpress_xmlrpc_hook ($args = array ()) {
2341 $args['weblogUpdates.ping'] = 'feedwordpress_pong';
2342 return $args;
2343 }
2344
2345 function feedwordpress_pong ($args) {
2346 $feedwordpress =& new FeedWordPress;
2347 $delta = @$feedwordpress->update($args[1]);
2348 if (is_null($delta)):
2349 return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
2350 else:
2351 $mesg = array();
2352 if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
2353 if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
2354
2355 return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
2356 endif;
2357 }
2358
2359 # Relative URI static class: PHP class for resolving relative URLs
2360 #
2361 # This class is derived (under the terms of the GPL) from URL Class 0.3 by
2362 # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
2363 # for FeedWordPress's purposes. The class has been stripped down to a single
2364 # public method: Relative_URI::resolve($url, $base), which resolves the URI in
2365 # $url relative to the URI in $base
2366 #
2367 # The upgraded MagpieRSS also uses this class. So if we have it loaded
2368 # in, don't load it again.
2369 if (!class_exists('Relative_URI')) {
2370
2371 class Relative_URI
2372 {
2373 // Resolve relative URI in $url against the base URI in $base. If $base
2374 // is not supplied, then we use the REQUEST_URI of this script.
2375 //
2376 // I'm hoping this method reflects RFC 2396 Section 5.2
2377 function resolve ($url, $base = NULL)
2378 {
2379 if (is_null($base)):
2380 $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
2381 endif;
2382
2383 $base = Relative_URI::_encode(trim($base));
2384 $uri_parts = Relative_URI::_parse_url($base);
2385
2386 $url = Relative_URI::_encode(trim($url));
2387 $parts = Relative_URI::_parse_url($url);
2388
2389 $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
2390 $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
2391
2392 // if path is empty, and scheme, host, and query are undefined,
2393 // the URL is referring the base URL
2394
2395 if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
2396 // If the URI is empty or only a fragment, return the base URI
2397 return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
2398 } elseif (isset($parts['scheme'])) {
2399 // If the scheme is set, then the URI is absolute.
2400 return $url;
2401 } elseif (isset($parts['host'])) {
2402 $uri_parts['host'] = $parts['host'];
2403 $uri_parts['path'] = $parts['path'];
2404 } else {
2405 // We have a relative path but not a host.
2406
2407 // start ugly fix:
2408 // prepend slash to path if base host is set, base path is not set, and url path is not absolute
2409 if ($uri_parts['host'] && ($uri_parts['path'] == '')
2410 && (strlen($parts['path']) > 0)
2411 && (substr($parts['path'], 0, 1) != '/')) {
2412 $parts['path'] = '/'.$parts['path'];
2413 } // end ugly fix
2414
2415 if (substr($parts['path'], 0, 1) == '/') {
2416 $uri_parts['path'] = $parts['path'];
2417 } else {
2418 // copy base path excluding any characters after the last (right-most) slash character
2419 $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
2420 // append relative path
2421 $buffer .= $parts['path'];
2422 // remove "./" where "." is a complete path segment.
2423 $buffer = str_replace('/./', '/', $buffer);
2424 if (substr($buffer, 0, 2) == './') {
2425 $buffer = substr($buffer, 2);
2426 }
2427 // if buffer ends with "." as a complete path segment, remove it
2428 if (substr($buffer, -2) == '/.') {
2429 $buffer = substr($buffer, 0, -1);
2430 }
2431 // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
2432 $search_finished = false;
2433 $segment = explode('/', $buffer);
2434 while (!$search_finished) {
2435 for ($x=0; $x+1 < count($segment);) {
2436 if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
2437 if ($x+2 == count($segment)) $segment[] = '';
2438 unset($segment[$x], $segment[$x+1]);
2439 $segment = array_values($segment);
2440 continue 2;
2441 } else {
2442 $x++;
2443 }
2444 }
2445 $search_finished = true;
2446 }
2447 $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
2448 $uri_parts['path'] = $buffer;
2449
2450 }
2451 }
2452
2453 // If we've gotten to this point, we can try to put the pieces
2454 // back together.
2455 $ret = '';
2456 if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
2457 if (isset($uri_parts['user'])) {
2458 $ret .= $uri_parts['user'];
2459 if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
2460 $ret .= '@';
2461 }
2462 if (isset($uri_parts['host'])) {
2463 $ret .= '//'.$uri_parts['host'];
2464 if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
2465 }
2466 $ret .= $uri_parts['path'];
2467 if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
2468 if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
2469
2470 return $ret;
2471 }
2472
2473 /**
2474 * Parse URL
2475 *
2476 * Regular expression grabbed from RFC 2396 Appendix B.
2477 * This is a replacement for PHPs builtin parse_url().
2478 * @param string $url
2479 * @access private
2480 * @return array
2481 */
2482 function _parse_url($url)
2483 {
2484 // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
2485 // generates a warning.
2486 if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
2487 $parts = array();
2488 if ($match[1] != '') $parts['scheme'] = $match[2];
2489 if ($match[3] != '') $parts['auth'] = $match[4];
2490 // parse auth
2491 if (isset($parts['auth'])) {
2492 // store user info
2493 if (($at_pos = strpos($parts['auth'], '@')) !== false) {
2494 $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
2495 $parts['user'] = $userinfo[0];
2496 if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
2497 $parts['auth'] = substr($parts['auth'], $at_pos+1);
2498 }
2499 // get port number
2500 if ($port_pos = strrpos($parts['auth'], ':')) {
2501 $parts['host'] = substr($parts['auth'], 0, $port_pos);
2502 $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
2503 if ($parts['port'] < 1) $parts['port'] = null;
2504 } else {
2505 $parts['host'] = $parts['auth'];
2506 }
2507 }
2508 unset($parts['auth']);
2509 $parts['path'] = $match[5];
2510 if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
2511 if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
2512 return $parts;
2513 }
2514 // shouldn't reach here
2515 return array('path'=>'');
2516 }
2517
2518 function _encode($string)
2519 {
2520 static $replace = array();
2521 if (!count($replace)) {
2522 $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
2523 $find = array_merge(range(0, 31), $find);
2524 $find = array_map('chr', $find);
2525 foreach ($find as $char) {
2526 $replace[$char] = '%'.bin2hex($char);
2527 }
2528 }
2529 // escape control characters and a few other characters
2530 $encoded = strtr($string, $replace);
2531 // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
2532 return preg_replace('/[^\x21-\x7e]/', '', $encoded);
2533 }
2534 } // class Relative_URI
2535 }
2536
2537 // take your best guess at the realname and e-mail, given a string
2538 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2539 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
2540 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
2541 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
2542 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
2543 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
2544
2545 function parse_email_with_realname ($email) {
2546 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
2547 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
2548 $ret['email'] = $matches[1];
2549 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
2550 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2551 $ret['email'] = $matches[4];
2552 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
2553 $ret['name'] = NULL; $ret['email'] = $matches[1];
2554 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
2555 $ret['email'] = NULL;
2556 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2557 else :
2558 $ret['name'] = NULL; $ret['email'] = NULL;
2559 endif;
2560 return $ret;
2561 }
2562
2563