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

2,279 lines 74.4 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.0502
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 */
11
12 /**
13 * @package FeedWordPress
14 * @version 2013.0502
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.0502');
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
798 // Put in Manual Editing checkbox
799 add_meta_box('feedwordpress-post-controls', __('Syndication'), 'feedwordpress_post_edit_controls', 'post', 'side', 'high');
800
801 add_filter('user_can_richedit', array($feedwordpress, 'user_can_richedit'), 1000, 1);
802
803 if (FeedWordPress::diagnostic_on('syndicated_posts:static_meta_data')) :
804 $GLOBALS['inspectPostMeta'] = new InspectPostMeta;
805 endif;
806 } // function FeedWordPress::postEditControls
807
808 function feedwordpress_post_edit_controls () {
809 global $post;
810
811 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $post->ID);
812 $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
813
814 if (is_syndicated($post->ID)) :
815 ?>
816 <p>This is a syndicated post, which originally appeared at
817 <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>.
818 <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
819
820 <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
821 <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
822 If set, FeedWordPress will not overwrite the changes you make manually
823 to this post, if the syndicated content is updated on the
824 feed.</label></p>
825 <?php
826 else :
827 ?>
828 <p>This post was created locally at this website.</p>
829 <?php
830 endif;
831 } // function feedwordpress_post_edit_controls () */
832
833 function feedwordpress_save_post_edit_controls ( $post_id ) {
834 global $post;
835
836 if (!isset($_POST['feedwordpress_noncename']) or !wp_verify_nonce($_POST['feedwordpress_noncename'], plugin_basename(__FILE__))) :
837 return $post_id;
838 endif;
839
840 // Verify if this is an auto save routine. If it is our form has
841 // not been submitted, so we don't want to do anything.
842 if ( defined('DOING_AUTOSAVE') and DOING_AUTOSAVE ) :
843 return $post_id;
844 endif;
845
846 // Check permissions
847 if ( !current_user_can( 'edit_'.$_POST['post_type'], $post_id) ) :
848 return $post_id;
849 endif;
850
851 // OK, we're golden. Now let's save some data.
852 if (isset($_POST['freeze_updates'])) :
853 update_post_meta($post_id, '_syndication_freeze_updates', $_POST['freeze_updates']);
854 $ret = $_POST['freeze_updates'];
855 else :
856 delete_post_meta($post_id, '_syndication_freeze_updates');
857 $ret = NULL;
858 endif;
859
860 return $ret;
861 } // function feedwordpress_save_edit_controls
862
863 ################################################################################
864 ## class FeedWordPress #########################################################
865 ################################################################################
866
867 // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
868 class FeedWordPress {
869 var $strip_attrs = array (
870 array('[a-z]+', 'style'),
871 array('[a-z]+', 'target'),
872 );
873 var $uri_attrs = array (
874 array('a', 'href'),
875 array('applet', 'codebase'),
876 array('area', 'href'),
877 array('blockquote', 'cite'),
878 array('body', 'background'),
879 array('del', 'cite'),
880 array('form', 'action'),
881 array('frame', 'longdesc'),
882 array('frame', 'src'),
883 array('iframe', 'longdesc'),
884 array('iframe', 'src'),
885 array('head', 'profile'),
886 array('img', 'longdesc'),
887 array('img', 'src'),
888 array('img', 'usemap'),
889 array('input', 'src'),
890 array('input', 'usemap'),
891 array('ins', 'cite'),
892 array('link', 'href'),
893 array('object', 'classid'),
894 array('object', 'codebase'),
895 array('object', 'data'),
896 array('object', 'usemap'),
897 array('q', 'cite'),
898 array('script', 'src')
899 );
900
901 var $feeds = NULL;
902 var $feedurls = NULL;
903 var $httpauth = NULL;
904
905 # function FeedWordPress (): Contructor; retrieve a list of feeds
906 function FeedWordPress () {
907 $this->feeds = array ();
908 $this->feedurls = array();
909 $links = FeedWordPress::syndicated_links();
910 if ($links): foreach ($links as $link):
911 $id = intval($link->link_id);
912 $url = $link->link_rss;
913
914 // Store for later reference.
915 $this->feeds[$id] = $id;
916 if (strlen($url) > 0) :
917 $this->feedurls[$url] = $id;
918 endif;
919 endforeach; endif;
920
921 $this->httpauth = new FeedWordPressHTTPAuthenticator;
922 } // FeedWordPress::FeedWordPress ()
923
924 function subscribed ($id) {
925 return (isset($this->feedurls[$id]) or isset($this->feeds[$id]));
926 } /* FeedWordPress::subscribed () */
927
928 function subscription ($which) {
929 $sub = NULL;
930
931 if (is_string($which) and isset($this->feedurls[$which])) :
932 $which = $this->feedurls[$which];
933 endif;
934
935 if (isset($this->feeds[$which])) :
936 $sub = $this->feeds[$which];
937 endif;
938
939 // If it's not in the in-memory cache already, try to load it from DB.
940 // This is necessary to fill requests for subscriptions that we don't
941 // cache in memory, e.g. for deactivated feeds.
942 if (is_null($sub)) :
943 $sub = get_bookmark($which);
944 endif;
945
946 // Load 'er up if you haven't already.
947 if (!is_null($sub) and !($sub InstanceOf SyndicatedLink)) :
948 $link = new SyndicatedLink($sub);
949 $this->feeds[$which] = $link;
950 $sub = $link;
951 endif;
952
953 return $sub;
954 } /* FeedWordPress::subscription () */
955
956 # function update (): polls for updates on one or more Contributor feeds
957 #
958 # Arguments:
959 # ----------
960 # * $uri (string): either the URI of the feed to poll, the URI of the
961 # (human-readable) website whose feed you want to poll, or NULL.
962 #
963 # If $uri is NULL, then FeedWordPress will poll any feeds that are
964 # ready for polling. It will not poll feeds that are marked as
965 # "Invisible" Links (signifying that the subscription has been
966 # de-activated), or feeds that are not yet stale according to their
967 # TTL setting (which is either set in the feed, or else set
968 # randomly within a window of 30 minutes - 2 hours).
969 #
970 # Returns:
971 # --------
972 # * Normally returns an associative array, with 'new' => the number
973 # of new posts added during the update, and 'updated' => the number
974 # of old posts that were updated during the update. If both numbers
975 # are zero, there was no change since the last poll on that URI.
976 #
977 # * Returns NULL if URI it was passed was not a URI that this
978 # installation of FeedWordPress syndicates.
979 #
980 # Effects:
981 # --------
982 # * One or more feeds are polled for updates
983 #
984 # * If the feed Link does not have a hardcoded name set, its Link
985 # Name is synchronized with the feed's title element
986 #
987 # * If the feed Link does not have a hardcoded URI set, its Link URI
988 # is synchronized with the feed's human-readable link element
989 #
990 # * If the feed Link does not have a hardcoded description set, its
991 # Link Description is synchronized with the feed's description,
992 # tagline, or subtitle element.
993 #
994 # * The time of polling is recorded in the feed's settings, and the
995 # TTL (time until the feed is next available for polling) is set
996 # either from the feed (if it is supplied in the ttl or syndication
997 # module elements) or else from a randomly-generated time window
998 # (between 30 minutes and 2 hours).
999 #
1000 # * New posts from the polled feed are added to the WordPress store.
1001 #
1002 # * Updates to existing posts since the last poll are mirrored in the
1003 # WordPress store.
1004 #
1005 function update ($uri = null, $crash_ts = null) {
1006 global $wpdb;
1007
1008 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
1009 return NULL;
1010 endif;
1011
1012 if (!is_null($uri) and $uri != '*') :
1013 $uri = trim($uri);
1014 else : // Update all
1015 if ($this->update_hooked) :
1016 $diag = $this->update_hooked;
1017 else :
1018 $diag = 'Initiating a MANUAL check-in on the update schedule at '.date('r', time());
1019 endif;
1020 $this->diagnostic('update_schedule:check', $diag);
1021
1022 update_option('feedwordpress_last_update_all', time());
1023 endif;
1024
1025 do_action('feedwordpress_update', $uri);
1026
1027 if (is_null($crash_ts)) :
1028 $crash_ts = $this->crash_ts();
1029 endif;
1030
1031 // Randomize order for load balancing purposes
1032 $feed_set = array_keys($this->feeds);
1033 shuffle($feed_set);
1034
1035 $updateWindow = (int) get_option('feedwordpress_update_window', DEFAULT_UPDATE_PERIOD) * 60 /* sec/min */;
1036 $interval = (int) get_option('feedwordpress_freshness', FEEDWORDPRESS_FRESHNESS_INTERVAL);
1037 $portion = max(
1038 (int) ceil(count($feed_set) / ($updateWindow / $interval)),
1039 10
1040 );
1041
1042 $max_polls = apply_filters('feedwordpress_polls_per_update', get_option(
1043 'feedwordpress_polls_per_update', $portion
1044 ), $uri);
1045
1046 $feed_set = apply_filters('feedwordpress_update_feeds', $feed_set, $uri);
1047
1048
1049 // Loop through and check for new posts
1050 $delta = NULL; $remaining = $max_polls;
1051 foreach ($feed_set as $feed_id) :
1052
1053 $feed = $this->subscription($feed_id);
1054
1055 // Has this process overstayed its welcome?
1056 if (
1057 // Over time limit?
1058 (!is_null($crash_ts) and (time() > $crash_ts))
1059
1060 // Over feed count?
1061 or ($remaining == 0)
1062 ) :
1063 break;
1064 endif;
1065
1066 $pinged_that = (is_null($uri) or ($uri=='*') or in_array($uri, array($feed->uri(), $feed->homepage())));
1067
1068 if (!is_null($uri)) : // A site-specific ping always updates
1069 $timely = true;
1070 else :
1071 $timely = $feed->stale();
1072 endif;
1073
1074 if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
1075 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
1076 endif;
1077
1078 if ($pinged_that and $timely) :
1079 $remaining = $remaining - 1;
1080
1081 do_action('feedwordpress_check_feed', $feed->settings);
1082 $start_ts = time();
1083 $added = $feed->poll($crash_ts);
1084 do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
1085
1086 if (is_array($added)) : // Success
1087 if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1088 if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1089 endif;
1090 endif;
1091 endforeach;
1092
1093 do_action('feedwordpress_update_complete', $delta);
1094
1095 return $delta;
1096 }
1097
1098 function crash_ts ($default = NULL) {
1099 $crash_dt = (int) get_option('feedwordpress_update_time_limit', 0);
1100 if ($crash_dt > 0) :
1101 $crash_ts = time() + $crash_dt;
1102 else :
1103 $crash_ts = $default;
1104 endif;
1105 return $crash_ts;
1106 }
1107
1108 function secret_key () {
1109 $secret = get_option('feedwordpress_secret_key', false);
1110 if (!$secret) : // Generate random key.
1111 $secret = substr(md5(uniqid(microtime())), 0, 6);
1112 update_option('feedwordpress_secret_key', $secret);
1113 endif;
1114 return $secret;
1115 }
1116
1117 function has_secret () {
1118 return ($this->by_request('feedwordpress_key', $this->secret_key()));
1119 }
1120
1121 // Utility function so I don't have to repeat myself w/ 1,000,003 isset()'s
1122 function by_request ($param, $eq = NULL) {
1123 $match = false;
1124 if (isset($_REQUEST[$param])) :
1125 $match = (is_null($eq) ? $_REQUEST[$param] : ($eq==$_REQUEST[$param]));
1126 endif;
1127 return $match;
1128 }
1129
1130 var $update_hooked = NULL;
1131 function automatic_update_hook ($params = array()) {
1132 $params = wp_parse_args($params, array( // Defaults
1133 'setting only' => false,
1134 ));
1135 $hook = get_option('feedwordpress_automatic_updates', NULL);
1136 $method = 'FeedWordPress option';
1137
1138 // Allow for forced behavior in testing.
1139 if (
1140 !$params['setting only']
1141 and $this->has_secret()
1142 and $this->by_request('automatic_update')
1143 ) :
1144 $hook = $_REQUEST['automatic_update'];
1145 $method = 'URL parameter';
1146 endif;
1147
1148 $exact = $hook; // Before munging
1149
1150 if (!!$hook) :
1151 if ($hook != 'init') : // Constrain values.
1152 $hook = 'shutdown';
1153 endif;
1154 endif;
1155
1156 if ($hook) :
1157 $this->update_hooked = "Initiating an AUTOMATIC CHECK FOR UPDATES ON PAGE LOAD ".$hook." due to ".$method." = ".trim($this->val($exact));
1158 endif;
1159
1160 return $hook;
1161 }
1162 function last_update_all () {
1163 $last = get_option('feedwordpress_last_update_all');
1164 if ($this->has_secret() and $this->by_request('automatic_update')) :
1165 $last = 1; // A long, long time ago.
1166 elseif ($this->has_secret() and $this->by_request('last_update_all')) :
1167 $last = $_REQUEST['last_update_all'];
1168 endif;
1169 return $last;
1170 }
1171 function force_update_all () {
1172 return ($this->has_secret() and $this->by_request('force_update_feeds'));
1173 }
1174
1175 function stale () {
1176 if (!is_null($this->automatic_update_hook())) :
1177 // Do our best to avoid possible simultaneous
1178 // updates by getting up-to-the-minute settings.
1179
1180 $last = $this->last_update_all();
1181
1182 // If we haven't updated all yet, give it a time window
1183 if (false === $last) :
1184 $ret = false;
1185 update_option('feedwordpress_last_update_all', time());
1186
1187 // Otherwise, check against freshness interval
1188 elseif (is_numeric($last)) : // Expect a timestamp
1189 $freshness = get_option('feedwordpress_freshness');
1190 if (false === $freshness) : // Use default
1191 $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
1192 endif;
1193 $ret = ( (time() - $last) > $freshness );
1194
1195 // This should never happen.
1196 else :
1197 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__, __FILE__);
1198 endif;
1199
1200 else :
1201 $ret = false;
1202 endif;
1203 return $ret;
1204 } // FeedWordPress::stale()
1205
1206 static function admin_init () {
1207 // WordPress 3.5+ compat: the WP devs are in the midst of removing Links from the WordPress core. Eventually we'll have to deal
1208 // with the possible disappearance of the wp_links table as a whole; but in the meantime, we just need to turn on the interface
1209 // element to avoid problems with user capabilities that presume the presence of the Links Manager in the admin UI.
1210 if (!intval(get_option('link_manager_enabled', false))) :
1211 update_option('link_manager_enabled', true);
1212 endif;
1213 } /* FeedWordPress::admin_init() */
1214
1215 function admin_api () {
1216 // This sucks, but WordPress doesn't give us any other way to
1217 // easily invoke a permanent-delete from a plugged in post
1218 // actions link. So we create a magic parameter, and when this
1219 // magic parameter is activated, the WordPress trashcan is
1220 // temporarily de-activated.
1221
1222 if (MyPHP::request('fwp_post_delete')=='nuke') :
1223 // Get post ID #
1224 $post_id = MyPHP::request('post');
1225 if (!$post_id) :
1226 $post_id = MyPHP::request('post_ID');
1227 endif;
1228
1229 // Make sure we've got the right nonce and all that.
1230 check_admin_referer('delete-post_' . $post_id);
1231
1232 // If so, disable the trashcan.
1233 define('EMPTY_TRASH_DAYS', 0);
1234 endif;
1235
1236 }
1237
1238 function init () {
1239 global $fwp_path;
1240
1241 // If this is a FeedWordPress admin page, queue up scripts for AJAX
1242 // functions that FWP uses. If it is a display page or a non-FWP admin
1243 // page, don't.
1244 wp_register_style('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.css');
1245 if (FeedWordPressSettingsUI::is_admin()) :
1246 // For JavaScript that needs to be generated dynamically
1247 add_action('admin_print_scripts', array('FeedWordPressSettingsUI', 'admin_scripts'));
1248
1249 // For CSS that needs to be generated dynamically.
1250 add_action('admin_print_styles', array('FeedWordPressSettingsUI', 'admin_styles'));
1251
1252 wp_enqueue_style('dashboard');
1253 wp_enqueue_style('feedwordpress-elements');
1254
1255 /*if (function_exists('wp_admin_css')) :
1256 wp_admin_css('css/dashboard');
1257 endif;*/
1258 endif;
1259
1260 // This is a special post status for hiding posts that have expired
1261 register_post_status('fwpretired', array(
1262 'label' => _x('Retired', 'post'),
1263 'label_count' => _n_noop('Retired <span class="count">(%s)</span>', 'Retired <span class="count">(%s)</span>'),
1264 'exclude_from_search' => true,
1265 'public' => false,
1266 'publicly_queryable' => false,
1267 'show_in_admin_all_list' => false,
1268 'show_in_admin_status_list' => true,
1269 ));
1270 add_action(
1271 /*hook=*/ 'template_redirect',
1272 /*function=*/ array($this, 'redirect_retired'),
1273 /*priority=*/ -100
1274 );
1275
1276 add_action('wp_ajax_fwp_feeds', array($this, 'fwp_feeds'));
1277 add_action('wp_ajax_fwp_feedcontents', array($this, 'fwp_feedcontents'));
1278 add_action('wp_ajax_fwp_xpathtest', array($this, 'fwp_xpathtest'));
1279
1280 $this->clear_cache_magic_url();
1281 $this->update_magic_url();
1282 } /* FeedWordPress::init() */
1283
1284 function fwp_feeds () {
1285 $feeds = array();
1286 $feed_ids = $this->feeds;
1287
1288 foreach ($feed_ids as $id) :
1289 $sub = $this->subscription($id);
1290 $feeds[] = array(
1291 "id" => $id,
1292 "url" => $sub->uri(),
1293 "name" => $sub->name(/*fromFeed=*/ false),
1294 );
1295 endforeach;
1296
1297 header("Content-Type: application/json");
1298 echo json_encode($feeds);
1299 exit;
1300 }
1301
1302 function fwp_feedcontents () {
1303 $feed_id = MyPHP::request('feed_id');
1304
1305 // Let's load up some data from the feed . . .
1306 $feed = $this->subscription($feed_id);
1307 $posts = $feed->live_posts();
1308
1309 if (is_wp_error($posts)) :
1310 header("HTTP/1.1 502 Bad Gateway");
1311 $result = $posts;
1312 else :
1313 $result = array();
1314
1315 foreach ($posts as $post) :
1316 $p = new SyndicatedPost($post, $feed);
1317
1318 $result[] = array(
1319 "post_title" => $p->entry->get_title(),
1320 "post_link" => $p->permalink(),
1321 "guid" => $p->guid(),
1322 "post_date" => $p->published(),
1323 );
1324 endforeach;
1325 endif;
1326
1327 header("Content-Type: application/json");
1328
1329 echo json_encode($result);
1330
1331 // This is an AJAX request, so close it out thus.
1332 die;
1333 } /* FeedWordPress::fwp_feedcontents () */
1334
1335 function fwp_xpathtest () {
1336 $xpath = MyPHP::request('xpath');
1337 $feed_id = MyPHP::request('feed_id');
1338 $post_id = MyPHP::request('post_id');
1339
1340 $expr = new FeedWordPressParsedPostMeta($xpath);
1341
1342 // Let's load up some data from the feed . . .
1343 $feed = $this->subscription($feed_id);
1344 $posts = $feed->live_posts();
1345
1346 if (!is_wp_error($posts)) :
1347 if (strlen($post_id) == 0) :
1348 $post = $posts[0];
1349 else :
1350 $post = null;
1351
1352 foreach ($posts as $p) :
1353 if ($p->get_id() == $post_id) :
1354 $post = $p;
1355 endif;
1356 endforeach;
1357 endif;
1358
1359 $post = new SyndicatedPost($post, $feed);
1360 $meta = $expr->do_substitutions($post);
1361
1362 $result = array(
1363 "post_title" => $post->entry->get_title(),
1364 "post_link" => $post->permalink(),
1365 "guid" => $post->guid(),
1366 "expression" => $xpath,
1367 "results" => $meta
1368 );
1369 else :
1370 $result = array(
1371 "expression" => $xpath,
1372 "feed_id" => $feed_id,
1373 "post_id" => $post_id,
1374 "results" => $posts
1375 );
1376
1377 header("HTTP/1.1 503 Bad Gateway");
1378 endif;
1379
1380 header("Content-Type: application/json");
1381
1382 echo json_encode($result);
1383
1384 // This is an AJAX request, so close it out thus.
1385 die;
1386 } /* FeedWordPress::fwp_xpathtest () */
1387
1388 function redirect_retired () {
1389 global $wp_query;
1390 if (is_singular()) :
1391 if ('fwpretired'==$wp_query->post->post_status) :
1392 do_action('feedwordpress_redirect_retired', $wp_query->post);
1393
1394 if (!($template = get_404_template())) :
1395 $template = get_index_template();
1396 endif;
1397 if ($template = apply_filters('template_include', $template)) :
1398 header("HTTP/1.1 410 Gone");
1399 include($template);
1400 endif;
1401 exit;
1402 endif;
1403 endif;
1404 }
1405
1406 function row_actions ($actions, $post) {
1407 if (is_syndicated($post->ID)) :
1408 $link = get_delete_post_link($post->ID, '', true);
1409 $link = MyPHP::url($link, array("fwp_post_delete" => "nuke"));
1410
1411 $caption = 'Erase the record of this post (will be re-syndicated if it still appears on the feed).';
1412 $linktext = 'Erase/Resyndicate';
1413
1414 $keys = array_keys($actions);
1415 $links = array();
1416 foreach ($keys as $key) :
1417 $links[$key] = $actions[$key];
1418
1419 if ('trash'==$key) :
1420 $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>";
1421
1422 // Placeholder.
1423 $links['delete'] = '';
1424 endif;
1425 endforeach;
1426
1427 $links['delete'] = '<a class="submitdelete" title="'.esc_attr(__($caption)).'" href="' . $link . '">' . __($linktext) . '</a>';
1428
1429 $actions = $links;
1430 endif;
1431 return $actions;
1432 }
1433
1434 function dashboard_setup () {
1435 $see_it = FeedWordPress::menu_cap();
1436
1437 if (current_user_can($see_it)) :
1438 // Get the stylesheet
1439 wp_enqueue_style('feedwordpress-elements');
1440
1441 $widget_id = 'feedwordpress_dashboard';
1442 $widget_name = __('Syndicated Sources');
1443 $column = 'side';
1444 $priority = 'core';
1445
1446 // I would love to use wp_add_dashboard_widget() here and save
1447 // myself some trouble. But WP 3 does not yet have any way to
1448 // push a dashboard widget onto the side, or to give it a default
1449 // location.
1450 add_meta_box(
1451 /*id=*/ $widget_id,
1452 /*title=*/ $widget_name,
1453 /*callback=*/ array($this, 'dashboard'),
1454 /*page=*/ 'dashboard',
1455 /*context=*/ $column,
1456 /*priority=*/ $priority
1457 );
1458 /*control_callback= array($this, 'dashboard_control') */
1459
1460 // This is kind of rude, I know, but the dashboard widget isn't
1461 // worth much if users don't know that it exists, and I don't
1462 // know of any better way to reorder the boxen.
1463 //
1464 // Gleefully ripped off of codex.wordpress.org/Dashboard_Widgets_API
1465
1466 // Globalize the metaboxes array, this holds all the widgets for wp-admin
1467 global $wp_meta_boxes;
1468
1469 // Get the regular dashboard widgets array
1470 // (which has our new widget already but at the end)
1471
1472 $normal_dashboard = $wp_meta_boxes['dashboard'][$column][$priority];
1473
1474 // Backup and delete our new dashbaord widget from the end of the array
1475 if (isset($normal_dashboard[$widget_id])) :
1476 $backup = array();
1477 $backup[$widget_id] = $normal_dashboard[$widget_id];
1478 unset($normal_dashboard[$widget_id]);
1479
1480 // Merge the two arrays together so our widget is at the
1481 // beginning
1482 $sorted_dashboard = array_merge($backup, $normal_dashboard);
1483
1484 // Save the sorted array back into the original metaboxes
1485 $wp_meta_boxes['dashboard'][$column][$priority] = $sorted_dashboard;
1486 endif;
1487 endif;
1488 } /* FeedWordPress::dashboard_setup () */
1489
1490 function dashboard () {
1491 $syndicationPage = new FeedWordPressSyndicationPage(dirname(__FILE__).'/syndication.php');
1492 $syndicationPage->dashboard_box($syndicationPage);
1493 } /* FeedWordPress::dashboard () */
1494
1495 function user_can_richedit ($rich_edit) {
1496
1497 $post = new FeedWordPressLocalPost;
1498
1499 if (!$post->is_exposed_to_formatting_filters()) :
1500 // Disable visual editor and only allow operations
1501 // directly on HTML if post is bypassing fmt filters
1502 $rich_edit = false;
1503 endif;
1504
1505 return $rich_edit;
1506
1507 } /* FeedWordPress::user_can_richedit () */
1508
1509 function update_magic_url () {
1510 global $wpdb;
1511
1512 // Explicit update request in the HTTP request (e.g. from a cron job)
1513 if ($this->update_requested()) :
1514 $this->update_hooked = "Initiating a CRON JOB CHECK-IN ON UPDATE SCHEDULE due to URL parameter = ".trim($this->val($_REQUEST['update_feedwordpress']));
1515
1516 $this->update($this->update_requested_url());
1517
1518 if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
1519 $mysqlTime = 0.0;
1520 $byTime = array();
1521 foreach ($wpdb->queries as $query) :
1522 $time = $query[1] * 1000000.0;
1523 $mysqlTime += $query[1];
1524 if (!isset($byTime[$time])) : $byTime[$time] = array(); endif;
1525 $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2];
1526 endforeach;
1527 krsort($byTime);
1528
1529 foreach ($byTime as $time => $querySet) :
1530 foreach ($querySet as $query) :
1531 print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n";
1532 endforeach;
1533 endforeach;
1534 echo $this->log_prefix()."$wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds.";
1535 endif;
1536
1537 debug_out_feedwordpress_footer();
1538
1539 // Magic URL should return nothing but a 200 OK header packet
1540 // when successful.
1541 exit;
1542 endif;
1543 } /* FeedWordPress::magic_update_url () */
1544
1545 function clear_cache_magic_url () {
1546 if ($this->clear_cache_requested()) :
1547 $this->clear_cache();
1548 endif;
1549 } /* FeedWordPress::clear_cache_magic_url() */
1550
1551 function clear_cache_requested () {
1552 return MyPHP::request('clear_cache');
1553 } /* FeedWordPress::clear_cache_requested() */
1554
1555 function update_requested () {
1556 return FeedWordPress::by_request('update_feedwordpress');
1557 } // FeedWordPress::update_requested()
1558
1559 function update_requested_url () {
1560 $ret = null;
1561
1562 if (($_REQUEST['update_feedwordpress']=='*')
1563 or (preg_match('|^http://.*|i', $_REQUEST['update_feedwordpress']))) :
1564 $ret = $_REQUEST['update_feedwordpress'];
1565 endif;
1566
1567 return $ret;
1568 } // FeedWordPress::update_requested_url()
1569
1570 function auto_update () {
1571 if ($this->stale()) :
1572 $this->update();
1573 endif;
1574 } /* FeedWordPress::auto_update () */
1575
1576 function find_link ($uri, $field = 'link_rss') {
1577 global $wpdb;
1578
1579 $unslashed = untrailingslashit($uri);
1580 $slashed = trailingslashit($uri);
1581 $link_id = $wpdb->get_var($wpdb->prepare("
1582 SELECT link_id FROM $wpdb->links WHERE $field IN ('%s', '%s')
1583 LIMIT 1", $unslashed, $slashed
1584 ));
1585
1586 return $link_id;
1587 } /* FeedWordPress::find_link () */
1588
1589 function syndicate_link ($name, $uri, $rss) {
1590 // Get the category ID#
1591 $cat_id = FeedWordPress::link_category_id();
1592 if (!is_wp_error($cat_id)) :
1593 $link_category = array($cat_id);
1594 else :
1595 $link_category = array();
1596 endif;
1597
1598 // WordPress gets cranky if there's no homepage URI
1599 if (!is_string($uri) or strlen($uri)<1) : $uri = $rss; endif;
1600
1601 // Check if this feed URL is already being syndicated.
1602 $link_id = wp_insert_link(array(
1603 "link_id" => FeedWordPress::find_link($rss), // insert if nothing was found; else update
1604 "link_rss" => $rss,
1605 "link_name" => $name,
1606 "link_url" => $uri,
1607 "link_category" => $link_category,
1608 "link_visible" => 'Y', // reactivate if inactivated
1609 ));
1610
1611 return $link_id;
1612 } /* function FeedWordPress::syndicate_link() */
1613
1614 /*static*/ function syndicated_status ($what, $default) {
1615 $ret = get_option("feedwordpress_syndicated_{$what}_status");
1616 if (!$ret) :
1617 $ret = $default;
1618 endif;
1619 return $ret;
1620 } /* FeedWordPress::syndicated_status() */
1621
1622 function on_unfamiliar ($what = 'author') {
1623 switch ($what) :
1624 case 'category' : $suffix = ':category'; break;
1625 case 'post_tag' : $suffix = ':post_tag'; break;
1626 default: $suffix = '';
1627 endswitch;
1628
1629 return get_option('feedwordpress_unfamiliar_'.$what, 'create'.$suffix);
1630 } // function FeedWordPress::on_unfamiliar()
1631
1632 function null_email_set () {
1633 $base = get_option('feedwordpress_null_email_set');
1634
1635 if ($base===false) :
1636 $ret = array('noreply@blogger.com'); // default
1637 else :
1638 $ret = array_map('strtolower',
1639 array_map('trim', explode("\n", $base)));
1640 endif;
1641 $ret = apply_filters('syndicated_item_author_null_email_set', $ret);
1642 return $ret;
1643
1644 } /* FeedWordPress::null_email_set () */
1645
1646 function is_null_email ($email) {
1647 $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set());
1648 $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email);
1649 return $ret;
1650 } /* FeedWordPress::is_null_email () */
1651
1652 function use_aggregator_source_data () {
1653 $ret = get_option('feedwordpress_use_aggregator_source_data');
1654 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1655 }
1656
1657 /**
1658 * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1659 * should rewrite permalinks for syndicated items to reflect their
1660 * original location.
1661 *
1662 * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1663 */
1664 /*static*/ function munge_permalinks () {
1665 return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1666 } /* FeedWordPress::munge_permalinks() */
1667
1668 function syndicated_links ($args = array()) {
1669 $contributors = FeedWordPress::link_category_id();
1670 if (!is_wp_error($contributors)) :
1671 $links = get_bookmarks(array_merge(
1672 array("category" => $contributors),
1673 $args
1674 ));
1675 else :
1676 $links = array();
1677 endif;
1678
1679 return $links;
1680 } // function FeedWordPress::syndicated_links()
1681
1682 function link_category_id () {
1683 global $wpdb, $wp_db_version;
1684
1685 $cat_id = get_option('feedwordpress_cat_id');
1686
1687 // If we don't yet have the category ID stored, search by name
1688 if (!$cat_id) :
1689 $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY);
1690
1691 if ($cat_id) :
1692 // We found it; let's stamp it.
1693 update_option('feedwordpress_cat_id', $cat_id);
1694 endif;
1695
1696 // If we *do* have the category ID stored, verify that it exists
1697 else :
1698 $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id');
1699 endif;
1700
1701 // If we could not find an appropriate link category,
1702 // make a new one for ourselves.
1703 if (!$cat_id) :
1704 $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY);
1705 if (!is_wp_error($cat_id)) :
1706 // Stamp it
1707 update_option('feedwordpress_cat_id', $cat_id);
1708 endif;
1709 endif;
1710
1711 return $cat_id;
1712 } // function FeedWordPress::link_category_id()
1713
1714 # Upgrades and maintenance...
1715 function needs_upgrade () {
1716 global $wpdb;
1717 $fwp_db_version = get_option('feedwordpress_version');
1718 $ret = false; // innocent until proven guilty
1719 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
1720 // This is an older version or a fresh install. Does it
1721 // require a database upgrade or database initialization?
1722 if ($fwp_db_version <= 0.96) :
1723 // Yes. Check to see whether this is a fresh install or an upgrade.
1724 $syn = $wpdb->get_col("
1725 SELECT post_id
1726 FROM $wpdb->postmeta
1727 WHERE meta_key = 'syndication_feed'
1728 ");
1729 if (count($syn) > 0) : // contains at least one syndicated post
1730 $ret = true;
1731 else : // fresh install; brand it as ours
1732 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1733 endif;
1734 elseif ($fwp_db_version < 2009.0707) :
1735 // We need to clear out any busted AJAX crap
1736 $wpdb->query("
1737 DELETE FROM $wpdb->usermeta
1738 WHERE LOCATE('feedwordpress', meta_key)
1739 AND LOCATE('box', meta_key);
1740 ");
1741
1742 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1743 elseif ($fwp_db_version < 2010.0814) :
1744 // Change in terminology.
1745 if (get_option('feedwordpress_unfamiliar_category', 'create')=='default') :
1746 update_option('feedwordpress_unfamiliar_category', 'null');
1747 endif;
1748 foreach (FeedWordPress::syndicated_links() as $link) :
1749 $sub = new SyndicatedLink($link);
1750
1751 $remap_uf = array(
1752 'default' => 'null',
1753 'filter' => 'null',
1754 'create' => 'create:category',
1755 'tag' => 'create:post_tag'
1756 );
1757 if (isset($sub->settings['unfamiliar category'])) :
1758 if ($sub->settings['unfamiliar category']=='filter') :
1759 $sub->settings['match/filter'] = array('category');
1760 endif;
1761 foreach ($remap_uf as $from => $to) :
1762 if ($sub->settings['unfamiliar category']==$from) :
1763 $sub->settings['unfamiliar category'] = $to;
1764 endif;
1765 endforeach;
1766 endif;
1767
1768 if (isset($sub->settings['add global categories'])) :
1769 $sub->settings['add/category'] = $sub->settings['add global categories'];
1770 unset($sub->settings['add global categories']);
1771 endif;
1772
1773 $sub->save_settings(/*reload=*/ true);
1774 endforeach;
1775 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1776 else :
1777 // No. Just brand it with the new version.
1778 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1779 endif;
1780 endif;
1781 return $ret;
1782 }
1783
1784 function upgrade_database ($from = NULL) {
1785 global $wpdb;
1786
1787 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1788
1789 switch ($from) :
1790 case 0.96:
1791 // Dropping legacy upgrade code. If anyone is still
1792 // using 0.96 and just now decided to upgrade, well, I'm
1793 // sorry about that. You'll just have to cope with a few
1794 // duplicate posts.
1795
1796 // Mark the upgrade as successful.
1797 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1798 endswitch;
1799 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1800 } /* FeedWordPress::upgrade_database() */
1801
1802 function has_guid_index () {
1803 global $wpdb;
1804
1805 $found = false; // Guilty until proven innocent.
1806
1807 $results = $wpdb->get_results("
1808 SHOW INDEXES FROM {$wpdb->posts}
1809 ");
1810 if ($results) :
1811 foreach ($results as $index) :
1812 if (isset($index->Column_name)
1813 and ('guid' == $index->Column_name)) :
1814 $found = true;
1815 endif;
1816 endforeach;
1817 endif;
1818 return $found;
1819 } /* FeedWordPress::has_guid_index () */
1820
1821 function create_guid_index () {
1822 global $wpdb;
1823
1824 $wpdb->query("
1825 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
1826 ");
1827 } /* FeedWordPress::create_guid_index () */
1828
1829 function remove_guid_index () {
1830 global $wpdb;
1831
1832 $wpdb->query("
1833 DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
1834 ");
1835 }
1836
1837 /*static*/ function fetch_timeout () {
1838 return apply_filters(
1839 'feedwordpress_fetch_timeout',
1840 intval(get_option('feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT))
1841 );
1842 }
1843
1844 /*static*/ function fetch ($url, $params = array()) {
1845 if (is_wp_error($url)) :
1846 // Let's bounce.
1847 return $url;
1848 endif;
1849
1850 $force_feed = true; // Default
1851
1852 // 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' =>
1853 $timeout = FeedWordPress::fetch_timeout();
1854
1855 if (!is_array($params)) :
1856 $force_feed = $params;
1857 else : // Parameter array
1858 $args = wp_parse_args(array(
1859 'force_feed' => $force_feed,
1860 'timeout' => $timeout
1861 ), $params);
1862
1863 extract($args);
1864 endif;
1865 $timeout = intval($timeout);
1866
1867 $pie_class = apply_filters('feedwordpress_simplepie_class', 'FeedWordPie');
1868 $cache_class = apply_filters('feedwordpress_cache_class', 'WP_Feed_Cache');
1869 $file_class = apply_filters('feedwordpress_file_class', 'FeedWordPress_File');
1870 $parser_class = apply_filters('feedwordpress_parser_class', 'FeedWordPress_Parser');
1871 $item_class = apply_filters('feedwordpress_item_class', 'FeedWordPie_Item');
1872 $sniffer_class = apply_filters('feedwordpress_sniffer_class', 'FeedWordPress_Content_Type_Sniffer');
1873
1874 $feed = new $pie_class;
1875 $feed->set_feed_url($url);
1876 $feed->set_cache_class($cache_class);
1877 $feed->set_timeout($timeout);
1878
1879 $feed->set_content_type_sniffer_class($sniffer_class);
1880 $feed->set_file_class($file_class);
1881 $feed->set_parser_class($parser_class);
1882 $feed->set_item_class($item_class);
1883 $feed->force_feed($force_feed);
1884 $feed->set_cache_duration(FeedWordPress::cache_duration($params));
1885 $feed->init();
1886 $feed->handle_content_type();
1887
1888 if ($feed->error()) :
1889 $ret = new WP_Error('simplepie-error', $feed->error());
1890 else :
1891 $ret = $feed;
1892 endif;
1893 return $ret;
1894 } /* FeedWordPress::fetch () */
1895
1896 function clear_cache () {
1897 global $wpdb;
1898
1899 // Just in case, clear out any old MagpieRSS cache records.
1900 $magpies = $wpdb->query("
1901 DELETE FROM {$wpdb->options}
1902 WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32
1903 ");
1904
1905 // The WordPress SimplePie module stores its cached feeds as
1906 // transient records in the options table. The data itself is
1907 // stored in `_transient_feed_{md5 of url}` and the last-modified
1908 // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
1909 // these records are stored in `_transient_timeout_feed_{md5}`.
1910 // Since the md5 is always 32 characters in length, the
1911 // option_name is always over 32 characters.
1912 $simplepies = $wpdb->query("
1913 DELETE FROM {$wpdb->options}
1914 WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
1915 ");
1916 $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each
1917
1918 return ($magpies + $simplepies);
1919 } /* FeedWordPress::clear_cache () */
1920
1921 function cache_duration ($params = array()) {
1922 $params = wp_parse_args($params, array(
1923 "cache" => true,
1924 ));
1925
1926 $duration = NULL;
1927 if (!$params['cache']) :
1928 $duration = 0;
1929 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1930 $duration = FEEDWORDPRESS_CACHE_AGE;
1931 endif;
1932 return $duration;
1933 }
1934 function cache_lifetime ($duration) {
1935 // Check for explicit setting of a lifetime duration
1936 if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
1937 $duration = FEEDWORDPRESS_CACHE_LIFETIME;
1938
1939 // Fall back to the cache freshness duration
1940 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1941 $duration = FEEDWORDPRESS_CACHE_AGE;
1942 endif;
1943
1944 // Fall back to WordPress default
1945 return $duration;
1946 } /* FeedWordPress::cache_lifetime () */
1947
1948 # Utility functions for handling text settings
1949 function negative ($f, $setting) {
1950 $nego = array ('n', 'no', 'f', 'false');
1951 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
1952 }
1953
1954 function affirmative ($f, $setting) {
1955 $affirmo = array ('y', 'yes', 't', 'true', 1);
1956 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
1957 }
1958
1959 # Internal debugging functions
1960 function critical_bug ($varname, $var, $line, $file = NULL) {
1961 global $wp_version;
1962
1963 if (!is_null($file)) :
1964 $location = "line # ${line} of ".basename($file);
1965 else :
1966 $location = "line # ${line}";
1967 endif;
1968
1969 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>';
1970 print "\n<plaintext>";
1971 print "Triggered at ${location}\n";
1972 print "FeedWordPress: ".FEEDWORDPRESS_VERSION."\n";
1973 print "WordPress: {$wp_version}\n";
1974 print "PHP: ".phpversion()."\n";
1975 print "Error data: ";
1976 print $varname.": "; var_dump($var); echo "\n";
1977 die;
1978 }
1979
1980 function noncritical_bug ($varname, $var, $line, $file = NULL) {
1981 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
1982 FeedWordPress::critical_bug($varname, $var, $line, $file);
1983 endif;
1984 }
1985
1986 function val ($v, $no_newlines = false) {
1987 ob_start();
1988 var_dump($v);
1989 $out = ob_get_contents(); ob_end_clean();
1990
1991 if ($no_newlines) :
1992 $out = preg_replace('/\s+/', " ", $out);
1993 endif;
1994 return $out;
1995 } /* FeedWordPress::val () */
1996
1997 function diagnostic_on ($level) {
1998 $show = get_option('feedwordpress_diagnostics_show', array());
1999 return (in_array($level, $show));
2000 } /* FeedWordPress::diagnostic_on () */
2001
2002 function diagnostic ($level, $out, $persist = NULL, $since = NULL, $mostRecent = NULL) {
2003 global $feedwordpress_admin_footer;
2004
2005 $output = get_option('feedwordpress_diagnostics_output', array());
2006 $dlog = get_option('feedwordpress_diagnostics_log', array());
2007
2008 $diagnostic_nesting = count(explode(":", $level));
2009
2010 if (FeedWordPress::diagnostic_on($level)) :
2011 foreach ($output as $method) :
2012 switch ($method) :
2013 case 'echo' :
2014 if (!FeedWordPress::update_requested()) :
2015 echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.$out."</pre></div>";
2016 endif;
2017 break;
2018 case 'echo_in_cronjob' :
2019 if (FeedWordPress::update_requested()) :
2020 echo FeedWordPress::log_prefix()." ".$out."\n";
2021 endif;
2022 break;
2023 case 'admin_footer' :
2024 $feedwordpress_admin_footer[] = $out;
2025 break;
2026 case 'error_log' :
2027 error_log(FeedWordPress::log_prefix().' '.$out);
2028 break;
2029 case 'email' :
2030
2031 if (is_null($persist)) :
2032 $sect = 'occurrent';
2033 $hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0);
2034 $line = array("Time" => time(), "Message" => $out);
2035 else :
2036 $sect = 'persistent';
2037 $hook = md5($level."\n".$persist);
2038 $line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent);
2039 endif;
2040
2041 if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
2042 if (!isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif;
2043
2044 $dlog['mesg'][$sect][$hook] = $line;
2045 endswitch;
2046 endforeach;
2047 endif;
2048
2049 update_option('feedwordpress_diagnostics_log', $dlog);
2050 } /* FeedWordPress::diagnostic () */
2051
2052 function email_diagnostics_override () {
2053 return ($this->has_secret() and isset($_REQUEST['feedwordpress_email_diagnostics']) and !!$_REQUEST['feedwordpress_email_diagnostics']);
2054 }
2055 function has_emailed_diagnostics ($dlog) {
2056 $ret = false;
2057 if ($this->email_diagnostics_override()
2058 or (isset($dlog['schedule']) and isset($dlog['schedule']['last']))) :
2059 $ret = true;
2060 endif;
2061 return $ret;
2062 }
2063 function ready_to_email_diagnostics ($dlog) {
2064 $ret = false;
2065 if ($this->email_diagnostics_override()
2066 or (time() > ($dlog['schedule']['last'] + $dlog['schedule']['freq']))) :
2067 $ret = true;
2068 endif;
2069 return $ret;
2070 }
2071
2072 function email_diagnostic_log ($params = array()) {
2073 $params = wp_parse_args($params, array(
2074 "force" => false,
2075 ));
2076
2077 $dlog = get_option('feedwordpress_diagnostics_log', array());
2078
2079 if ($this->has_emailed_diagnostics($dlog)) :
2080 if ($this->ready_to_email_diagnostics($dlog)) :
2081 // No news is good news; only send if
2082 // there are some messages to send.
2083 $body = NULL;
2084 if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
2085
2086 foreach ($dlog['mesg'] as $sect => $mesgs) :
2087 if (count($mesgs) > 0) :
2088 if (is_null($body)) : $body = ''; endif;
2089
2090 $paradigm = reset($mesgs);
2091 $body .= "<h2>".ucfirst($sect)." issues</h2>\n"
2092 ."<table>\n"
2093 ."<thead><tr>\n";
2094 foreach ($paradigm as $col => $value) :
2095 $body .= '<th scope="col">'.$col."</th>\n";
2096 endforeach;
2097 $body .= "</tr></thead>\n"
2098 ."<tbody>\n";
2099
2100 foreach ($mesgs as $line) :
2101 $body .= "<tr>\n";
2102 foreach ($line as $col => $cell) :
2103 if (is_numeric($cell)) :
2104 $cell = date('j-M-y, h:i a', $cell);
2105 endif;
2106 $class = strtolower(preg_replace('/\s+/', '-', $col));
2107 $body .= "<td class=\"$class\">${cell}</td>";
2108 endforeach;
2109 $body .= "</tr>\n";
2110 endforeach;
2111
2112 $body .= "</tbody>\n</table>\n\n";
2113 endif;
2114 endforeach;
2115
2116 $body = apply_filters('feedwordpress_diagnostic_email_body', $body, $dlog);
2117 if (!is_null($body)) :
2118 $home = feedwordpress_display_url(get_bloginfo('url'));
2119 $subj = apply_filters('feedwordpress_diagnostic_email_subject', $home . " syndication issues", $dlog);
2120 $agent = 'FeedWordPress '.FEEDWORDPRESS_VERSION;
2121 $body = <<<EOMAIL
2122 <html>
2123 <head>
2124 <title>$subj</title>
2125 <style type="text/css">
2126 body { background-color: white; color: black; }
2127 table { width: 100%; border: 1px solid black; }
2128 table thead tr th { background-color: #ff7700; color: white; border-bottom: 1px solid black; }
2129 table thead tr { border-bottom: 1px solid black; }
2130 table tr { vertical-align: top; }
2131 table .since { width: 20%; }
2132 table .time { width: 20%; }
2133 table .most-recently { width: 20%; }
2134 table .message { width: auto; }
2135 </style>
2136 </head>
2137 <body>
2138 <h1>Syndication Issues encountered by $agent on $home</h1>
2139 $body
2140 </body>
2141 </html>
2142
2143 EOMAIL;
2144
2145 $ded = get_option('feedwordpress_diagnostics_email_destination', 'admins');
2146
2147 // e-mail address
2148 if (preg_match('/^mailto:(.*)$/', $ded, $ref)) :
2149 $recipients = array($ref[1]);
2150
2151 // userid
2152 elseif (preg_match('/^user:(.*)$/', $ded, $ref)) :
2153 $userdata = get_userdata((int) $ref[1]);
2154 $recipients = array($userdata->user_email);
2155
2156 // admins
2157 else :
2158 $recipients = FeedWordPressDiagnostic::admin_emails();
2159 endif;
2160
2161 $mesgId = 'feedwordpress+'.time().'@'.$home;
2162 $parentId = get_option('feedwordpress_diagnostics_email_root_message_id', NULL);
2163
2164 $head = array("Message-ID: <$mesgId>");
2165 if (!is_null($parentId)) :
2166 // We've already sent off a diagnostic message in the past. Let's do some
2167 // magic to help with threading, in the hopes that all diagnostic messages
2168 // get threaded together.
2169 $head[] = "References: <$parentId>";
2170 $head[] = "In-Reply-To: <$parentId>";
2171 $subj = "Re: ".$subj;
2172 else :
2173 // This is the first of its kind. Let's mark it as such.
2174 update_option('feedwordpress_diagnostics_email_root_message_id', $mesgId);
2175 endif;
2176 $head = apply_filters('feedwordpress_diagnostic_email_headers', $head);
2177
2178 foreach ($recipients as $email) :
2179 add_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
2180 wp_mail($email, $subj, $body, $head);
2181 remove_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
2182 endforeach;
2183 endif;
2184
2185 // Clear the logs
2186 $dlog['mesg']['persistent'] = array();
2187 $dlog['mesg']['occurrent'] = array();
2188
2189 // Set schedule for next update
2190 $dlog['schedule']['last'] = time();
2191 endif;
2192 else :
2193 $dlog['schedule'] = array(
2194 'freq' => 24 /*hr*/ * 60 /*min*/ * 60 /*s*/,
2195 'last' => time(),
2196 );
2197 endif;
2198
2199 update_option('feedwordpress_diagnostics_log', $dlog);
2200 } /* FeedWordPress::email_diagnostic_log () */
2201
2202 function allow_html_mail () {
2203 return 'text/html';
2204 } /* FeedWordPress::allow_html_mail () */
2205
2206 function admin_footer () {
2207 global $feedwordpress_admin_footer;
2208 foreach ($feedwordpress_admin_footer as $line) :
2209 echo '<div><pre>'.$line.'</pre></div>';
2210 endforeach;
2211 } /* FeedWordPress::admin_footer () */
2212
2213 function log_prefix ($date = false) {
2214 $home = get_bloginfo('url');
2215 $prefix = '['.feedwordpress_display_url($home).'] [feedwordpress] ';
2216 if ($date) :
2217 $prefix = "[".date('Y-m-d H:i:s')."]".$prefix;
2218 endif;
2219 return $prefix;
2220 } /* FeedWordPress::log_prefix () */
2221
2222 function menu_cap ($sub = false) {
2223 if ($sub) :
2224 $cap = apply_filters('feedwordpress_menu_settings_capacity', 'manage_options');
2225 else :
2226 $cap = apply_filters('feedwordpress_menu_main_capacity', 'manage_links');
2227 endif;
2228 return $cap;
2229 } /* FeedWordPress::menu_cap () */
2230
2231 function path ($filename = '') {
2232 global $fwp_path;
2233
2234 $path = $fwp_path;
2235 if (strlen($filename) > 0) :
2236 $path .= '/'.$filename;
2237 endif;
2238 return $path;
2239 }
2240
2241 // These are superceded by MyPHP::param/post/get/request, but kept
2242 // here for backward compatibility.
2243 function param ($key, $type = 'REQUEST', $default = NULL) {
2244 return MyPHP::param($key, $default, $type);
2245 }
2246 function post ($key, $default = NULL) {
2247 return MyPHP::post($key, $default);
2248 }
2249 } // class FeedWordPress
2250
2251 $feedwordpress_admin_footer = array();
2252
2253 // take your best guess at the realname and e-mail, given a string
2254 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2255 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
2256 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
2257 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
2258 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
2259 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
2260
2261 function parse_email_with_realname ($email) {
2262 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
2263 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
2264 $ret['email'] = $matches[1];
2265 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
2266 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2267 $ret['email'] = $matches[4];
2268 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
2269 $ret['name'] = NULL; $ret['email'] = $matches[1];
2270 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
2271 $ret['email'] = NULL;
2272 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2273 else :
2274 $ret['name'] = NULL; $ret['email'] = NULL;
2275 endif;
2276 return $ret;
2277 }
2278
2279