PluginProbe
FeedWordPress / 2020.0118
FeedWordPress v2020.0118
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 2020.0118, at feedwordpress.php

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