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

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