PluginProbe
FeedWordPress / 2016.1213
FeedWordPress v2016.1213
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 2016.1213, at feedwordpress.php

2,605 lines 86.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: FeedWordPress
4 Plugin URI: http://feedwordpress.radgeek.com/
5 Description: simple and flexible Atom/RSS syndication for WordPress
6 Version: 2016.1213
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 */
11
12 /**
13 * @package FeedWordPress
14 * @version 2016.1213
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', '2016.1213');
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
1827 $this->update_hooked = "Initiating a CRON JOB CHECK-IN ON UPDATE SCHEDULE due to URL parameter = ".trim($this->val($_REQUEST['update_feedwordpress']));
1828
1829 $this->update($this->update_requested_url());
1830
1831 if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
1832 $mysqlTime = 0.0;
1833 $byTime = array();
1834 foreach ($wpdb->queries as $query) :
1835 $time = $query[1] * 1000000.0;
1836 $mysqlTime += $query[1];
1837 if (!isset($byTime[$time])) : $byTime[$time] = array(); endif;
1838 $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2];
1839 endforeach;
1840 krsort($byTime);
1841
1842 foreach ($byTime as $time => $querySet) :
1843 foreach ($querySet as $query) :
1844 print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n";
1845 endforeach;
1846 endforeach;
1847 echo self::log_prefix()."$wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds.";
1848 endif;
1849
1850 debug_out_feedwordpress_footer();
1851
1852 // Magic URL should return nothing but a 200 OK header packet
1853 // when successful.
1854 exit;
1855 endif;
1856 } /* FeedWordPress::update_magic_url () */
1857
1858 public static function update_requested () {
1859 return MyPHP::request('update_feedwordpress');
1860 } /* FeedWordPress::update_requested() */
1861
1862 public function update_requested_url () {
1863 $ret = null;
1864
1865 if (($_REQUEST['update_feedwordpress']=='*')
1866 or (preg_match('|^[a-z]+://.*|i', $_REQUEST['update_feedwordpress']))) :
1867 $ret = $_REQUEST['update_feedwordpress'];
1868 endif;
1869
1870 return $ret;
1871 } /* FeedWordPress::update_requested_url() */
1872
1873 public function auto_update () {
1874 if ($this->stale()) :
1875 $this->update();
1876 endif;
1877 } /* FeedWordPress::auto_update () */
1878
1879 public static function find_link ($uri, $field = 'link_rss') {
1880 global $wpdb;
1881
1882 $unslashed = untrailingslashit($uri);
1883 $slashed = trailingslashit($uri);
1884 $link_id = $wpdb->get_var($wpdb->prepare("
1885 SELECT link_id FROM $wpdb->links WHERE $field IN ('%s', '%s')
1886 LIMIT 1", $unslashed, $slashed
1887 ));
1888
1889 return $link_id;
1890 } /* FeedWordPress::find_link () */
1891
1892 /**
1893 * FeedWordPress:syndicate_link(): add or update a feed subscription
1894 *
1895 * Add a new subscription to, or update an existing subscription in,
1896 * FWP's list of subscribed feeds.
1897 *
1898 * Postcondition: If $rss is the URL of a feed not yet on FWP's list of
1899 * subscribed feeds, then a new subscription will be added, using $name
1900 * as its initial title and $uri as its initial homepage URL (normally
1901 * these will be updated with new values taken from the feed, the first
1902 * time the new feed is checked for syndicated content, unless feed
1903 * settings prevent this). If $rss is the URL of a feed that is already
1904 * on FWP's list of subscribed feeds, then that feed will be updated to
1905 * use the title provided in $name and the homepage URL in $uri
1906 *
1907 * @param string $name The human-readable title of the feed (for example, "Rad Geek People's Daily")
1908 * @param string $uri The URI for the human-readable homepage associated with the feed (for example, <http://radgeek.com/>)
1909 * @param string $rss The URI for the feed itself (for example, <http://radgeek.com/feed/>)
1910 *
1911 * @return mixed Returns an int with the numeric ID of the new
1912 * subscription's wp_links record if successful or a WP_Error object
1913 * if wp_insert_link() failed.
1914 *
1915 * @uses FeedWordPress::link_category_id()
1916 * @uses FeedWordPress::find_link()
1917 * @uses is_wp_error()
1918 * @uses wp_insert_link()
1919 *
1920 */
1921 public static function syndicate_link ($name, $uri, $rss) {
1922 // Get the category ID#
1923 $cat_id = FeedWordPress::link_category_id();
1924 if (!is_wp_error($cat_id)) :
1925 $link_category = array($cat_id);
1926 else :
1927 $link_category = array();
1928 endif;
1929
1930 // WordPress gets cranky if there's no homepage URI
1931 if (!is_string($uri) or strlen($uri)<1) : $uri = $rss; endif;
1932
1933 // Check if this feed URL is already being syndicated.
1934 $link_id = wp_insert_link(/*linkdata=*/ array(
1935 "link_id" => FeedWordPress::find_link($rss), // insert if nothing was found; else update
1936 "link_rss" => $rss,
1937 "link_name" => $name,
1938 "link_url" => $uri,
1939 "link_category" => $link_category,
1940 "link_visible" => 'Y', // reactivate if inactivated
1941 ), /*wp_error=*/ true);
1942
1943 return $link_id;
1944 } /* function FeedWordPress::syndicate_link() */
1945
1946 static function syndicated_status ($what, $default) {
1947 $ret = get_option("feedwordpress_syndicated_{$what}_status");
1948 if (!$ret) :
1949 $ret = $default;
1950 endif;
1951 return $ret;
1952 } /* FeedWordPress::syndicated_status() */
1953
1954 public static function on_unfamiliar ($what = 'author') {
1955 switch ($what) :
1956 case 'category' : $suffix = ':category'; break;
1957 case 'post_tag' : $suffix = ':post_tag'; break;
1958 default: $suffix = '';
1959 endswitch;
1960
1961 return get_option('feedwordpress_unfamiliar_'.$what, 'create'.$suffix);
1962 } // function FeedWordPress::on_unfamiliar()
1963
1964 public static function null_email_set () {
1965 $base = get_option('feedwordpress_null_email_set');
1966
1967 if ($base===false) :
1968 $ret = array('noreply@blogger.com'); // default
1969 else :
1970 $ret = array_map('strtolower',
1971 array_map('trim', explode("\n", $base)));
1972 endif;
1973 $ret = apply_filters('syndicated_item_author_null_email_set', $ret);
1974 return $ret;
1975
1976 } /* FeedWordPress::null_email_set () */
1977
1978 public static function is_null_email ($email) {
1979 $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set());
1980 $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email);
1981 return $ret;
1982 } /* FeedWordPress::is_null_email () */
1983
1984 public static function use_aggregator_source_data () {
1985 $ret = get_option('feedwordpress_use_aggregator_source_data');
1986 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1987 } /* FeedWordPress::use_aggregator_source_data () */
1988
1989 /**
1990 * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1991 * should rewrite permalinks for syndicated items to reflect their
1992 * original location.
1993 *
1994 * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1995 */
1996 static function munge_permalinks () {
1997 return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1998 } /* FeedWordPress::munge_permalinks() */
1999
2000 public static function syndicated_links ($args = array()) {
2001 $contributors = FeedWordPress::link_category_id();
2002 if (!is_wp_error($contributors)) :
2003 $links = get_bookmarks(array_merge(
2004 array("category" => $contributors),
2005 $args
2006 ));
2007 else :
2008 $links = array();
2009 endif;
2010
2011 return $links;
2012 } /* FeedWordPress::syndicated_links() */
2013
2014 public static function link_category_id () {
2015 global $wpdb, $wp_db_version;
2016
2017 $cat_id = get_option('feedwordpress_cat_id');
2018
2019 // If we don't yet have the category ID stored, search by name
2020 if (!$cat_id) :
2021 $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY);
2022
2023 if ($cat_id) :
2024 // We found it; let's stamp it.
2025 update_option('feedwordpress_cat_id', $cat_id);
2026 endif;
2027
2028 // If we *do* have the category ID stored, verify that it exists
2029 else :
2030 $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id');
2031 endif;
2032
2033 // If we could not find an appropriate link category,
2034 // make a new one for ourselves.
2035 if (!$cat_id) :
2036 $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY);
2037 if (!is_wp_error($cat_id)) :
2038 // Stamp it
2039 update_option('feedwordpress_cat_id', $cat_id);
2040 endif;
2041 endif;
2042
2043 return $cat_id;
2044 } /* FeedWordPress::link_category_id() */
2045
2046 # Upgrades and maintenance...
2047 static function needs_upgrade () {
2048
2049 global $wpdb;
2050 $fwp_db_version = get_option('feedwordpress_version', NULL);
2051 $ret = false; // innocent until proven guilty
2052 if (is_null($fwp_db_version) or ($fwp_db_version < FEEDWORDPRESS_VERSION)) :
2053
2054 // This is an older version or a fresh install. Does it require a database
2055 // upgrade or database initialization?
2056
2057 if (is_null($fwp_db_version)) :
2058
2059 // Fresh install; brand it as ours. Or possibly a version of FWP
2060 // from before 0.96. But I'm no longer supporting upgrade paths
2061 // for versions from the previous decade. Sorry.
2062 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2063
2064 elseif ($fwp_db_version < 2010.0814) :
2065
2066 // Change in terminology.
2067 if (get_option('feedwordpress_unfamiliar_category', 'create')=='default') :
2068 update_option('feedwordpress_unfamiliar_category', 'null');
2069 endif;
2070 foreach (FeedWordPress::syndicated_links() as $link) :
2071 $sub = new SyndicatedLink($link);
2072
2073 $remap_uf = array(
2074 'default' => 'null',
2075 'filter' => 'null',
2076 'create' => 'create:category',
2077 'tag' => 'create:post_tag'
2078 );
2079 if (isset($sub->settings['unfamiliar category'])) :
2080 if ($sub->settings['unfamiliar category']=='filter') :
2081 $sub->settings['match/filter'] = array('category');
2082 endif;
2083 foreach ($remap_uf as $from => $to) :
2084 if ($sub->settings['unfamiliar category']==$from) :
2085 $sub->settings['unfamiliar category'] = $to;
2086 endif;
2087 endforeach;
2088 endif;
2089
2090 if (isset($sub->settings['add global categories'])) :
2091 $sub->settings['add/category'] = $sub->settings['add global categories'];
2092 unset($sub->settings['add global categories']);
2093 endif;
2094
2095 $sub->save_settings(/*reload=*/ true);
2096 endforeach;
2097 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2098
2099 else :
2100
2101 // No upgrade needed. Just brand it with the new version.
2102 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2103
2104 endif;
2105
2106 endif;
2107 return $ret;
2108 } /* FeedWordPress::needs_upgrade () */
2109
2110 static function upgrade_database ($from = NULL) {
2111 global $wpdb;
2112
2113 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
2114
2115 switch ($from) :
2116 case 0.96:
2117 // Dropping legacy upgrade code. If anyone is still
2118 // using 0.96 and just now decided to upgrade, well, I'm
2119 // sorry about that. You'll just have to cope with a few
2120 // duplicate posts.
2121
2122 // Mark the upgrade as successful.
2123 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2124 endswitch;
2125 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
2126 } /* FeedWordPress::upgrade_database() */
2127
2128 public static function has_guid_index () {
2129 global $wpdb;
2130
2131 $found = false; // Guilty until proven innocent.
2132
2133 $results = $wpdb->get_results("
2134 SHOW INDEXES FROM {$wpdb->posts}
2135 ");
2136 if ($results) :
2137 foreach ($results as $index) :
2138 if (isset($index->Column_name)
2139 and ('guid' == $index->Column_name)) :
2140 $found = true;
2141 endif;
2142 endforeach;
2143 endif;
2144 return $found;
2145 } /* FeedWordPress::has_guid_index () */
2146
2147 public static function create_guid_index () {
2148 global $wpdb;
2149
2150 $wpdb->query("
2151 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
2152 ");
2153 } /* FeedWordPress::create_guid_index () */
2154
2155 public static function remove_guid_index () {
2156 global $wpdb;
2157
2158 $wpdb->query("
2159 DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
2160 ");
2161 }
2162
2163 static function fetch_timeout () {
2164 return apply_filters(
2165 'feedwordpress_fetch_timeout',
2166 intval(get_option('feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT))
2167 );
2168 }
2169
2170 static function fetch ($url, $params = array()) {
2171 if (is_wp_error($url)) :
2172 // Let's bounce.
2173 return $url;
2174 endif;
2175
2176 $force_feed = true; // Default
2177
2178 // Allow user to change default feed-fetch timeout with a global setting.
2179 // Props Erigami Scholey-Fuller <http://www.piepalace.ca/blog/2010/11/feedwordpress-broke-my-heart.html>
2180 $timeout = FeedWordPress::fetch_timeout();
2181
2182 if (!is_array($params)) :
2183 $force_feed = $params;
2184 else : // Parameter array
2185 $args = wp_parse_args(array(
2186 'force_feed' => $force_feed,
2187 'timeout' => $timeout
2188 ), $params);
2189
2190 extract($args);
2191 endif;
2192 $timeout = intval($timeout);
2193
2194 $pie_class = apply_filters('feedwordpress_simplepie_class', 'FeedWordPie');
2195 $cache_class = apply_filters('feedwordpress_cache_class', 'WP_Feed_Cache');
2196 $file_class = apply_filters('feedwordpress_file_class', 'FeedWordPress_File');
2197 $parser_class = apply_filters('feedwordpress_parser_class', 'FeedWordPress_Parser');
2198 $item_class = apply_filters('feedwordpress_item_class', 'FeedWordPie_Item');
2199 $sniffer_class = apply_filters('feedwordpress_sniffer_class', 'FeedWordPress_Content_Type_Sniffer');
2200
2201 $feed = new $pie_class;
2202 $feed->set_feed_url($url);
2203 $feed->set_cache_class($cache_class);
2204 $feed->set_timeout($timeout);
2205
2206 $feed->set_content_type_sniffer_class($sniffer_class);
2207 $feed->set_file_class($file_class);
2208 $feed->set_parser_class($parser_class);
2209 $feed->set_item_class($item_class);
2210 $feed->force_feed($force_feed);
2211 $feed->set_cache_duration(FeedWordPress::cache_duration($params));
2212 $feed->init();
2213 $feed->handle_content_type();
2214
2215 if ($feed->error()) :
2216 $ret = new WP_Error('simplepie-error', $feed->error());
2217 else :
2218 $ret = $feed;
2219 endif;
2220 return $ret;
2221 } /* FeedWordPress::fetch () */
2222
2223 public function clear_cache () {
2224 global $wpdb;
2225
2226 // Just in case, clear out any old MagpieRSS cache records.
2227 $magpies = $wpdb->query("
2228 DELETE FROM {$wpdb->options}
2229 WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32
2230 ");
2231
2232 // The WordPress SimplePie module stores its cached feeds as
2233 // transient records in the options table. The data itself is
2234 // stored in `_transient_feed_{md5 of url}` and the last-modified
2235 // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
2236 // these records are stored in `_transient_timeout_feed_{md5}`.
2237 // Since the md5 is always 32 characters in length, the
2238 // option_name is always over 32 characters.
2239 $simplepies = $wpdb->query("
2240 DELETE FROM {$wpdb->options}
2241 WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
2242 ");
2243 $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each
2244
2245 return ($magpies + $simplepies);
2246 } /* FeedWordPress::clear_cache () */
2247
2248 static public function cache_duration ($params = array()) {
2249 $params = wp_parse_args($params, array(
2250 "cache" => true,
2251 ));
2252
2253 $duration = NULL;
2254 if (!$params['cache']) :
2255 $duration = 0;
2256 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
2257 $duration = FEEDWORDPRESS_CACHE_AGE;
2258 endif;
2259 return $duration;
2260 }
2261
2262 static public function cache_lifetime ($duration) {
2263 // Check for explicit setting of a lifetime duration
2264 if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
2265 $duration = FEEDWORDPRESS_CACHE_LIFETIME;
2266
2267 // Fall back to the cache freshness duration
2268 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
2269 $duration = FEEDWORDPRESS_CACHE_AGE;
2270 endif;
2271
2272 // Fall back to WordPress default
2273 return $duration;
2274 } /* FeedWordPress::cache_lifetime () */
2275
2276 # Utility functions for handling text settings
2277 static function negative ($f, $setting) {
2278 $nego = array ('n', 'no', 'f', 'false');
2279 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
2280 } /* FeedWordPress::negative () */
2281
2282 static function affirmative ($f, $setting) {
2283 $affirmo = array ('y', 'yes', 't', 'true', 1);
2284 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
2285 } /* FeedWordPress::affirmative () */
2286
2287 # Internal debugging functions
2288 static function critical_bug ($varname, $var, $line, $file = NULL) {
2289 global $wp_version;
2290
2291 if (!is_null($file)) :
2292 $location = "line # ${line} of ".basename($file);
2293 else :
2294 $location = "line # ${line}";
2295 endif;
2296
2297 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>';
2298 print "\n<plaintext>";
2299 print "Triggered at ${location}\n";
2300 print "FeedWordPress: ".FEEDWORDPRESS_VERSION."\n";
2301 print "WordPress: {$wp_version}\n";
2302 print "PHP: ".phpversion()."\n";
2303 print "Error data: ";
2304 print $varname.": "; var_dump($var); echo "\n";
2305 die;
2306 } /* FeedWordPress::critical_bug () */
2307
2308 static function noncritical_bug ($varname, $var, $line, $file = NULL) {
2309 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
2310 FeedWordPress::critical_bug($varname, $var, $line, $file);
2311 endif;
2312 } /* FeedWordPress::noncritical_bug () */
2313
2314 static function val ($v, $no_newlines = false) {
2315 return MyPHP::val($v, $no_newlines);
2316 } /* FeedWordPress::val () */
2317
2318 static function diagnostic_on ($level) {
2319 $show = get_option('feedwordpress_diagnostics_show', array());
2320 return (in_array($level, $show));
2321 } /* FeedWordPress::diagnostic_on () */
2322
2323 static function diagnostic ($level, $out, $persist = NULL, $since = NULL, $mostRecent = NULL) {
2324 global $feedwordpress_admin_footer;
2325
2326 $output = get_option('feedwordpress_diagnostics_output', array());
2327 $dlog = get_option('feedwordpress_diagnostics_log', array());
2328
2329 $diagnostic_nesting = count(explode(":", $level));
2330
2331 if (self::diagnostic_on($level)) :
2332 foreach ($output as $method) :
2333 switch ($method) :
2334 case 'echo' :
2335 if (!self::update_requested()) :
2336 echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.$out."</pre></div>\n";
2337 endif;
2338 break;
2339 case 'echo_in_cronjob' :
2340 if (self::update_requested()) :
2341 echo self::log_prefix()." ".$out."\n";
2342 endif;
2343 break;
2344 case 'admin_footer' :
2345 $feedwordpress_admin_footer[] = $out;
2346 break;
2347 case 'error_log' :
2348 error_log(self::log_prefix().' '.$out);
2349 break;
2350 case 'email' :
2351
2352 if (is_null($persist)) :
2353 $sect = 'occurrent';
2354 $hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0);
2355 $line = array("Time" => time(), "Message" => $out);
2356 else :
2357 $sect = 'persistent';
2358 $hook = md5($level."\n".$persist);
2359 $line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent);
2360 endif;
2361
2362 if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
2363 if (!isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif;
2364
2365 $dlog['mesg'][$sect][$hook] = $line;
2366 endswitch;
2367 endforeach;
2368 endif;
2369
2370 update_option('feedwordpress_diagnostics_log', $dlog);
2371 } /* FeedWordPress::diagnostic () */
2372
2373 public function email_diagnostics_override () {
2374 return ($this->has_secret() and isset($_REQUEST['feedwordpress_email_diagnostics']) and !!$_REQUEST['feedwordpress_email_diagnostics']);
2375 } /* FeedWordPress::email_diagnostics_override () */
2376
2377 public function has_emailed_diagnostics ($dlog) {
2378 $ret = false;
2379 if ($this->email_diagnostics_override()
2380 or (isset($dlog['schedule']) and isset($dlog['schedule']['last']))) :
2381 $ret = true;
2382 endif;
2383 return $ret;
2384 } /* FeedWordPress::has_emailed_diagnostics () */
2385
2386 public function ready_to_email_diagnostics ($dlog) {
2387 $ret = false;
2388 if ($this->email_diagnostics_override()
2389 or (time() > ($dlog['schedule']['last'] + $dlog['schedule']['freq']))) :
2390 $ret = true;
2391 endif;
2392 return $ret;
2393 } /* FeedWordPress::ready_to_email_diagnostics () */
2394
2395 public function email_diagnostic_log ($params = array()) {
2396 $params = wp_parse_args($params, array(
2397 "force" => false,
2398 ));
2399
2400 $dlog = get_option('feedwordpress_diagnostics_log', array());
2401
2402 if ($this->has_emailed_diagnostics($dlog)) :
2403 if ($this->ready_to_email_diagnostics($dlog)) :
2404 // No news is good news; only send if
2405 // there are some messages to send.
2406 $body = NULL;
2407 if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
2408
2409 foreach ($dlog['mesg'] as $sect => $mesgs) :
2410 if (count($mesgs) > 0) :
2411 if (is_null($body)) : $body = ''; endif;
2412
2413 $paradigm = reset($mesgs);
2414 $body .= "<h2>".ucfirst($sect)." issues</h2>\n"
2415 ."<table>\n"
2416 ."<thead><tr>\n";
2417 foreach ($paradigm as $col => $value) :
2418 $body .= '<th scope="col">'.$col."</th>\n";
2419 endforeach;
2420 $body .= "</tr></thead>\n"
2421 ."<tbody>\n";
2422
2423 foreach ($mesgs as $line) :
2424 $body .= "<tr>\n";
2425 foreach ($line as $col => $cell) :
2426 if (is_numeric($cell)) :
2427 $cell = date('j-M-y, h:i a', $cell);
2428 endif;
2429 $class = strtolower(preg_replace('/\s+/', '-', $col));
2430 $body .= "<td class=\"$class\">${cell}</td>";
2431 endforeach;
2432 $body .= "</tr>\n";
2433 endforeach;
2434
2435 $body .= "</tbody>\n</table>\n\n";
2436 endif;
2437 endforeach;
2438
2439 $body = apply_filters('feedwordpress_diagnostic_email_body', $body, $dlog);
2440 if (!is_null($body)) :
2441 $home = feedwordpress_display_url(get_bloginfo('url'));
2442 $subj = apply_filters('feedwordpress_diagnostic_email_subject', $home . " syndication issues", $dlog);
2443 $agent = 'FeedWordPress '.FEEDWORDPRESS_VERSION;
2444 $body = <<<EOMAIL
2445 <html>
2446 <head>
2447 <title>$subj</title>
2448 <style type="text/css">
2449 body { background-color: white; color: black; }
2450 table { width: 100%; border: 1px solid black; }
2451 table thead tr th { background-color: #ff7700; color: white; border-bottom: 1px solid black; }
2452 table thead tr { border-bottom: 1px solid black; }
2453 table tr { vertical-align: top; }
2454 table .since { width: 20%; }
2455 table .time { width: 20%; }
2456 table .most-recently { width: 20%; }
2457 table .message { width: auto; }
2458 </style>
2459 </head>
2460 <body>
2461 <h1>Syndication Issues encountered by $agent on $home</h1>
2462 $body
2463 </body>
2464 </html>
2465
2466 EOMAIL;
2467
2468 $ded = get_option('feedwordpress_diagnostics_email_destination', 'admins');
2469
2470 // e-mail address
2471 if (preg_match('/^mailto:(.*)$/', $ded, $ref)) :
2472 $recipients = array($ref[1]);
2473
2474 // userid
2475 elseif (preg_match('/^user:(.*)$/', $ded, $ref)) :
2476 $userdata = get_userdata((int) $ref[1]);
2477 $recipients = array($userdata->user_email);
2478
2479 // admins
2480 else :
2481 $recipients = FeedWordPressDiagnostic::admin_emails();
2482 endif;
2483
2484 $mesgId = 'feedwordpress+'.time().'@'.$home;
2485 $parentId = get_option('feedwordpress_diagnostics_email_root_message_id', NULL);
2486
2487 $head = array("Message-ID: <$mesgId>");
2488 if (!is_null($parentId)) :
2489 // We've already sent off a diagnostic message in the past. Let's do some
2490 // magic to help with threading, in the hopes that all diagnostic messages
2491 // get threaded together.
2492 $head[] = "References: <$parentId>";
2493 $head[] = "In-Reply-To: <$parentId>";
2494 $subj = "Re: ".$subj;
2495 else :
2496 // This is the first of its kind. Let's mark it as such.
2497 update_option('feedwordpress_diagnostics_email_root_message_id', $mesgId);
2498 endif;
2499 $head = apply_filters('feedwordpress_diagnostic_email_headers', $head);
2500
2501 foreach ($recipients as $email) :
2502 add_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
2503 wp_mail($email, $subj, $body, $head);
2504 remove_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
2505 endforeach;
2506 endif;
2507
2508 // Clear the logs
2509 $dlog['mesg']['persistent'] = array();
2510 $dlog['mesg']['occurrent'] = array();
2511
2512 // Set schedule for next update
2513 $dlog['schedule']['last'] = time();
2514 endif;
2515 else :
2516 $dlog['schedule'] = array(
2517 'freq' => 24 /*hr*/ * 60 /*min*/ * 60 /*s*/,
2518 'last' => time(),
2519 );
2520 endif;
2521
2522 update_option('feedwordpress_diagnostics_log', $dlog);
2523 } /* FeedWordPress::email_diagnostic_log () */
2524
2525 static function allow_html_mail () {
2526 return 'text/html';
2527 } /* FeedWordPress::allow_html_mail () */
2528
2529 static function admin_footer () {
2530 global $feedwordpress_admin_footer;
2531 foreach ($feedwordpress_admin_footer as $line) :
2532 echo '<div><pre>'.$line.'</pre></div>';
2533 endforeach;
2534 } /* FeedWordPress::admin_footer () */
2535
2536 static function log_prefix ($date = false) {
2537 $home = get_bloginfo('url');
2538 $prefix = '['.feedwordpress_display_url($home).'] [feedwordpress] ';
2539 if ($date) :
2540 $prefix = "[".date('Y-m-d H:i:s')."]".$prefix;
2541 endif;
2542 return $prefix;
2543 } /* FeedWordPress::log_prefix () */
2544
2545 static function menu_cap ($sub = false) {
2546 if ($sub) :
2547 $cap = apply_filters('feedwordpress_menu_settings_capacity', 'manage_options');
2548 else :
2549 $cap = apply_filters('feedwordpress_menu_main_capacity', 'manage_links');
2550 endif;
2551 return $cap;
2552 } /* FeedWordPress::menu_cap () */
2553
2554 static function path ($filename = '') {
2555 global $fwp_path;
2556
2557 $path = $fwp_path;
2558 if (strlen($filename) > 0) :
2559 $path .= '/'.$filename;
2560 endif;
2561 return $path;
2562 } /* FeedWordPress::path () */
2563
2564 // These are superceded by MyPHP::param/post/get/request, but kept
2565 // here for backward compatibility.
2566
2567 static function param ($key, $type = 'REQUEST', $default = NULL) {
2568 return MyPHP::param($key, $default, $type);
2569 } /* FeedWordPress::param () */
2570
2571 static function post ($key, $default = NULL) {
2572 return MyPHP::post($key, $default);
2573 } /* FeedWordPress::post () */
2574
2575 } /* class FeedWordPress */
2576
2577 $feedwordpress_admin_footer = array();
2578
2579 // take your best guess at the realname and e-mail, given a string
2580 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2581 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
2582 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
2583 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
2584 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
2585 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
2586
2587 function parse_email_with_realname ($email) {
2588 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
2589 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
2590 $ret['email'] = $matches[1];
2591 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
2592 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2593 $ret['email'] = $matches[4];
2594 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
2595 $ret['name'] = NULL; $ret['email'] = $matches[1];
2596 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
2597 $ret['email'] = NULL;
2598 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
2599 else :
2600 $ret['name'] = NULL; $ret['email'] = NULL;
2601 endif;
2602 return $ret;
2603 }
2604
2605