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

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