PluginProbe
FeedWordPress / 2024.1119
FeedWordPress v2024.1119
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 2024.1119, at feedwordpress.php

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