PluginProbe
FeedWordPress / 2011.0512
FeedWordPress v2011.0512
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 2011.0512, at feedwordpress.php

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