PluginProbe
FeedWordPress / 2010.0623
FeedWordPress v2010.0623
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 2010.0623, at feedwordpress.php

1,628 lines 56.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: FeedWordPress
4 Plugin URI: http://feedwordpress.radgeek.com/
5 Description: simple and flexible Atom/RSS syndication for WordPress
6 Version: 2010.0623
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 */
11
12 /**
13 * @package FeedWordPress
14 * @version 2010.0623
15 */
16
17 # This uses code derived from:
18 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
19 # - MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net>
20 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
21 # - WordPress Blog Tool and Publishing Platform <http://wordpress.org/>
22 # according to the terms of the GNU General Public License.
23 #
24 # INSTALLATION: see readme.txt or <http://projects.radgeek.com/install>
25 #
26 # USAGE: once FeedWordPress is installed, you manage just about everything from
27 # the WordPress Dashboard, under the Syndication menu. To ensure that fresh
28 # content is added as it becomes available, you can convince your contributors
29 # to put your XML-RPC URI (if WordPress is installed at
30 # <http://www.zyx.com/blog>, XML-RPC requests should be sent to
31 # <http://www.zyx.com/blog/xmlrpc.php>), or update manually under the
32 # Syndication menu, or set up automatic updates under Syndication --> Settings,
33 # or use a cron job.
34
35 # -- Don't change these unless you know what you're doing...
36
37 define ('FEEDWORDPRESS_VERSION', '2010.0623');
38 define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
39
40 // Defaults
41 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
42 define ('DEFAULT_UPDATE_PERIOD', 60); // value in minutes
43
44 if (isset($_REQUEST['feedwordpress_debug'])) :
45 $feedwordpress_debug = $_REQUEST['feedwordpress_debug'];
46 else :
47 $feedwordpress_debug = get_option('feedwordpress_debug');
48 if (is_string($feedwordpress_debug)) :
49 $feedwordpress_debug = ($feedwordpress_debug == 'yes');
50 endif;
51 endif;
52 define ('FEEDWORDPRESS_DEBUG', $feedwordpress_debug);
53
54 define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
55 define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
56
57 define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi');
58
59 define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes
60
61 define ('FWP_SCHEMA_HAS_USERMETA', 2966);
62 define ('FWP_SCHEMA_USES_ARGS_TAXONOMY', 12694); // Revision # for using $args['taxonomy'] to get link categories
63 define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0
64 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1
65 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
66 define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5
67 define ('FWP_SCHEMA_26', 8201); // Database schema # for WP 2.6
68 define ('FWP_SCHEMA_27', 9872); // Database schema # for WP 2.7
69 define ('FWP_SCHEMA_28', 11548); // Database schema # for WP 2.8
70 define ('FWP_SCHEMA_29', 12329); // Database schema # for WP 2.9
71 define ('FWP_SCHEMA_30', 12694); // Database schema # for WP 3.0
72
73 if (FEEDWORDPRESS_DEBUG) :
74 // Help us to pick out errors, if any.
75 ini_set('error_reporting', E_ALL & ~E_NOTICE);
76 ini_set('display_errors', true);
77
78 // When testing we don't want cache issues to interfere. But this is
79 // a VERY BAD SETTING for a production server. Webmasters will eat your
80 // face for breakfast if you use it, and the baby Jesus will cry. So
81 // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
82 // used for more than testing purposes!
83 define('FEEDWORDPRESS_CACHE_AGE', 1);
84 define('FEEDWORDPRESS_CACHE_LIFETIME', 1);
85 define('FEEDWORDPRESS_FETCH_TIME_OUT', 60);
86 else :
87 // Hold onto data all day for conditional GET purposes,
88 // but consider it stale after 1 min (requiring a conditional GET)
89 define('FEEDWORDPRESS_CACHE_LIFETIME', 24*60*60);
90 define('FEEDWORDPRESS_CACHE_AGE', 1*60);
91 define('FEEDWORDPRESS_FETCH_TIME_OUT', 10);
92 endif;
93
94 // Use our the cache settings that we want.
95 add_filter('wp_feed_cache_transient_lifetime', array('FeedWordPress', 'cache_lifetime'));
96
97 // Ensure that we have SimplePie loaded up and ready to go.
98 // We no longer need a MagpieRSS upgrade module. Hallelujah!
99 require_once(ABSPATH . WPINC . '/feed.php');
100 require_once(ABSPATH . WPINC . '/class-feed.php');
101 require_once(ABSPATH . WPINC . '/class-simplepie.php');
102
103 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
104
105 require_once(dirname(__FILE__) . '/admin-ui.php');
106 require_once(dirname(__FILE__) . '/compatability.php'); // LEGACY API: Replicate or mock up functions for legacy support purposes
107 require_once(dirname(__FILE__) . '/feedwordpresshtml.class.php');
108 require_once(dirname(__FILE__) . '/feedwordpress-content-type-sniffer.class.php');
109
110 // Magic quotes are just about the stupidest thing ever.
111 if (is_array($_POST)) :
112 $fwp_post = stripslashes_deep($_POST);
113 endif;
114
115 // Get the path relative to the plugins directory in which FWP is stored
116 preg_match (
117 '|/wp-content/plugins/(.+)$|',
118 dirname(__FILE__),
119 $ref
120 );
121
122 if (isset($ref[1])) :
123 $fwp_path = $ref[1];
124 else : // Something went wrong. Let's just guess.
125 $fwp_path = 'feedwordpress';
126 endif;
127
128 // If this is a FeedWordPress admin page, queue up scripts for AJAX functions that FWP uses
129 // If it is a display page or a non-FeedWordPress admin page, don't.
130 if (FeedWordPressSettingsUI::is_admin()) :
131 add_action('admin_print_scripts', array('FeedWordPressSettingsUI', 'admin_scripts'));
132
133 wp_register_style('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.css');
134
135 wp_enqueue_style('dashboard');
136 wp_enqueue_style('feedwordpress-elements');
137
138 if (function_exists('wp_admin_css')) :
139 if (fwp_test_wp_version(FWP_SCHEMA_25)) :
140 wp_admin_css('css/dashboard');
141 endif;
142 endif;
143 endif;
144
145 if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
146
147 # Syndicated items are generally received in output-ready (X)HTML and
148 # should not be folded, crumpled, mutilated, or spindled by WordPress
149 # formatting filters. But we don't want to interfere with filters for
150 # any locally-authored posts, either.
151 #
152 # What WordPress should really have is a way for upstream filters to
153 # stop downstream filters from running at all. Since it doesn't, and
154 # since a downstream filter can't access the original copy of the text
155 # that is being filtered, what we will do here is (1) save a copy of the
156 # original text upstream, before any other filters run, and then (2)
157 # retrieve that copy downstream, after all the other filters run, *if*
158 # this is a syndicated post
159
160 add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
161 add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
162
163 add_action('atom_entry', 'feedwordpress_item_feed_data');
164
165 # Filter in original permalinks if the user wants that
166 add_filter('post_link', 'syndication_permalink', /*priority=*/ 1, /*arguments=*/ 3);
167
168 # When foreign URLs are used for permalinks in feeds or display
169 # contexts, they need to be escaped properly.
170 add_filter('the_permalink', 'syndication_permalink_escaped');
171 add_filter('the_permalink_rss', 'syndication_permalink_escaped');
172
173 add_filter('post_comments_feed_link', 'syndication_comments_feed_link');
174
175 # WTF? By default, wp_insert_link runs incoming link_url and link_rss
176 # URIs through default filters that include `wp_kses()`. But `wp_kses()`
177 # just happens to escape any occurrence of & to &amp; -- which just
178 # happens to fuck up any URI with a & to separate GET parameters.
179 remove_filter('pre_link_rss', 'wp_filter_kses');
180 remove_filter('pre_link_url', 'wp_filter_kses');
181
182 # Admin menu
183 add_action('admin_menu', 'fwp_add_pages');
184 add_action('admin_notices', 'fwp_check_debug');
185
186 add_action('admin_menu', 'feedwordpress_add_post_edit_controls');
187 add_action('save_post', 'feedwordpress_save_post_edit_controls');
188
189 add_action('admin_footer', array('FeedWordPress', 'admin_footer'));
190
191 # Inbound XML-RPC update methods
192 add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
193
194 # Outbound XML-RPC ping reform
195 remove_action('publish_post', 'generic_ping'); // WP 1.5.x
196 remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2
197 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3
198
199 add_action('publish_post', 'fwp_publish_post_hook', 5, 1);
200 add_action('do_pings', 'fwp_do_pings', 10, 1);
201 add_action('feedwordpress_update', 'fwp_hold_pings');
202 add_action('feedwordpress_update_complete', 'fwp_release_pings');
203
204 # Hook in logging functions only if the logging option is ON
205 $update_logging = get_option('feedwordpress_update_logging');
206 if ($update_logging == 'yes') :
207 add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
208 add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
209 add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
210 add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
211 add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
212 endif;
213
214 if (FeedWordPress::update_requested()) :
215 if (FEEDWORDPRESS_DEBUG) :
216 add_action('post_syndicated_item', 'debug_out_feedwordpress_post', 100);
217 add_action('update_syndicated_item', 'debug_out_feedwordpress_update_post', 100);
218 add_action('feedwordpress_update', 'debug_out_feedwordpress_update_feeds', 100);
219 add_action('feedwordpress_check_feed', 'debug_out_feedwordpress_check_feed', 100);
220 add_action('feedwordpress_update_complete', 'debug_out_feedwordpress_update_complete', 100);
221 endif;
222
223 add_action('feedwordpress_check_feed_complete', 'debug_out_feedwordpress_feed_error', 100, 3);
224 endif;
225
226 add_action('wp_footer', 'debug_out_feedwordpress_footer', -100);
227 add_action('admin_footer', 'debug_out_feedwordpress_footer', -100);
228
229 add_action('init', 'feedwordpress_clear_cache_magic_url');
230
231 # Cron-less auto-update. Hooray!
232 $autoUpdateHook = get_option('feedwordpress_automatic_updates');
233 if ($autoUpdateHook != 'init') :
234 $autoUpdateHook = 'shutdown';
235 endif;
236 add_action($autoUpdateHook, 'feedwordpress_auto_update');
237
238 add_action('init', 'feedwordpress_update_magic_url');
239
240 # Default sanitizers
241 add_filter('syndicated_item_content', array('SyndicatedPost', 'resolve_relative_uris'), 0, 2);
242 add_filter('syndicated_item_content', array('SyndicatedPost', 'sanitize_content'), 0, 2);
243
244 else :
245 # Hook in the menus, which will just point to the upgrade interface
246 add_action('admin_menu', 'fwp_add_pages');
247 endif; // if (!FeedWordPress::needs_upgrade())
248
249 function feedwordpress_auto_update () {
250 if (FeedWordPress::stale()) :
251 $feedwordpress = new FeedWordPress;
252 $feedwordpress->update();
253 endif;
254 } /* feedwordpress_auto_update () */
255
256 function feedwordpress_clear_cache_magic_url () {
257 if (FeedWordPress::clear_cache_requested()) :
258 FeedWordPress::clear_cache();
259 endif;
260 }
261
262 function feedwordpress_update_magic_url () {
263 global $wpdb;
264
265 // Explicit update request in the HTTP request (e.g. from a cron job)
266 if (FeedWordPress::update_requested()) :
267 $feedwordpress = new FeedWordPress;
268 $feedwordpress->update(FeedWordPress::update_requested_url());
269
270 if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
271 $mysqlTime = 0.0;
272 $byTime = array();
273 foreach ($wpdb->queries as $query) :
274 $time = $query[1] * 1000000.0;
275 $mysqlTime += $query[1];
276 if (!isset($byTime[$time])) : $byTime[$time] = array(); endif;
277 $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2];
278 endforeach;
279 krsort($byTime);
280
281 foreach ($byTime as $time => $querySet) :
282 foreach ($querySet as $query) :
283 print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n";
284 endforeach;
285 endforeach;
286 echo "[feedwordpress] $wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds.";
287 endif;
288
289 debug_out_feedwordpress_footer();
290
291 // Magic URL should return nothing but a 200 OK header packet
292 // when successful.
293 exit;
294 endif;
295 } /* feedwordpress_magic_update_url () */
296
297 ################################################################################
298 ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
299 ################################################################################
300
301 function log_feedwordpress_post ($id) {
302 $post = wp_get_single_post($id);
303 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
304 ."'{$post->post_title}' ({$post->post_date})");
305 }
306
307 function log_feedwordpress_update_post ($id) {
308 $post = wp_get_single_post($id);
309 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
310 ."'{$post->post_title}' ({$post->post_date})"
311 ." (as of {$post->post_modified})");
312 }
313
314 function log_feedwordpress_update_feeds ($uri) {
315 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
316 }
317
318 function log_feedwordpress_check_feed ($feed) {
319 $uri = $feed['link/uri']; $name = $feed['link/name'];
320 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
321 }
322
323 function log_feedwordpress_update_complete ($delta) {
324 $mesg = array();
325 if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
326 if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
327 if (empty($mesg)) $mesg[] = 'nothing changed';
328
329 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
330 .(is_null($delta) ? "Error: I don't syndicate that URI"
331 : implode(' and ', $mesg)));
332 }
333
334 function debug_out_feedwordpress_post ($id) {
335 $post = wp_get_single_post($id);
336 print ("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
337 ."'{$post->post_title}' ({$post->post_date})\n");
338 }
339
340 function debug_out_feedwordpress_update_post ($id) {
341 $post = wp_get_single_post($id);
342 print ("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
343 ."'{$post->post_title}' ({$post->post_date})"
344 ." (as of {$post->post_modified})\n");
345 }
346
347 function debug_out_feedwordpress_update_feeds ($uri) {
348 print ("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')\n");
349 }
350
351 function debug_out_feedwordpress_check_feed ($feed) {
352 $uri = $feed['link/uri']; $name = $feed['link/name'];
353 print ("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>\n");
354 }
355
356 function debug_out_feedwordpress_update_complete ($delta) {
357 $mesg = array();
358 if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
359 if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
360 if (empty($mesg)) $mesg[] = 'nothing changed';
361
362 print ("[".date('Y-m-d H:i:s')."][feedwordpress] "
363 .(is_null($delta) ? "Error: I don't syndicate that URI"
364 : implode(' and ', $mesg))."\n");
365 }
366
367 function debug_out_feedwordpress_feed_error ($feed, $added, $dt) {
368 if (is_wp_error($added)) :
369 $mesgs = $added->get_error_messages();
370 foreach ($mesgs as $mesg) :
371 echo "[feedwordpress] Error updating [{$feed['link/uri']}]: $mesg\n";
372 endforeach;
373 endif;
374 }
375
376 function debug_out_human_readable_bytes ($quantity) {
377 $quantity = (int) $quantity;
378 $magnitude = 'B';
379 $orders = array('KB', 'MB', 'GB', 'TB');
380 while (($quantity > 1024) and (count($orders) > 0)) :
381 $quantity = floor($quantity / 1024);
382 $magnitude = array_shift($orders);
383 endwhile;
384 return "${quantity} ${magnitude}";
385 }
386
387 function debug_out_feedwordpress_footer () {
388 if (FeedWordPress::diagnostic_on('memory_usage')) :
389 if (function_exists('memory_get_usage')) :
390 FeedWordPress::diagnostic ('memory_usage', "Memory: Current usage: ".debug_out_human_readable_bytes(memory_get_usage()));
391 endif;
392 if (function_exists('memory_get_peak_usage')) :
393 FeedWordPress::diagnostic ('memory_usage', "Memory: Peak usage: ".debug_out_human_readable_bytes(memory_get_peak_usage()));
394 endif;
395 endif;
396 } /* debug_out_feedwordpress_footer() */
397
398 ################################################################################
399 ## TEMPLATE API: functions to make your templates syndication-aware ############
400 ################################################################################
401
402 /**
403 * is_syndicated: Tests whether the current post in a Loop context, or a post
404 * given by ID number, was syndicated by FeedWordPress. Useful for templates
405 * to determine whether or not to retrieve syndication-related meta-data in
406 * displaying a post.
407 *
408 * @param int $id The post to check for syndicated status. Defaults to the current post in a Loop context.
409 * @return bool TRUE if the post's meta-data indicates it was syndicated; FALSE otherwise
410 */
411 function is_syndicated ($id = NULL) {
412 return (strlen(get_syndication_feed_id($id)) > 0);
413 } /* function is_syndicated() */
414
415 function get_syndication_source_link ($original = NULL, $id = NULL) {
416 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
417 endif;
418
419 if ($original) : $vals = get_post_custom_values('syndication_source_uri_original', $id);
420 else : $vals = array();
421 endif;
422
423 if (count($vals) == 0) : $vals = get_post_custom_values('syndication_source_uri', $id);
424 endif;
425
426 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
427
428 return $ret;
429 } /* function get_syndication_source_link() */
430
431 function the_syndication_source_link ($original = NULL, $id = NULL) {
432 echo get_syndication_source_link($original, $id);
433 }
434
435 function feedwordpress_display_url ($url, $before = 60, $after = 0) {
436 $bits = parse_url($url);
437
438 // Strip out crufty subdomains
439 $bits['host'] = preg_replace('/^www[0-9]*\./i', '', $bits['host']);
440
441 // Reassemble bit-by-bit with minimum of crufty elements
442 $url = (isset($bits['user'])?$bits['user'].'@':'')
443 .(isset($bits['host'])?$bits['host']:'')
444 .(isset($bits['path'])?$bits['path']:'')
445 .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
446 .(isset($bits['query'])?'?'.$bits['query']:'');
447
448 if (strlen($url) > ($before+$after)) :
449 $url = substr($url, 0, $before).'…'.substr($url, 0 - $after, $after);
450 endif;
451
452 return $url;
453 }
454
455 function get_syndication_source ($original = NULL, $id = NULL) {
456 if (is_null($original)) :
457 $original = FeedWordPress::use_aggregator_source_data();
458 endif;
459
460 if ($original) :
461 $vals = get_post_custom_values('syndication_source_original', $id);
462 else :
463 $vals = array();
464 endif;
465
466 if (count($vals) == 0) :
467 $vals = get_post_custom_values('syndication_source', $id);
468 endif;
469
470 if (count($vals) > 0) :
471 $ret = $vals[0];
472 else :
473 $ret = NULL;
474 endif;
475
476 if (is_null($ret) or strlen(trim($ret)) == 0) :
477 // Fall back to URL of blog
478 $ret = feedwordpress_display_url(get_syndication_source_link());
479 endif;
480
481 return $ret;
482 } /* function get_syndication_source() */
483
484 function the_syndication_source ($original = NULL, $id = NULL) { echo get_syndication_source($original, $id); }
485
486 function get_syndication_feed ($original = NULL, $id = NULL) {
487 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
488 endif;
489
490 if ($original) : $vals = get_post_custom_values('syndication_feed_original', $id);
491 else : $vals = array();
492 endif;
493
494 if (count($vals) == 0) : $vals = get_post_custom_values('syndication_feed', $id);
495 endif;
496
497 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
498
499 return $ret;
500 } /* function get_syndication_feed() */
501
502 function the_syndication_feed ($original = NULL, $id = NULL) { echo get_syndication_feed($original, $id); }
503
504 function get_syndication_feed_guid ($original = NULL, $id = NULL) {
505 if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
506 endif;
507
508 if ($original) : $vals = get_post_custom_values('syndication_source_id_original', $id);
509 else : $vals = array();
510 endif;
511
512 if (count($vals) == 0) : $vals = array(get_feed_meta('feed/id', $id));
513 endif;
514
515 if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
516
517 return $ret;
518 } /* function get_syndication_feed_guid () */
519
520 function the_syndication_feed_guid ($original = NULL, $id = NULL) { echo get_syndication_feed_guid($original, $id); }
521
522 function get_syndication_feed_id ($id = NULL) { list($u) = get_post_custom_values('syndication_feed_id', $id); return $u; }
523 function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); }
524
525 $feedwordpress_linkcache = array (); // only load links from database once
526 function get_syndication_feed_object ($id = NULL) {
527 global $feedwordpress_linkcache;
528
529 $link = NULL;
530
531 $feed_id = get_syndication_feed_id($id);
532 if (strlen($feed_id) > 0):
533 if (isset($feedwordpress_linkcache[$feed_id])) :
534 $link = $feedwordpress_linkcache[$feed_id];
535 else :
536 $link = new SyndicatedLink($feed_id);
537 $feedwordpress_linkcache[$feed_id] = $link;
538 endif;
539 endif;
540 return $link;
541 }
542
543 function get_feed_meta ($key, $id = NULL) {
544 $ret = NULL;
545
546 $link = get_syndication_feed_object($id);
547 if (is_object($link) and isset($link->settings[$key])) :
548 $ret = $link->settings[$key];
549 endif;
550 return $ret;
551 } /* get_feed_meta() */
552
553 function get_syndication_permalink ($id = NULL) {
554 list($u) = get_post_custom_values('syndication_permalink', $id); return $u;
555 }
556 function the_syndication_permalink ($id = NULL) {
557 echo get_syndication_permalink($id);
558 }
559
560 /**
561 * get_local_permalink: returns a string containing the internal permalink
562 * for a post (whether syndicated or not) on your local WordPress installation.
563 * This may be useful if you want permalinks to point to the original source of
564 * an article for most purposes, but want to retrieve a URL for the local
565 * representation of the post for one or two limited purposes (for example,
566 * linking to a comments page on your local aggregator site).
567 *
568 * @param $id The numerical ID of the post to get the permalink for. If empty,
569 * defaults to the current post in a Loop context.
570 * @return string The URL of the local permalink for this post.
571 *
572 * @uses get_permalink()
573 * @global $feedwordpress_the_original_permalink
574 *
575 * @since 2010.0217
576 */
577 function get_local_permalink ($id = NULL) {
578 global $feedwordpress_the_original_permalink;
579
580 // get permalink, and thus activate filter and force global to be filled
581 // with original URL.
582 $url = get_permalink($id);
583 return $feedwordpress_the_original_permalink;
584 } /* get_local_permalink() */
585
586 /**
587 * the_original_permalink: displays the contents of get_original_permalink()
588 *
589 * @param $id The numerical ID of the post to get the permalink for. If empty,
590 * defaults to the current post in a Loop context.
591 *
592 * @uses get_local_permalinks()
593 * @uses apply_filters
594 *
595 * @since 2010.0217
596 */
597 function the_local_permalink ($id = NULL) {
598 print apply_filters('the_permalink', get_local_permalink($id));
599 } /* function the_local_permalink() */
600
601 ################################################################################
602 ## FILTERS: syndication-aware handling of post data for templates and feeds ####
603 ################################################################################
604
605 $feedwordpress_the_syndicated_content = NULL;
606 $feedwordpress_the_original_permalink = NULL;
607
608 function feedwordpress_preserve_syndicated_content ($text) {
609 global $feedwordpress_the_syndicated_content;
610
611 $globalExpose = (get_option('feedwordpress_formatting_filters') == 'yes');
612 $localExpose = get_post_custom_values('_feedwordpress_formatting_filters');
613 $expose = ($globalExpose or ((count($localExpose) > 0) and $localExpose[0]));
614
615 if ( is_syndicated() and !$expose ) :
616 $feedwordpress_the_syndicated_content = $text;
617 else :
618 $feedwordpress_the_syndicated_content = NULL;
619 endif;
620 return $text;
621 }
622
623 function feedwordpress_restore_syndicated_content ($text) {
624 global $feedwordpress_the_syndicated_content;
625
626 if ( !is_null($feedwordpress_the_syndicated_content) ) :
627 $text = $feedwordpress_the_syndicated_content;
628 endif;
629
630 return $text;
631 }
632
633 function feedwordpress_item_feed_data () {
634 // In a post context....
635 if (is_syndicated()) :
636 ?>
637 <source>
638 <title><?php print htmlspecialchars(get_syndication_source()); ?></title>
639 <link rel="alternate" type="text/html" href="<?php print htmlspecialchars(get_syndication_source_link()); ?>" />
640 <link rel="self" href="<?php print htmlspecialchars(get_syndication_feed()); ?>" />
641 <?php
642 $id = get_syndication_feed_guid();
643 if (strlen($id) > 0) :
644 ?>
645 <id><?php print htmlspecialchars($id); ?></id>
646 <?php
647 endif;
648 $updated = get_feed_meta('feed/updated');
649 if (strlen($updated) > 0) : ?>
650 <updated><?php print $updated; ?></updated>
651 <?php
652 endif;
653 ?>
654 </source>
655 <?php
656 endif;
657 }
658
659 /**
660 * syndication_permalink: Allow WordPress to use the original remote URL of
661 * syndicated posts as their permalink. Can be turned on or off by by setting in
662 * Syndication => Posts & Links. Saves the old internal permalink in a global
663 * variable for later use.
664 *
665 * @param string $permalink The internal permalink
666 * @return string The new permalink. Same as the old if the post is not
667 * syndicated, or if FWP is set to use internal permalinks, or if the post
668 * was syndicated, but didn't have a proper permalink recorded.
669 *
670 * @uses SyndicatedLink::setting()
671 * @uses get_syndication_permalink()
672 * @uses get_syndication_feed_object()
673 * @uses url_to_postid()
674 * @global $id
675 * @global $feedwordpress_the_original_permalink
676 */
677 function syndication_permalink ($permalink = '', $post = null, $leavename = false) {
678 global $id;
679 global $feedwordpress_the_original_permalink;
680
681 // Save the local permalink in case we need to retrieve it later.
682 $feedwordpress_the_original_permalink = $permalink;
683
684 if (is_object($post) and isset($post->ID) and !empty($post->ID)) :
685 // Use the post ID we've been provided with.
686 $postId = $post->ID;
687 elseif (is_string($permalink) and strlen($permalink) > 0) :
688 // Map this permalink to a post ID so we can get the correct
689 // permalink even outside of the Post Loop. Props Björn.
690 $postId = url_to_postid($permalink);
691 else :
692 // If the permalink string is empty but Post Loop context
693 // provides an id.
694 $postId = $id;
695 endif;
696
697 $munge = false;
698 $link = get_syndication_feed_object($postId);
699 if (is_object($link)) :
700 $munge = ($link->setting('munge permalink', 'munge_permalink', 'yes') != 'no');
701 endif;
702
703 if ($munge):
704 $uri = get_syndication_permalink($postId);
705 $permalink = ((strlen($uri) > 0) ? $uri : $permalink);
706 endif;
707 return $permalink;
708 } /* function syndication_permalink () */
709
710 /**
711 * syndication_permalink_escaped: Escape XML special characters in syndicated
712 * permalinks when used in feed contexts and HTML display contexts.
713 *
714 * @param string $permalink
715 * @return string
716 *
717 * @uses is_syndicated()
718 * @uses FeedWordPress::munge_permalinks()
719 *
720 */
721 function syndication_permalink_escaped ($permalink) {
722 /* FIXME: This should review link settings not just global settings */
723 if (is_syndicated() and FeedWordPress::munge_permalinks()) :
724 // This is a foreign link; WordPress can't vouch for its not
725 // having any entities that need to be &-escaped. So we'll do
726 // it here.
727 $permalink = esc_html($permalink);
728 endif;
729 return $permalink;
730 } /* function syndication_permalink_escaped() */
731
732 /**
733 * syndication_comments_feed_link: Escape XML special characters in comments
734 * feed links
735 *
736 * @param string $link
737 * @return string
738 *
739 * @uses is_syndicated()
740 * @uses FeedWordPress::munge_permalinks()
741 */
742 function syndication_comments_feed_link ($link) {
743 global $feedwordpress_the_original_permalink, $id;
744
745 if (is_syndicated() and FeedWordPress::munge_permalinks()) :
746 // If the source post provided a comment feed URL using
747 // wfw:commentRss or atom:link/@rel="replies" we can make use of
748 // that value here.
749 $source = get_syndication_feed_object();
750 $replacement = NULL;
751 if ($source->setting('munge comments feed links', 'munge_comments_feed_links', 'yes') != 'no') :
752 $commentFeeds = get_post_custom_values('wfw:commentRSS');
753 if (
754 is_array($commentFeeds)
755 and (count($commentFeeds) > 0)
756 and (strlen($commentFeeds[0]) > 0)
757 ) :
758 $replacement = $commentFeeds[0];
759
760 // This is a foreign link; WordPress can't vouch for its not
761 // having any entities that need to be &-escaped. So we'll do it
762 // here.
763 $replacement = esc_html($replacement);
764 endif;
765 endif;
766
767 if (is_null($replacement)) :
768 // Q: How can we get the proper feed format, since the
769 // format is, stupidly, not passed to the filter?
770 // A: Kludge kludge kludge kludge!
771 $fancy_permalinks = ('' != get_option('permalink_structure'));
772 if ($fancy_permalinks) :
773 preg_match('|/feed(/([^/]+))?/?$|', $link, $ref);
774
775 $format = (isset($ref[2]) ? $ref[2] : '');
776 if (strlen($format) == 0) : $format = get_default_feed(); endif;
777
778 $replacement = trailingslashit($feedwordpress_the_original_permalink) . 'feed';
779 if ($format != get_default_feed()) :
780 $replacement .= '/'.$format;
781 endif;
782 $replacement = user_trailingslashit($replacement, 'single_feed');
783 else :
784 // No fancy permalinks = no problem
785 // WordPress doesn't call get_permalink() to
786 // generate the comment feed URL, so the
787 // comments feed link is never munged by FWP.
788 endif;
789 endif;
790
791 if (!is_null($replacement)) : $link = $replacement; endif;
792 endif;
793 return $link;
794 } /* function syndication_comments_feed_link() */
795
796 ################################################################################
797 ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
798 ################################################################################
799
800 function fwp_add_pages () {
801 global $fwp_capability;
802 global $fwp_path;
803
804 $menu = array('Syndicated Sites', 'Syndication', $fwp_capability['manage_links'], $fwp_path.'/syndication.php', NULL);
805 if (fwp_test_wp_version(FWP_SCHEMA_27)) :
806 // add icon parameter
807 $menu[] = WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-tiny.png';
808 endif;
809
810 call_user_func_array('add_menu_page', $menu);
811 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Feeds & Updates', 'Feeds & Updates', $fwp_capability['manage_options'], $fwp_path.'/feeds-page.php');
812 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Posts & Links', 'Posts & Links', $fwp_capability['manage_options'], $fwp_path.'/posts-page.php');
813 add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Authors', 'Authors', $fwp_capability['manage_options'], $fwp_path.'/authors-page.php');
814 add_submenu_page($fwp_path.'/syndication.php', 'Categories'.FEEDWORDPRESS_AND_TAGS, 'Categories'.FEEDWORDPRESS_AND_TAGS, $fwp_capability['manage_options'], $fwp_path.'/categories-page.php');
815 add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Performance', 'Performance', $fwp_capability['manage_options'], $fwp_path.'/performance-page.php');
816 add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Diagnostics', 'Diagnostics', $fwp_capability['manage_options'], $fwp_path.'/diagnostics-page.php');
817 } /* function fwp_add_pages () */
818
819 function fwp_check_debug () {
820 // This is a horrible fucking kludge that I have to do because the
821 // admin notice code is triggered before the code that updates the
822 // setting.
823 if (isset($_POST['feedwordpress_debug'])) :
824 $feedwordpress_debug = $_POST['feedwordpress_debug'];
825 else :
826 $feedwordpress_debug = get_option('feedwordpress_debug');
827 endif;
828 if ($feedwordpress_debug==='yes') :
829 ?>
830 <div class="error">
831 <p><strong>FeedWordPress warning.</strong> Debugging mode is <strong>ON</strong>.
832 While it remains on, FeedWordPress displays many diagnostic error messages,
833 warnings, and notices that are ordinarily suppressed, and also turns off all
834 caching of feeds. Use with caution: this setting is absolutely inappropriate
835 for a production server.</p>
836 </div>
837 <?php
838 endif;
839 } /* function fwp_check_debug () */
840
841 ################################################################################
842 ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
843 ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
844 ################################################################################
845
846 $fwp_held_ping = NULL; // NULL: not holding pings yet
847
848 function fwp_hold_pings () {
849 global $fwp_held_ping;
850 if (is_null($fwp_held_ping)):
851 $fwp_held_ping = 0; // 0: ready to hold pings; none yet received
852 endif;
853 }
854
855 function fwp_release_pings () {
856 global $fwp_held_ping;
857 if ($fwp_held_ping):
858 if (function_exists('wp_schedule_single_event')) :
859 wp_schedule_single_event(time(), 'do_pings');
860 else :
861 generic_ping($fwp_held_ping);
862 endif;
863 endif;
864 $fwp_held_ping = NULL; // NULL: not holding pings anymore
865 }
866
867 function fwp_do_pings () {
868 if (!is_null($fwp_held_ping) and $post_id) : // Defer until we're done updating
869 $fwp_held_ping = $post_id;
870 elseif (function_exists('do_all_pings')) :
871 do_all_pings();
872 else :
873 generic_ping($fwp_held_ping);
874 endif;
875 }
876
877 function fwp_publish_post_hook ($post_id) {
878 global $fwp_held_ping;
879
880 if (!is_null($fwp_held_ping)) : // Syndicated post. Don't mark with _pingme
881 if ( defined('XMLRPC_REQUEST') )
882 do_action('xmlrpc_publish_post', $post_id);
883 if ( defined('APP_REQUEST') )
884 do_action('app_publish_post', $post_id);
885
886 if ( defined('WP_IMPORTING') )
887 return;
888
889 // Defer sending out pings until we finish updating
890 $fwp_held_ping = $post_id;
891 else :
892 if (function_exists('_publish_post_hook')) : // WordPress 2.3
893 _publish_post_hook($post_id);
894 endif;
895 endif;
896 }
897
898 function feedwordpress_add_post_edit_controls () {
899 add_meta_box('feedwordpress-post-controls', __('Syndication'), 'feedwordpress_post_edit_controls', 'post', 'side', 'high');
900 } // function FeedWordPress::postEditControls
901
902 function feedwordpress_post_edit_controls () {
903 global $post;
904
905 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $post->ID);
906 $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
907
908 if (is_syndicated($post->ID)) :
909 ?>
910 <p>This is a syndicated post, which originally appeared at
911 <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>.
912 <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
913
914 <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
915 <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
916 If set, FeedWordPress will not overwrite the changes you make manually
917 to this post, if the syndicated content is updated on the
918 feed.</label></p>
919 <?php
920 else :
921 ?>
922 <p>This post was created locally at this website.</p>
923 <?php
924 endif;
925 } // function feedwordpress_post_edit_controls () */
926
927 function feedwordpress_save_post_edit_controls ( $post_id ) {
928 global $post;
929
930 if (!isset($_POST['feedwordpress_noncename']) or !wp_verify_nonce($_POST['feedwordpress_noncename'], plugin_basename(__FILE__))) :
931 return $post_id;
932 endif;
933
934 // Verify if this is an auto save routine. If it is our form has
935 // not been submitted, so we don't want to do anything.
936 if ( defined('DOING_AUTOSAVE') and DOING_AUTOSAVE ) :
937 return $post_id;
938 endif;
939
940 // Check permissions
941 if ( !current_user_can( 'edit_'.$_POST['post_type'], $post_id) ) :
942 return $post_id;
943 endif;
944
945 // OK, we're golden. Now let's save some data.
946 if (isset($_POST['freeze_updates'])) :
947 update_post_meta($post_id, '_syndication_freeze_updates', $_POST['freeze_updates']);
948 $ret = $_POST['freeze_updates'];
949 else :
950 delete_post_meta($post_id, '_syndication_freeze_updates');
951 $ret = NULL;
952 endif;
953
954 return $ret;
955 } // function feedwordpress_save_edit_controls
956
957 ################################################################################
958 ## class FeedWordPress #########################################################
959 ################################################################################
960
961 // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
962 class FeedWordPress {
963 var $strip_attrs = array (
964 array('[a-z]+', 'style'),
965 array('[a-z]+', 'target'),
966 );
967 var $uri_attrs = array (
968 array('a', 'href'),
969 array('applet', 'codebase'),
970 array('area', 'href'),
971 array('blockquote', 'cite'),
972 array('body', 'background'),
973 array('del', 'cite'),
974 array('form', 'action'),
975 array('frame', 'longdesc'),
976 array('frame', 'src'),
977 array('iframe', 'longdesc'),
978 array('iframe', 'src'),
979 array('head', 'profile'),
980 array('img', 'longdesc'),
981 array('img', 'src'),
982 array('img', 'usemap'),
983 array('input', 'src'),
984 array('input', 'usemap'),
985 array('ins', 'cite'),
986 array('link', 'href'),
987 array('object', 'classid'),
988 array('object', 'codebase'),
989 array('object', 'data'),
990 array('object', 'usemap'),
991 array('q', 'cite'),
992 array('script', 'src')
993 );
994
995 var $feeds = NULL;
996
997 # function FeedWordPress (): Contructor; retrieve a list of feeds
998 function FeedWordPress () {
999 $this->feeds = array ();
1000 $links = FeedWordPress::syndicated_links();
1001 if ($links): foreach ($links as $link):
1002 $this->feeds[] = new SyndicatedLink($link);
1003 endforeach; endif;
1004 } // FeedWordPress::FeedWordPress ()
1005
1006 # function update (): polls for updates on one or more Contributor feeds
1007 #
1008 # Arguments:
1009 # ----------
1010 # * $uri (string): either the URI of the feed to poll, the URI of the
1011 # (human-readable) website whose feed you want to poll, or NULL.
1012 #
1013 # If $uri is NULL, then FeedWordPress will poll any feeds that are
1014 # ready for polling. It will not poll feeds that are marked as
1015 # "Invisible" Links (signifying that the subscription has been
1016 # de-activated), or feeds that are not yet stale according to their
1017 # TTL setting (which is either set in the feed, or else set
1018 # randomly within a window of 30 minutes - 2 hours).
1019 #
1020 # Returns:
1021 # --------
1022 # * Normally returns an associative array, with 'new' => the number
1023 # of new posts added during the update, and 'updated' => the number
1024 # of old posts that were updated during the update. If both numbers
1025 # are zero, there was no change since the last poll on that URI.
1026 #
1027 # * Returns NULL if URI it was passed was not a URI that this
1028 # installation of FeedWordPress syndicates.
1029 #
1030 # Effects:
1031 # --------
1032 # * One or more feeds are polled for updates
1033 #
1034 # * If the feed Link does not have a hardcoded name set, its Link
1035 # Name is synchronized with the feed's title element
1036 #
1037 # * If the feed Link does not have a hardcoded URI set, its Link URI
1038 # is synchronized with the feed's human-readable link element
1039 #
1040 # * If the feed Link does not have a hardcoded description set, its
1041 # Link Description is synchronized with the feed's description,
1042 # tagline, or subtitle element.
1043 #
1044 # * The time of polling is recorded in the feed's settings, and the
1045 # TTL (time until the feed is next available for polling) is set
1046 # either from the feed (if it is supplied in the ttl or syndication
1047 # module elements) or else from a randomly-generated time window
1048 # (between 30 minutes and 2 hours).
1049 #
1050 # * New posts from the polled feed are added to the WordPress store.
1051 #
1052 # * Updates to existing posts since the last poll are mirrored in the
1053 # WordPress store.
1054 #
1055 function update ($uri = null, $crash_ts = null) {
1056 global $wpdb;
1057
1058 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
1059 return NULL;
1060 endif;
1061
1062 if (!is_null($uri)) :
1063 $uri = trim($uri);
1064 else : // Update all
1065 update_option('feedwordpress_last_update_all', time());
1066 endif;
1067
1068 do_action('feedwordpress_update', $uri);
1069
1070 if (is_null($crash_ts)) :
1071 $crash_dt = (int) get_option('feedwordpress_update_time_limit');
1072 if ($crash_dt > 0) :
1073 $crash_ts = time() + $crash_dt;
1074 else :
1075 $crash_ts = NULL;
1076 endif;
1077 endif;
1078
1079 // Randomize order for load balancing purposes
1080 $feed_set = $this->feeds;
1081 shuffle($feed_set);
1082
1083 // Loop through and check for new posts
1084 $delta = NULL;
1085 foreach ($feed_set as $feed) :
1086 if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit
1087 break;
1088 endif;
1089
1090 $pinged_that = (is_null($uri) or ($uri=='*') or in_array($uri, array($feed->uri(), $feed->homepage())));
1091
1092 if (!is_null($uri)) : // A site-specific ping always updates
1093 $timely = true;
1094 else :
1095 $timely = $feed->stale();
1096 endif;
1097
1098 if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
1099 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
1100 endif;
1101
1102 if ($pinged_that and $timely) :
1103 do_action('feedwordpress_check_feed', $feed->settings);
1104 $start_ts = time();
1105 $added = $feed->poll($crash_ts);
1106 do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
1107
1108 if (is_array($added)) : // Success
1109 if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1110 if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1111 endif;
1112 endif;
1113 endforeach;
1114
1115 do_action('feedwordpress_update_complete', $delta);
1116
1117 return $delta;
1118 }
1119
1120 function stale () {
1121 if (get_option('feedwordpress_automatic_updates')) :
1122 // Do our best to avoid possible simultaneous
1123 // updates by getting up-to-the-minute settings.
1124
1125 $last = get_option('feedwordpress_last_update_all');
1126
1127 // If we haven't updated all yet, give it a time window
1128 if (false === $last) :
1129 $ret = false;
1130 update_option('feedwordpress_last_update_all', time());
1131
1132 // Otherwise, check against freshness interval
1133 elseif (is_numeric($last)) : // Expect a timestamp
1134 $freshness = get_option('feedwordpress_freshness');
1135 if (false === $freshness) : // Use default
1136 $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
1137 endif;
1138 $ret = ( (time() - $last) > $freshness);
1139
1140 // This should never happen.
1141 else :
1142 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__);
1143 endif;
1144
1145 else :
1146 $ret = false;
1147 endif;
1148 return $ret;
1149 } // FeedWordPress::stale()
1150
1151 function clear_cache_requested () {
1152 return (
1153 isset($_GET['clear_cache'])
1154 and $_GET['clear_cache']
1155 );
1156 } /* FeedWordPress::clear_cache_requested() */
1157
1158 function update_requested () {
1159 return (
1160 isset($_REQUEST['update_feedwordpress'])
1161 and $_REQUEST['update_feedwordpress']
1162 );
1163 } // FeedWordPress::update_requested()
1164
1165 function update_requested_url () {
1166 $ret = null;
1167
1168 if (($_REQUEST['update_feedwordpress']=='*')
1169 or (preg_match('|^http://.*|i', $_REQUEST['update_feedwordpress']))) :
1170 $ret = $_REQUEST['update_feedwordpress'];
1171 endif;
1172
1173 return $ret;
1174 } // FeedWordPress::update_requested_url()
1175
1176 function syndicate_link ($name, $uri, $rss) {
1177 global $wpdb;
1178
1179 // Get the category ID#
1180 $cat_id = FeedWordPress::link_category_id();
1181
1182 // WordPress gets cranky if there's no homepage URI
1183 if (!isset($uri) or strlen($uri)<1) : $uri = $rss; endif;
1184
1185 if (function_exists('wp_insert_link')) : // WordPress 2.x
1186 if (FeedWordPressCompatibility::test_version(0, FWP_SCHEMA_21)) :
1187 // Morons.
1188 $name = $wpdb->escape($name);
1189 $uri = $wpdb->escape($uri);
1190 $rss = $wpdb->escape($rss);
1191
1192 // Comes in as a single category
1193 $linkCats = $cat_id;
1194 else :
1195 // Comes in as an array of categories
1196 $linkCats = array($cat_id);
1197 endif;
1198
1199 $link_id = wp_insert_link(array(
1200 "link_name" => $name,
1201 "link_url" => $uri,
1202 "link_category" => $linkCats,
1203 "link_rss" => $rss
1204 ));
1205 else : // WordPress 1.5.x
1206 $result = $wpdb->query("
1207 INSERT INTO $wpdb->links
1208 SET
1209 link_name = '".$wpdb->escape($name)."',
1210 link_url = '".$wpdb->escape($uri)."',
1211 link_category = '".$wpdb->escape($cat_id)."',
1212 link_rss = '".$wpdb->escape($rss)."'
1213 ");
1214 $link_id = $wpdb->insert_id;
1215 endif;
1216 return $link_id;
1217 } // function FeedWordPress::syndicate_link()
1218
1219 /*static*/ function syndicated_status ($what, $default) {
1220 $ret = get_option("feedwordpress_syndicated_{$what}_status");
1221 if (!$ret) :
1222 $ret = $default;
1223 endif;
1224 return $ret;
1225 } /* FeedWordPress::syndicated_status() */
1226
1227 function on_unfamiliar ($what = 'author', $override = NULL) {
1228 $set = array(
1229 'author' => array('create', 'default', 'filter'),
1230 'category' => array('create', 'tag', 'default', 'filter'),
1231 );
1232
1233 if (is_string($override)) :
1234 $ret = strtolower($override);
1235 else :
1236 $ret = NULL;
1237 endif;
1238
1239 if (!is_numeric($override) and !in_array($ret, $set[$what])) :
1240 $ret = get_option('feedwordpress_unfamiliar_'.$what);
1241 if (!is_numeric($ret) and !in_array($ret, $set[$what])) :
1242 $ret = 'create';
1243 endif;
1244 endif;
1245
1246 return $ret;
1247 } // function FeedWordPress::on_unfamiliar()
1248
1249 function null_email_set () {
1250 $base = get_option('feedwordpress_null_email_set');
1251
1252 if ($base===false) :
1253 $ret = array('noreply@blogger.com'); // default
1254 else :
1255 $ret = array_map('strtolower',
1256 array_map('trim', explode("\n", $base)));
1257 endif;
1258 $ret = apply_filters('syndicated_item_author_null_email_set', $ret);
1259 return $ret;
1260
1261 } /* FeedWordPress::null_email_set () */
1262
1263 function is_null_email ($email) {
1264 $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set());
1265 $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email);
1266 return $ret;
1267 } /* FeedWordPress::is_null_email () */
1268
1269 function use_aggregator_source_data () {
1270 $ret = get_option('feedwordpress_use_aggregator_source_data');
1271 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1272 }
1273
1274 /**
1275 * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1276 * should rewrite permalinks for syndicated items to reflect their
1277 * original location.
1278 *
1279 * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1280 */
1281 /*static*/ function munge_permalinks () {
1282 return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1283 } /* FeedWordPress::munge_permalinks() */
1284
1285 function syndicated_links ($args = array()) {
1286 $contributors = FeedWordPress::link_category_id();
1287 $links = get_bookmarks(array_merge(
1288 array("category" => $contributors),
1289 $args
1290 ));
1291 return $links;
1292 } // function FeedWordPress::syndicated_links()
1293
1294 function link_category_id () {
1295 global $wpdb, $wp_db_version;
1296
1297 $cat_id = get_option('feedwordpress_cat_id');
1298
1299 // If we don't yet have the category ID stored, search by name
1300 if (!$cat_id) :
1301 $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY);
1302
1303 if ($cat_id) :
1304 // We found it; let's stamp it.
1305 update_option('feedwordpress_cat_id', $cat_id);
1306 endif;
1307
1308 // If we *do* have the category ID stored, verify that it exists
1309 else :
1310 $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id');
1311 endif;
1312
1313 // If we could not find an appropriate link category,
1314 // make a new one for ourselves.
1315 if (!$cat_id) :
1316 $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY);
1317
1318 // Stamp it
1319 update_option('feedwordpress_cat_id', $cat_id);
1320 endif;
1321
1322 return $cat_id;
1323 } // function FeedWordPress::link_category_id()
1324
1325 # Upgrades and maintenance...
1326 function needs_upgrade () {
1327 global $wpdb;
1328 $fwp_db_version = get_option('feedwordpress_version');
1329 $ret = false; // innocent until proven guilty
1330 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
1331 // This is an older version or a fresh install. Does it
1332 // require a database upgrade or database initialization?
1333 if ($fwp_db_version <= 0.96) :
1334 // Yes. Check to see whether this is a fresh install or an upgrade.
1335 $syn = $wpdb->get_col("
1336 SELECT post_id
1337 FROM $wpdb->postmeta
1338 WHERE meta_key = 'syndication_feed'
1339 ");
1340 if (count($syn) > 0) : // contains at least one syndicated post
1341 $ret = true;
1342 else : // fresh install; brand it as ours
1343 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1344 endif;
1345 elseif ($fwp_db_version < 2009.0707) :
1346 // We need to clear out any busted AJAX crap
1347 if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
1348 $wpdb->query("
1349 DELETE FROM $wpdb->usermeta
1350 WHERE LOCATE('feedwordpress', meta_key)
1351 AND LOCATE('box', meta_key);
1352 ");
1353 endif;
1354 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1355 else :
1356 // No. Just brand it with the new version.
1357 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1358 endif;
1359 endif;
1360 return $ret;
1361 }
1362
1363 function upgrade_database ($from = NULL) {
1364 global $wpdb;
1365
1366 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1367
1368 switch ($from) :
1369 case 0.96:
1370 // Dropping legacy upgrade code. If anyone is still
1371 // using 0.96 and just now decided to upgrade, well, I'm
1372 // sorry about that. You'll just have to cope with a few
1373 // duplicate posts.
1374
1375 // Mark the upgrade as successful.
1376 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1377 endswitch;
1378 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1379 } /* FeedWordPress::upgrade_database() */
1380
1381 function has_guid_index () {
1382 global $wpdb;
1383
1384 $found = false; // Guilty until proven innocent.
1385
1386 $results = $wpdb->get_results("
1387 SHOW INDEXES FROM {$wpdb->posts}
1388 ");
1389 if ($results) :
1390 foreach ($results as $index) :
1391 if (isset($index->Column_name)
1392 and ('guid' == $index->Column_name)) :
1393 $found = true;
1394 endif;
1395 endforeach;
1396 endif;
1397 return $found;
1398 } /* FeedWordPress::has_guid_index () */
1399
1400 function create_guid_index () {
1401 global $wpdb;
1402
1403 $wpdb->query("
1404 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
1405 ");
1406 } /* FeedWordPress::create_guid_index () */
1407
1408 function remove_guid_index () {
1409 global $wpdb;
1410
1411 $wpdb->query("
1412 DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
1413 ");
1414 }
1415
1416 /*static*/ function fetch ($url, $force_feed = true) {
1417 $feed = new SimplePie();
1418 $feed->set_feed_url($url);
1419 $feed->set_cache_class('WP_Feed_Cache');
1420 $feed->set_file_class('WP_SimplePie_File');
1421 $feed->set_content_type_sniffer_class('FeedWordPress_Content_Type_Sniffer');
1422 $feed->set_file_class('FeedWordPress_File');
1423 $feed->force_feed($force_feed);
1424 $feed->set_cache_duration(FeedWordPress::cache_duration());
1425 $feed->init();
1426 $feed->handle_content_type();
1427
1428 if ($feed->error()) :
1429 $ret = new WP_Error('simplepie-error', $feed->error());
1430 else :
1431 $ret = $feed;
1432 endif;
1433 return $ret;
1434 } /* FeedWordPress::fetch () */
1435
1436 function clear_cache () {
1437 global $wpdb;
1438
1439 // Just in case, clear out any old MagpieRSS cache records.
1440 $magpies = $wpdb->query("
1441 DELETE FROM {$wpdb->options}
1442 WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32
1443 ");
1444
1445 // The WordPress SimplePie module stores its cached feeds as
1446 // transient records in the options table. The data itself is
1447 // stored in `_transient_feed_{md5 of url}` and the last-modified
1448 // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
1449 // these records are stored in `_transient_timeout_feed_{md5}`.
1450 // Since the md5 is always 32 characters in length, the
1451 // option_name is always over 32 characters.
1452 $simplepies = $wpdb->query("
1453 DELETE FROM {$wpdb->options}
1454 WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
1455 ");
1456 $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each
1457
1458 return ($magpies + $simplepies);
1459 } /* FeedWordPress::clear_cache () */
1460
1461 function cache_duration () {
1462 $duration = NULL;
1463 if (defined('FEEDWORDPRESS_CACHE_AGE')) :
1464 $duration = FEEDWORDPRESS_CACHE_AGE;
1465 endif;
1466 return $duration;
1467 }
1468 function cache_lifetime ($duration) {
1469 // Check for explicit setting of a lifetime duration
1470 if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
1471 $duration = FEEDWORDPRESS_CACHE_LIFETIME;
1472
1473 // Fall back to the cache freshness duration
1474 elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1475 $duration = FEEDWORDPRESS_CACHE_AGE;
1476 endif;
1477
1478 // Fall back to WordPress default
1479 return $duration;
1480 } /* FeedWordPress::cache_lifetime () */
1481
1482 # Utility functions for handling text settings
1483 function negative ($f, $setting) {
1484 $nego = array ('n', 'no', 'f', 'false');
1485 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
1486 }
1487
1488 function affirmative ($f, $setting) {
1489 $affirmo = array ('y', 'yes', 't', 'true', 1);
1490 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
1491 }
1492
1493 # Internal debugging functions
1494 function critical_bug ($varname, $var, $line) {
1495 global $wp_version;
1496
1497 echo '<p>There may be a bug in FeedWordPress. Please <a href="'.FEEDWORDPRESS_AUTHOR_CONTACT.'">contact the author</a> and paste the following information into your e-mail:</p>';
1498 echo "\n<plaintext>";
1499 echo "Triggered at line # ".$line."\n";
1500 echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
1501 echo "WordPress version: {$wp_version}\n";
1502 echo "PHP version: ".phpversion()."\n";
1503 echo "\n";
1504 echo $varname.": "; var_dump($var); echo "\n";
1505 die;
1506 }
1507
1508 function noncritical_bug ($varname, $var, $line) {
1509 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
1510 FeedWordPress::critical_bug($varname, $var, $line);
1511 endif;
1512 }
1513
1514 function val ($v, $no_newlines = false) {
1515 ob_start();
1516 var_dump($v);
1517 $out = ob_get_contents(); ob_end_clean();
1518
1519 if ($no_newlines) :
1520 $out = preg_replace('/\s+/', " ", $out);
1521 endif;
1522 return $out;
1523 } /* FeedWordPress::val () */
1524
1525 function diagnostic_on ($level) {
1526 $show = get_option('feedwordpress_diagnostics_show', array());
1527 return (in_array($level, $show));
1528 } /* FeedWordPress::diagnostic_on () */
1529
1530 function diagnostic ($level, $out) {
1531 global $feedwordpress_admin_footer;
1532
1533 $output = get_option('feedwordpress_diagnostics_output', array());
1534
1535 $diagnostic_nesting = count(explode(":", $level));
1536
1537 if (FeedWordPress::diagnostic_on($level)) :
1538 foreach ($output as $method) :
1539 switch ($method) :
1540 case 'echo' :
1541 echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.esc_html($out)."</pre></div>";
1542 break;
1543 case 'admin_footer' :
1544 $feedwordpress_admin_footer[] = $out;
1545 break;
1546 case 'error_log' :
1547 error_log('[feedwordpress]' . $out);
1548 break;
1549 endswitch;
1550 endforeach;
1551 endif;
1552 } /* FeedWordPress::diagnostic () */
1553
1554 function admin_footer () {
1555 global $feedwordpress_admin_footer;
1556 foreach ($feedwordpress_admin_footer as $line) :
1557 echo '<div><pre>'.esc_html($line).'</pre></div>';
1558 endforeach;
1559 } /* FeedWordPress::admin_footer () */
1560 } // class FeedWordPress
1561
1562 class FeedWordPress_File extends WP_SimplePie_File {
1563 function FeedWordPress_File ($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) {
1564 WP_SimplePie_File::WP_SimplePie_File($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
1565
1566 // SimplePie makes a strongly typed check against integers with
1567 // this, but WordPress puts a string in. Which causes caching
1568 // to break and fall on its ass when SimplePie is getting a 304,
1569 // but doesn't realize it because this member is "304" instead.
1570 $this->status_code = (int) $this->status_code;
1571 }
1572 } /* class FeedWordPress_File () */
1573
1574 $feedwordpress_admin_footer = array();
1575
1576 require_once(dirname(__FILE__) . '/syndicatedpost.class.php');
1577 require_once(dirname(__FILE__) . '/syndicatedlink.class.php');
1578
1579 ################################################################################
1580 ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
1581 ################################################################################
1582
1583 function feedwordpress_xmlrpc_hook ($args = array ()) {
1584 $args['weblogUpdates.ping'] = 'feedwordpress_pong';
1585 return $args;
1586 }
1587
1588 function feedwordpress_pong ($args) {
1589 $feedwordpress = new FeedWordPress;
1590 $delta = @$feedwordpress->update($args[1]);
1591 if (is_null($delta)):
1592 return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
1593 else:
1594 $mesg = array();
1595 if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
1596 if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
1597
1598 return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
1599 endif;
1600 }
1601
1602 // take your best guess at the realname and e-mail, given a string
1603 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
1604 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
1605 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
1606 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
1607 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
1608 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
1609
1610 function parse_email_with_realname ($email) {
1611 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
1612 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
1613 $ret['email'] = $matches[1];
1614 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
1615 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
1616 $ret['email'] = $matches[4];
1617 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
1618 $ret['name'] = NULL; $ret['email'] = $matches[1];
1619 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
1620 $ret['email'] = NULL;
1621 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
1622 else :
1623 $ret['name'] = NULL; $ret['email'] = NULL;
1624 endif;
1625 return $ret;
1626 }
1627
1628