PluginProbe
FeedWordPress / 2022.0204
FeedWordPress v2022.0204
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 2022.0204, at feedwordpress.php

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