PluginProbe
FeedWordPress / 2013.0504
FeedWordPress v2013.0504
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 2013.0504, at feedwordpress.php

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