PluginProbe
FeedWordPress / 2017.1020
FeedWordPress v2017.1020
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 2017.1020, at feedwordpress.php

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