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

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