PluginProbe
FeedWordPress / 2010.0905
FeedWordPress v2010.0905
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 2010.0905, at feedwordpress.php

2,048 lines 68.2 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://feedwordpress.radgeek.com/
5 Description: simple and flexible Atom/RSS syndication for WordPress
6 Version: 2010.0905
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 */
11
12 /**
13 * @package FeedWordPress
14 * @version 2010.0905
15 */
16
17 # This uses code derived from:
18 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
19 # - MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net>
20 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
21 # - WordPress Blog Tool and Publishing Platform <http://wordpress.org/>
22 # according to the terms of the GNU General Public License.
23 #
24 # INSTALLATION: see readme.txt or <http://projects.radgeek.com/install>
25 #
26 # USAGE: once FeedWordPress is installed, you manage just about everything from
27 # the WordPress Dashboard, under the Syndication menu. To ensure that fresh
28 # content is added as it becomes available, you can convince your contributors
29 # to put your XML-RPC URI (if WordPress is installed at
30 # <http://www.zyx.com/blog>, XML-RPC requests should be sent to
31 # <http://www.zyx.com/blog/xmlrpc.php>), or update manually under the
32 # Syndication menu, or set up automatic updates under Syndication --> Settings,
33 # or use a cron job.
34
35 # -- Don't change these unless you know what you're doing...
36
37 define ('FEEDWORDPRESS_VERSION', '2010.0905');
38 define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
39
40 if (!defined('FEEDWORDPRESS_BLEG')) :
41 define ('FEEDWORDPRESS_BLEG', true);
42 endif;
43
44 // Defaults
45 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
46 define ('DEFAULT_UPDATE_PERIOD', 60); // value in minutes
47
48 if (isset($_REQUEST['feedwordpress_debug'])) :
49 $feedwordpress_debug = $_REQUEST['feedwordpress_debug'];
50 else :
51 $feedwordpress_debug = get_option('feedwordpress_debug');
52 if (is_string($feedwordpress_debug)) :
53 $feedwordpress_debug = ($feedwordpress_debug == 'yes');
54 endif;
55 endif;
56 define ('FEEDWORDPRESS_DEBUG', $feedwordpress_debug);
57
58 define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
59 define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
60
61 define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi');
62
63 define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes
64
65 define ('FWP_SCHEMA_HAS_USERMETA', 2966);
66 define ('FWP_SCHEMA_USES_ARGS_TAXONOMY', 12694); // Revision # for using $args['taxonomy'] to get link categories
67 define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0
68 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1
69 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
70 define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5
71 define ('FWP_SCHEMA_26', 8201); // Database schema # for WP 2.6
72 define ('FWP_SCHEMA_27', 9872); // Database schema # for WP 2.7
73 define ('FWP_SCHEMA_28', 11548); // Database schema # for WP 2.8
74 define ('FWP_SCHEMA_29', 12329); // Database schema # for WP 2.9
75 define ('FWP_SCHEMA_30', 12694); // Database schema # for WP 3.0
76
77 if (FEEDWORDPRESS_DEBUG) :
78 // Help us to pick out errors, if any.
79 ini_set('error_reporting', E_ALL & ~E_NOTICE);
80 ini_set('display_errors', true);
81
82 // When testing we don't want cache issues to interfere. But this is
83 // a VERY BAD SETTING for a production server. Webmasters will eat your
84 // face for breakfast if you use it, and the baby Jesus will cry. So
85 // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
86 // used for more than testing purposes!
87 define('FEEDWORDPRESS_CACHE_AGE', 1);
88 define('FEEDWORDPRESS_CACHE_LIFETIME', 1);
89 define('FEEDWORDPRESS_FETCH_TIME_OUT', 60);
90 else :
91 // Hold onto data all day for conditional GET purposes,
92 // but consider it stale after 1 min (requiring a conditional GET)
93 define('FEEDWORDPRESS_CACHE_LIFETIME', 24*60*60);
94 define('FEEDWORDPRESS_CACHE_AGE', 1*60);
95 define('FEEDWORDPRESS_FETCH_TIME_OUT', 10);
96 endif;
97
98 // Use our the cache settings that we want.
99 add_filter('wp_feed_cache_transient_lifetime', array('FeedWordPress', 'cache_lifetime'));
100
101 // Ensure that we have SimplePie loaded up and ready to go.
102 // We no longer need a MagpieRSS upgrade module. Hallelujah!
103 if (!class_exists('SimplePie')) :
104 require_once(ABSPATH . WPINC . '/class-simplepie.php');
105 endif;
106 require_once(ABSPATH . WPINC . '/class-feed.php');
107
108 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
109
110 require_once(dirname(__FILE__) . '/admin-ui.php');
111 require_once(dirname(__FILE__) . '/feedwordpresssyndicationpage.class.php');
112 require_once(dirname(__FILE__) . '/compatability.php'); // LEGACY API: Replicate or mock up functions for legacy support purposes
113
114 require_once(dirname(__FILE__) . '/syndicatedpost.class.php');
115 require_once(dirname(__FILE__) . '/syndicatedlink.class.php');
116 require_once(dirname(__FILE__) . '/feedwordpresshtml.class.php');
117 require_once(dirname(__FILE__) . '/feedwordpress-content-type-sniffer.class.php');
118
119 // Magic quotes are just about the stupidest thing ever.
120 if (is_array($_POST)) :
121 $fwp_post = stripslashes_deep($_POST);
122 endif;
123
124 // Get the path relative to the plugins directory in which FWP is stored
125 preg_match (
126 '|'.preg_quote(WP_PLUGIN_DIR).'/(.+)$|',
127 dirname(__FILE__),
128 $ref
129 );
130
131 if (isset($ref[1])) :
132 $fwp_path = $ref[1];
133 else : // Something went wrong. Let's just guess.
134 $fwp_path = 'feedwordpress';
135 endif;
136
137 // If this is a FeedWordPress admin page, queue up scripts for AJAX functions that FWP uses
138 // If it is a display page or a non-FeedWordPress admin page, don't.
139 wp_register_style('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.css');
140 if (FeedWordPressSettingsUI::is_admin()) :
141 // For JavaScript that needs to be generated dynamically
142 add_action('admin_print_scripts', array('FeedWordPressSettingsUI', 'admin_scripts'));
143
144 // For CSS that needs to be generated dynamically.
145 add_action('admin_print_styles', array('FeedWordPressSettingsUI', 'admin_styles'));
146
147 wp_enqueue_style('dashboard');
148 wp_enqueue_style('feedwordpress-elements');
149
150 if (function_exists('wp_admin_css')) :
151 wp_admin_css('css/dashboard');
152 endif;
153 endif;
154
155 if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
156
157 # Syndicated items are generally received in output-ready (X)HTML and
158 # should not be folded, crumpled, mutilated, or spindled by WordPress
159 # formatting filters. But we don't want to interfere with filters for
160 # any locally-authored posts, either.
161 #
162 # What WordPress should really have is a way for upstream filters to
163 # stop downstream filters from running at all. Since it doesn't, and
164 # since a downstream filter can't access the original copy of the text
165 # that is being filtered, what we will do here is (1) save a copy of the
166 # original text upstream, before any other filters run, and then (2)
167 # retrieve that copy downstream, after all the other filters run, *if*
168 # this is a syndicated post
169
170 add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
171 add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
172
173 add_action('atom_entry', 'feedwordpress_item_feed_data');
174
175 # Filter in original permalinks if the user wants that
176 add_filter('post_link', 'syndication_permalink', /*priority=*/ 1, /*arguments=*/ 3);
177
178 # When foreign URLs are used for permalinks in feeds or display
179 # contexts, they need to be escaped properly.
180 add_filter('the_permalink', 'syndication_permalink_escaped');
181 add_filter('the_permalink_rss', 'syndication_permalink_escaped');
182
183 add_filter('post_comments_feed_link', 'syndication_comments_feed_link');
184
185 # WTF? By default, wp_insert_link runs incoming link_url and link_rss
186 # URIs through default filters that include `wp_kses()`. But `wp_kses()`
187 # just happens to escape any occurrence of & to &amp; -- which just
188 # happens to fuck up any URI with a & to separate GET parameters.
189 remove_filter('pre_link_rss', 'wp_filter_kses');
190 remove_filter('pre_link_url', 'wp_filter_kses');
191
192 # Admin menu
193 add_action('admin_menu', 'fwp_add_pages');
194 add_action('admin_notices', 'fwp_check_debug');
195
196 add_action('admin_menu', 'feedwordpress_add_post_edit_controls');
197 add_action('save_post', 'feedwordpress_save_post_edit_controls');
198
199 add_action('admin_footer', array('FeedWordPress', 'admin_footer'));
200
201 # Inbound XML-RPC update methods
202 $feedwordpressRPC = new FeedWordPressRPC;
203
204 # Outbound XML-RPC ping reform
205 remove_action('publish_post', 'generic_ping'); // WP 1.5.x
206 remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2
207 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3
208
209 add_action('publish_post', 'fwp_publish_post_hook', 5, 1);
210 add_action('do_pings', 'fwp_do_pings', 10, 1);
211 add_action('feedwordpress_update', 'fwp_hold_pings');
212 add_action('feedwordpress_update_complete', 'fwp_release_pings');
213
214 add_action('syndicated_feed_error', array('FeedWordPressDiagnostic', 'feed_error'), 100, 3);
215
216 add_action('wp_footer', 'debug_out_feedwordpress_footer', -100);
217 add_action('admin_footer', 'debug_out_feedwordpress_footer', -100);
218
219 $feedwordpress = new FeedWordPress;
220
221 # Cron-less auto-update. Hooray!
222 $autoUpdateHook = get_option('feedwordpress_automatic_updates');
223 if ($autoUpdateHook != 'init') :
224 $autoUpdateHook = 'shutdown';
225 endif;
226 add_action($autoUpdateHook, array(&$feedwordpress, 'auto_update'));
227 add_action('init', array(&$feedwordpress, 'init'));
228 add_action('shutdown', array(&$feedwordpress, 'email_diagnostic_log'));
229 add_action('wp_dashboard_setup', array(&$feedwordpress, 'dashboard_setup'));
230
231 # Default sanitizers
232 add_filter('syndicated_item_content', array('SyndicatedPost', 'resolve_relative_uris'), 0, 2);
233 add_filter('syndicated_item_content', array('SyndicatedPost', 'sanitize_content'), 0, 2);
234
235 else :
236 # Hook in the menus, which will just point to the upgrade interface
237 add_action('admin_menu', 'fwp_add_pages');
238 endif; // if (!FeedWordPress::needs_upgrade())
239
240 ################################################################################
241 ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
242 ################################################################################
243
244 class FeedWordPressDiagnostic {
245 function feed_error ($error, $old, $link) {
246 $wpError = $error['object'];
247 $url = $link->uri();
248
249 $mesgs = $wpError->get_error_messages();
250 foreach ($mesgs as $mesg) :
251 $mesg = esc_html($mesg);
252 FeedWordPress::diagnostic(
253 'updated_feeds:errors',
254 "Feed Error: [${url}] update returned error: $mesg"
255 );
256 if ($error['ts'] > $error['since']) :
257 $since = date('r', $error['since']);
258 $mostRecent = date('r', $error['ts']);
259 FeedWordPress::diagnostic(
260 'updated_feeds:errors:persistent',
261 "Feed Update Error: [${url}] returning errors"
262 ." since ${since}:<br/><code>$mesg</code>",
263 $url, $error['since'], $error['ts']
264 );
265 endif;
266 endforeach;
267 }
268
269 function admin_emails ($id = '') {
270 $users = get_users_of_blog($id);
271 $recipients = array();
272 foreach ($users as $user) :
273 $dude = new WP_User($user->user_id);
274 if ($dude->has_cap('administrator')) :
275 if ($dude->user_email) :
276 $recipients[] = $dude->user_email;
277 endif;
278 endif;
279 endforeach;
280 return $recipients;
281 }
282 } /* class FeedWordPressDiagnostic */
283
284 function debug_out_human_readable_bytes ($quantity) {
285 $quantity = (int) $quantity;
286 $magnitude = 'B';
287 $orders = array('KB', 'MB', 'GB', 'TB');
288 while (($quantity > (1024*100)) and (count($orders) > 0)) :
289 $quantity = floor($quantity / 1024);
290 $magnitude = array_shift($orders);
291 endwhile;
292 return "${quantity} ${magnitude}";
293 }
294
295 function debug_out_feedwordpress_footer () {
296 if (FeedWordPress::diagnostic_on('memory_usage')) :
297 if (function_exists('memory_get_usage')) :
298 FeedWordPress::diagnostic ('memory_usage', "Memory: Current usage: ".debug_out_human_readable_bytes(memory_get_usage()));
299 endif;
300 if (function_exists('memory_get_peak_usage')) :
301 FeedWordPress::diagnostic ('memory_usage', "Memory: Peak usage: ".debug_out_human_readable_bytes(memory_get_peak_usage()));
302 endif;
303 endif;
304 } /* debug_out_feedwordpress_footer() */
305
306 ################################################################################
307 ## TEMPLATE API: functions to make your templates syndication-aware ############
308 ################################################################################
309
310 /**
311 * is_syndicated: Tests whether the current post in a Loop context, or a post
312 * given by ID number, was syndicated by FeedWordPress. Useful for templates
313 * to determine whether or not to retrieve syndication-related meta-data in
314 * displaying a post.
315 *
316 * @param int $id The post to check for syndicated status. Defaults to the current post in a Loop context.
317 * @return bool TRUE if the post's meta-data indicates it was syndicated; FALSE otherwise
318 */
319 function is_syndicated ($id = NULL) {
320 return (strlen(get_syndication_feed_id($id)) > 0);
321 } /* function is_syndicated() */
322
323 function feedwordpress_display_url ($url, $before = 60, $after = 0) {
324 $bits = parse_url($url);
325
326 // Strip out crufty subdomains
327 if (isset($bits['host'])) :
328 $bits['host'] = preg_replace('/^www[0-9]*\./i', '', $bits['host']);
329 endif;
330
331 // Reassemble bit-by-bit with minimum of crufty elements
332 $url = (isset($bits['user'])?$bits['user'].'@':'')
333 .(isset($bits['host'])?$bits['host']:'')
334 .(isset($bits['path'])?$bits['path']:'')
335 .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
336 .(isset($bits['query'])?'?'.$bits['query']:'');
337
338 if (strlen($url) > ($before+$after)) :
339 $url = substr($url, 0, $before).'…'.substr($url, 0 - $after, $after);
340 endif;
341
342 return $url;
343 } /* feedwordpress_display_url () */
344
345 function get_syndication_source_property ($original, $id, $local, $remote = NULL) {
346 $ret = NULL;
347 if (is_null($original)) :
348 $original = FeedWordPress::use_aggregator_source_data();
349 endif;
350
351 if (is_null($remote)) :
352 $remote = $local . '_original';
353 endif;
354
355 $vals = ($original ? get_post_custom_values($remote, $id) : array());
356 if (count($vals) < 1) :
357 $vals = get_post_custom_values($local, $id);
358 endif;
359
360 if (count($vals) > 0) :
361 $ret = $vals[0];
362 endif;
363 return $ret;
364 } /* function get_syndication_source_property () */
365
366 function the_syndication_source_link ($original = NULL, $id = NULL) { echo get_syndication_source_link($original, $id); }
367 function get_syndication_source_link ($original = NULL, $id = NULL) {
368 return get_syndication_source_property($original, $id, 'syndication_source_uri');
369 } /* function get_syndication_source_link() */
370
371 function the_syndication_source ($original = NULL, $id = NULL) { echo get_syndication_source($original, $id); }
372 function get_syndication_source ($original = NULL, $id = NULL) {
373 $ret = get_syndication_source_property($original, $id, 'syndication_source');
374 if (is_null($ret) or strlen(trim($ret)) == 0) : // Fall back to URL of blog
375 $ret = feedwordpress_display_url(get_syndication_source_link());
376 endif;
377 return $ret;
378 } /* function get_syndication_source() */
379
380 function the_syndication_feed ($original = NULL, $id = NULL) { echo get_syndication_feed($original, $id); }
381 function get_syndication_feed ($original = NULL, $id = NULL) {
382 return get_syndication_source_property($original, $id, 'syndication_feed');
383 } /* function get_syndication_feed() */
384
385 function the_syndication_feed_guid ($original = NULL, $id = NULL) { echo get_syndication_feed_guid($original, $id); }
386 function get_syndication_feed_guid ($original = NULL, $id = NULL) {
387 $ret = get_syndication_source_property($original, $id, 'syndication_source_id');
388 if (is_null($ret) or strlen(trim($ret))==0) : // Fall back to URL of feed
389 $ret = get_syndication_feed();
390 endif;
391 return $ret;
392 } /* function get_syndication_feed_guid () */
393
394 function get_syndication_feed_id ($id = NULL) { list($u) = get_post_custom_values('syndication_feed_id', $id); return $u; }
395 function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); }
396
397 $feedwordpress_linkcache = array (); // only load links from database once
398 function get_syndication_feed_object ($id = NULL) {
399 global $feedwordpress_linkcache;
400
401 $link = NULL;
402
403 $feed_id = get_syndication_feed_id($id);
404 if (strlen($feed_id) > 0):
405 if (isset($feedwordpress_linkcache[$feed_id])) :
406 $link = $feedwordpress_linkcache[$feed_id];
407 else :
408 $link = new SyndicatedLink($feed_id);
409 $feedwordpress_linkcache[$feed_id] = $link;
410 endif;
411 endif;
412 return $link;
413 }
414
415 function get_feed_meta ($key, $id = NULL) {
416 $ret = NULL;
417
418 $link = get_syndication_feed_object($id);
419 if (is_object($link) and isset($link->settings[$key])) :
420 $ret = $link->settings[$key];
421 endif;
422 return $ret;
423 } /* get_feed_meta() */
424
425 function get_syndication_permalink ($id = NULL) {
426 list($u) = get_post_custom_values('syndication_permalink', $id); return $u;
427 }
428 function the_syndication_permalink ($id = NULL) {
429 echo get_syndication_permalink($id);
430 }
431
432 /**
433 * get_local_permalink: returns a string containing the internal permalink
434 * for a post (whether syndicated or not) on your local WordPress installation.
435 * This may be useful if you want permalinks to point to the original source of
436 * an article for most purposes, but want to retrieve a URL for the local
437 * representation of the post for one or two limited purposes (for example,
438 * linking to a comments page on your local aggregator site).
439 *
440 * @param $id The numerical ID of the post to get the permalink for. If empty,
441 * defaults to the current post in a Loop context.
442 * @return string The URL of the local permalink for this post.
443 *
444 * @uses get_permalink()
445 * @global $feedwordpress_the_original_permalink
446 *
447 * @since 2010.0217
448 */
449 function get_local_permalink ($id = NULL) {
450 global $feedwordpress_the_original_permalink;
451
452 // get permalink, and thus activate filter and force global to be filled
453 // with original URL.
454 $url = get_permalink($id);
455 return $feedwordpress_the_original_permalink;
456 } /* get_local_permalink() */
457
458 /**
459 * the_original_permalink: displays the contents of get_original_permalink()
460 *
461 * @param $id The numerical ID of the post to get the permalink for. If empty,
462 * defaults to the current post in a Loop context.
463 *
464 * @uses get_local_permalinks()
465 * @uses apply_filters
466 *
467 * @since 2010.0217
468 */
469 function the_local_permalink ($id = NULL) {
470 print apply_filters('the_permalink', get_local_permalink($id));
471 } /* function the_local_permalink() */
472
473 ################################################################################
474 ## FILTERS: syndication-aware handling of post data for templates and feeds ####
475 ################################################################################
476
477 $feedwordpress_the_syndicated_content = NULL;
478 $feedwordpress_the_original_permalink = NULL;
479
480 function feedwordpress_preserve_syndicated_content ($text) {
481 global $feedwordpress_the_syndicated_content;
482
483 $globalExpose = (get_option('feedwordpress_formatting_filters') == 'yes');
484 $localExpose = get_post_custom_values('_feedwordpress_formatting_filters');
485 $expose = ($globalExpose or ((count($localExpose) > 0) and $localExpose[0]));
486
487 if ( is_syndicated() and !$expose ) :
488 $feedwordpress_the_syndicated_content = $text;
489 else :
490 $feedwordpress_the_syndicated_content = NULL;
491 endif;
492 return $text;
493 }
494
495 function feedwordpress_restore_syndicated_content ($text) {
496 global $feedwordpress_the_syndicated_content;
497
498 if ( !is_null($feedwordpress_the_syndicated_content) ) :
499 $text = $feedwordpress_the_syndicated_content;
500 endif;
501
502 return $text;
503 }
504
505 function feedwordpress_item_feed_data () {
506 // In a post context....
507 if (is_syndicated()) :
508 ?>
509 <source>
510 <title><?php print htmlspecialchars(get_syndication_source()); ?></title>
511 <link rel="alternate" type="text/html" href="<?php print htmlspecialchars(get_syndication_source_link()); ?>" />
512 <link rel="self" href="<?php print htmlspecialchars(get_syndication_feed()); ?>" />
513 <?php
514 $id = get_syndication_feed_guid();
515 if (strlen($id) > 0) :
516 ?>
517 <id><?php print htmlspecialchars($id); ?></id>
518 <?php
519 endif;
520 $updated = get_feed_meta('feed/updated');
521 if (strlen($updated) > 0) : ?>
522 <updated><?php print $updated; ?></updated>
523 <?php
524 endif;
525 ?>
526 </source>
527 <?php
528 endif;
529 }
530
531 /**
532 * syndication_permalink: Allow WordPress to use the original remote URL of
533 * syndicated posts as their permalink. Can be turned on or off by by setting in
534 * Syndication => Posts & Links. Saves the old internal permalink in a global
535 * variable for later use.
536 *
537 * @param string $permalink The internal permalink
538 * @return string The new permalink. Same as the old if the post is not
539 * syndicated, or if FWP is set to use internal permalinks, or if the post
540 * was syndicated, but didn't have a proper permalink recorded.
541 *
542 * @uses SyndicatedLink::setting()
543 * @uses get_syndication_permalink()
544 * @uses get_syndication_feed_object()
545 * @uses url_to_postid()
546 * @global $id
547 * @global $feedwordpress_the_original_permalink
548 */
549 function syndication_permalink ($permalink = '', $post = null, $leavename = false) {
550 global $id;
551 global $feedwordpress_the_original_permalink;
552
553 // Save the local permalink in case we need to retrieve it later.
554 $feedwordpress_the_original_permalink = $permalink;
555
556 if (is_object($post) and isset($post->ID) and !empty($post->ID)) :
557 // Use the post ID we've been provided with.
558 $postId = $post->ID;
559 elseif (is_string($permalink) and strlen($permalink) > 0) :
560 // Map this permalink to a post ID so we can get the correct
561 // permalink even outside of the Post Loop. Props Björn.
562 $postId = url_to_postid($permalink);
563 else :
564 // If the permalink string is empty but Post Loop context
565 // provides an id.
566 $postId = $id;
567 endif;
568
569 $munge = false;
570 $link = get_syndication_feed_object($postId);
571 if (is_object($link)) :
572 $munge = ($link->setting('munge permalink', 'munge_permalink', 'yes') != 'no');
573 endif;
574
575 if ($munge):
576 $uri = get_syndication_permalink($postId);
577 $permalink = ((strlen($uri) > 0) ? $uri : $permalink);
578 endif;
579 return $permalink;
580 } /* function syndication_permalink () */
581
582 /**
583 * syndication_permalink_escaped: Escape XML special characters in syndicated
584 * permalinks when used in feed contexts and HTML display contexts.
585 *
586 * @param string $permalink
587 * @return string
588 *
589 * @uses is_syndicated()
590 * @uses FeedWordPress::munge_permalinks()
591 *
592 */
593 function syndication_permalink_escaped ($permalink) {
594 /* FIXME: This should review link settings not just global settings */
595 if (is_syndicated() and FeedWordPress::munge_permalinks()) :
596 // This is a foreign link; WordPress can't vouch for its not
597 // having any entities that need to be &-escaped. So we'll do
598 // it here.
599 $permalink = esc_html($permalink);
600 endif;
601 return $permalink;
602 } /* function syndication_permalink_escaped() */
603
604 /**
605 * syndication_comments_feed_link: Escape XML special characters in comments
606 * feed links
607 *
608 * @param string $link
609 * @return string
610 *
611 * @uses is_syndicated()
612 * @uses FeedWordPress::munge_permalinks()
613 */
614 function syndication_comments_feed_link ($link) {
615 global $feedwordpress_the_original_permalink, $id;
616
617 if (is_syndicated() and FeedWordPress::munge_permalinks()) :
618 // If the source post provided a comment feed URL using
619 // wfw:commentRss or atom:link/@rel="replies" we can make use of
620 // that value here.
621 $source = get_syndication_feed_object();
622 $replacement = NULL;
623 if ($source->setting('munge comments feed links', 'munge_comments_feed_links', 'yes') != 'no') :
624 $commentFeeds = get_post_custom_values('wfw:commentRSS');
625 if (
626 is_array($commentFeeds)
627 and (count($commentFeeds) > 0)
628 and (strlen($commentFeeds[0]) > 0)
629 ) :
630 $replacement = $commentFeeds[0];
631
632 // This is a foreign link; WordPress can't vouch for its not
633 // having any entities that need to be &-escaped. So we'll do it
634 // here.
635 $replacement = esc_html($replacement);
636 endif;
637 endif;
638
639 if (is_null($replacement)) :
640 // Q: How can we get the proper feed format, since the
641 // format is, stupidly, not passed to the filter?
642 // A: Kludge kludge kludge kludge!
643 $fancy_permalinks = ('' != get_option('permalink_structure'));
644 if ($fancy_permalinks) :
645 preg_match('|/feed(/([^/]+))?/?$|', $link, $ref);
646
647 $format = (isset($ref[2]) ? $ref[2] : '');
648 if (strlen($format) == 0) : $format = get_default_feed(); endif;
649
650 $replacement = trailingslashit($feedwordpress_the_original_permalink) . 'feed';
651 if ($format != get_default_feed()) :
652 $replacement .= '/'.$format;
653 endif;
654 $replacement = user_trailingslashit($replacement, 'single_feed');
655 else :
656 // No fancy permalinks = no problem
657 // WordPress doesn't call get_permalink() to
658 // generate the comment feed URL, so the
659 // comments feed link is never munged by FWP.
660 endif;
661 endif;
662
663 if (!is_null($replacement)) : $link = $replacement; endif;
664 endif;
665 return $link;
666 } /* function syndication_comments_feed_link() */
667
668 ################################################################################
669 ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
670 ################################################################################
671
672 function fwp_add_pages () {
673 global $fwp_path;
674
675 add_menu_page('Syndicated Sites', 'Syndication', 'manage_links', $fwp_path.'/syndication.php', NULL, WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-tiny.png');
676 do_action('feedwordpress_admin_menu_pre_feeds');
677 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Feeds & Updates', 'Feeds & Updates', 'manage_options', $fwp_path.'/feeds-page.php');
678 do_action('feedwordpress_admin_menu_pre_posts');
679 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Posts & Links', 'Posts & Links', 'manage_options', $fwp_path.'/posts-page.php');
680 do_action('feedwordpress_admin_menu_pre_authors');
681 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Authors', 'Authors', 'manage_options', $fwp_path.'/authors-page.php');
682 do_action('feedwordpress_admin_menu_pre_categories');
683 add_submenu_page($fwp_path.'/syndication.php', 'Categories'.FEEDWORDPRESS_AND_TAGS, 'Categories'.FEEDWORDPRESS_AND_TAGS, 'manage_options', $fwp_path.'/categories-page.php');
684 do_action('feedwordpress_admin_menu_pre_performance');
685 add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Performance', 'Performance', 'manage_options', $fwp_path.'/performance-page.php');
686 do_action('feedwordpress_admin_menu_pre_diagnostics');
687 add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Diagnostics', 'Diagnostics', 'manage_options', $fwp_path.'/diagnostics-page.php');
688 } /* function fwp_add_pages () */
689
690 function fwp_check_debug () {
691 // This is a horrible fucking kludge that I have to do because the
692 // admin notice code is triggered before the code that updates the
693 // setting.
694 if (isset($_POST['feedwordpress_debug'])) :
695 $feedwordpress_debug = $_POST['feedwordpress_debug'];
696 else :
697 $feedwordpress_debug = get_option('feedwordpress_debug');
698 endif;
699 if ($feedwordpress_debug==='yes') :
700 ?>
701 <div class="error">
702 <p><strong>FeedWordPress warning.</strong> Debugging mode is <strong>ON</strong>.
703 While it remains on, FeedWordPress displays many diagnostic error messages,
704 warnings, and notices that are ordinarily suppressed, and also turns off all
705 caching of feeds. Use with caution: this setting is absolutely inappropriate
706 for a production server.</p>
707 </div>
708 <?php
709 endif;
710 } /* function fwp_check_debug () */
711
712 ################################################################################
713 ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
714 ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
715 ################################################################################
716
717 $fwp_held_ping = NULL; // NULL: not holding pings yet
718
719 function fwp_hold_pings () {
720 global $fwp_held_ping;
721 if (is_null($fwp_held_ping)):
722 $fwp_held_ping = 0; // 0: ready to hold pings; none yet received
723 endif;
724 }
725
726 function fwp_release_pings () {
727 global $fwp_held_ping;
728 if ($fwp_held_ping):
729 if (function_exists('wp_schedule_single_event')) :
730 wp_schedule_single_event(time(), 'do_pings');
731 else :
732 generic_ping($fwp_held_ping);
733 endif;
734 endif;
735 $fwp_held_ping = NULL; // NULL: not holding pings anymore
736 }
737
738 function fwp_do_pings () {
739 if (!is_null($fwp_held_ping) and $post_id) : // Defer until we're done updating
740 $fwp_held_ping = $post_id;
741 elseif (function_exists('do_all_pings')) :
742 do_all_pings();
743 else :
744 generic_ping($fwp_held_ping);
745 endif;
746 }
747
748 function fwp_publish_post_hook ($post_id) {
749 global $fwp_held_ping;
750
751 if (!is_null($fwp_held_ping)) : // Syndicated post. Don't mark with _pingme
752 if ( defined('XMLRPC_REQUEST') )
753 do_action('xmlrpc_publish_post', $post_id);
754 if ( defined('APP_REQUEST') )
755 do_action('app_publish_post', $post_id);
756
757 if ( defined('WP_IMPORTING') )
758 return;
759
760 // Defer sending out pings until we finish updating
761 $fwp_held_ping = $post_id;
762 else :
763 if (function_exists('_publish_post_hook')) : // WordPress 2.3
764 _publish_post_hook($post_id);
765 endif;
766 endif;
767 }
768
769 function feedwordpress_add_post_edit_controls () {
770 add_meta_box('feedwordpress-post-controls', __('Syndication'), 'feedwordpress_post_edit_controls', 'post', 'side', 'high');
771 } // function FeedWordPress::postEditControls
772
773 function feedwordpress_post_edit_controls () {
774 global $post;
775
776 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $post->ID);
777 $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
778
779 if (is_syndicated($post->ID)) :
780 ?>
781 <p>This is a syndicated post, which originally appeared at
782 <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>.
783 <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
784
785 <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
786 <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
787 If set, FeedWordPress will not overwrite the changes you make manually
788 to this post, if the syndicated content is updated on the
789 feed.</label></p>
790 <?php
791 else :
792 ?>
793 <p>This post was created locally at this website.</p>
794 <?php
795 endif;
796 } // function feedwordpress_post_edit_controls () */
797
798 function feedwordpress_save_post_edit_controls ( $post_id ) {
799 global $post;
800
801 if (!isset($_POST['feedwordpress_noncename']) or !wp_verify_nonce($_POST['feedwordpress_noncename'], plugin_basename(__FILE__))) :
802 return $post_id;
803 endif;
804
805 // Verify if this is an auto save routine. If it is our form has
806 // not been submitted, so we don't want to do anything.
807 if ( defined('DOING_AUTOSAVE') and DOING_AUTOSAVE ) :
808 return $post_id;
809 endif;
810
811 // Check permissions
812 if ( !current_user_can( 'edit_'.$_POST['post_type'], $post_id) ) :
813 return $post_id;
814 endif;
815
816 // OK, we're golden. Now let's save some data.
817 if (isset($_POST['freeze_updates'])) :
818 update_post_meta($post_id, '_syndication_freeze_updates', $_POST['freeze_updates']);
819 $ret = $_POST['freeze_updates'];
820 else :
821 delete_post_meta($post_id, '_syndication_freeze_updates');
822 $ret = NULL;
823 endif;
824
825 return $ret;
826 } // function feedwordpress_save_edit_controls
827
828 ################################################################################
829 ## class FeedWordPress #########################################################
830 ################################################################################
831
832 // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
833 class FeedWordPress {
834 var $strip_attrs = array (
835 array('[a-z]+', 'style'),
836 array('[a-z]+', 'target'),
837 );
838 var $uri_attrs = array (
839 array('a', 'href'),
840 array('applet', 'codebase'),
841 array('area', 'href'),
842 array('blockquote', 'cite'),
843 array('body', 'background'),
844 array('del', 'cite'),
845 array('form', 'action'),
846 array('frame', 'longdesc'),
847 array('frame', 'src'),
848 array('iframe', 'longdesc'),
849 array('iframe', 'src'),
850 array('head', 'profile'),
851 array('img', 'longdesc'),
852 array('img', 'src'),
853 array('img', 'usemap'),
854 array('input', 'src'),
855 array('input', 'usemap'),
856 array('ins', 'cite'),
857 array('link', 'href'),
858 array('object', 'classid'),
859 array('object', 'codebase'),
860 array('object', 'data'),
861 array('object', 'usemap'),
862 array('q', 'cite'),
863 array('script', 'src')
864 );
865
866 var $feeds = NULL;
867
868 # function FeedWordPress (): Contructor; retrieve a list of feeds
869 function FeedWordPress () {
870 $this->feeds = array ();
871 $links = FeedWordPress::syndicated_links();
872 if ($links): foreach ($links as $link):
873 $this->feeds[] = new SyndicatedLink($link);
874 endforeach; endif;
875 } // FeedWordPress::FeedWordPress ()
876
877 # function update (): polls for updates on one or more Contributor feeds
878 #
879 # Arguments:
880 # ----------
881 # * $uri (string): either the URI of the feed to poll, the URI of the
882 # (human-readable) website whose feed you want to poll, or NULL.
883 #
884 # If $uri is NULL, then FeedWordPress will poll any feeds that are
885 # ready for polling. It will not poll feeds that are marked as
886 # "Invisible" Links (signifying that the subscription has been
887 # de-activated), or feeds that are not yet stale according to their
888 # TTL setting (which is either set in the feed, or else set
889 # randomly within a window of 30 minutes - 2 hours).
890 #
891 # Returns:
892 # --------
893 # * Normally returns an associative array, with 'new' => the number
894 # of new posts added during the update, and 'updated' => the number
895 # of old posts that were updated during the update. If both numbers
896 # are zero, there was no change since the last poll on that URI.
897 #
898 # * Returns NULL if URI it was passed was not a URI that this
899 # installation of FeedWordPress syndicates.
900 #
901 # Effects:
902 # --------
903 # * One or more feeds are polled for updates
904 #
905 # * If the feed Link does not have a hardcoded name set, its Link
906 # Name is synchronized with the feed's title element
907 #
908 # * If the feed Link does not have a hardcoded URI set, its Link URI
909 # is synchronized with the feed's human-readable link element
910 #
911 # * If the feed Link does not have a hardcoded description set, its
912 # Link Description is synchronized with the feed's description,
913 # tagline, or subtitle element.
914 #
915 # * The time of polling is recorded in the feed's settings, and the
916 # TTL (time until the feed is next available for polling) is set
917 # either from the feed (if it is supplied in the ttl or syndication
918 # module elements) or else from a randomly-generated time window
919 # (between 30 minutes and 2 hours).
920 #
921 # * New posts from the polled feed are added to the WordPress store.
922 #
923 # * Updates to existing posts since the last poll are mirrored in the
924 # WordPress store.
925 #
926 function update ($uri = null, $crash_ts = null) {
927 global $wpdb;
928
929 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
930 return NULL;
931 endif;
932
933 if (!is_null($uri) and $uri != '*') :
934 $uri = trim($uri);
935 else : // Update all
936 update_option('feedwordpress_last_update_all', time());
937 endif;
938
939 do_action('feedwordpress_update', $uri);
940
941 if (is_null($crash_ts)) :
942 $crash_dt = (int) get_option('feedwordpress_update_time_limit');
943 if ($crash_dt > 0) :
944 $crash_ts = time() + $crash_dt;
945 else :
946 $crash_ts = NULL;
947 endif;
948 endif;
949
950 // Randomize order for load balancing purposes
951 $feed_set = $this->feeds;
952 shuffle($feed_set);
953
954 $feed_set = apply_filters('feedwordpress_update_feeds', $feed_set, $uri);
955
956 // Loop through and check for new posts
957 $delta = NULL;
958 foreach ($feed_set as $feed) :
959 if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit
960 break;
961 endif;
962
963 $pinged_that = (is_null($uri) or ($uri=='*') or in_array($uri, array($feed->uri(), $feed->homepage())));
964
965 if (!is_null($uri)) : // A site-specific ping always updates
966 $timely = true;
967 else :
968 $timely = $feed->stale();
969 endif;
970
971 if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
972 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
973 endif;
974
975 if ($pinged_that and $timely) :
976 do_action('feedwordpress_check_feed', $feed->settings);
977 $start_ts = time();
978 $added = $feed->poll($crash_ts);
979 do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
980
981 if (is_array($added)) : // Success
982 if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
983 if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
984 endif;
985 endif;
986 endforeach;
987
988 do_action('feedwordpress_update_complete', $delta);
989
990 return $delta;
991 }
992
993 function stale () {
994 if (get_option('feedwordpress_automatic_updates')) :
995 // Do our best to avoid possible simultaneous
996 // updates by getting up-to-the-minute settings.
997
998 $last = get_option('feedwordpress_last_update_all');
999
1000 // If we haven't updated all yet, give it a time window
1001 if (false === $last) :
1002 $ret = false;
1003 update_option('feedwordpress_last_update_all', time());
1004
1005 // Otherwise, check against freshness interval
1006 elseif (is_numeric($last)) : // Expect a timestamp
1007 $freshness = get_option('feedwordpress_freshness');
1008 if (false === $freshness) : // Use default
1009 $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
1010 endif;
1011 $ret = ( (time() - $last) > $freshness);
1012
1013 // This should never happen.
1014 else :
1015 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__);
1016 endif;
1017
1018 else :
1019 $ret = false;
1020 endif;
1021 return $ret;
1022 } // FeedWordPress::stale()
1023
1024 function init () {
1025 $this->clear_cache_magic_url();
1026 $this->update_magic_url();
1027 } /* FeedWordPress::init() */
1028
1029 function dashboard_setup () {
1030 // Get the stylesheet
1031 wp_enqueue_style('feedwordpress-elements');
1032
1033 $widget_id = 'feedwordpress_dashboard';
1034 $widget_name = __('Syndicated Sources');
1035 $column = 'side';
1036 $priority = 'core';
1037
1038 // I would love to use wp_add_dashboard_widget() here and save
1039 // myself some trouble. But WP 3 does not yet have any way to
1040 // push a dashboard widget onto the side, or to give it a default
1041 // location.
1042 add_meta_box(
1043 /*id=*/ $widget_id,
1044 /*title=*/ $widget_name,
1045 /*callback=*/ array(&$this, 'dashboard'),
1046 /*page=*/ 'dashboard',
1047 /*context=*/ $column,
1048 /*priority=*/ $priority
1049 );
1050 /*control_callback= array(&$this, 'dashboard_control') */
1051
1052 // This is kind of rude, I know, but the dashboard widget isn't
1053 // worth much if users don't know that it exists, and I don't
1054 // know of any better way to reorder the boxen.
1055 //
1056 // Gleefully ripped off of codex.wordpress.org/Dashboard_Widgets_API
1057
1058 // Globalize the metaboxes array, this holds all the widgets for wp-admin
1059 global $wp_meta_boxes;
1060
1061 // Get the regular dashboard widgets array
1062 // (which has our new widget already but at the end)
1063
1064 $normal_dashboard = $wp_meta_boxes['dashboard'][$column][$priority];
1065
1066 // Backup and delete our new dashbaord widget from the end of the array
1067 if (isset($normal_dashboard[$widget_id])) :
1068 $backup = array();
1069 $backup[$widget_id] = $normal_dashboard[$widget_id];
1070 unset($normal_dashboard[$widget_id]);
1071
1072 // Merge the two arrays together so our widget is at the
1073 // beginning
1074 $sorted_dashboard = array_merge($backup, $normal_dashboard);
1075
1076 // Save the sorted array back into the original metaboxes
1077 $wp_meta_boxes['dashboard'][$column][$priority] = $sorted_dashboard;
1078 endif;
1079 } /* FeedWordPress::dashboard_setup () */
1080
1081 function dashboard () {
1082 $syndicationPage = new FeedWordPressSyndicationPage(dirname(__FILE__).'/syndication.php');
1083 $syndicationPage->dashboard_box($syndicationPage);
1084 } /* FeedWordPress::dashboard () */
1085
1086 function update_magic_url () {
1087 global $wpdb;
1088
1089 // Explicit update request in the HTTP request (e.g. from a cron job)
1090 if ($this->update_requested()) :
1091 $this->update($this->update_requested_url());
1092
1093 if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
1094 $mysqlTime = 0.0;
1095 $byTime = array();
1096 foreach ($wpdb->queries as $query) :
1097 $time = $query[1] * 1000000.0;
1098 $mysqlTime += $query[1];
1099 if (!isset($byTime[$time])) : $byTime[$time] = array(); endif;
1100 $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2];
1101 endforeach;
1102 krsort($byTime);
1103
1104 foreach ($byTime as $time => $querySet) :
1105 foreach ($querySet as $query) :
1106 print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n";
1107 endforeach;
1108 endforeach;
1109 echo $this->log_prefix()."$wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds.";
1110 endif;
1111
1112 debug_out_feedwordpress_footer();
1113
1114 // Magic URL should return nothing but a 200 OK header packet
1115 // when successful.
1116 exit;
1117 endif;
1118 } /* FeedWordPress::magic_update_url () */
1119
1120 function clear_cache_magic_url () {
1121 if ($this->clear_cache_requested()) :
1122 $this->clear_cache();
1123 endif;
1124 } /* FeedWordPress::clear_cache_magic_url() */
1125
1126 function clear_cache_requested () {
1127 return (
1128 isset($_GET['clear_cache'])
1129 and $_GET['clear_cache']
1130 );
1131 } /* FeedWordPress::clear_cache_requested() */
1132
1133 function update_requested () {
1134 return (
1135 isset($_REQUEST['update_feedwordpress'])
1136 and $_REQUEST['update_feedwordpress']
1137 );
1138 } // FeedWordPress::update_requested()
1139
1140 function update_requested_url () {
1141 $ret = null;
1142
1143 if (($_REQUEST['update_feedwordpress']=='*')
1144 or (preg_match('|^http://.*|i', $_REQUEST['update_feedwordpress']))) :
1145 $ret = $_REQUEST['update_feedwordpress'];
1146 endif;
1147
1148 return $ret;
1149 } // FeedWordPress::update_requested_url()
1150
1151 function auto_update () {
1152 if ($this->stale()) :
1153 $this->update();
1154 endif;
1155 } /* FeedWordPress::auto_update () */
1156
1157 function find_link ($uri, $field = 'link_rss') {
1158 global $wpdb;
1159
1160 $unslashed = untrailingslashit($uri);
1161 $slashed = trailingslashit($uri);
1162 $link_id = $wpdb->get_var($wpdb->prepare("
1163 SELECT link_id FROM $wpdb->links WHERE $field IN ('%s', '%s')
1164 LIMIT 1", $unslashed, $slashed
1165 ));
1166
1167 return $link_id;
1168 } /* FeedWordPress::find_link () */
1169
1170 function syndicate_link ($name, $uri, $rss) {
1171 // Get the category ID#
1172 $cat_id = FeedWordPress::link_category_id();
1173
1174 // WordPress gets cranky if there's no homepage URI
1175 if (!is_string($uri) or strlen($uri)<1) : $uri = $rss; endif;
1176
1177 // Check if this feed URL is already being syndicated.
1178 $link_id = wp_insert_link(array(
1179 "link_id" => FeedWordPress::find_link($rss), // insert if nothing was found; else update
1180 "link_rss" => $rss,
1181 "link_name" => $name,
1182 "link_url" => $uri,
1183 "link_category" => array($cat_id),
1184 "link_visible" => 'Y', // reactivate if inactivated
1185 ));
1186
1187 return $link_id;
1188 } /* function FeedWordPress::syndicate_link() */
1189
1190 /*static*/ function syndicated_status ($what, $default) {
1191 $ret = get_option("feedwordpress_syndicated_{$what}_status");
1192 if (!$ret) :
1193 $ret = $default;
1194 endif;
1195 return $ret;
1196 } /* FeedWordPress::syndicated_status() */
1197
1198 function on_unfamiliar ($what = 'author') {
1199 switch ($what) :
1200 case 'category' : $suffix = ':category'; break;
1201 case 'post_tag' : $suffix = ':post_tag'; break;
1202 default: $suffix = '';
1203 endswitch;
1204
1205 return get_option('feedwordpress_unfamiliar_'.$what, 'create'.$suffix);
1206 } // function FeedWordPress::on_unfamiliar()
1207
1208 function null_email_set () {
1209 $base = get_option('feedwordpress_null_email_set');
1210
1211 if ($base===false) :
1212 $ret = array('noreply@blogger.com'); // default
1213 else :
1214 $ret = array_map('strtolower',
1215 array_map('trim', explode("\n", $base)));
1216 endif;
1217 $ret = apply_filters('syndicated_item_author_null_email_set', $ret);
1218 return $ret;
1219
1220 } /* FeedWordPress::null_email_set () */
1221
1222 function is_null_email ($email) {
1223 $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set());
1224 $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email);
1225 return $ret;
1226 } /* FeedWordPress::is_null_email () */
1227
1228 function use_aggregator_source_data () {
1229 $ret = get_option('feedwordpress_use_aggregator_source_data');
1230 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1231 }
1232
1233 /**
1234 * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1235 * should rewrite permalinks for syndicated items to reflect their
1236 * original location.
1237 *
1238 * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1239 */
1240 /*static*/ function munge_permalinks () {
1241 return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1242 } /* FeedWordPress::munge_permalinks() */
1243
1244 function syndicated_links ($args = array()) {
1245 $contributors = FeedWordPress::link_category_id();
1246 $links = get_bookmarks(array_merge(
1247 array("category" => $contributors),
1248 $args
1249 ));
1250 return $links;
1251 } // function FeedWordPress::syndicated_links()
1252
1253 function link_category_id () {
1254 global $wpdb, $wp_db_version;
1255
1256 $cat_id = get_option('feedwordpress_cat_id');
1257
1258 // If we don't yet have the category ID stored, search by name
1259 if (!$cat_id) :
1260 $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY);
1261
1262 if ($cat_id) :
1263 // We found it; let's stamp it.
1264 update_option('feedwordpress_cat_id', $cat_id);
1265 endif;
1266
1267 // If we *do* have the category ID stored, verify that it exists
1268 else :
1269 $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id');
1270 endif;
1271
1272 // If we could not find an appropriate link category,
1273 // make a new one for ourselves.
1274 if (!$cat_id) :
1275 $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY);
1276
1277 // Stamp it
1278 update_option('feedwordpress_cat_id', $cat_id);
1279 endif;
1280
1281 return $cat_id;
1282 } // function FeedWordPress::link_category_id()
1283
1284 # Upgrades and maintenance...
1285 function needs_upgrade () {
1286 global $wpdb;
1287 $fwp_db_version = get_option('feedwordpress_version');
1288 $ret = false; // innocent until proven guilty
1289 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
1290 // This is an older version or a fresh install. Does it
1291 // require a database upgrade or database initialization?
1292 if ($fwp_db_version <= 0.96) :
1293 // Yes. Check to see whether this is a fresh install or an upgrade.
1294 $syn = $wpdb->get_col("
1295 SELECT post_id
1296 FROM $wpdb->postmeta
1297 WHERE meta_key = 'syndication_feed'
1298 ");
1299 if (count($syn) > 0) : // contains at least one syndicated post
1300 $ret = true;
1301 else : // fresh install; brand it as ours
1302 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1303 endif;
1304 elseif ($fwp_db_version < 2009.0707) :
1305 // We need to clear out any busted AJAX crap
1306 $wpdb->query("
1307 DELETE FROM $wpdb->usermeta
1308 WHERE LOCATE('feedwordpress', meta_key)
1309 AND LOCATE('box', meta_key);
1310 ");
1311
1312 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1313 elseif ($fwp_db_version < 2010.0814) :
1314 // Change in terminology.
1315 if (get_option('feedwordpress_unfamiliar_category', 'create')=='default') :
1316 update_option('feedwordpress_unfamiliar_category', 'null');
1317 endif;
1318 foreach (FeedWordPress::syndicated_links() as $link) :
1319 $sub = new SyndicatedLink($link);
1320
1321 $remap_uf = array(
1322 'default' => 'null',
1323 'filter' => 'null',
1324 'create' => 'create:category',
1325 'tag' => 'create:post_tag'
1326 );
1327 if (isset($sub->settings['unfamiliar category'])) :
1328 if ($sub->settings['unfamiliar category']=='filter') :
1329 $sub->settings['match/filter'] = array('category');
1330 endif;
1331 foreach ($remap_uf as $from => $to) :
1332 if ($sub->settings['unfamiliar category']==$from) :
1333 $sub->settings['unfamiliar category'] = $to;
1334 endif;
1335 endforeach;
1336 endif;
1337
1338 if (isset($sub->settings['add global categories'])) :
1339 $sub->settings['add/category'] = $sub->settings['add global categories'];
1340 unset($sub->settings['add global categories']);
1341 endif;
1342
1343 $sub->save_settings(/*reload=*/ true);
1344 endforeach;
1345 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1346 else :
1347 // No. Just brand it with the new version.
1348 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1349 endif;
1350 endif;
1351 return $ret;
1352 }
1353
1354 function upgrade_database ($from = NULL) {
1355 global $wpdb;
1356
1357 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1358
1359 switch ($from) :
1360 case 0.96:
1361 // Dropping legacy upgrade code. If anyone is still
1362 // using 0.96 and just now decided to upgrade, well, I'm
1363 // sorry about that. You'll just have to cope with a few
1364 // duplicate posts.
1365
1366 // Mark the upgrade as successful.
1367 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1368 endswitch;
1369 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1370 } /* FeedWordPress::upgrade_database() */
1371
1372 function has_guid_index () {
1373 global $wpdb;
1374
1375 $found = false; // Guilty until proven innocent.
1376
1377 $results = $wpdb->get_results("
1378 SHOW INDEXES FROM {$wpdb->posts}
1379 ");
1380 if ($results) :
1381 foreach ($results as $index) :
1382 if (isset($index->Column_name)
1383 and ('guid' == $index->Column_name)) :
1384 $found = true;
1385 endif;
1386 endforeach;
1387 endif;
1388 return $found;
1389 } /* FeedWordPress::has_guid_index () */
1390
1391 function create_guid_index () {
1392 global $wpdb;
1393
1394 $wpdb->query("
1395 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
1396 ");
1397 } /* FeedWordPress::create_guid_index () */
1398
1399 function remove_guid_index () {
1400 global $wpdb;
1401
1402 $wpdb->query("
1403 DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
1404 ");
1405 }
1406
1407 /*static*/ function fetch ($url, $force_feed = true) {
1408 $feed = new SimplePie();
1409 $feed->set_feed_url($url);
1410 $feed->set_cache_class('WP_Feed_Cache');
1411 $feed->set_file_class('WP_SimplePie_File');
1412 $feed->set_content_type_sniffer_class('FeedWordPress_Content_Type_Sniffer');
1413 $feed->set_file_class('FeedWordPress_File');
1414 $feed->set_parser_class('FeedWordPress_Parser');
1415 $feed->force_feed($force_feed);
1416 $feed->set_cache_duration(FeedWordPress::cache_duration());
1417 $feed->init();
1418 $feed->handle_content_type();
1419
1420 if ($feed->error()) :
1421 $ret = new WP_Error('simplepie-error', $feed->error());
1422 else :
1423 $ret = $feed;
1424 endif;
1425 return $ret;
1426 } /* FeedWordPress::fetch () */
1427
1428 function clear_cache () {
1429 global $wpdb;
1430
1431 // Just in case, clear out any old MagpieRSS cache records.
1432 $magpies = $wpdb->query("
1433 DELETE FROM {$wpdb->options}
1434 WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32
1435 ");
1436
1437 // The WordPress SimplePie module stores its cached feeds as
1438 // transient records in the options table. The data itself is
1439 // stored in `_transient_feed_{md5 of url}` and the last-modified
1440 // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
1441 // these records are stored in `_transient_timeout_feed_{md5}`.
1442 // Since the md5 is always 32 characters in length, the
1443 // option_name is always over 32 characters.
1444 $simplepies = $wpdb->query("
1445 DELETE FROM {$wpdb->options}
1446 WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
1447 ");
1448 $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each
1449
1450 return ($magpies + $simplepies);
1451 } /* FeedWordPress::clear_cache () */
1452
1453 function cache_duration () {
1454 $duration = NULL;
1455 if (defined('FEEDWORDPRESS_CACHE_AGE')) :
1456 $duration = FEEDWORDPRESS_CACHE_AGE;
1457 endif;
1458 return $duration;
1459 }
1460 function cache_lifetime ($duration) {
1461 // Check for explicit setting of a lifetime duration
1462 if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
1463 $duration = FEEDWORDPRESS_CACHE_LIFETIME;
1464
1465 // Fall back to the cache freshness duration
1466 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1467 $duration = FEEDWORDPRESS_CACHE_AGE;
1468 endif;
1469
1470 // Fall back to WordPress default
1471 return $duration;
1472 } /* FeedWordPress::cache_lifetime () */
1473
1474 # Utility functions for handling text settings
1475 function negative ($f, $setting) {
1476 $nego = array ('n', 'no', 'f', 'false');
1477 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
1478 }
1479
1480 function affirmative ($f, $setting) {
1481 $affirmo = array ('y', 'yes', 't', 'true', 1);
1482 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
1483 }
1484
1485 # Internal debugging functions
1486 function critical_bug ($varname, $var, $line) {
1487 global $wp_version;
1488
1489 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>';
1490 echo "\n<plaintext>";
1491 echo "Triggered at line # ".$line."\n";
1492 echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
1493 echo "WordPress version: {$wp_version}\n";
1494 echo "PHP version: ".phpversion()."\n";
1495 echo "\n";
1496 echo $varname.": "; var_dump($var); echo "\n";
1497 die;
1498 }
1499
1500 function noncritical_bug ($varname, $var, $line) {
1501 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
1502 FeedWordPress::critical_bug($varname, $var, $line);
1503 endif;
1504 }
1505
1506 function val ($v, $no_newlines = false) {
1507 ob_start();
1508 var_dump($v);
1509 $out = ob_get_contents(); ob_end_clean();
1510
1511 if ($no_newlines) :
1512 $out = preg_replace('/\s+/', " ", $out);
1513 endif;
1514 return $out;
1515 } /* FeedWordPress::val () */
1516
1517 function diagnostic_on ($level) {
1518 $show = get_option('feedwordpress_diagnostics_show', array());
1519 return (in_array($level, $show));
1520 } /* FeedWordPress::diagnostic_on () */
1521
1522 function diagnostic ($level, $out, $persist = NULL, $since = NULL, $mostRecent = NULL) {
1523 global $feedwordpress_admin_footer;
1524
1525 $output = get_option('feedwordpress_diagnostics_output', array());
1526 $dlog = get_option('feedwordpress_diagnostics_log', array());
1527
1528 $diagnostic_nesting = count(explode(":", $level));
1529
1530 if (FeedWordPress::diagnostic_on($level)) :
1531 foreach ($output as $method) :
1532 switch ($method) :
1533 case 'echo' :
1534 echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.$out."</pre></div>";
1535 break;
1536 case 'echo_in_cronjob' :
1537 if (FeedWordPress::update_requested()) :
1538 echo FeedWordPress::log_prefix()." ".$out."\n";
1539 endif;
1540 break;
1541 case 'admin_footer' :
1542 $feedwordpress_admin_footer[] = $out;
1543 break;
1544 case 'error_log' :
1545 error_log(FeedWordPress::log_prefix().' '.$out);
1546 break;
1547 case 'email' :
1548 if (is_null($persist)) :
1549 $sect = 'occurrent';
1550 $hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0);
1551 $line = array("Time" => time(), "Message" => $out);
1552 else :
1553 $sect = 'persistent';
1554 $hook = md5($level."\n".$persist);
1555 $line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent);
1556 endif;
1557
1558 if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
1559 if (!isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif;
1560
1561 $dlog['mesg'][$sect][$hook] = $line;
1562 endswitch;
1563 endforeach;
1564 endif;
1565
1566 update_option('feedwordpress_diagnostics_log', $dlog);
1567 } /* FeedWordPress::diagnostic () */
1568
1569 function email_diagnostic_log () {
1570 $dlog = get_option('feedwordpress_diagnostics_log', array());
1571
1572 $recipients = get_option('feedwordpress_diagnostics_log_recipients', NULL);
1573 if (is_null($recipients)) :
1574 $recipients = FeedWordPressDiagnostic::admin_emails();
1575 endif;
1576
1577 if (isset($dlog['schedule']) and isset($dlog['schedule']['last'])) :
1578 if (time() > ($dlog['schedule']['last'] + $dlog['schedule']['freq'])) :
1579 // No news is good news; only send if
1580 // there are some messages to send.
1581 $body = NULL;
1582 foreach ($dlog['mesg'] as $sect => $mesgs) :
1583 if (count($mesgs) > 0) :
1584 if (is_null($body)) : $body = ''; endif;
1585
1586 $paradigm = reset($mesgs);
1587 $body .= "<h2>".ucfirst($sect)." issues</h2>\n"
1588 ."<table>\n"
1589 ."<thead><tr>\n";
1590 foreach ($paradigm as $col => $value) :
1591 $body .= '<th scope="col">'.$col."</th>\n";
1592 endforeach;
1593 $body .= "</tr></thead>\n"
1594 ."<tbody>\n";
1595
1596 foreach ($mesgs as $line) :
1597 $body .= "<tr>\n";
1598 foreach ($line as $col => $cell) :
1599 if (is_numeric($cell)) :
1600 $cell = date('j-M-y, h:i a', $cell);
1601 endif;
1602 $class = strtolower(preg_replace('/\s+/', '-', $col));
1603 $body .= "<td class=\"$class\">${cell}</td>";
1604 endforeach;
1605 $body .= "</tr>\n";
1606 endforeach;
1607
1608 $body .= "</tbody>\n</table>\n\n";
1609 endif;
1610 endforeach;
1611
1612 if (!is_null($body)) :
1613 $home = feedwordpress_display_url(get_bloginfo('url'));
1614 $subj = $home . " syndication issues for ".date('j-M-y', time());
1615 $agent = 'FeedWordPress '.FEEDWORDPRESS_VERSION;
1616 $body = <<<EOMAIL
1617 <html>
1618 <head>
1619 <title>$subj</title>
1620 <style type="text/css">
1621 body { background-color: white; color: black; }
1622 table { width: 100%; border: 1px solid black; }
1623 table thead tr th { background-color: #ff7700; color: white; border-bottom: 1px solid black; }
1624 table thead tr { border-bottom: 1px solid black; }
1625 table tr { vertical-align: top; }
1626 table .since { width: 20%; }
1627 table .time { width: 20%; }
1628 table .most-recently { width: 20%; }
1629 table .message { width: auto; }
1630 </style>
1631 </head>
1632 <body>
1633 <h1>Syndication Issues encountered by $agent on $home</h1>
1634 $body
1635 </body>
1636 </html>
1637
1638 EOMAIL;
1639 foreach ($recipients as $email) :
1640 add_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
1641 wp_mail($email, $subj, $body);
1642 remove_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
1643 endforeach;
1644 endif;
1645
1646 // Clear the logs
1647 $dlog['mesg']['persistent'] = array();
1648 $dlog['mesg']['occurrent'] = array();
1649
1650 // Set schedule for next update
1651 $dlog['schedule']['last'] = time();
1652 endif;
1653 else :
1654 $dlog['schedule'] = array(
1655 'freq' =>24 /*hr*/ * 60 /*min*/ * 60 /*s*/,
1656 'last' => time(),
1657 );
1658 endif;
1659
1660 update_option('feedwordpress_diagnostics_log', $dlog);
1661 } /* FeedWordPress::email_diagnostic_log () */
1662
1663 function allow_html_mail () {
1664 return 'text/html';
1665 } /* FeedWordPress::allow_html_mail () */
1666
1667 function admin_footer () {
1668 global $feedwordpress_admin_footer;
1669 foreach ($feedwordpress_admin_footer as $line) :
1670 echo '<div><pre>'.$line.'</pre></div>';
1671 endforeach;
1672 } /* FeedWordPress::admin_footer () */
1673
1674 function log_prefix ($date = false) {
1675 $home = get_bloginfo('url');
1676 $prefix = '['.feedwordpress_display_url($home).'] [feedwordpress] ';
1677 if ($date) :
1678 $prefix = "[".date('Y-m-d H:i:s')."]".$prefix;
1679 endif;
1680 return $prefix;
1681 } /* FeedWordPress::log_prefix () */
1682 } // class FeedWordPress
1683
1684 class FeedWordPress_File extends WP_SimplePie_File {
1685 function FeedWordPress_File ($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) {
1686 WP_SimplePie_File::WP_SimplePie_File($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
1687
1688 // SimplePie makes a strongly typed check against integers with
1689 // this, but WordPress puts a string in. Which causes caching
1690 // to break and fall on its ass when SimplePie is getting a 304,
1691 // but doesn't realize it because this member is "304" instead.
1692 $this->status_code = (int) $this->status_code;
1693 }
1694 } /* class FeedWordPress_File () */
1695
1696 class FeedWordPress_Parser extends SimplePie_Parser {
1697 function parse (&$data, $encoding) {
1698 // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
1699 if (strtoupper($encoding) === 'US-ASCII')
1700 {
1701 $this->encoding = 'UTF-8';
1702 }
1703 else
1704 {
1705 $this->encoding = $encoding;
1706 }
1707
1708 // Strip BOM:
1709 // UTF-32 Big Endian BOM
1710 if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
1711 {
1712 $data = substr($data, 4);
1713 }
1714 // UTF-32 Little Endian BOM
1715 elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
1716 {
1717 $data = substr($data, 4);
1718 }
1719 // UTF-16 Big Endian BOM
1720 elseif (substr($data, 0, 2) === "\xFE\xFF")
1721 {
1722 $data = substr($data, 2);
1723 }
1724 // UTF-16 Little Endian BOM
1725 elseif (substr($data, 0, 2) === "\xFF\xFE")
1726 {
1727 $data = substr($data, 2);
1728 }
1729 // UTF-8 BOM
1730 elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
1731 {
1732 $data = substr($data, 3);
1733 }
1734
1735 if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false)
1736 {
1737 $declaration =& new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
1738 if ($declaration->parse())
1739 {
1740 $data = substr($data, $pos + 2);
1741 $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data;
1742 }
1743 else
1744 {
1745 $this->error_string = 'SimplePie bug! Please report this!';
1746 return false;
1747 }
1748 }
1749
1750 $return = true;
1751
1752 static $xml_is_sane = null;
1753 if ($xml_is_sane === null)
1754 {
1755 $parser_check = xml_parser_create();
1756 xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
1757 xml_parser_free($parser_check);
1758 $xml_is_sane = isset($values[0]['value']);
1759 }
1760
1761 // Create the parser
1762 if ($xml_is_sane)
1763 {
1764 $xml = xml_parser_create_ns($this->encoding, $this->separator);
1765 xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
1766 xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
1767 xml_set_object($xml, $this);
1768 xml_set_character_data_handler($xml, 'cdata');
1769 xml_set_element_handler($xml, 'tag_open', 'tag_close');
1770 xml_set_start_namespace_decl_handler($xml, 'start_xmlns');
1771
1772 // Parse!
1773 if (!xml_parse($xml, $data, true))
1774 {
1775 $this->error_code = xml_get_error_code($xml);
1776 $this->error_string = xml_error_string($this->error_code);
1777 $return = false;
1778 }
1779
1780 $this->current_line = xml_get_current_line_number($xml);
1781 $this->current_column = xml_get_current_column_number($xml);
1782 $this->current_byte = xml_get_current_byte_index($xml);
1783 xml_parser_free($xml);
1784
1785 return $return;
1786 }
1787 else
1788 {
1789 libxml_clear_errors();
1790 $xml =& new XMLReader();
1791 $xml->xml($data);
1792 while (@$xml->read())
1793 {
1794 switch ($xml->nodeType)
1795 {
1796
1797 case constant('XMLReader::END_ELEMENT'):
1798 if ($xml->namespaceURI !== '')
1799 {
1800 $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1801 }
1802 else
1803 {
1804 $tagName = $xml->localName;
1805 }
1806 $this->tag_close(null, $tagName);
1807 break;
1808 case constant('XMLReader::ELEMENT'):
1809 $empty = $xml->isEmptyElement;
1810 if ($xml->namespaceURI !== '')
1811 {
1812 $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1813 }
1814 else
1815 {
1816 $tagName = $xml->localName;
1817 }
1818 $attributes = array();
1819 while ($xml->moveToNextAttribute())
1820 {
1821 if ($xml->namespaceURI !== '')
1822 {
1823 $attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1824 }
1825 else
1826 {
1827 $attrName = $xml->localName;
1828 }
1829 $attributes[$attrName] = $xml->value;
1830 }
1831
1832 foreach ($attributes as $attr => $value) :
1833 list($ns, $local) = $this->split_ns($attr);
1834 if ($ns=='http://www.w3.org/2000/xmlns/') :
1835 if ('xmlns' == $local) : $local = false; endif;
1836 $this->start_xmlns(null, $local, $value);
1837 endif;
1838 endforeach;
1839
1840 $this->tag_open(null, $tagName, $attributes);
1841 if ($empty)
1842 {
1843 $this->tag_close(null, $tagName);
1844 }
1845 break;
1846 case constant('XMLReader::TEXT'):
1847
1848 case constant('XMLReader::CDATA'):
1849 $this->cdata(null, $xml->value);
1850 break;
1851 }
1852 }
1853 if ($error = libxml_get_last_error())
1854 {
1855 $this->error_code = $error->code;
1856 $this->error_string = $error->message;
1857 $this->current_line = $error->line;
1858 $this->current_column = $error->column;
1859 return false;
1860 }
1861 else
1862 {
1863 return true;
1864 }
1865 }
1866 } /* FeedWordPress_Parser::parse() */
1867
1868 var $xmlns_stack = array();
1869 var $xmlns_current = array();
1870 function tag_open ($parser, $tag, $attributes) {
1871 $ret = parent::tag_open($parser, $tag, $attributes);
1872 if ($this->current_xhtml_construct < 0) :
1873 $this->data['xmlns'] = $this->xmlns_current;
1874 $this->xmlns_stack[] = $this->xmlns_current;
1875 endif;
1876 return $ret;
1877 }
1878
1879 function tag_close($parser, $tag) {
1880 if ($this->current_xhtml_construct < 0) :
1881 $this->xmlns_current = array_pop($this->xmlns_stack);
1882 endif;
1883 $ret = parent::tag_close($parser, $tag);
1884 return $ret;
1885 }
1886
1887 function start_xmlns ($parser, $prefix, $uri) {
1888 if (!$prefix) :
1889 $prefix = '';
1890 endif;
1891 if ($this->current_xhtml_construct < 0) :
1892 $this->xmlns_current[$prefix] = $uri;
1893 endif;
1894 return true;
1895 } /* FeedWordPress_Parser::start_xmlns() */
1896 }
1897
1898 $feedwordpress_admin_footer = array();
1899
1900 ################################################################################
1901 ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
1902 ################################################################################
1903
1904 class FeedWordPressRPC {
1905 function FeedWordPressRPC () {
1906 add_filter('xmlrpc_methods', array(&$this, 'xmlrpc_methods'));
1907 }
1908
1909 function xmlrpc_methods ($args = array()) {
1910 $args['weblogUpdates.ping'] = array(&$this, 'ping');
1911 $args['feedwordpress.subscribe'] = array(&$this, 'subscribe');
1912 $args['feedwordpress.deactivate'] = array(&$this, 'deactivate');
1913 $args['feedwordpress.delete'] = array(&$this, 'delete');
1914 $args['feedwordpress.nuke'] = array(&$this, 'nuke');
1915 return $args;
1916 }
1917
1918 function ping ($args) {
1919 global $feedwordpress;
1920
1921 $delta = @$feedwordpress->update($args[1]);
1922 if (is_null($delta)):
1923 return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
1924 else:
1925 $mesg = array();
1926 if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
1927 if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
1928
1929 return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
1930 endif;
1931 }
1932
1933 function validate (&$args) {
1934 global $wp_xmlrpc_server;
1935
1936 // First two params are username/password
1937 $username = $wp_xmlrpc_server->escape(array_shift($args));
1938 $password = $wp_xmlrpc_server->escape(array_shift($args));
1939
1940 $ret = array();
1941 if ( !$user = $wp_xmlrpc_server->login($username, $password) ) :
1942 $ret = $wp_xmlrpc_server->error;
1943 elseif (!current_user_can('manage_links')) :
1944 $ret = new IXR_Error(401, 'Sorry, you cannot change the subscription list.');
1945 endif;
1946 return $ret;
1947 }
1948
1949 function subscribe ($args) {
1950 $ret = $this->validate($args);
1951 if (is_array($ret)) : // Success
1952 // The remaining params are feed URLs
1953 foreach ($args as $arg) :
1954 $finder = new FeedFinder($arg, /*verify=*/ false, /*fallbacks=*/ 1);
1955 $feeds = array_values(array_unique($finder->find()));
1956
1957 if (count($feeds) > 0) :
1958 $link_id = FeedWordPress::syndicate_link(
1959 /*title=*/ feedwordpress_display_url($feeds[0]),
1960 /*homepage=*/ $feeds[0],
1961 /*feed=*/ $feeds[0]
1962 );
1963 $ret[] = array(
1964 'added',
1965 $feeds[0],
1966 $arg,
1967 );
1968 else :
1969 $ret[] = array(
1970 'error',
1971 $arg
1972 );
1973 endif;
1974 endforeach;
1975 endif;
1976 return $ret;
1977 } /* FeedWordPressRPC::subscribe () */
1978
1979 function unsubscribe ($method, $args) {
1980 $ret = $this->validate($args);
1981 if (is_array($ret)) : // Success
1982 // The remaining params are feed URLs
1983 foreach ($args as $arg) :
1984 $link_id = FeedWordPress::find_link($arg);
1985
1986 if (!$link_id) :
1987 $link_id = FeedWordPress::find_link($arg, 'link_url');
1988 endif;
1989
1990 if ($link_id) :
1991 $link = new SyndicatedLink($link_id);
1992
1993 $link->{$method}();
1994 $ret[] = array(
1995 'deactivated',
1996 $arg,
1997 );
1998 else :
1999 $ret[] = array(
2000 'error',
2001 $arg,
2002 );
2003 endif;
2004 endforeach;
2005 endif;
2006 return $ret;
2007 } /* FeedWordPress::unsubscribe () */
2008
2009 function deactivate ($args) {
2010 return $this->unsubscribe('deactivate', $args);
2011 } /* FeedWordPressRPC::deactivate () */
2012
2013 function delete ($args) {
2014 return $this->unsubscribe('delete', $args);
2015 } /* FeedWordPressRPC::delete () */
2016
2017 function nuke ($args) {
2018 return $this->unsubscribe('nuke', $args);
2019 } /* FeedWordPressRPC::nuke () */
2020 } /* class FeedWordPressRPC */
2021
2022 // take your best guess at the realname and e-mail, given a string
2023 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2024 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
2025 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
2026 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
2027 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
2028 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
2029
2030 function parse_email_with_realname ($email) {
2031 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
2032 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
2033 $ret['email'] = $matches[1];
2034 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
2035 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2036 $ret['email'] = $matches[4];
2037 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
2038 $ret['name'] = NULL; $ret['email'] = $matches[1];
2039 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
2040 $ret['email'] = NULL;
2041 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2042 else :
2043 $ret['name'] = NULL; $ret['email'] = NULL;
2044 endif;
2045 return $ret;
2046 }
2047
2048