PluginProbe
FeedWordPress / 2011.0211.2
FeedWordPress v2011.0211.2
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
← All changes | feedwordpress.php +1463 -2151 2008.12142011.0211.2 View file →
@@ -1,20 +1,25 @@
1 1 <?php
2 2 /*
3 3 Plugin Name: FeedWordPress
4 -Plugin URI: http://projects.radgeek.com/feedwordpress
4 +Plugin URI: http://feedwordpress.radgeek.com/
5 5 Description: simple and flexible Atom/RSS syndication for WordPress
6 -Version: 2008.1214
6 +Version: 2011.0211.2
7 7 Author: Charles Johnson
8 8 Author URI: http://radgeek.com/
9 9 License: GPL
10 -Last modified: 2008-12-14 4:29pm PST
11 10 */
12 11
12 +/**
13 + * @package FeedWordPress
14 + * @version 2011.0211.2
15 + */
16 +
13 17 # This uses code derived from:
14 18 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
15 -# - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
19 +# - MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net>
16 20 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
21 +# - WordPress Blog Tool and Publishing Platform <http://wordpress.org/>
17 22 # according to the terms of the GNU General Public License.
18 23 #
19 24 # INSTALLATION: see readme.txt or <http://projects.radgeek.com/install>
20 25 #
@@ -28,13 +33,28 @@
28 33 # or use a cron job.
29 34
30 35 # -- Don't change these unless you know what you're doing...
31 36
32 -define ('FEEDWORDPRESS_VERSION', '2008.1214');
37 +define ('FEEDWORDPRESS_VERSION', '2011.0211.2');
33 38 define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
39 +
40 +if (!defined('FEEDWORDPRESS_BLEG')) :
41 + define ('FEEDWORDPRESS_BLEG', true);
42 +endif;
43 +
44 +// Defaults
34 45 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
46 +define ('DEFAULT_UPDATE_PERIOD', 60); // value in minutes
35 47
36 -define ('FEEDWORDPRESS_DEBUG', false);
48 +if (isset($_REQUEST['feedwordpress_debug'])) :
49 + $feedwordpress_debug = $_REQUEST['feedwordpress_debug'];
50 +else :
51 + $feedwordpress_debug = get_option('feedwordpress_debug');
52 + if (is_string($feedwordpress_debug)) :
53 + $feedwordpress_debug = ($feedwordpress_debug == 'yes');
54 + endif;
55 +endif;
56 +define ('FEEDWORDPRESS_DEBUG', $feedwordpress_debug);
37 57
38 58 define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
39 59 define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
40 60
@@ -42,8 +62,9 @@
42 62
43 63 define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes
44 64
45 65 define ('FWP_SCHEMA_HAS_USERMETA', 2966);
66 +define ('FWP_SCHEMA_USES_ARGS_TAXONOMY', 12694); // Revision # for using $args['taxonomy'] to get link categories
46 67 define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0
47 68 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1
48 69 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
49 70 define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5
@@ -48,14 +69,16 @@
48 69 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
49 70 define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5
50 71 define ('FWP_SCHEMA_26', 8201); // Database schema # for WP 2.6
51 72 define ('FWP_SCHEMA_27', 9872); // Database schema # for WP 2.7
73 +define ('FWP_SCHEMA_28', 11548); // Database schema # for WP 2.8
74 +define ('FWP_SCHEMA_29', 12329); // Database schema # for WP 2.9
75 +define ('FWP_SCHEMA_30', 12694); // Database schema # for WP 3.0
52 76
53 77 if (FEEDWORDPRESS_DEBUG) :
54 78 // Help us to pick out errors, if any.
55 79 ini_set('error_reporting', E_ALL & ~E_NOTICE);
56 80 ini_set('display_errors', true);
57 - define('MAGPIE_DEBUG', true);
58 81
59 82 // When testing we don't want cache issues to interfere. But this is
60 83 // a VERY BAD SETTING for a production server. Webmasters will eat your
61 84 // face for breakfast if you use it, and the baby Jesus will cry. So
@@ -60,45 +83,53 @@
60 83 // a VERY BAD SETTING for a production server. Webmasters will eat your
61 84 // face for breakfast if you use it, and the baby Jesus will cry. So
62 85 // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
63 86 // used for more than testing purposes!
64 - define('MAGPIE_CACHE_AGE', 1);
87 + define('FEEDWORDPRESS_CACHE_AGE', 1);
88 + define('FEEDWORDPRESS_CACHE_LIFETIME', 1);
89 + define('FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT', 60);
65 90 else :
66 - define('MAGPIE_DEBUG', false);
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);
94 + define('FEEDWORDPRESS_CACHE_AGE', 1*60);
95 + define('FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT', 20);
67 96 endif;
68 97
69 -// Note that the rss-functions.php that comes prepackaged with WordPress is
70 -// old & busted. For the new hotness, drop a copy of rss.php from
71 -// this archive into wp-includes/rss.php
98 +// Use our the cache settings that we want.
99 +add_filter('wp_feed_cache_transient_lifetime', array('FeedWordPress', 'cache_lifetime'));
72 100
73 -if (is_readable(ABSPATH . WPINC . '/rss.php')) :
74 - require_once (ABSPATH . WPINC . '/rss.php');
75 -else :
76 - require_once (ABSPATH . WPINC . '/rss-functions.php');
101 +// Ensure that we have SimplePie loaded up and ready to go.
102 +// We no longer need a MagpieRSS upgrade module. Hallelujah!
103 +if (!class_exists('SimplePie')) :
104 + require_once(ABSPATH . WPINC . '/class-simplepie.php');
77 105 endif;
106 +require_once(ABSPATH . WPINC . '/class-feed.php');
78 107
79 -if (isset($wp_db_version)) :
80 - if ($wp_db_version >= FWP_SCHEMA_23) :
81 - require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
82 - elseif ($wp_db_version >= FWP_SCHEMA_21) : // WordPress 2.1 and 2.2, but not 2.3
83 - require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
84 - require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
85 - elseif ($wp_db_version >= FWP_SCHEMA_20) : // WordPress 2.0
86 - require_once (ABSPATH . WPINC . '/registration-functions.php'); // for wp_insert_user
87 - require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
88 - endif;
108 +if (!function_exists('wp_insert_user')) :
109 + require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
89 110 endif;
90 111
112 +require_once(dirname(__FILE__) . '/admin-ui.php');
113 +require_once(dirname(__FILE__) . '/feedwordpresssyndicationpage.class.php');
91 114 require_once(dirname(__FILE__) . '/compatability.php'); // LEGACY API: Replicate or mock up functions for legacy support purposes
92 115
116 +require_once(dirname(__FILE__) . '/syndicatedpost.class.php');
117 +require_once(dirname(__FILE__) . '/syndicatedlink.class.php');
118 +require_once(dirname(__FILE__) . '/feedwordpresshtml.class.php');
119 +require_once(dirname(__FILE__) . '/feedwordpress-content-type-sniffer.class.php');
120 +
93 121 // Magic quotes are just about the stupidest thing ever.
94 122 if (is_array($_POST)) :
95 - $fwp_post = stripslashes_deep($_POST);
123 + $fwp_post = $_POST;
124 + if (get_magic_quotes_gpc()) :
125 + $fwp_post = stripslashes_deep($fwp_post);
126 + endif;
96 127 endif;
97 128
98 129 // Get the path relative to the plugins directory in which FWP is stored
99 130 preg_match (
100 - '|/wp-content/plugins/(.+)$|',
131 + '|'.preg_quote(WP_PLUGIN_DIR).'/(.+)$|',
101 132 dirname(__FILE__),
102 133 $ref
103 134 );
104 135
@@ -109,27 +140,21 @@
109 140 endif;
110 141
111 142 // If this is a FeedWordPress admin page, queue up scripts for AJAX functions that FWP uses
112 143 // If it is a display page or a non-FeedWordPress admin page, don't.
113 -if (is_admin() and isset($_REQUEST['page']) and preg_match("|^{$fwp_path}/|", $_REQUEST['page'])) :
114 - if (function_exists('wp_enqueue_script')) :
115 - if (isset($wp_db_version) and $wp_db_version >= FWP_SCHEMA_25) :
116 - wp_enqueue_script('post'); // for magic tag and category boxes
117 - wp_enqueue_script('thickbox'); // for fold-up boxes
118 - wp_enqueue_script('admin-forms'); // for checkbox selection
119 - else :
120 - wp_enqueue_script( 'ajaxcat' ); // Provides the handy-dandy new category text box
121 - endif;
122 - endif;
123 - if (function_exists('wp_enqueue_style')) :
124 - if (fwp_test_wp_version(FWP_SCHEMA_25)) :
125 - wp_enqueue_style('dashboard');
126 - endif;
127 - endif;
144 +wp_register_style('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.css');
145 +if (FeedWordPressSettingsUI::is_admin()) :
146 + // For JavaScript that needs to be generated dynamically
147 + add_action('admin_print_scripts', array('FeedWordPressSettingsUI', 'admin_scripts'));
148 +
149 + // For CSS that needs to be generated dynamically.
150 + add_action('admin_print_styles', array('FeedWordPressSettingsUI', 'admin_styles'));
151 +
152 + wp_enqueue_style('dashboard');
153 + wp_enqueue_style('feedwordpress-elements');
154 +
128 155 if (function_exists('wp_admin_css')) :
129 - if (fwp_test_wp_version(FWP_SCHEMA_25)) :
130 - wp_admin_css('css/dashboard');
131 - endif;
156 + wp_admin_css('css/dashboard');
132 157 endif;
133 158 endif;
134 159
135 160 if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
@@ -152,10 +177,17 @@
152 177
153 178 add_action('atom_entry', 'feedwordpress_item_feed_data');
154 179
155 180 # Filter in original permalinks if the user wants that
156 - add_filter('post_link', 'syndication_permalink', 1);
181 + add_filter('post_link', 'syndication_permalink', /*priority=*/ 1, /*arguments=*/ 3);
157 182
183 + # When foreign URLs are used for permalinks in feeds or display
184 + # contexts, they need to be escaped properly.
185 + add_filter('the_permalink', 'syndication_permalink_escaped');
186 + add_filter('the_permalink_rss', 'syndication_permalink_escaped');
187 +
188 + add_filter('post_comments_feed_link', 'syndication_comments_feed_link');
189 +
158 190 # WTF? By default, wp_insert_link runs incoming link_url and link_rss
159 191 # URIs through default filters that include `wp_kses()`. But `wp_kses()`
160 192 # just happens to escape any occurrence of & to &amp; -- which just
161 193 # happens to fuck up any URI with a & to separate GET parameters.
@@ -163,12 +195,18 @@
163 195 remove_filter('pre_link_url', 'wp_filter_kses');
164 196
165 197 # Admin menu
166 198 add_action('admin_menu', 'fwp_add_pages');
199 + add_action('admin_notices', 'fwp_check_debug');
200 +
201 + add_action('admin_menu', 'feedwordpress_add_post_edit_controls');
202 + add_action('save_post', 'feedwordpress_save_post_edit_controls');
203 +
204 + add_action('admin_footer', array('FeedWordPress', 'admin_footer'));
167 205
168 206 # Inbound XML-RPC update methods
169 - add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
170 -
207 + $feedwordpressRPC = new FeedWordPressRPC;
208 +
171 209 # Outbound XML-RPC ping reform
172 210 remove_action('publish_post', 'generic_ping'); // WP 1.5.x
173 211 remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2
174 212 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3
@@ -177,22 +215,27 @@
177 215 add_action('do_pings', 'fwp_do_pings', 10, 1);
178 216 add_action('feedwordpress_update', 'fwp_hold_pings');
179 217 add_action('feedwordpress_update_complete', 'fwp_release_pings');
180 218
181 - # Hook in logging functions only if the logging option is ON
182 - $update_logging = get_option('feedwordpress_update_logging');
183 - if ($update_logging == 'yes') :
184 - add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
185 - add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
186 - add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
187 - add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
188 - add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
219 + add_action('syndicated_feed_error', array('FeedWordPressDiagnostic', 'feed_error'), 100, 3);
220 +
221 + add_action('wp_footer', 'debug_out_feedwordpress_footer', -100);
222 + add_action('admin_footer', 'debug_out_feedwordpress_footer', -100);
223 +
224 + $feedwordpress = new FeedWordPress;
225 +
226 + # Cron-less auto-update. Hooray!
227 + $autoUpdateHook = get_option('feedwordpress_automatic_updates');
228 + if ($autoUpdateHook != 'init') :
229 + $autoUpdateHook = 'shutdown';
189 230 endif;
190 -
191 - # Cron-less auto-update. Hooray!
192 - add_action('init', 'feedwordpress_auto_update');
193 -
231 + add_action($autoUpdateHook, array(&$feedwordpress, 'auto_update'));
232 + add_action('init', array(&$feedwordpress, 'init'));
233 + add_action('shutdown', array(&$feedwordpress, 'email_diagnostic_log'));
234 + add_action('wp_dashboard_setup', array(&$feedwordpress, 'dashboard_setup'));
235 +
194 236 # Default sanitizers
237 + add_filter('syndicated_item_content', array('SyndicatedPost', 'resolve_relative_uris'), 0, 2);
195 238 add_filter('syndicated_item_content', array('SyndicatedPost', 'sanitize_content'), 0, 2);
196 239
197 240 else :
198 241 # Hook in the menus, which will just point to the upgrade interface
@@ -198,174 +241,261 @@
198 241 # Hook in the menus, which will just point to the upgrade interface
199 242 add_action('admin_menu', 'fwp_add_pages');
200 243 endif; // if (!FeedWordPress::needs_upgrade())
201 244
202 -function feedwordpress_auto_update () {
203 - if (FeedWordPress::stale()) :
204 - $feedwordpress =& new FeedWordPress;
205 - $feedwordpress->update();
206 - endif;
207 -
208 - if (FeedWordPress::update_requested()) :
209 - exit;
210 - endif;
211 -} /* feedwordpress_auto_update () */
212 -
213 245 ################################################################################
214 246 ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
215 247 ################################################################################
216 248
217 -function log_feedwordpress_post ($id) {
218 - $post = wp_get_single_post($id);
219 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
220 - ."'{$post->post_title}' ({$post->post_date})");
221 -}
249 +class FeedWordPressDiagnostic {
250 + function feed_error ($error, $old, $link) {
251 + $wpError = $error['object'];
252 + $url = $link->uri();
253 +
254 + $mesgs = $wpError->get_error_messages();
255 + foreach ($mesgs as $mesg) :
256 + $mesg = esc_html($mesg);
257 + FeedWordPress::diagnostic(
258 + 'updated_feeds:errors',
259 + "Feed Error: [${url}] update returned error: $mesg"
260 + );
261 +
262 + $hours = get_option('feedwordpress_diagnostics_persistent_errors_hours', 2);
263 + $span = ($error['ts'] - $error['since']);
222 264
223 -function log_feedwordpress_update_post ($id) {
224 - $post = wp_get_single_post($id);
225 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
226 - ."'{$post->post_title}' ({$post->post_date})"
227 - ." (as of {$post->post_modified})");
228 -}
265 + if ($span >= ($hours * 60 * 60)) :
266 + $since = date('r', $error['since']);
267 + $mostRecent = date('r', $error['ts']);
268 + FeedWordPress::diagnostic(
269 + 'updated_feeds:errors:persistent',
270 + "Feed Update Error: [${url}] returning errors"
271 + ." since ${since}:<br/><code>$mesg</code>",
272 + $url, $error['since'], $error['ts']
273 + );
274 + endif;
275 + endforeach;
276 + }
229 277
230 -function log_feedwordpress_update_feeds ($uri) {
231 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
232 -}
278 + function admin_emails ($id = '') {
279 + $users = get_users_of_blog($id);
280 + $recipients = array();
281 + foreach ($users as $user) :
282 + $user_id = (isset($user->user_id) ? $user->user_id : $user->ID);
283 + $dude = new WP_User($user_id);
284 + if ($dude->has_cap('administrator')) :
285 + if ($dude->user_email) :
286 + $recipients[] = $dude->user_email;
287 + endif;
288 + endif;
289 + endforeach;
290 + return $recipients;
291 + }
292 +} /* class FeedWordPressDiagnostic */
233 293
234 -function log_feedwordpress_check_feed ($feed) {
235 - $uri = $feed['link/uri']; $name = $feed['link/name'];
236 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
294 +function debug_out_human_readable_bytes ($quantity) {
295 + $quantity = (int) $quantity;
296 + $magnitude = 'B';
297 + $orders = array('KB', 'MB', 'GB', 'TB');
298 + while (($quantity > (1024*100)) and (count($orders) > 0)) :
299 + $quantity = floor($quantity / 1024);
300 + $magnitude = array_shift($orders);
301 + endwhile;
302 + return "${quantity} ${magnitude}";
237 303 }
238 304
239 -function log_feedwordpress_update_complete ($delta) {
240 - $mesg = array();
241 - if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
242 - if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
243 - if (empty($mesg)) $mesg[] = 'nothing changed';
305 +function debug_out_feedwordpress_footer () {
306 + if (FeedWordPress::diagnostic_on('memory_usage')) :
307 + if (function_exists('memory_get_usage')) :
308 + FeedWordPress::diagnostic ('memory_usage', "Memory: Current usage: ".debug_out_human_readable_bytes(memory_get_usage()));
309 + endif;
310 + if (function_exists('memory_get_peak_usage')) :
311 + FeedWordPress::diagnostic ('memory_usage', "Memory: Peak usage: ".debug_out_human_readable_bytes(memory_get_peak_usage()));
312 + endif;
313 + endif;
314 +} /* debug_out_feedwordpress_footer() */
244 315
245 - error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
246 - .(is_null($delta) ? "Error: I don't syndicate that URI"
247 - : implode(' and ', $mesg)));
248 -}
249 -
250 316 ################################################################################
251 317 ## TEMPLATE API: functions to make your templates syndication-aware ############
252 318 ################################################################################
253 319
254 -function is_syndicated () { return (strlen(get_syndication_feed_id()) > 0); }
320 +/**
321 + * is_syndicated: Tests whether the current post in a Loop context, or a post
322 + * given by ID number, was syndicated by FeedWordPress. Useful for templates
323 + * to determine whether or not to retrieve syndication-related meta-data in
324 + * displaying a post.
325 + *
326 + * @param int $id The post to check for syndicated status. Defaults to the current post in a Loop context.
327 + * @return bool TRUE if the post's meta-data indicates it was syndicated; FALSE otherwise
328 + */
329 +function is_syndicated ($id = NULL) {
330 + return (strlen(get_syndication_feed_id($id)) > 0);
331 +} /* function is_syndicated() */
255 332
256 -function get_syndication_source_link ($original = NULL) {
257 - if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
333 +function feedwordpress_display_url ($url, $before = 60, $after = 0) {
334 + $bits = parse_url($url);
335 +
336 + // Strip out crufty subdomains
337 + if (isset($bits['host'])) :
338 + $bits['host'] = preg_replace('/^www[0-9]*\./i', '', $bits['host']);
339 + endif;
340 +
341 + // Reassemble bit-by-bit with minimum of crufty elements
342 + $url = (isset($bits['user'])?$bits['user'].'@':'')
343 + .(isset($bits['host'])?$bits['host']:'')
344 + .(isset($bits['path'])?$bits['path']:'')
345 + .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
346 + .(isset($bits['query'])?'?'.$bits['query']:'');
347 +
348 + if (strlen($url) > ($before+$after)) :
349 + $url = substr($url, 0, $before).'…'.substr($url, 0 - $after, $after);
258 350 endif;
259 351
260 - if ($original) : $vals = get_post_custom_values('syndication_source_uri_original');
261 - else : $vals = array();
352 + return $url;
353 +} /* feedwordpress_display_url () */
354 +
355 +function get_syndication_source_property ($original, $id, $local, $remote = NULL) {
356 + $ret = NULL;
357 + if (is_null($original)) :
358 + $original = FeedWordPress::use_aggregator_source_data();
262 359 endif;
263 360
264 - if (count($vals) == 0) : $vals = get_post_custom_values('syndication_source_uri');
361 + if (is_null($remote)) :
362 + $remote = $local . '_original';
265 363 endif;
266 364
267 - if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
268 -
269 - return $ret;
270 -} /* function get_syndication_source_link() */
271 -
272 -function the_syndication_source_link ($original = NULL) { echo get_syndication_source_link($original); }
273 -
274 -function get_syndication_source ($original = NULL) {
275 - if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
365 + $vals = ($original ? get_post_custom_values($remote, $id) : array());
366 + if (count($vals) < 1) :
367 + $vals = get_post_custom_values($local, $id);
276 368 endif;
277 -
278 - if ($original) : $vals = get_post_custom_values('syndication_source_original');
279 - else : $vals = array();
280 - endif;
281 369
282 - if (count($vals) == 0) : $vals = get_post_custom_values('syndication_source');
370 + if (count($vals) > 0) :
371 + $ret = $vals[0];
283 372 endif;
284 -
285 - if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
286 -
287 373 return $ret;
288 -} /* function get_syndication_source() */
374 +} /* function get_syndication_source_property () */
289 375
290 -function the_syndication_source ($original = NULL) { echo get_syndication_source($original); }
376 +function the_syndication_source_link ($original = NULL, $id = NULL) { echo get_syndication_source_link($original, $id); }
377 +function get_syndication_source_link ($original = NULL, $id = NULL) {
378 + return get_syndication_source_property($original, $id, 'syndication_source_uri');
379 +} /* function get_syndication_source_link() */
291 380
292 -function get_syndication_feed ($original = NULL) {
293 - if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
381 +function the_syndication_source ($original = NULL, $id = NULL) { echo get_syndication_source($original, $id); }
382 +function get_syndication_source ($original = NULL, $id = NULL) {
383 + $ret = get_syndication_source_property($original, $id, 'syndication_source');
384 + if (is_null($ret) or strlen(trim($ret)) == 0) : // Fall back to URL of blog
385 + $ret = feedwordpress_display_url(get_syndication_source_link());
294 386 endif;
387 + return $ret;
388 +} /* function get_syndication_source() */
295 389
296 - if ($original) : $vals = get_post_custom_values('syndication_feed_original');
297 - else : $vals = array();
298 - endif;
299 -
300 - if (count($vals) == 0) : $vals = get_post_custom_values('syndication_feed');
301 - endif;
302 -
303 - if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
304 -
305 - return $ret;
390 +function the_syndication_feed ($original = NULL, $id = NULL) { echo get_syndication_feed($original, $id); }
391 +function get_syndication_feed ($original = NULL, $id = NULL) {
392 + return get_syndication_source_property($original, $id, 'syndication_feed');
306 393 } /* function get_syndication_feed() */
307 394
308 -function the_syndication_feed ($original = NULL) { echo get_syndication_feed ($original); }
309 -
310 -function get_syndication_feed_guid ($original = NULL) {
311 - if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
395 +function the_syndication_feed_guid ($original = NULL, $id = NULL) { echo get_syndication_feed_guid($original, $id); }
396 +function get_syndication_feed_guid ($original = NULL, $id = NULL) {
397 + $ret = get_syndication_source_property($original, $id, 'syndication_source_id');
398 + if (is_null($ret) or strlen(trim($ret))==0) : // Fall back to URL of feed
399 + $ret = get_syndication_feed();
312 400 endif;
313 -
314 - if ($original) : $vals = get_post_custom_values('syndication_source_id_original');
315 - else : $vals = array();
316 - endif;
317 -
318 - if (count($vals) == 0) : $vals = array(get_feed_meta('feed/id'));
319 - endif;
320 -
321 - if (count($vals) > 0) : $ret = $vals[0]; else : $ret = NULL; endif;
322 -
323 401 return $ret;
324 402 } /* function get_syndication_feed_guid () */
325 403
326 -function the_syndication_feed_guid ($original = NULL) { echo get_syndication_feed_guid($original); }
404 +function get_syndication_feed_id ($id = NULL) { list($u) = get_post_custom_values('syndication_feed_id', $id); return $u; }
405 +function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); }
327 406
328 -function get_syndication_feed_id () { list($u) = get_post_custom_values('syndication_feed_id'); return $u; }
329 -function the_syndication_feed_id () { echo get_syndication_feed_id(); }
330 -
331 407 $feedwordpress_linkcache = array (); // only load links from database once
408 +function get_syndication_feed_object ($id = NULL) {
409 + global $feedwordpress_linkcache;
332 410
333 -function get_feed_meta ($key) {
334 - global $wpdb, $feedwordpress_linkcache;
335 - $feed_id = get_syndication_feed_id();
411 + $link = NULL;
336 412
337 - $ret = NULL;
413 + $feed_id = get_syndication_feed_id($id);
338 414 if (strlen($feed_id) > 0):
339 415 if (isset($feedwordpress_linkcache[$feed_id])) :
340 416 $link = $feedwordpress_linkcache[$feed_id];
341 417 else :
342 - $link =& new SyndicatedLink($feed_id);
418 + $link = new SyndicatedLink($feed_id);
343 419 $feedwordpress_linkcache[$feed_id] = $link;
344 420 endif;
421 + endif;
422 + return $link;
423 +}
345 424
425 +function get_feed_meta ($key, $id = NULL) {
426 + $ret = NULL;
427 +
428 + $link = get_syndication_feed_object($id);
429 + if (is_object($link) and isset($link->settings[$key])) :
346 430 $ret = $link->settings[$key];
347 431 endif;
348 432 return $ret;
349 433 } /* get_feed_meta() */
350 434
351 -function get_syndication_permalink () {
352 - list($u) = get_post_custom_values('syndication_permalink'); return $u;
435 +function get_syndication_permalink ($id = NULL) {
436 + list($u) = get_post_custom_values('syndication_permalink', $id); return $u;
353 437 }
354 -function the_syndication_permalink () {
355 - echo get_syndication_permalink();
438 +function the_syndication_permalink ($id = NULL) {
439 + echo get_syndication_permalink($id);
356 440 }
357 441
442 +/**
443 + * get_local_permalink: returns a string containing the internal permalink
444 + * for a post (whether syndicated or not) on your local WordPress installation.
445 + * This may be useful if you want permalinks to point to the original source of
446 + * an article for most purposes, but want to retrieve a URL for the local
447 + * representation of the post for one or two limited purposes (for example,
448 + * linking to a comments page on your local aggregator site).
449 + *
450 + * @param $id The numerical ID of the post to get the permalink for. If empty,
451 + * defaults to the current post in a Loop context.
452 + * @return string The URL of the local permalink for this post.
453 + *
454 + * @uses get_permalink()
455 + * @global $feedwordpress_the_original_permalink
456 + *
457 + * @since 2010.0217
458 + */
459 +function get_local_permalink ($id = NULL) {
460 + global $feedwordpress_the_original_permalink;
461 +
462 + // get permalink, and thus activate filter and force global to be filled
463 + // with original URL.
464 + $url = get_permalink($id);
465 + return $feedwordpress_the_original_permalink;
466 +} /* get_local_permalink() */
467 +
468 +/**
469 + * the_original_permalink: displays the contents of get_original_permalink()
470 + *
471 + * @param $id The numerical ID of the post to get the permalink for. If empty,
472 + * defaults to the current post in a Loop context.
473 + *
474 + * @uses get_local_permalinks()
475 + * @uses apply_filters
476 + *
477 + * @since 2010.0217
478 + */
479 +function the_local_permalink ($id = NULL) {
480 + print apply_filters('the_permalink', get_local_permalink($id));
481 +} /* function the_local_permalink() */
482 +
358 483 ################################################################################
359 484 ## FILTERS: syndication-aware handling of post data for templates and feeds ####
360 485 ################################################################################
361 486
362 487 $feedwordpress_the_syndicated_content = NULL;
488 +$feedwordpress_the_original_permalink = NULL;
363 489
364 490 function feedwordpress_preserve_syndicated_content ($text) {
365 491 global $feedwordpress_the_syndicated_content;
366 492
367 - if ( is_syndicated() and get_option('feedwordpress_formatting_filters') != 'yes' ) :
493 + $globalExpose = (get_option('feedwordpress_formatting_filters') == 'yes');
494 + $localExpose = get_post_custom_values('_feedwordpress_formatting_filters');
495 + $expose = ($globalExpose or ((count($localExpose) > 0) and $localExpose[0]));
496 +
497 + if ( is_syndicated() and !$expose ) :
368 498 $feedwordpress_the_syndicated_content = $text;
369 499 else :
370 500 $feedwordpress_the_syndicated_content = NULL;
371 501 endif;
@@ -386,16 +516,16 @@
386 516 // In a post context....
387 517 if (is_syndicated()) :
388 518 ?>
389 519 <source>
390 - <title><?php the_syndication_source(); ?></title>
391 - <link rel="alternate" type="text/html" href="<?php the_syndication_source_link(); ?>" />
392 - <link rel="self" href="<?php the_syndication_feed(); ?>" />
520 + <title><?php print htmlspecialchars(get_syndication_source()); ?></title>
521 + <link rel="alternate" type="text/html" href="<?php print htmlspecialchars(get_syndication_source_link()); ?>" />
522 + <link rel="self" href="<?php print htmlspecialchars(get_syndication_feed()); ?>" />
393 523 <?php
394 524 $id = get_syndication_feed_guid();
395 525 if (strlen($id) > 0) :
396 526 ?>
397 - <id><?php print $id; ?></id>
527 + <id><?php print htmlspecialchars($id); ?></id>
398 528 <?php
399 529 endif;
400 530 $updated = get_feed_meta('feed/updated');
401 531 if (strlen($updated) > 0) : ?>
@@ -407,46 +537,221 @@
407 537 <?php
408 538 endif;
409 539 }
410 540
411 -function syndication_permalink ($permalink = '') {
412 - if (get_option('feedwordpress_munge_permalink') != 'no'):
413 - $uri = get_syndication_permalink();
414 - return ((strlen($uri) > 0) ? $uri : $permalink);
415 - else:
416 - return $permalink;
541 +/**
542 + * syndication_permalink: Allow WordPress to use the original remote URL of
543 + * syndicated posts as their permalink. Can be turned on or off by by setting in
544 + * Syndication => Posts & Links. Saves the old internal permalink in a global
545 + * variable for later use.
546 + *
547 + * @param string $permalink The internal permalink
548 + * @return string The new permalink. Same as the old if the post is not
549 + * syndicated, or if FWP is set to use internal permalinks, or if the post
550 + * was syndicated, but didn't have a proper permalink recorded.
551 + *
552 + * @uses SyndicatedLink::setting()
553 + * @uses get_syndication_permalink()
554 + * @uses get_syndication_feed_object()
555 + * @uses url_to_postid()
556 + * @global $id
557 + * @global $feedwordpress_the_original_permalink
558 + */
559 +function syndication_permalink ($permalink = '', $post = null, $leavename = false) {
560 + global $id;
561 + global $feedwordpress_the_original_permalink;
562 +
563 + // Save the local permalink in case we need to retrieve it later.
564 + $feedwordpress_the_original_permalink = $permalink;
565 +
566 + if (is_object($post) and isset($post->ID) and !empty($post->ID)) :
567 + // Use the post ID we've been provided with.
568 + $postId = $post->ID;
569 + elseif (is_string($permalink) and strlen($permalink) > 0) :
570 + // Map this permalink to a post ID so we can get the correct
571 + // permalink even outside of the Post Loop. Props Björn.
572 + $postId = url_to_postid($permalink);
573 + else :
574 + // If the permalink string is empty but Post Loop context
575 + // provides an id.
576 + $postId = $id;
417 577 endif;
418 -} // function syndication_permalink ()
419 578
579 + $munge = false;
580 + $link = get_syndication_feed_object($postId);
581 + if (is_object($link)) :
582 + $munge = ($link->setting('munge permalink', 'munge_permalink', 'yes') != 'no');
583 + endif;
584 +
585 + if ($munge):
586 + $uri = get_syndication_permalink($postId);
587 + $permalink = ((strlen($uri) > 0) ? $uri : $permalink);
588 + endif;
589 + return $permalink;
590 +} /* function syndication_permalink () */
591 +
592 +/**
593 + * syndication_permalink_escaped: Escape XML special characters in syndicated
594 + * permalinks when used in feed contexts and HTML display contexts.
595 + *
596 + * @param string $permalink
597 + * @return string
598 + *
599 + * @uses is_syndicated()
600 + * @uses FeedWordPress::munge_permalinks()
601 + *
602 + */
603 +function syndication_permalink_escaped ($permalink) {
604 + /* FIXME: This should review link settings not just global settings */
605 + if (is_syndicated() and FeedWordPress::munge_permalinks()) :
606 + // This is a foreign link; WordPress can't vouch for its not
607 + // having any entities that need to be &-escaped. So we'll do
608 + // it here.
609 + $permalink = esc_html($permalink);
610 + endif;
611 + return $permalink;
612 +} /* function syndication_permalink_escaped() */
613 +
614 +/**
615 + * syndication_comments_feed_link: Escape XML special characters in comments
616 + * feed links
617 + *
618 + * @param string $link
619 + * @return string
620 + *
621 + * @uses is_syndicated()
622 + * @uses FeedWordPress::munge_permalinks()
623 + */
624 +function syndication_comments_feed_link ($link) {
625 + global $feedwordpress_the_original_permalink, $id;
626 +
627 + if (is_syndicated() and FeedWordPress::munge_permalinks()) :
628 + // If the source post provided a comment feed URL using
629 + // wfw:commentRss or atom:link/@rel="replies" we can make use of
630 + // that value here.
631 + $source = get_syndication_feed_object();
632 + $replacement = NULL;
633 + if ($source->setting('munge comments feed links', 'munge_comments_feed_links', 'yes') != 'no') :
634 + $commentFeeds = get_post_custom_values('wfw:commentRSS');
635 + if (
636 + is_array($commentFeeds)
637 + and (count($commentFeeds) > 0)
638 + and (strlen($commentFeeds[0]) > 0)
639 + ) :
640 + $replacement = $commentFeeds[0];
641 +
642 + // This is a foreign link; WordPress can't vouch for its not
643 + // having any entities that need to be &-escaped. So we'll do it
644 + // here.
645 + $replacement = esc_html($replacement);
646 + endif;
647 + endif;
648 +
649 + if (is_null($replacement)) :
650 + // Q: How can we get the proper feed format, since the
651 + // format is, stupidly, not passed to the filter?
652 + // A: Kludge kludge kludge kludge!
653 + $fancy_permalinks = ('' != get_option('permalink_structure'));
654 + if ($fancy_permalinks) :
655 + preg_match('|/feed(/([^/]+))?/?$|', $link, $ref);
656 +
657 + $format = (isset($ref[2]) ? $ref[2] : '');
658 + if (strlen($format) == 0) : $format = get_default_feed(); endif;
659 +
660 + $replacement = trailingslashit($feedwordpress_the_original_permalink) . 'feed';
661 + if ($format != get_default_feed()) :
662 + $replacement .= '/'.$format;
663 + endif;
664 + $replacement = user_trailingslashit($replacement, 'single_feed');
665 + else :
666 + // No fancy permalinks = no problem
667 + // WordPress doesn't call get_permalink() to
668 + // generate the comment feed URL, so the
669 + // comments feed link is never munged by FWP.
670 + endif;
671 + endif;
672 +
673 + if (!is_null($replacement)) : $link = $replacement; endif;
674 + endif;
675 + return $link;
676 +} /* function syndication_comments_feed_link() */
677 +
420 678 ################################################################################
421 679 ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
422 680 ################################################################################
423 681
424 682 function fwp_add_pages () {
425 - global $fwp_capability;
426 - global $fwp_path;
683 + $menu_cap = FeedWordPress::menu_cap();
684 + $settings_cap = FeedWordPress::menu_cap(/*sub=*/ true);
685 + $syndicationMenu = FeedWordPress::path('syndication.php');
686 +
687 + add_menu_page(
688 + 'Syndicated Sites', 'Syndication',
689 + $menu_cap,
690 + $syndicationMenu,
691 + NULL,
692 + WP_PLUGIN_URL.'/'.FeedWordPress::path('feedwordpress-tiny.png')
693 + );
427 694
428 - $menu = array('Syndicated Sites', 'Syndication', $fwp_capability['manage_links'], $fwp_path.'/syndication.php', NULL);
429 - if (fwp_test_wp_version(FWP_SCHEMA_27)) :
430 - // add icon parameter
431 - $menu[] = WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-tiny.png';
432 - endif;
695 + do_action('feedwordpress_admin_menu_pre_feeds', $menu_cap, $settings_cap);
696 + add_submenu_page(
697 + $syndicationMenu, 'Syndicated Feeds & Updates', 'Feeds & Updates',
698 + $settings_cap, FeedWordPress::path('feeds-page.php')
699 + );
433 700
434 - if (fwp_test_wp_version(FWP_SCHEMA_26)) :
435 - $options = __('Settings');
436 - $longoptions = __('Syndication Settings');
701 + do_action('feedwordpress_admin_menu_pre_posts', $menu_cap, $settings_cap);
702 + add_submenu_page(
703 + $syndicationMenu, 'Syndicated Posts & Links', 'Posts & Links',
704 + $settings_cap, FeedWordPress::path('posts-page.php')
705 + );
706 +
707 + do_action('feedwordpress_admin_menu_pre_authors', $menu_cap, $settings_cap);
708 + add_submenu_page(
709 + $syndicationMenu, 'Syndicated Authors', 'Authors',
710 + $settings_cap, FeedWordPress::path('authors-page.php')
711 + );
712 +
713 + do_action('feedwordpress_admin_menu_pre_categories', $menu_cap, $settings_cap);
714 + add_submenu_page(
715 + $syndicationMenu, 'Categories'.FEEDWORDPRESS_AND_TAGS, 'Categories'.FEEDWORDPRESS_AND_TAGS,
716 + $settings_cap, FeedWordPress::path('categories-page.php')
717 + );
718 +
719 + do_action('feedwordpress_admin_menu_pre_performance', $menu_cap, $settings_cap);
720 + add_submenu_page(
721 + $syndicationMenu, 'FeedWordPress Performance', 'Performance',
722 + $settings_cap, FeedWordPress::path('performance-page.php')
723 + );
724 +
725 + do_action('feedwordpress_admin_menu_pre_diagnostics', $menu_cap, $settings_cap);
726 + add_submenu_page(
727 + $syndicationMenu, 'FeedWordPress Diagnostics', 'Diagnostics',
728 + $settings_cap, FeedWordPress::path('diagnostics-page.php')
729 + );
730 +} /* function fwp_add_pages () */
731 +
732 +function fwp_check_debug () {
733 + // This is a horrible fucking kludge that I have to do because the
734 + // admin notice code is triggered before the code that updates the
735 + // setting.
736 + if (isset($_POST['feedwordpress_debug'])) :
737 + $feedwordpress_debug = $_POST['feedwordpress_debug'];
437 738 else :
438 - $options = __('Options');
439 - $longoptions = __('Syndication Options');
739 + $feedwordpress_debug = get_option('feedwordpress_debug');
440 740 endif;
741 + if ($feedwordpress_debug==='yes') :
742 +?>
743 + <div class="error">
744 +<p><strong>FeedWordPress warning.</strong> Debugging mode is <strong>ON</strong>.
745 +While it remains on, FeedWordPress displays many diagnostic error messages,
746 +warnings, and notices that are ordinarily suppressed, and also turns off all
747 +caching of feeds. Use with caution: this setting is absolutely inappropriate
748 +for a production server.</p>
749 + </div>
750 +<?php
751 + endif;
752 +} /* function fwp_check_debug () */
441 753
442 - call_user_func_array('add_menu_page', $menu);
443 - add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Authors', 'Authors', $fwp_capability['manage_options'], $fwp_path.'/authors.php');
444 - add_submenu_page($fwp_path.'/syndication.php', $longoptions, $options, $fwp_capability['manage_options'], $fwp_path.'/syndication-options.php');
445 -
446 - add_options_page($longoptions, 'Syndication', $fwp_capability['manage_options'], $fwp_path.'/syndication-options.php');
447 -} // function fwp_add_pages () */
448 -
449 754 ################################################################################
450 755 ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
451 756 ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
452 757 ################################################################################
@@ -502,8 +807,67 @@
502 807 endif;
503 808 endif;
504 809 }
505 810
811 + function feedwordpress_add_post_edit_controls () {
812 + add_meta_box('feedwordpress-post-controls', __('Syndication'), 'feedwordpress_post_edit_controls', 'post', 'side', 'high');
813 + } // function FeedWordPress::postEditControls
814 +
815 + function feedwordpress_post_edit_controls () {
816 + global $post;
817 +
818 + $frozen_values = get_post_custom_values('_syndication_freeze_updates', $post->ID);
819 + $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
820 +
821 + if (is_syndicated($post->ID)) :
822 + ?>
823 + <p>This is a syndicated post, which originally appeared at
824 + <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>.
825 + <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
826 +
827 + <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
828 + <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
829 + If set, FeedWordPress will not overwrite the changes you make manually
830 + to this post, if the syndicated content is updated on the
831 + feed.</label></p>
832 + <?php
833 + else :
834 + ?>
835 + <p>This post was created locally at this website.</p>
836 + <?php
837 + endif;
838 + } // function feedwordpress_post_edit_controls () */
839 +
840 + function feedwordpress_save_post_edit_controls ( $post_id ) {
841 + global $post;
842 +
843 + if (!isset($_POST['feedwordpress_noncename']) or !wp_verify_nonce($_POST['feedwordpress_noncename'], plugin_basename(__FILE__))) :
844 + return $post_id;
845 + endif;
846 +
847 + // Verify if this is an auto save routine. If it is our form has
848 + // not been submitted, so we don't want to do anything.
849 + if ( defined('DOING_AUTOSAVE') and DOING_AUTOSAVE ) :
850 + return $post_id;
851 + endif;
852 +
853 + // Check permissions
854 + if ( !current_user_can( 'edit_'.$_POST['post_type'], $post_id) ) :
855 + return $post_id;
856 + endif;
857 +
858 + // OK, we're golden. Now let's save some data.
859 + if (isset($_POST['freeze_updates'])) :
860 + update_post_meta($post_id, '_syndication_freeze_updates', $_POST['freeze_updates']);
861 + $ret = $_POST['freeze_updates'];
862 + else :
863 + delete_post_meta($post_id, '_syndication_freeze_updates');
864 + $ret = NULL;
865 + endif;
866 +
867 + return $ret;
868 + } // function feedwordpress_save_edit_controls
869 +
506 870 ################################################################################
507 871 ## class FeedWordPress #########################################################
508 872 ################################################################################
509 873
@@ -547,9 +911,9 @@
547 911 function FeedWordPress () {
548 912 $this->feeds = array ();
549 913 $links = FeedWordPress::syndicated_links();
550 914 if ($links): foreach ($links as $link):
551 - $this->feeds[] =& new SyndicatedLink($link);
915 + $this->feeds[] = new SyndicatedLink($link);
552 916 endforeach; endif;
553 917 } // FeedWordPress::FeedWordPress ()
554 918
555 919 # function update (): polls for updates on one or more Contributor feeds
@@ -607,9 +971,9 @@
607 971 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
608 972 return NULL;
609 973 endif;
610 974
611 - if (!is_null($uri)) :
975 + if (!is_null($uri) and $uri != '*') :
612 976 $uri = trim($uri);
613 977 else : // Update all
614 978 update_option('feedwordpress_last_update_all', time());
615 979 endif;
@@ -616,20 +980,17 @@
616 980
617 981 do_action('feedwordpress_update', $uri);
618 982
619 983 if (is_null($crash_ts)) :
620 - $crash_dt = (int) get_option('feedwordpress_update_time_limit');
621 - if ($crash_dt > 0) :
622 - $crash_ts = time() + $crash_dt;
623 - else :
624 - $crash_ts = NULL;
625 - endif;
984 + $crash_ts = $this->crash_ts();
626 985 endif;
627 -
986 +
628 987 // Randomize order for load balancing purposes
629 988 $feed_set = $this->feeds;
630 989 shuffle($feed_set);
631 990
991 + $feed_set = apply_filters('feedwordpress_update_feeds', $feed_set, $uri);
992 +
632 993 // Loop through and check for new posts
633 994 $delta = NULL;
634 995 foreach ($feed_set as $feed) :
635 996 if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit
@@ -635,9 +996,9 @@
635 996 if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit
636 997 break;
637 998 endif;
638 999
639 - $pinged_that = (is_null($uri) or in_array($uri, array($feed->uri(), $feed->homepage())));
1000 + $pinged_that = (is_null($uri) or ($uri=='*') or in_array($uri, array($feed->uri(), $feed->homepage())));
640 1001
641 1002 if (!is_null($uri)) : // A site-specific ping always updates
642 1003 $timely = true;
643 1004 else :
@@ -643,9 +1004,9 @@
643 1004 else :
644 1005 $timely = $feed->stale();
645 1006 endif;
646 1007
647 - if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
1008 + if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
648 1009 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
649 1010 endif;
650 1011
651 1012 if ($pinged_that and $timely) :
@@ -653,10 +1014,12 @@
653 1014 $start_ts = time();
654 1015 $added = $feed->poll($crash_ts);
655 1016 do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
656 1017
657 - if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
658 - if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1018 + if (is_array($added)) : // Success
1019 + if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1020 + if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1021 + endif;
659 1022 endif;
660 1023 endforeach;
661 1024
662 1025 do_action('feedwordpress_update_complete', $delta);
@@ -663,10 +1026,23 @@
663 1026
664 1027 return $delta;
665 1028 }
666 1029
1030 + function crash_ts ($default = NULL) {
1031 + $crash_dt = (int) get_option('feedwordpress_update_time_limit', 0);
1032 + if ($crash_dt > 0) :
1033 + $crash_ts = time() + $crash_dt;
1034 + else :
1035 + $crash_ts = $default;
1036 + endif;
1037 + return $crash_ts;
1038 + }
1039 +
667 1040 function stale () {
668 1041 if (get_option('feedwordpress_automatic_updates')) :
1042 + // Do our best to avoid possible simultaneous
1043 + // updates by getting up-to-the-minute settings.
1044 +
669 1045 $last = get_option('feedwordpress_last_update_all');
670 1046
671 1047 // If we haven't updated all yet, give it a time window
672 1048 if (false === $last) :
@@ -685,70 +1061,205 @@
685 1061 else :
686 1062 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__);
687 1063 endif;
688 1064
689 - // Explicit request for an update (e.g. from a cron job).
690 - elseif (FeedWordPress::update_requested()) :
691 - $ret = true;
692 -
693 1065 else :
694 1066 $ret = false;
695 1067 endif;
696 1068 return $ret;
697 1069 } // FeedWordPress::stale()
1070 +
1071 + function init () {
1072 + $this->clear_cache_magic_url();
1073 + $this->update_magic_url();
1074 + } /* FeedWordPress::init() */
698 1075
1076 + function dashboard_setup () {
1077 + $see_it = FeedWordPress::menu_cap();
1078 +
1079 + if (current_user_can($see_it)) :
1080 + // Get the stylesheet
1081 + wp_enqueue_style('feedwordpress-elements');
1082 +
1083 + $widget_id = 'feedwordpress_dashboard';
1084 + $widget_name = __('Syndicated Sources');
1085 + $column = 'side';
1086 + $priority = 'core';
1087 +
1088 + // I would love to use wp_add_dashboard_widget() here and save
1089 + // myself some trouble. But WP 3 does not yet have any way to
1090 + // push a dashboard widget onto the side, or to give it a default
1091 + // location.
1092 + add_meta_box(
1093 + /*id=*/ $widget_id,
1094 + /*title=*/ $widget_name,
1095 + /*callback=*/ array(&$this, 'dashboard'),
1096 + /*page=*/ 'dashboard',
1097 + /*context=*/ $column,
1098 + /*priority=*/ $priority
1099 + );
1100 + /*control_callback= array(&$this, 'dashboard_control') */
1101 +
1102 + // This is kind of rude, I know, but the dashboard widget isn't
1103 + // worth much if users don't know that it exists, and I don't
1104 + // know of any better way to reorder the boxen.
1105 + //
1106 + // Gleefully ripped off of codex.wordpress.org/Dashboard_Widgets_API
1107 +
1108 + // Globalize the metaboxes array, this holds all the widgets for wp-admin
1109 + global $wp_meta_boxes;
1110 +
1111 + // Get the regular dashboard widgets array
1112 + // (which has our new widget already but at the end)
1113 +
1114 + $normal_dashboard = $wp_meta_boxes['dashboard'][$column][$priority];
1115 +
1116 + // Backup and delete our new dashbaord widget from the end of the array
1117 + if (isset($normal_dashboard[$widget_id])) :
1118 + $backup = array();
1119 + $backup[$widget_id] = $normal_dashboard[$widget_id];
1120 + unset($normal_dashboard[$widget_id]);
1121 +
1122 + // Merge the two arrays together so our widget is at the
1123 + // beginning
1124 + $sorted_dashboard = array_merge($backup, $normal_dashboard);
1125 +
1126 + // Save the sorted array back into the original metaboxes
1127 + $wp_meta_boxes['dashboard'][$column][$priority] = $sorted_dashboard;
1128 + endif;
1129 + endif;
1130 + } /* FeedWordPress::dashboard_setup () */
1131 +
1132 + function dashboard () {
1133 + $syndicationPage = new FeedWordPressSyndicationPage(dirname(__FILE__).'/syndication.php');
1134 + $syndicationPage->dashboard_box($syndicationPage);
1135 + } /* FeedWordPress::dashboard () */
1136 +
1137 + function update_magic_url () {
1138 + global $wpdb;
1139 +
1140 + // Explicit update request in the HTTP request (e.g. from a cron job)
1141 + if ($this->update_requested()) :
1142 + $this->update($this->update_requested_url());
1143 +
1144 + if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
1145 + $mysqlTime = 0.0;
1146 + $byTime = array();
1147 + foreach ($wpdb->queries as $query) :
1148 + $time = $query[1] * 1000000.0;
1149 + $mysqlTime += $query[1];
1150 + if (!isset($byTime[$time])) : $byTime[$time] = array(); endif;
1151 + $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2];
1152 + endforeach;
1153 + krsort($byTime);
1154 +
1155 + foreach ($byTime as $time => $querySet) :
1156 + foreach ($querySet as $query) :
1157 + print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n";
1158 + endforeach;
1159 + endforeach;
1160 + echo $this->log_prefix()."$wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds.";
1161 + endif;
1162 +
1163 + debug_out_feedwordpress_footer();
1164 +
1165 + // Magic URL should return nothing but a 200 OK header packet
1166 + // when successful.
1167 + exit;
1168 + endif;
1169 + } /* FeedWordPress::magic_update_url () */
1170 +
1171 + function clear_cache_magic_url () {
1172 + if ($this->clear_cache_requested()) :
1173 + $this->clear_cache();
1174 + endif;
1175 + } /* FeedWordPress::clear_cache_magic_url() */
1176 +
1177 + function clear_cache_requested () {
1178 + return (
1179 + isset($_GET['clear_cache'])
1180 + and $_GET['clear_cache']
1181 + );
1182 + } /* FeedWordPress::clear_cache_requested() */
1183 +
699 1184 function update_requested () {
700 - return (isset($_REQUEST['update_feedwordpress']) and $_REQUEST['update_feedwordpress']);
1185 + return (
1186 + isset($_REQUEST['update_feedwordpress'])
1187 + and $_REQUEST['update_feedwordpress']
1188 + );
701 1189 } // FeedWordPress::update_requested()
702 1190
703 - function syndicate_link ($name, $uri, $rss) {
1191 + function update_requested_url () {
1192 + $ret = null;
1193 +
1194 + if (($_REQUEST['update_feedwordpress']=='*')
1195 + or (preg_match('|^http://.*|i', $_REQUEST['update_feedwordpress']))) :
1196 + $ret = $_REQUEST['update_feedwordpress'];
1197 + endif;
1198 +
1199 + return $ret;
1200 + } // FeedWordPress::update_requested_url()
1201 +
1202 + function auto_update () {
1203 + if ($this->stale()) :
1204 + $this->update();
1205 + endif;
1206 + } /* FeedWordPress::auto_update () */
1207 +
1208 + function find_link ($uri, $field = 'link_rss') {
704 1209 global $wpdb;
705 1210
1211 + $unslashed = untrailingslashit($uri);
1212 + $slashed = trailingslashit($uri);
1213 + $link_id = $wpdb->get_var($wpdb->prepare("
1214 + SELECT link_id FROM $wpdb->links WHERE $field IN ('%s', '%s')
1215 + LIMIT 1", $unslashed, $slashed
1216 + ));
1217 +
1218 + return $link_id;
1219 + } /* FeedWordPress::find_link () */
1220 +
1221 + function syndicate_link ($name, $uri, $rss) {
706 1222 // Get the category ID#
707 1223 $cat_id = FeedWordPress::link_category_id();
708 -
1224 + if (!is_wp_error($cat_id)) :
1225 + $link_category = array($cat_id);
1226 + else :
1227 + $link_category = array();
1228 + endif;
1229 +
709 1230 // WordPress gets cranky if there's no homepage URI
710 - if (!isset($uri) or strlen($uri)<1) : $uri = $rss; endif;
1231 + if (!is_string($uri) or strlen($uri)<1) : $uri = $rss; endif;
711 1232
712 - if (function_exists('wp_insert_link')) { // WordPress 2.x
713 - $link_id = wp_insert_link(array(
714 - "link_name" => $name,
715 - "link_url" => $uri,
716 - "link_category" => (fwp_test_wp_version(0, FWP_SCHEMA_21) ? $cat_id : array($cat_id)),
717 - "link_rss" => $rss
718 - ));
719 - } else { // WordPress 1.5.x
720 - $result = $wpdb->query("
721 - INSERT INTO $wpdb->links
722 - SET
723 - link_name = '".$wpdb->escape($name)."',
724 - link_url = '".$wpdb->escape($uri)."',
725 - link_category = '".$wpdb->escape($cat_id)."',
726 - link_rss = '".$wpdb->escape($rss)."'
727 - ");
728 - $link_id = $wpdb->insert_id;
729 - } // if
1233 + // Check if this feed URL is already being syndicated.
1234 + $link_id = wp_insert_link(array(
1235 + "link_id" => FeedWordPress::find_link($rss), // insert if nothing was found; else update
1236 + "link_rss" => $rss,
1237 + "link_name" => $name,
1238 + "link_url" => $uri,
1239 + "link_category" => $link_category,
1240 + "link_visible" => 'Y', // reactivate if inactivated
1241 + ));
1242 +
730 1243 return $link_id;
731 - } // function FeedWordPress::syndicate_link()
1244 + } /* function FeedWordPress::syndicate_link() */
732 1245
733 - function on_unfamiliar ($what = 'author', $override = NULL) {
734 - $set = array(
735 - 'author' => array('create', 'default', 'filter'),
736 - 'category' => array('create', 'tag', 'default', 'filter'),
737 - );
738 -
739 - if (is_string($override)) :
740 - $ret = strtolower($override);
1246 + /*static*/ function syndicated_status ($what, $default) {
1247 + $ret = get_option("feedwordpress_syndicated_{$what}_status");
1248 + if (!$ret) :
1249 + $ret = $default;
741 1250 endif;
1251 + return $ret;
1252 + } /* FeedWordPress::syndicated_status() */
742 1253
743 - if (!is_numeric($override) and !in_array($ret, $set[$what])) :
744 - $ret = get_option('feedwordpress_unfamiliar_'.$what);
745 - if (!is_numeric($ret) and !in_array($ret, $set[$what])) :
746 - $ret = 'create';
747 - endif;
748 - endif;
749 -
750 - return $ret;
1254 + function on_unfamiliar ($what = 'author') {
1255 + switch ($what) :
1256 + case 'category' : $suffix = ':category'; break;
1257 + case 'post_tag' : $suffix = ':post_tag'; break;
1258 + default: $suffix = '';
1259 + endswitch;
1260 +
1261 + return get_option('feedwordpress_unfamiliar_'.$what, 'create'.$suffix);
751 1262 } // function FeedWordPress::on_unfamiliar()
752 1263
753 1264 function null_email_set () {
754 1265 $base = get_option('feedwordpress_null_email_set');
@@ -774,14 +1285,28 @@
774 1285 $ret = get_option('feedwordpress_use_aggregator_source_data');
775 1286 return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
776 1287 }
777 1288
778 - function syndicated_links () {
1289 + /**
1290 + * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1291 + * should rewrite permalinks for syndicated items to reflect their
1292 + * original location.
1293 + *
1294 + * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1295 + */
1296 + /*static*/ function munge_permalinks () {
1297 + return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1298 + } /* FeedWordPress::munge_permalinks() */
1299 +
1300 + function syndicated_links ($args = array()) {
779 1301 $contributors = FeedWordPress::link_category_id();
780 - if (function_exists('get_bookmarks')) :
781 - $links = get_bookmarks(array("category" => $contributors));
782 - else:
783 - $links = get_linkobjects($contributors); // deprecated as of WP 2.1
1302 + if (!is_wp_error($contributors)) :
1303 + $links = get_bookmarks(array_merge(
1304 + array("category" => $contributors),
1305 + $args
1306 + ));
1307 + else :
1308 + $links = array();
784 1309 endif;
785 1310 return $links;
786 1311 } // function FeedWordPress::syndicated_links()
787 1312
@@ -789,61 +1314,32 @@
789 1314 global $wpdb, $wp_db_version;
790 1315
791 1316 $cat_id = get_option('feedwordpress_cat_id');
792 1317
793 - // If we don't yet *have* the category, we'll have to create it
794 - if ($cat_id === false) :
795 - $cat = $wpdb->escape(DEFAULT_SYNDICATION_CATEGORY);
796 -
797 - // Look for something with the right name...
798 - // -----------------------------------------
1318 + // If we don't yet have the category ID stored, search by name
1319 + if (!$cat_id) :
1320 + $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY);
799 1321
800 - // WordPress 2.3 introduces a new taxonomy/term API
801 - if (function_exists('is_term')) :
802 - $cat_id = is_term($cat, 'link_category');
803 - // WordPress 2.1 and 2.2 use a common table for both link and post categories
804 - elseif (isset($wp_db_version) and ($wp_db_version >= FWP_SCHEMA_21 and $wp_db_version < FWP_SCHEMA_23) ) :
805 - $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->categories} WHERE cat_name='$cat'");
806 - // WordPress 1.5 and 2.0.x have a separate table for link categories
807 - elseif (!isset($wp_db_version) or $wp_db_version < FWP_SCHEMA_21) :
808 - $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->linkcategories} WHERE cat_name='$cat'");
809 - // This should never happen.
810 - else :
811 - FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
1322 + if ($cat_id) :
1323 + // We found it; let's stamp it.
1324 + update_option('feedwordpress_cat_id', $cat_id);
812 1325 endif;
813 1326
814 - // If you still can't find anything, make it for yourself.
815 - // -------------------------------------------------------
816 - if (!$cat_id) :
817 - // WordPress 2.3+ term/taxonomy API
818 - if (function_exists('wp_insert_term')) :
819 - $term = wp_insert_term($cat, 'link_category');
820 - $cat_id = $term['term_id'];
821 - // WordPress 2.1, 2.2 category API. By the way, why the fuck is this API function only available in a wp-admin module?
822 - elseif (function_exists('wp_insert_category') and !fwp_test_wp_version(FWP_SCHEMA_20, FWP_SCHEMA_21)) :
823 - $cat_id = wp_insert_category(array('cat_name' => $cat));
824 - // WordPress 1.5 and 2.0.x
825 - elseif (fwp_test_wp_version(0, FWP_SCHEMA_21)) :
826 - $result = $wpdb->query("
827 - INSERT INTO $wpdb->linkcategories
828 - SET
829 - cat_id = 0,
830 - cat_name='$cat',
831 - show_images='N',
832 - show_description='N',
833 - show_rating='N',
834 - show_updated='N',
835 - sort_order='name'
836 - ");
837 - $cat_id = $wpdb->insert_id;
838 - // This should never happen.
839 - else :
840 - FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
841 - endif;
1327 + // If we *do* have the category ID stored, verify that it exists
1328 + else :
1329 + $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id');
1330 + endif;
1331 +
1332 + // If we could not find an appropriate link category,
1333 + // make a new one for ourselves.
1334 + if (!$cat_id) :
1335 + $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY);
1336 + if (!is_wp_error($cat_id)) :
1337 + // Stamp it
1338 + update_option('feedwordpress_cat_id', $cat_id);
842 1339 endif;
1340 + endif;
843 1341
844 - update_option('feedwordpress_cat_id', $cat_id);
845 - endif;
846 1342 return $cat_id;
847 1343 } // function FeedWordPress::link_category_id()
848 1344
849 1345 # Upgrades and maintenance...
@@ -853,12 +1349,9 @@
853 1349 $ret = false; // innocent until proven guilty
854 1350 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
855 1351 // This is an older version or a fresh install. Does it
856 1352 // require a database upgrade or database initialization?
857 - if ($fwp_db_version > 0.96) :
858 - // No. Just brand it with the new version.
859 - update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
860 - else :
1353 + if ($fwp_db_version <= 0.96) :
861 1354 // Yes. Check to see whether this is a fresh install or an upgrade.
862 1355 $syn = $wpdb->get_col("
863 1356 SELECT post_id
864 1357 FROM $wpdb->postmeta
@@ -868,8 +1361,53 @@
868 1361 $ret = true;
869 1362 else : // fresh install; brand it as ours
870 1363 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
871 1364 endif;
1365 + elseif ($fwp_db_version < 2009.0707) :
1366 + // We need to clear out any busted AJAX crap
1367 + $wpdb->query("
1368 + DELETE FROM $wpdb->usermeta
1369 + WHERE LOCATE('feedwordpress', meta_key)
1370 + AND LOCATE('box', meta_key);
1371 + ");
1372 +
1373 + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1374 + elseif ($fwp_db_version < 2010.0814) :
1375 + // Change in terminology.
1376 + if (get_option('feedwordpress_unfamiliar_category', 'create')=='default') :
1377 + update_option('feedwordpress_unfamiliar_category', 'null');
1378 + endif;
1379 + foreach (FeedWordPress::syndicated_links() as $link) :
1380 + $sub = new SyndicatedLink($link);
1381 +
1382 + $remap_uf = array(
1383 + 'default' => 'null',
1384 + 'filter' => 'null',
1385 + 'create' => 'create:category',
1386 + 'tag' => 'create:post_tag'
1387 + );
1388 + if (isset($sub->settings['unfamiliar category'])) :
1389 + if ($sub->settings['unfamiliar category']=='filter') :
1390 + $sub->settings['match/filter'] = array('category');
1391 + endif;
1392 + foreach ($remap_uf as $from => $to) :
1393 + if ($sub->settings['unfamiliar category']==$from) :
1394 + $sub->settings['unfamiliar category'] = $to;
1395 + endif;
1396 + endforeach;
1397 + endif;
1398 +
1399 + if (isset($sub->settings['add global categories'])) :
1400 + $sub->settings['add/category'] = $sub->settings['add global categories'];
1401 + unset($sub->settings['add global categories']);
1402 + endif;
1403 +
1404 + $sub->save_settings(/*reload=*/ true);
1405 + endforeach;
1406 + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1407 + else :
1408 + // No. Just brand it with the new version.
1409 + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
872 1410 endif;
873 1411 endif;
874 1412 return $ret;
875 1413 }
@@ -879,56 +1417,14 @@
879 1417
880 1418 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
881 1419
882 1420 switch ($from) :
883 - case 0.96: // account for changes to syndication custom values and guid
884 - echo "<p>Upgrading database from {$from} to ".FEEDWORDPRESS_VERSION."...</p>\n";
1421 + case 0.96:
1422 + // Dropping legacy upgrade code. If anyone is still
1423 + // using 0.96 and just now decided to upgrade, well, I'm
1424 + // sorry about that. You'll just have to cope with a few
1425 + // duplicate posts.
885 1426
886 - $cat_id = FeedWordPress::link_category_id();
887 -
888 - // Avoid duplicates
889 - $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'");
890 -
891 - // Look up all the link IDs
892 - $wpdb->query("
893 - CREATE TEMPORARY TABLE tmp_custom_values
894 - SELECT
895 - NULL AS meta_id,
896 - post_id,
897 - 'syndication_feed_id' AS meta_key,
898 - link_id AS meta_value
899 - FROM `{$wpdb->postmeta}`, `{$wpdb->links}`
900 - WHERE
901 - meta_key='syndication_feed'
902 - AND meta_value=link_rss
903 - AND link_category = {$cat_id}
904 - ");
905 -
906 - // Now attach them to their posts
907 - $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values");
908 -
909 - // And clean up after ourselves.
910 - $wpdb->query("DROP TABLE tmp_custom_values");
911 -
912 - // Now fix the guids to avoid duplicate posts
913 - echo "<ul>";
914 - foreach ($this->feeds as $feed) :
915 - echo "<li>Fixing post meta-data for <cite>".$feed['link/name']."</cite> &#8230; "; flush();
916 - $rss = @fetch_rss($feed['link/uri']);
917 - if (is_array($rss->items)) :
918 - foreach ($rss->items as $item) :
919 - $guid = $wpdb->escape(FeedWordPress::guid($item, $feed)); // new GUID algorithm
920 - $link = $wpdb->escape($item['link']);
921 -
922 - $wpdb->query("
923 - UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}'
924 - ");
925 - endforeach;
926 - endif;
927 - echo "<strong>complete.</strong></li>\n";
928 - endforeach;
929 - echo "</ul>\n";
930 -
931 1427 // Mark the upgrade as successful.
932 1428 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
933 1429 endswitch;
934 1430 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
@@ -933,8 +1429,27 @@
933 1429 endswitch;
934 1430 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
935 1431 } /* FeedWordPress::upgrade_database() */
936 1432
1433 + function has_guid_index () {
1434 + global $wpdb;
1435 +
1436 + $found = false; // Guilty until proven innocent.
1437 +
1438 + $results = $wpdb->get_results("
1439 + SHOW INDEXES FROM {$wpdb->posts}
1440 + ");
1441 + if ($results) :
1442 + foreach ($results as $index) :
1443 + if (isset($index->Column_name)
1444 + and ('guid' == $index->Column_name)) :
1445 + $found = true;
1446 + endif;
1447 + endforeach;
1448 + endif;
1449 + return $found;
1450 + } /* FeedWordPress::has_guid_index () */
1451 +
937 1452 function create_guid_index () {
938 1453 global $wpdb;
939 1454
940 1455 $wpdb->query("
@@ -941,8 +1456,116 @@
941 1456 CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid)
942 1457 ");
943 1458 } /* FeedWordPress::create_guid_index () */
944 1459
1460 + function remove_guid_index () {
1461 + global $wpdb;
1462 +
1463 + $wpdb->query("
1464 + DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
1465 + ");
1466 + }
1467 +
1468 + /*static*/ function fetch_timeout () {
1469 + return apply_filters(
1470 + 'feedwordpress_fetch_timeout',
1471 + intval(get_option('feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT))
1472 + );
1473 + }
1474 +
1475 + /*static*/ function fetch ($url, $params = array()) {
1476 + $force_feed = true; // Default
1477 +
1478 + // Allow user to change default feed-fetch timeout with a global setting. Props Erigami Scholey-Fuller <http://www.piepalace.ca/blog/2010/11/feedwordpress-broke-my-heart.html> 'timeout' =>
1479 + $timeout = FeedWordPress::fetch_timeout();
1480 +
1481 + if (!is_array($params)) :
1482 + $force_feed = $params;
1483 + else : // Parameter array
1484 + $args = shortcode_atts(array(
1485 + 'force_feed' => $force_feed,
1486 + 'timeout' => $timeout
1487 + ), $params);
1488 +
1489 + extract($args);
1490 + endif;
1491 + $timeout = intval($timeout);
1492 +
1493 + $pie_class = apply_filters('feedwordpress_simplepie_class', 'SimplePie');
1494 + $cache_class = apply_filters('feedwordpress_cache_class', 'WP_Feed_Cache');
1495 + $file_class = apply_filters('feedwordpress_file_class', 'FeedWordPress_File');
1496 + $parser_class = apply_filters('feedwordpress_parser_class', 'FeedWordPress_Parser');
1497 +
1498 + $sniffer_class = apply_filters('feedwordpress_sniffer_class', 'FeedWordPress_Content_Type_Sniffer');
1499 +
1500 + $feed = new $pie_class;
1501 + $feed->set_feed_url($url);
1502 + $feed->set_cache_class($cache_class);
1503 + $feed->set_timeout($timeout);
1504 +
1505 + //$feed->set_file_class('WP_SimplePie_File');
1506 + $feed->set_content_type_sniffer_class($sniffer_class);
1507 + $feed->set_file_class($file_class);
1508 + $feed->set_parser_class($parser_class);
1509 + $feed->force_feed($force_feed);
1510 + $feed->set_cache_duration(FeedWordPress::cache_duration());
1511 + $feed->init();
1512 + $feed->handle_content_type();
1513 +
1514 + if ($feed->error()) :
1515 + $ret = new WP_Error('simplepie-error', $feed->error());
1516 + else :
1517 + $ret = $feed;
1518 + endif;
1519 + return $ret;
1520 + } /* FeedWordPress::fetch () */
1521 +
1522 + function clear_cache () {
1523 + global $wpdb;
1524 +
1525 + // Just in case, clear out any old MagpieRSS cache records.
1526 + $magpies = $wpdb->query("
1527 + DELETE FROM {$wpdb->options}
1528 + WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32
1529 + ");
1530 +
1531 + // The WordPress SimplePie module stores its cached feeds as
1532 + // transient records in the options table. The data itself is
1533 + // stored in `_transient_feed_{md5 of url}` and the last-modified
1534 + // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
1535 + // these records are stored in `_transient_timeout_feed_{md5}`.
1536 + // Since the md5 is always 32 characters in length, the
1537 + // option_name is always over 32 characters.
1538 + $simplepies = $wpdb->query("
1539 + DELETE FROM {$wpdb->options}
1540 + WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
1541 + ");
1542 + $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each
1543 +
1544 + return ($magpies + $simplepies);
1545 + } /* FeedWordPress::clear_cache () */
1546 +
1547 + function cache_duration () {
1548 + $duration = NULL;
1549 + if (defined('FEEDWORDPRESS_CACHE_AGE')) :
1550 + $duration = FEEDWORDPRESS_CACHE_AGE;
1551 + endif;
1552 + return $duration;
1553 + }
1554 + function cache_lifetime ($duration) {
1555 + // Check for explicit setting of a lifetime duration
1556 + if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
1557 + $duration = FEEDWORDPRESS_CACHE_LIFETIME;
1558 +
1559 + // Fall back to the cache freshness duration
1560 + elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1561 + $duration = FEEDWORDPRESS_CACHE_AGE;
1562 + endif;
1563 +
1564 + // Fall back to WordPress default
1565 + return $duration;
1566 + } /* FeedWordPress::cache_lifetime () */
1567 +
945 1568 # Utility functions for handling text settings
946 1569 function negative ($f, $setting) {
947 1570 $nego = array ('n', 'no', 'f', 'false');
948 1571 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
@@ -952,9 +1575,8 @@
952 1575 $affirmo = array ('y', 'yes', 't', 'true', 1);
953 1576 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
954 1577 }
955 1578
956 -
957 1579 # Internal debugging functions
958 1580 function critical_bug ($varname, $var, $line) {
959 1581 global $wp_version;
960 1582
@@ -961,9 +1583,9 @@
961 1583 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>';
962 1584 echo "\n<plaintext>";
963 1585 echo "Triggered at line # ".$line."\n";
964 1586 echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
965 - echo "WordPress version: $wp_version\n";
1587 + echo "WordPress version: {$wp_version}\n";
966 1588 echo "PHP version: ".phpversion()."\n";
967 1589 echo "\n";
968 1590 echo $varname.": "; var_dump($var); echo "\n";
969 1591 die;
@@ -973,1882 +1595,572 @@
973 1595 if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging
974 1596 FeedWordPress::critical_bug($varname, $var, $line);
975 1597 endif;
976 1598 }
977 -} // class FeedWordPress
978 -
979 -class SyndicatedPost {
980 - var $item = null;
981 1599
982 - var $link = null;
983 - var $feed = null;
984 - var $feedmeta = null;
985 -
986 - var $post = array ();
987 - var $_base = null;
1600 + function val ($v, $no_newlines = false) {
1601 + ob_start();
1602 + var_dump($v);
1603 + $out = ob_get_contents(); ob_end_clean();
1604 +
1605 + if ($no_newlines) :
1606 + $out = preg_replace('/\s+/', " ", $out);
1607 + endif;
1608 + return $out;
1609 + } /* FeedWordPress::val () */
988 1610
989 - var $_freshness = null;
990 - var $_wp_id = null;
1611 + function diagnostic_on ($level) {
1612 + $show = get_option('feedwordpress_diagnostics_show', array());
1613 + return (in_array($level, $show));
1614 + } /* FeedWordPress::diagnostic_on () */
991 1615
992 - function SyndicatedPost ($item, $link) {
993 - global $wpdb;
1616 + function diagnostic ($level, $out, $persist = NULL, $since = NULL, $mostRecent = NULL) {
1617 + global $feedwordpress_admin_footer;
994 1618
995 - $this->link = $link;
996 - $feedmeta = $link->settings;
997 - $feed = $link->magpie;
1619 + $output = get_option('feedwordpress_diagnostics_output', array());
1620 + $dlog = get_option('feedwordpress_diagnostics_log', array());
998 1621
999 - # This is ugly as all hell. I'd like to use apply_filters()'s
1000 - # alleged support for a variable argument count, but this seems
1001 - # to have been broken in WordPress 1.5. It'll be fixed somehow
1002 - # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
1003 - # the board here.
1004 - #
1005 - # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
1006 - global $fwp_channel, $fwp_feedmeta;
1007 - $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
1622 + $diagnostic_nesting = count(explode(":", $level));
1008 1623
1009 - $this->feed = $feed;
1010 - $this->feedmeta = $feedmeta;
1011 -
1012 - $this->item = $item;
1013 - $this->item = apply_filters('syndicated_item', $this->item, $this);
1014 -
1015 - # Filters can halt further processing by returning NULL
1016 - if (is_null($this->item)) :
1017 - $this->post = NULL;
1018 - else :
1019 - # Note that nothing is run through $wpdb->escape() here.
1020 - # That's deliberate. The escaping is done at the point
1021 - # of insertion, not here, to avoid double-escaping and
1022 - # to avoid screwing with syndicated_post filters
1023 -
1024 - $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
1025 -
1026 - // This just gives us an alphanumeric representation of
1027 - // the author. We will look up (or create) the numeric
1028 - // ID for the author in SyndicatedPost::add()
1029 - $this->post['named']['author'] = apply_filters('syndicated_item_author', $this->author(), $this);
1030 -
1031 - # Identify content and sanitize it.
1032 - # ---------------------------------
1033 - if (isset($this->item['xhtml']['body'])) :
1034 - $content = $this->item['xhtml']['body'];
1035 - elseif (isset($this->item['xhtml']['div'])) :
1036 - $content = $this->item['xhtml']['div'];
1037 - elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
1038 - $content = $this->item['content']['encoded'];
1039 - else:
1040 - $content = $this->item['description'];
1041 - endif;
1042 - $this->post['post_content'] = apply_filters('syndicated_item_content', $content, $this);
1043 -
1044 - # Identify and sanitize excerpt
1045 - $excerpt = NULL;
1046 - if ( isset($this->item['description']) and $this->item['description'] ) :
1047 - $excerpt = $this->item['description'];
1048 - elseif ( isset($content) and $content ) :
1049 - $excerpt = strip_tags($content);
1050 - if (strlen($excerpt) > 255) :
1051 - $excerpt = substr($excerpt,0,252).'...';
1052 - endif;
1053 - endif;
1054 - $excerpt = apply_filters('syndicated_item_excerpt', $excerpt, $this);
1055 -
1056 - if (!is_null($excerpt)):
1057 - $this->post['post_excerpt'] = $excerpt;
1058 - endif;
1059 -
1060 - // This is unnecessary if we use wp_insert_post
1061 - if (!$this->use_api('wp_insert_post')) :
1062 - $this->post['post_name'] = sanitize_title($this->post['post_title']);
1063 - endif;
1064 -
1065 - $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
1066 - $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
1067 - $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
1068 -
1069 - // Dealing with timestamps in WordPress is so fucking fucked.
1070 - $offset = (int) get_option('gmt_offset') * 60 * 60;
1071 - $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
1072 - $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
1073 - $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
1074 - $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
1075 -
1076 - // Use feed-level preferences or the global default.
1077 - $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
1078 - $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
1079 - $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
1080 -
1081 - // Unique ID (hopefully a unique tag: URI); failing that, the permalink
1082 - $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
1083 -
1084 - // RSS 2.0 / Atom 1.0 enclosure support
1085 - if ( isset($this->item['enclosure#']) ) :
1086 - for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
1087 - $eid = (($i > 1) ? "#{$id}" : "");
1088 - $this->post['meta']['enclosure'][] =
1089 - apply_filters('syndicated_item_enclosure_url', $this->item["enclosure{$eid}@url"], $this)."\n".
1090 - apply_filters('syndicated_item_enclosure_length', $this->item["enclosure{$eid}@length"], $this)."\n".
1091 - apply_filters('syndicated_item_enclosure_type', $this->item["enclosure{$eid}@type"], $this);
1092 - endfor;
1093 - endif;
1094 -
1095 - // In case you want to point back to the blog this was syndicated from
1096 - if (isset($this->feed->channel['title'])) :
1097 - $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
1098 - endif;
1099 -
1100 - if (isset($this->feed->channel['link'])) :
1101 - $this->post['meta']['syndication_source_uri'] = apply_filters('syndicated_item_source_link', $this->feed->channel['link'], $this);
1102 - endif;
1103 -
1104 - // Make use of atom:source data, if present in an aggregated feed
1105 - if (isset($this->item['source_title'])) :
1106 - $this->post['meta']['syndication_source_original'] = $this->item['source_title'];
1107 - endif;
1108 -
1109 - if (isset($this->item['source_link'])) :
1110 - $this->post['meta']['syndication_source_uri_original'] = $this->item['source_link'];
1111 - endif;
1112 -
1113 - if (isset($this->item['source_id'])) :
1114 - $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
1115 - endif;
1116 -
1117 - // Store information on human-readable and machine-readable comment URIs
1118 - if (isset($this->item['comments'])) :
1119 - $this->post['meta']['rss:comments'] = apply_filters('syndicated_item_comments', $this->item['comments']);
1120 - endif;
1121 - if (isset($this->item['wfw']['commentrss'])) :
1122 - $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
1123 - endif;
1124 -
1125 - // Store information to identify the feed that this came from
1126 - $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
1127 - $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
1128 -
1129 - if (isset($this->item['source_link_self'])) :
1130 - $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
1131 - endif;
1132 -
1133 - // In case you want to know the external permalink...
1134 - $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->item['link']);
1135 -
1136 - // Store a hash of the post content for checking whether something needs to be updated
1137 - $this->post['meta']['syndication_item_hash'] = $this->update_hash();
1138 -
1139 - // Feed-by-feed options for author and category creation
1140 - $this->post['named']['unfamiliar']['author'] = $this->feedmeta['unfamiliar author'];
1141 - $this->post['named']['unfamiliar']['category'] = $this->feedmeta['unfamiliar category'];
1142 -
1143 - // Categories: start with default categories, if any
1144 - $fc = get_option("feedwordpress_syndication_cats");
1145 - if ($fc) :
1146 - $this->post['named']['preset/category'] = explode("\n", $fc);
1147 - else :
1148 - $this->post['named']['preset/category'] = array();
1149 - endif;
1150 -
1151 - if (is_array($this->feedmeta['cats'])) :
1152 - $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
1153 - endif;
1154 -
1155 - // Now add categories from the post, if we have 'em
1156 - $this->post['named']['category'] = array();
1157 - if ( isset($this->item['category#']) ) :
1158 - for ($i = 1; $i <= $this->item['category#']; $i++) :
1159 - $cat_idx = (($i > 1) ? "#{$i}" : "");
1160 - $cat = $this->item["category{$cat_idx}"];
1161 -
1162 - if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
1163 - $pcre = "\007".$this->feedmeta['cat_split']."\007";
1164 - $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
1624 + if (FeedWordPress::diagnostic_on($level)) :
1625 + foreach ($output as $method) :
1626 + switch ($method) :
1627 + case 'echo' :
1628 + echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.$out."</pre></div>";
1629 + break;
1630 + case 'echo_in_cronjob' :
1631 + if (FeedWordPress::update_requested()) :
1632 + echo FeedWordPress::log_prefix()." ".$out."\n";
1633 + endif;
1634 + break;
1635 + case 'admin_footer' :
1636 + $feedwordpress_admin_footer[] = $out;
1637 + break;
1638 + case 'error_log' :
1639 + error_log(FeedWordPress::log_prefix().' '.$out);
1640 + break;
1641 + case 'email' :
1642 + if (is_null($persist)) :
1643 + $sect = 'occurrent';
1644 + $hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0);
1645 + $line = array("Time" => time(), "Message" => $out);
1165 1646 else :
1166 - $this->post['named']['category'][] = $cat;
1647 + $sect = 'persistent';
1648 + $hook = md5($level."\n".$persist);
1649 + $line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent);
1167 1650 endif;
1168 - endfor;
1169 - endif;
1170 - $this->post['named']['category'] = apply_filters('syndicated_item_categories', $this->post['named']['category'], $this);
1171 -
1172 - // Tags: start with default tags, if any
1173 - $ft = get_option("feedwordpress_syndication_tags");
1174 - if ($ft) :
1175 - $this->post['tags_input'] = explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft);
1176 - else :
1177 - $this->post['tags_input'] = array();
1178 - endif;
1179 -
1180 - if (is_array($this->feedmeta['tags'])) :
1181 - $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
1182 - endif;
1183 -
1651 +
1652 + if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
1653 + if (!isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif;
1654 +
1655 + $dlog['mesg'][$sect][$hook] = $line;
1656 + endswitch;
1657 + endforeach;
1184 1658 endif;
1185 - } // SyndicatedPost::SyndicatedPost()
1186 -
1187 - function filtered () {
1188 - return is_null($this->post);
1189 - }
1190 -
1191 - function freshness () {
1192 - global $wpdb;
1193 -
1194 - if ($this->filtered()) : // This should never happen.
1195 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1196 - endif;
1197 1659
1198 - if (is_null($this->_freshness)) :
1199 - $guid = $wpdb->escape($this->guid());
1200 -
1201 - $result = $wpdb->get_row("
1202 - SELECT id, guid, post_modified_gmt
1203 - FROM $wpdb->posts WHERE guid='$guid'
1204 - ");
1205 -
1206 - if (!$result) :
1207 - $this->_freshness = 2; // New content
1208 - else:
1209 - $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
1210 - if (count($stored_update_hashes) > 0) :
1211 - $stored_update_hash = $stored_update_hashes[0];
1212 - $update_hash_changed = ($stored_update_hash != $this->update_hash());
1213 - else :
1214 - $update_hash_changed = false;
1215 - endif;
1216 -
1217 - preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
1218 -
1219 - $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
1220 - $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1221 - $updated = ((
1222 - !is_null($updated_ts)
1223 - and ($updated_ts > $last_rev_ts)
1224 - ) or $update_hash_changed);
1225 -
1226 - if ($updated) :
1227 - $this->_freshness = 1; // Updated content
1228 - $this->_wp_id = $result->id;
1229 - else :
1230 - $this->_freshness = 0; // Same old, same old
1231 - $this->_wp_id = $result->id;
1232 - endif;
1233 - endif;
1234 - endif;
1235 - return $this->_freshness;
1236 - }
1237 -
1238 - function wp_id () {
1239 - if ($this->filtered()) : // This should never happen.
1240 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1241 - endif;
1660 + update_option('feedwordpress_diagnostics_log', $dlog);
1661 + } /* FeedWordPress::diagnostic () */
1662 +
1663 + function email_diagnostic_log () {
1664 + $dlog = get_option('feedwordpress_diagnostics_log', array());
1242 1665
1243 - if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1244 - $fresh = $this->freshness(); // sets WP DB id in the process
1245 - endif;
1246 - return $this->_wp_id;
1247 - }
1666 + if (isset($dlog['schedule']) and isset($dlog['schedule']['last'])) :
1667 + if (time() > ($dlog['schedule']['last'] + $dlog['schedule']['freq'])) :
1668 + // No news is good news; only send if
1669 + // there are some messages to send.
1670 + $body = NULL;
1671 + foreach ($dlog['mesg'] as $sect => $mesgs) :
1672 + if (count($mesgs) > 0) :
1673 + if (is_null($body)) : $body = ''; endif;
1674 +
1675 + $paradigm = reset($mesgs);
1676 + $body .= "<h2>".ucfirst($sect)." issues</h2>\n"
1677 + ."<table>\n"
1678 + ."<thead><tr>\n";
1679 + foreach ($paradigm as $col => $value) :
1680 + $body .= '<th scope="col">'.$col."</th>\n";
1681 + endforeach;
1682 + $body .= "</tr></thead>\n"
1683 + ."<tbody>\n";
1684 +
1685 + foreach ($mesgs as $line) :
1686 + $body .= "<tr>\n";
1687 + foreach ($line as $col => $cell) :
1688 + if (is_numeric($cell)) :
1689 + $cell = date('j-M-y, h:i a', $cell);
1690 + endif;
1691 + $class = strtolower(preg_replace('/\s+/', '-', $col));
1692 + $body .= "<td class=\"$class\">${cell}</td>";
1693 + endforeach;
1694 + $body .= "</tr>\n";
1695 + endforeach;
1696 +
1697 + $body .= "</tbody>\n</table>\n\n";
1698 + endif;
1699 + endforeach;
1700 +
1701 + if (!is_null($body)) :
1702 + $home = feedwordpress_display_url(get_bloginfo('url'));
1703 + $subj = $home . " syndication issues for ".date('j-M-y', time());
1704 + $agent = 'FeedWordPress '.FEEDWORDPRESS_VERSION;
1705 + $body = <<<EOMAIL
1706 +<html>
1707 +<head>
1708 +<title>$subj</title>
1709 +<style type="text/css">
1710 + body { background-color: white; color: black; }
1711 + table { width: 100%; border: 1px solid black; }
1712 + table thead tr th { background-color: #ff7700; color: white; border-bottom: 1px solid black; }
1713 + table thead tr { border-bottom: 1px solid black; }
1714 + table tr { vertical-align: top; }
1715 + table .since { width: 20%; }
1716 + table .time { width: 20%; }
1717 + table .most-recently { width: 20%; }
1718 + table .message { width: auto; }
1719 +</style>
1720 +</head>
1721 +<body>
1722 +<h1>Syndication Issues encountered by $agent on $home</h1>
1723 +$body
1724 +</body>
1725 +</html>
1248 1726
1249 - function store () {
1250 - global $wpdb;
1727 +EOMAIL;
1251 1728
1252 - if ($this->filtered()) : // This should never happen.
1253 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1254 - endif;
1255 -
1256 - $freshness = $this->freshness();
1257 - if ($freshness > 0) :
1258 - # -- Look up, or create, numeric ID for author
1259 - $this->post['post_author'] = $this->author_id (
1260 - FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
1261 - );
1729 + $ded = get_option('feedwordpress_diagnostics_email_destination', 'admins');
1262 1730
1263 - if (is_null($this->post['post_author'])) :
1264 - $this->post = NULL;
1265 - endif;
1266 - endif;
1267 -
1268 - if (!$this->filtered() and $freshness > 0) :
1269 - # -- Look up, or create, numeric ID for categories
1270 - list($pcats, $ptags) = $this->category_ids (
1271 - $this->post['named']['category'],
1272 - FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
1273 - /*tags_too=*/ true
1274 - );
1731 + // e-mail address
1732 + if (preg_match('/^mailto:(.*)$/', $ded, $ref)) :
1733 + $recipients = array($ref[1]);
1734 +
1735 + // userid
1736 + elseif (preg_match('/^user:(.*)$/', $ded, $ref)) :
1737 + $userdata = get_userdata((int) $ref[1]);
1738 + $recipients = array($userdata->user_email);
1739 +
1740 + // admins
1741 + else :
1742 + $recipients = FeedWordPressDiagnostic::admin_emails();
1743 + endif;
1275 1744
1276 - $this->post['post_category'] = $pcats;
1277 - $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
1278 -
1279 - if (is_null($this->post['post_category'])) :
1280 - // filter mode on, no matching categories; drop the post
1281 - $this->post = NULL;
1282 - else :
1283 - // filter mode off or at least one match; now add on the feed and global presets
1284 - $this->post['post_category'] = array_merge (
1285 - $this->post['post_category'],
1286 - $this->category_ids (
1287 - $this->post['named']['preset/category'],
1288 - 'default'
1289 - )
1290 - );
1291 -
1292 - if (count($this->post['post_category']) < 1) :
1293 - $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
1745 + foreach ($recipients as $email) :
1746 + add_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
1747 + wp_mail($email, $subj, $body);
1748 + remove_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail'));
1749 + endforeach;
1294 1750 endif;
1751 +
1752 + // Clear the logs
1753 + $dlog['mesg']['persistent'] = array();
1754 + $dlog['mesg']['occurrent'] = array();
1755 +
1756 + // Set schedule for next update
1757 + $dlog['schedule']['last'] = time();
1295 1758 endif;
1296 - endif;
1297 -
1298 - if (!$this->filtered() and $freshness > 0) :
1299 - unset($this->post['named']);
1300 - $this->post = apply_filters('syndicated_post', $this->post, $this);
1301 - endif;
1302 -
1303 - if (!$this->filtered() and $freshness == 2) :
1304 - // The item has not yet been added. So let's add it.
1305 - $this->insert_new();
1306 - $this->add_rss_meta();
1307 - do_action('post_syndicated_item', $this->wp_id());
1308 -
1309 - $ret = 'new';
1310 - elseif (!$this->filtered() and $freshness == 1) :
1311 - $this->post['ID'] = $this->wp_id();
1312 - $this->update_existing();
1313 - $this->add_rss_meta();
1314 - do_action('update_syndicated_item', $this->wp_id());
1315 -
1316 - $ret = 'updated';
1317 1759 else :
1318 - $ret = false;
1760 + $dlog['schedule'] = array(
1761 + 'freq' => 24 /*hr*/ * 60 /*min*/ * 60 /*s*/,
1762 + 'last' => time(),
1763 + );
1319 1764 endif;
1320 1765
1321 - return $ret;
1322 - } // function SyndicatedPost::store ()
1766 + update_option('feedwordpress_diagnostics_log', $dlog);
1767 + } /* FeedWordPress::email_diagnostic_log () */
1323 1768
1324 - function insert_new () {
1325 - global $wpdb, $wp_db_version;
1326 -
1327 - // Why the fuck doesn't wp_insert_post already do this?
1328 - foreach ($this->post as $key => $value) :
1329 - if (is_string($value)) :
1330 - $dbpost[$key] = $wpdb->escape($value);
1331 - else :
1332 - $dbpost[$key] = $value;
1333 - endif;
1769 + function allow_html_mail () {
1770 + return 'text/html';
1771 + } /* FeedWordPress::allow_html_mail () */
1772 +
1773 + function admin_footer () {
1774 + global $feedwordpress_admin_footer;
1775 + foreach ($feedwordpress_admin_footer as $line) :
1776 + echo '<div><pre>'.$line.'</pre></div>';
1334 1777 endforeach;
1335 -
1336 - if (strlen($dbpost['post_title'].$dbpost['post_content'].$dbpost['post_excerpt']) == 0) :
1337 - // FIXME: Option for filtering out empty posts
1778 + } /* FeedWordPress::admin_footer () */
1779 +
1780 + function log_prefix ($date = false) {
1781 + $home = get_bloginfo('url');
1782 + $prefix = '['.feedwordpress_display_url($home).'] [feedwordpress] ';
1783 + if ($date) :
1784 + $prefix = "[".date('Y-m-d H:i:s')."]".$prefix;
1338 1785 endif;
1339 - if (strlen($dbpost['post_title'])==0) :
1340 - $dbpost['post_title'] = $this->post['meta']['syndication_source']
1341 - .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1342 - // FIXME: Option for what to fill a blank title with...
1343 - endif;
1344 -
1345 - if ($this->use_api('wp_insert_post')) :
1346 - $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1347 -
1348 - // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1349 - add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1350 -
1351 - // Kludge to prevent kses filters from stripping the
1352 - // content of posts when updating without a logged in
1353 - // user who has `unfiltered_html` capability.
1354 - add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1355 -
1356 - $this->_wp_id = wp_insert_post($dbpost);
1357 -
1358 - // Turn off ridiculous fucking kludges #1 and #2
1359 - remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1360 - remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1361 -
1362 - // This should never happen.
1363 - if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1364 - FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1365 - endif;
1786 + return $prefix;
1787 + } /* FeedWordPress::log_prefix () */
1366 1788
1367 - // Unfortunately, as of WordPress 2.3, wp_insert_post()
1368 - // *still* offers no way to use a guid of your choice,
1369 - // and munges your post modified timestamp, too.
1370 - $result = $wpdb->query("
1371 - UPDATE $wpdb->posts
1372 - SET
1373 - guid='{$dbpost['guid']}',
1374 - post_modified='{$dbpost['post_modified']}',
1375 - post_modified_gmt='{$dbpost['post_modified_gmt']}'
1376 - WHERE ID='{$this->_wp_id}'
1377 - ");
1789 + function menu_cap ($sub = false) {
1790 + if ($sub) :
1791 + $cap = apply_filters('feedwordpress_menu_settings_capacity', 'manage_options');
1378 1792 else :
1379 - # The right way to do this is the above. But, alas,
1380 - # in earlier versions of WordPress, wp_insert_post has
1381 - # too much behavior (mainly related to pings) that can't
1382 - # be overridden. In WordPress 1.5, it's enough of a
1383 - # resource hog to make PHP segfault after inserting
1384 - # 50-100 posts. This can get pretty annoying, especially
1385 - # if you are trying to update your feeds for the first
1386 - # time.
1387 -
1388 - $result = $wpdb->query("
1389 - INSERT INTO $wpdb->posts
1390 - SET
1391 - guid = '{$dbpost['guid']}',
1392 - post_author = '{$dbpost['post_author']}',
1393 - post_date = '{$dbpost['post_date']}',
1394 - post_date_gmt = '{$dbpost['post_date_gmt']}',
1395 - post_content = '{$dbpost['post_content']}',"
1396 - .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1397 - post_title = '{$dbpost['post_title']}',
1398 - post_name = '{$dbpost['post_name']}',
1399 - post_modified = '{$dbpost['post_modified']}',
1400 - post_modified_gmt = '{$dbpost['post_modified_gmt']}',
1401 - comment_status = '{$dbpost['comment_status']}',
1402 - ping_status = '{$dbpost['ping_status']}',
1403 - post_status = '{$dbpost['post_status']}'
1404 - ");
1405 - $this->_wp_id = $wpdb->insert_id;
1406 -
1407 - // This should never happen.
1408 - if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1409 - FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1410 - endif;
1411 -
1412 - // WordPress 1.5.x - 2.0.x
1413 - wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1793 + $cap = apply_filters('feedwordpress_menu_main_capacity', 'manage_links');
1794 + endif;
1795 + return $cap;
1796 + } /* FeedWordPress::menu_cap () */
1414 1797
1415 - // Since we are not going through official channels, we need to
1416 - // manually tell WordPress that we've published a new post.
1417 - // We need to make sure to do this in order for FeedWordPress
1418 - // to play well with the staticize-reloaded plugin (something
1419 - // that a large aggregator website is going to *want* to be
1420 - // able to use).
1421 - do_action('publish_post', $this->_wp_id);
1798 + function path ($filename = '') {
1799 + global $fwp_path;
1800 +
1801 + $path = $fwp_path;
1802 + if (strlen($filename) > 0) :
1803 + $path .= '/'.$filename;
1422 1804 endif;
1423 - } /* SyndicatedPost::insert_new() */
1424 -
1425 - function update_existing () {
1426 - global $wpdb;
1427 -
1428 - // Why the fuck doesn't wp_insert_post already do this?
1429 - $dbpost = array();
1430 - foreach ($this->post as $key => $value) :
1431 - if (is_string($value)) :
1432 - $dbpost[$key] = $wpdb->escape($value);
1433 - else :
1434 - $dbpost[$key] = $value;
1435 - endif;
1436 - endforeach;
1437 -
1438 - if ($this->use_api('wp_insert_post')) :
1439 - $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1440 -
1441 - // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1442 - add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1443 -
1444 - // Kludge to prevent kses filters from stripping the
1445 - // content of posts when updating without a logged in
1446 - // user who has `unfiltered_html` capability.
1447 - add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1448 -
1449 - $this->_wp_id = wp_insert_post($dbpost);
1450 -
1451 - // Turn off ridiculous fucking kludges #1 and #2
1452 - remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1453 - remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1454 -
1455 - // This should never happen.
1456 - if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1457 - FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1458 - endif;
1459 -
1460 - // Unfortunately, as of WordPress 2.3, wp_insert_post()
1461 - // munges your post modified timestamp.
1462 - $result = $wpdb->query("
1463 - UPDATE $wpdb->posts
1464 - SET
1465 - post_modified='{$dbpost['post_modified']}',
1466 - post_modified_gmt='{$dbpost['post_modified_gmt']}'
1467 - WHERE ID='{$this->_wp_id}'
1468 - ");
1469 - else :
1470 -
1471 - $result = $wpdb->query("
1472 - UPDATE $wpdb->posts
1473 - SET
1474 - post_author = '{$dbpost['post_author']}',
1475 - post_content = '{$dbpost['post_content']}',"
1476 - .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1477 - post_title = '{$dbpost['post_title']}',
1478 - post_name = '{$dbpost['post_name']}',
1479 - post_modified = '{$dbpost['post_modified']}',
1480 - post_modified_gmt = '{$dbpost['post_modified_gmt']}'
1481 - WHERE guid='{$dbpost['guid']}'
1482 - ");
1805 + return $path;
1806 + }
1483 1807
1484 - // WordPress 2.1.x and up
1485 - if (function_exists('wp_set_post_categories')) :
1486 - wp_set_post_categories($this->wp_id(), $this->post['post_category']);
1487 - // WordPress 1.5.x - 2.0.x
1488 - elseif (function_exists('wp_set_post_cats')) :
1489 - wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1490 - // This should never happen.
1491 - else :
1492 - FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1808 + function param ($key, $type = 'REQUEST', $default = NULL) {
1809 + $where = '_'.strtoupper($type);
1810 + $ret = $default;
1811 + if (isset($GLOBALS[$where]) and is_array($GLOBALS[$where])) :
1812 + if (isset($GLOBALS[$where][$key])) :
1813 + $ret = $GLOBALS[$where][$key];
1814 + if (get_magic_quotes_gpc()) :
1815 + $ret = stripslashes_deep($ret);
1816 + endif;
1493 1817 endif;
1494 -
1495 - // Since we are not going through official channels, we need to
1496 - // manually tell WordPress that we've published a new post.
1497 - // We need to make sure to do this in order for FeedWordPress
1498 - // to play well with the staticize-reloaded plugin (something
1499 - // that a large aggregator website is going to *want* to be
1500 - // able to use).
1501 - do_action('edit_post', $this->post['ID']);
1502 1818 endif;
1503 - } /* SyndicatedPost::update_existing() */
1819 + return $ret;
1820 + }
1821 + function post ($key, $default = NULL) {
1822 + return FeedWordPress::param($key, 'POST');
1823 + }
1824 +} // class FeedWordPress
1504 1825
1505 - /**
1506 - * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
1507 - * meta-data (authorship, etc.) when storing revisions of an updated
1508 - * syndicated post.
1509 - *
1510 - * In their infinite wisdom, the WordPress coders have made it completely
1511 - * impossible for a plugin that uses wp_insert_post() to set certain
1512 - * meta-data (such as the author) when you store an old revision of an
1513 - * updated post. Instead, it uses the WordPress defaults (= currently
1514 - * active user ID if the process is running with a user logged in, or
1515 - * = #0 if there is no user logged in). This results in bogus authorship
1516 - * data for revisions that are syndicated from off the feed, unless we
1517 - * use a ridiculous kludge like this to end-run the munging of meta-data
1518 - * by _wp_put_post_revision.
1519 - *
1520 - * @param int $revision_id The revision ID to fix up meta-data
1521 - */
1522 - function fix_revision_meta ($revision_id) {
1523 - global $wpdb;
1524 -
1525 - $post_author = (int) $this->post['post_author'];
1526 -
1527 - $revision_id = (int) $revision_id;
1528 - $wpdb->query("
1529 - UPDATE $wpdb->posts
1530 - SET post_author={$this->post['post_author']}
1531 - WHERE post_type = 'revision' AND ID='$revision_id'
1532 - ");
1533 - } /* SyndicatedPost::fix_revision_meta () */
1826 +class FeedWordPress_File extends WP_SimplePie_File {
1827 + function FeedWordPress_File ($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) {
1828 + WP_SimplePie_File::WP_SimplePie_File($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
1534 1829
1535 - /**
1536 - * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
1537 - * an automatic update, that generally means that wp_insert_post() is
1538 - * being called under the user credentials of whoever is viewing the
1539 - * blog at the time -- usually meaning no user at all. But if WordPress
1540 - * gets a wp_insert_post() when current_user_can('unfiltered_html') is
1541 - * false, it will run the content of the post through a kses function
1542 - * that strips out lots of HTML tags -- notably <object> and some others.
1543 - * This causes problems for syndicating (for example) feeds that contain
1544 - * YouTube videos. It also produces an unexpected asymmetry between
1545 - * automatically-initiated updates and updates initiated manually from
1546 - * the WordPress Dashboard (which are usually initiated under the
1547 - * credentials of a logged-in admin, and so don't get run through the
1548 - * kses function). So, to avoid the whole mess, what we do here is
1549 - * just forcibly disable the kses munging for a single syndicated post,
1550 - * by restoring the contents of the `post_content` field.
1551 - *
1552 - * @param string $content The content of the post, after other filters have gotten to it
1553 - * @return string The original content of the post, before other filters had a chance to munge it.
1554 - */
1555 - function avoid_kses_munge ($content) {
1556 - global $wpdb;
1557 - return $wpdb->escape($this->post['post_content']);
1558 - }
1559 -
1560 - // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
1561 - // using the space for custom keys. The set of keys and values to add is
1562 - // specified by the keys and values of $post['meta']. This is used to
1563 - // store anything that the WordPress user might want to access from a
1564 - // template concerning the post's original source that isn't provided
1565 - // for by standard WP meta-data (i.e., any interesting data about the
1566 - // syndicated post other than author, title, timestamp, categories, and
1567 - // guid). It's also used to hook into WordPress's support for
1568 - // enclosures.
1569 - function add_rss_meta () {
1570 - global $wpdb;
1571 - if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
1572 - $postId = $this->wp_id();
1573 -
1574 - // Aggregated posts should NOT send out pingbacks.
1575 - // WordPress 2.1-2.2 claim you can tell them not to
1576 - // using $post_pingback, but they don't listen, so we
1577 - // make sure here.
1578 - $result = $wpdb->query("
1579 - DELETE FROM $wpdb->postmeta
1580 - WHERE post_id='$postId' AND meta_key='_pingme'
1581 - ");
1830 + // SimplePie makes a strongly typed check against integers with
1831 + // this, but WordPress puts a string in. Which causes caching
1832 + // to break and fall on its ass when SimplePie is getting a 304,
1833 + // but doesn't realize it because this member is "304" instead.
1834 + $this->status_code = (int) $this->status_code;
1835 + }
1836 +} /* class FeedWordPress_File () */
1582 1837
1583 - foreach ( $this->post['meta'] as $key => $values ) :
1838 +class FeedWordPress_Parser extends SimplePie_Parser {
1839 + function parse (&$data, $encoding) {
1840 + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
1841 + if (strtoupper($encoding) === 'US-ASCII')
1842 + {
1843 + $this->encoding = 'UTF-8';
1844 + }
1845 + else
1846 + {
1847 + $this->encoding = $encoding;
1848 + }
1584 1849
1585 - $key = $wpdb->escape($key);
1850 + // Strip BOM:
1851 + // UTF-32 Big Endian BOM
1852 + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
1853 + {
1854 + $data = substr($data, 4);
1855 + }
1856 + // UTF-32 Little Endian BOM
1857 + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
1858 + {
1859 + $data = substr($data, 4);
1860 + }
1861 + // UTF-16 Big Endian BOM
1862 + elseif (substr($data, 0, 2) === "\xFE\xFF")
1863 + {
1864 + $data = substr($data, 2);
1865 + }
1866 + // UTF-16 Little Endian BOM
1867 + elseif (substr($data, 0, 2) === "\xFF\xFE")
1868 + {
1869 + $data = substr($data, 2);
1870 + }
1871 + // UTF-8 BOM
1872 + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
1873 + {
1874 + $data = substr($data, 3);
1875 + }
1586 1876
1587 - // If this is an update, clear out the old
1588 - // values to avoid duplication.
1589 - $result = $wpdb->query("
1590 - DELETE FROM $wpdb->postmeta
1591 - WHERE post_id='$postId' AND meta_key='$key'
1592 - ");
1877 + if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false)
1878 + {
1879 + $declaration =& new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
1880 + if ($declaration->parse())
1881 + {
1882 + $data = substr($data, $pos + 2);
1883 + $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data;
1884 + }
1885 + else
1886 + {
1887 + $this->error_string = 'SimplePie bug! Please report this!';
1888 + return false;
1889 + }
1890 + }
1593 1891
1594 - // Allow for either a single value or an array
1595 - if (!is_array($values)) $values = array($values);
1596 - foreach ( $values as $value ) :
1597 - $value = $wpdb->escape($value);
1598 - $result = $wpdb->query("
1599 - INSERT INTO $wpdb->postmeta
1600 - SET
1601 - post_id='$postId',
1602 - meta_key='$key',
1603 - meta_value='$value'
1604 - ");
1605 - endforeach;
1606 - endforeach;
1607 - endif;
1608 - } /* SyndicatedPost::add_rss_meta () */
1892 + $return = true;
1609 1893
1610 - // SyndicatedPost::author_id (): get the ID for an author name from
1611 - // the feed. Create the author if necessary.
1612 - function author_id ($unfamiliar_author = 'create') {
1613 - global $wpdb;
1894 + static $xml_is_sane = null;
1895 + if ($xml_is_sane === null)
1896 + {
1897 + $parser_check = xml_parser_create();
1898 + xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
1899 + xml_parser_free($parser_check);
1900 + $xml_is_sane = isset($values[0]['value']);
1901 + }
1614 1902
1615 - $a = $this->author();
1616 - $author = $a['name'];
1617 - $email = $a['email'];
1618 - $url = $a['uri'];
1903 + // Create the parser
1904 + if ($xml_is_sane)
1905 + {
1906 + $xml = xml_parser_create_ns($this->encoding, $this->separator);
1907 + xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
1908 + xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
1909 + xml_set_object($xml, $this);
1910 + xml_set_character_data_handler($xml, 'cdata');
1911 + xml_set_element_handler($xml, 'tag_open', 'tag_close');
1912 + xml_set_start_namespace_decl_handler($xml, 'start_xmlns');
1619 1913
1620 - $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
1621 - if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
1622 - $test_email = $email;
1623 - else :
1624 - $test_email = NULL;
1625 - endif;
1914 + // Parse!
1915 + if (!xml_parse($xml, $data, true))
1916 + {
1917 + $this->error_code = xml_get_error_code($xml);
1918 + $this->error_string = xml_error_string($this->error_code);
1919 + $return = false;
1920 + }
1626 1921
1627 - // Never can be too careful...
1628 - $login = sanitize_user($author, /*strict=*/ true);
1629 - $login = apply_filters('pre_user_login', $login);
1922 + $this->current_line = xml_get_current_line_number($xml);
1923 + $this->current_column = xml_get_current_column_number($xml);
1924 + $this->current_byte = xml_get_current_byte_index($xml);
1925 + xml_parser_free($xml);
1630 1926
1631 - $nice_author = sanitize_title($author);
1632 - $nice_author = apply_filters('pre_user_nicename', $nice_author);
1927 + return $return;
1928 + }
1929 + else
1930 + {
1931 + libxml_clear_errors();
1932 + $xml =& new XMLReader();
1933 + $xml->xml($data);
1934 + while (@$xml->read())
1935 + {
1936 + switch ($xml->nodeType)
1937 + {
1633 1938
1634 - $reg_author = $wpdb->escape(preg_quote($author));
1635 - $author = $wpdb->escape($author);
1636 - $email = $wpdb->escape($email);
1637 - $test_email = $wpdb->escape($test_email);
1638 - $url = $wpdb->escape($url);
1939 + case constant('XMLReader::END_ELEMENT'):
1940 + if ($xml->namespaceURI !== '')
1941 + {
1942 + $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1943 + }
1944 + else
1945 + {
1946 + $tagName = $xml->localName;
1947 + }
1948 + $this->tag_close(null, $tagName);
1949 + break;
1950 + case constant('XMLReader::ELEMENT'):
1951 + $empty = $xml->isEmptyElement;
1952 + if ($xml->namespaceURI !== '')
1953 + {
1954 + $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1955 + }
1956 + else
1957 + {
1958 + $tagName = $xml->localName;
1959 + }
1960 + $attributes = array();
1961 + while ($xml->moveToNextAttribute())
1962 + {
1963 + if ($xml->namespaceURI !== '')
1964 + {
1965 + $attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
1966 + }
1967 + else
1968 + {
1969 + $attrName = $xml->localName;
1970 + }
1971 + $attributes[$attrName] = $xml->value;
1972 + }
1973 +
1974 + foreach ($attributes as $attr => $value) :
1975 + list($ns, $local) = $this->split_ns($attr);
1976 + if ($ns=='http://www.w3.org/2000/xmlns/') :
1977 + if ('xmlns' == $local) : $local = false; endif;
1978 + $this->start_xmlns(null, $local, $value);
1979 + endif;
1980 + endforeach;
1981 +
1982 + $this->tag_open(null, $tagName, $attributes);
1983 + if ($empty)
1984 + {
1985 + $this->tag_close(null, $tagName);
1986 + }
1987 + break;
1988 + case constant('XMLReader::TEXT'):
1639 1989
1640 - // Check for an existing author rule....
1641 - if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
1642 - $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
1643 - else :
1644 - $author_rule = NULL;
1990 + case constant('XMLReader::CDATA'):
1991 + $this->cdata(null, $xml->value);
1992 + break;
1993 + }
1994 + }
1995 + if ($error = libxml_get_last_error())
1996 + {
1997 + $this->error_code = $error->code;
1998 + $this->error_string = $error->message;
1999 + $this->current_line = $error->line;
2000 + $this->current_column = $error->column;
2001 + return false;
2002 + }
2003 + else
2004 + {
2005 + return true;
2006 + }
2007 + }
2008 + } /* FeedWordPress_Parser::parse() */
2009 +
2010 + var $xmlns_stack = array();
2011 + var $xmlns_current = array();
2012 + function tag_open ($parser, $tag, $attributes) {
2013 + $ret = parent::tag_open($parser, $tag, $attributes);
2014 + if ($this->current_xhtml_construct < 0) :
2015 + $this->data['xmlns'] = $this->xmlns_current;
2016 + $this->xmlns_stack[] = $this->xmlns_current;
1645 2017 endif;
1646 -
1647 - // User name is mapped to a particular author. If that author ID exists, use it.
1648 - if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
1649 - $id = (int) $author_rule;
1650 -
1651 - // User name is filtered out
1652 - elseif ('filter' == $author_rule) :
1653 - $id = NULL;
1654 -
1655 - else :
1656 - // Check the database for an existing author record that might fit
1657 -
1658 - #-- WordPress 2.0+
1659 - if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
1660 -
1661 - // First try the user core data table.
1662 - $id = $wpdb->get_var(
1663 - "SELECT ID FROM $wpdb->users
1664 - WHERE
1665 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1666 - OR (
1667 - LENGTH(TRIM(LCASE(user_email))) > 0
1668 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1669 - )
1670 - OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
1671 - ");
2018 + return $ret;
2019 + }
1672 2020
1673 - // If that fails, look for aliases in the user meta data table
1674 - if (is_null($id)) :
1675 - $id = $wpdb->get_var(
1676 - "SELECT user_id FROM $wpdb->usermeta
1677 - WHERE
1678 - (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
1679 - OR (
1680 - meta_key = 'description'
1681 - AND TRIM(LCASE(meta_value))
1682 - RLIKE CONCAT(
1683 - '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1684 - TRIM(LCASE('$reg_author')),
1685 - '( |\\t|\\r)*(\\n|\$)'
1686 - )
1687 - )
1688 - ");
1689 - endif;
1690 -
1691 - #-- WordPress 1.5.x
1692 - else :
1693 - $id = $wpdb->get_var(
1694 - "SELECT ID from $wpdb->users
1695 - WHERE
1696 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
1697 - (
1698 - LENGTH(TRIM(LCASE(user_email))) > 0
1699 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1700 - ) OR
1701 - TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
1702 - TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
1703 - TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
1704 - TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
1705 - (
1706 - LOWER(user_description)
1707 - RLIKE CONCAT(
1708 - '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1709 - LCASE('$reg_author'),
1710 - '( |\\t|\\r)*(\\n|\$)'
1711 - )
1712 - )
1713 - ");
1714 -
1715 - endif;
1716 -
1717 - // ... if you don't find one, then do what you need to do
1718 - if (is_null($id)) :
1719 - if ($unfamiliar_author === 'create') :
1720 - $userdata = array();
1721 -
1722 - #-- user table data
1723 - $userdata['ID'] = NULL; // new user
1724 - $userdata['user_login'] = $login;
1725 - $userdata['user_nicename'] = $nice_author;
1726 - $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
1727 - $userdata['user_email'] = $email;
1728 - $userdata['user_url'] = $url;
1729 - $userdata['display_name'] = $author;
1730 -
1731 - $id = wp_insert_user($userdata);
1732 - elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
1733 - $id = (int) $unfamiliar_author;
1734 - elseif ($unfamiliar_author === 'default') :
1735 - $id = 1;
1736 - endif;
1737 - endif;
2021 + function tag_close($parser, $tag) {
2022 + if ($this->current_xhtml_construct < 0) :
2023 + $this->xmlns_current = array_pop($this->xmlns_stack);
1738 2024 endif;
1739 -
1740 - if ($id) :
1741 - $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
1742 - endif;
1743 - return $id;
1744 - } // function SyndicatedPost::author_id ()
1745 -
1746 - // look up (and create) category ids from a list of categories
1747 - function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
1748 - global $wpdb;
1749 -
1750 - // We need to normalize whitespace because (1) trailing
1751 - // whitespace can cause PHP and MySQL not to see eye to eye on
1752 - // VARCHAR comparisons for some versions of MySQL (cf.
1753 - // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1754 - // because I doubt most people want to make a semantic
1755 - // distinction between 'Computers' and 'Computers '
1756 - $cats = array_map('trim', $cats);
1757 -
1758 - $tags = array();
1759 -
1760 - $cat_ids = array ();
1761 - foreach ($cats as $cat_name) :
1762 - if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
1763 - $cat_id = (int) $backref[1];
1764 - if (function_exists('is_term') and is_term($cat_id, 'category')) :
1765 - $cat_ids[] = $cat_id;
1766 - elseif (get_category($cat_id)) :
1767 - $cat_ids[] = $cat_id;
1768 - endif;
1769 - elseif (strlen($cat_name) > 0) :
1770 - $esc = $wpdb->escape($cat_name);
1771 - $resc = $wpdb->escape(preg_quote($cat_name));
1772 -
1773 - // WordPress 2.3+
1774 - if (function_exists('is_term')) :
1775 - $cat_id = is_term($cat_name, 'category');
1776 - if ($cat_id) :
1777 - $cat_ids[] = $cat_id['term_id'];
1778 - // There must be a better way to do this...
1779 - elseif ($results = $wpdb->get_results(
1780 - "SELECT term_id
1781 - FROM $wpdb->term_taxonomy
1782 - WHERE
1783 - LOWER(description) RLIKE
1784 - CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
1785 - )) :
1786 - foreach ($results AS $term) :
1787 - $cat_ids[] = (int) $term->term_id;
1788 - endforeach;
1789 - elseif ('tag'==$unfamiliar_category) :
1790 - $tags[] = $cat_name;
1791 - elseif ('create'===$unfamiliar_category) :
1792 - $term = wp_insert_term($cat_name, 'category');
1793 - if (is_wp_error($term)) :
1794 - FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
1795 - else :
1796 - $cat_ids[] = $term['term_id'];
1797 - endif;
1798 - endif;
1799 -
1800 - // WordPress 1.5.x - 2.2.x
1801 - else :
1802 - $results = $wpdb->get_results(
1803 - "SELECT cat_ID
1804 - FROM $wpdb->categories
1805 - WHERE
1806 - (LOWER(cat_name) = LOWER('$esc'))
1807 - OR (LOWER(category_description)
1808 - RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
1809 - ");
1810 - if ($results) :
1811 - foreach ($results as $term) :
1812 - $cat_ids[] = (int) $term->cat_ID;
1813 - endforeach;
1814 - elseif ('create'===$unfamiliar_category) :
1815 - if (function_exists('wp_insert_category')) :
1816 - $cat_id = wp_insert_category(array('cat_name' => $cat_name));
1817 - // And into the database we go.
1818 - else :
1819 - $nice_kitty = sanitize_title($cat_name);
1820 - $wpdb->query(sprintf("
1821 - INSERT INTO $wpdb->categories
1822 - SET
1823 - cat_name='%s',
1824 - category_nicename='%s'
1825 - ", $wpdb->escape($cat_name), $nice_kitty
1826 - ));
1827 - $cat_id = $wpdb->insert_id;
1828 - endif;
1829 - $cat_ids[] = $cat_id;
1830 - endif;
1831 - endif;
1832 - endif;
1833 - endforeach;
1834 -
1835 - if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
1836 - $cat_ids = NULL; // Drop the post
1837 - else :
1838 - $cat_ids = array_unique($cat_ids);
1839 - endif;
1840 -
1841 - if ($tags_too) : $ret = array($cat_ids, $tags);
1842 - else : $ret = $cat_ids;
1843 - endif;
1844 -
2025 + $ret = parent::tag_close($parser, $tag);
1845 2026 return $ret;
1846 - } // function SyndicatedPost::category_ids ()
1847 -
1848 - function use_api ($tag) {
1849 - global $wp_db_version;
1850 - switch ($tag) :
1851 - case 'wp_insert_post':
1852 - // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
1853 - // In 1.5 it was such a resource hog it would make PHP segfault on big updates
1854 - $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
1855 - break;
1856 - case 'post_status_pending':
1857 - $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
1858 - break;
1859 - endswitch;
1860 - return $ret;
1861 - } // function SyndicatedPost::use_api ()
1862 -
1863 - #### EXTRACT DATA FROM FEED ITEM ####
1864 -
1865 - function created () {
1866 - $epoch = null;
1867 - if (isset($this->item['dc']['created'])) :
1868 - $epoch = @parse_w3cdtf($this->item['dc']['created']);
1869 - elseif (isset($this->item['dcterms']['created'])) :
1870 - $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
1871 - elseif (isset($this->item['created'])): // Atom 0.3
1872 - $epoch = @parse_w3cdtf($this->item['created']);
1873 - endif;
1874 - return $epoch;
1875 2027 }
1876 - function published ($fallback = true) {
1877 - $epoch = null;
1878 -
1879 - # RSS is a fucking mess. Figure out whether we have a date in
1880 - # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
1881 - # epoch format for reformatting. If we can't find anything,
1882 - # we'll use the last-updated time.
1883 - if (isset($this->item['dc']['date'])): // Dublin Core
1884 - $epoch = @parse_w3cdtf($this->item['dc']['date']);
1885 - elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
1886 - $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
1887 - elseif (isset($this->item['published'])) : // Atom 1.0
1888 - $epoch = @parse_w3cdtf($this->item['published']);
1889 - elseif (isset($this->item['issued'])): // Atom 0.3
1890 - $epoch = @parse_w3cdtf($this->item['issued']);
1891 - elseif (isset($this->item['pubdate'])): // RSS 2.0
1892 - $epoch = strtotime($this->item['pubdate']);
1893 - elseif ($fallback) : // Fall back to <updated> / <modified> if present
1894 - $epoch = $this->updated(/*fallback=*/ false);
2028 +
2029 + function start_xmlns ($parser, $prefix, $uri) {
2030 + if (!$prefix) :
2031 + $prefix = '';
1895 2032 endif;
1896 -
1897 - # If everything failed, then default to the current time.
1898 - if (is_null($epoch)) :
1899 - if (-1 == $default) :
1900 - $epoch = time();
1901 - else :
1902 - $epoch = $default;
1903 - endif;
2033 + if ($this->current_xhtml_construct < 0) :
2034 + $this->xmlns_current[$prefix] = $uri;
1904 2035 endif;
1905 -
1906 - return $epoch;
1907 - }
1908 - function updated ($fallback = true, $default = -1) {
1909 - $epoch = null;
2036 + return true;
2037 + } /* FeedWordPress_Parser::start_xmlns() */
2038 +}
1910 2039
1911 - # As far as I know, only dcterms and Atom have reliable ways to
1912 - # specify when something was *modified* last. If neither is
1913 - # available, then we'll try to get the time of publication.
1914 - if (isset($this->item['dc']['modified'])) : // Not really correct
1915 - $epoch = @parse_w3cdtf($this->item['dc']['modified']);
1916 - elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
1917 - $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
1918 - elseif (isset($this->item['modified'])): // Atom 0.3
1919 - $epoch = @parse_w3cdtf($this->item['modified']);
1920 - elseif (isset($this->item['updated'])): // Atom 1.0
1921 - $epoch = @parse_w3cdtf($this->item['updated']);
1922 - elseif ($fallback) : // Fall back to issued / dc:date
1923 - $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
1924 - endif;
1925 -
1926 - # If everything failed, then default to the current time.
1927 - if (is_null($epoch)) :
1928 - if (-1 == $default) :
1929 - $epoch = time();
1930 - else :
1931 - $epoch = $default;
1932 - endif;
1933 - endif;
2040 +$feedwordpress_admin_footer = array();
1934 2041
1935 - return $epoch;
1936 - }
2042 +################################################################################
2043 +## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
2044 +################################################################################
1937 2045
1938 - function update_hash () {
1939 - return md5(serialize($this->item));
2046 +class FeedWordPressRPC {
2047 + function FeedWordPressRPC () {
2048 + add_filter('xmlrpc_methods', array(&$this, 'xmlrpc_methods'));
1940 2049 }
1941 -
1942 - function guid () {
1943 - $guid = null;
1944 - if (isset($this->item['id'])): // Atom 0.3 / 1.0
1945 - $guid = $this->item['id'];
1946 - elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
1947 - $guid = $this->item['atom']['id'];
1948 - elseif (isset($this->item['guid'])) : // RSS 2.0
1949 - $guid = $this->item['guid'];
1950 - elseif (isset($this->item['dc']['identifier'])) :// yeah, right
1951 - $guid = $this->item['dc']['identifier'];
1952 - else :
1953 - // The feed does not seem to have provided us with a
1954 - // unique identifier, so we'll have to cobble together
1955 - // a tag: URI that might work for us. The base of the
1956 - // URI will be the host name of the feed source ...
1957 - $bits = parse_url($this->feedmeta['link/uri']);
1958 - $guid = 'tag:'.$bits['host'];
1959 -
1960 - // If we have a date of creation, then we can use that
1961 - // to uniquely identify the item. (On the other hand, if
1962 - // the feed producer was consicentious enough to
1963 - // generate dates of creation, she probably also was
1964 - // conscientious enough to generate unique identifiers.)
1965 - if (!is_null($this->created())) :
1966 - $guid .= '://post.'.date('YmdHis', $this->created());
1967 -
1968 - // Otherwise, use both the URI of the item, *and* the
1969 - // item's title. We have to use both because titles are
1970 - // often not unique, and sometimes links aren't unique
1971 - // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
1972 - // some podcasts). But it's rare to have *both* the same
1973 - // title *and* the same link for two different items. So
1974 - // this is about the best we can do.
1975 - else :
1976 - $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
1977 - endif;
1978 - endif;
1979 - return $guid;
2050 +
2051 + function xmlrpc_methods ($args = array()) {
2052 + $args['weblogUpdates.ping'] = array(&$this, 'ping');
2053 + $args['feedwordpress.subscribe'] = array(&$this, 'subscribe');
2054 + $args['feedwordpress.deactivate'] = array(&$this, 'deactivate');
2055 + $args['feedwordpress.delete'] = array(&$this, 'delete');
2056 + $args['feedwordpress.nuke'] = array(&$this, 'nuke');
2057 + return $args;
1980 2058 }
1981 2059
1982 - function author () {
1983 - $author = array ();
2060 + function ping ($args) {
2061 + global $feedwordpress;
1984 2062
1985 - if (isset($this->item['author_name'])):
1986 - $author['name'] = $this->item['author_name'];
1987 - elseif (isset($this->item['dc']['creator'])):
1988 - $author['name'] = $this->item['dc']['creator'];
1989 - elseif (isset($this->item['dc']['contributor'])):
1990 - $author['name'] = $this->item['dc']['contributor'];
1991 - elseif (isset($this->feed->channel['dc']['creator'])) :
1992 - $author['name'] = $this->feed->channel['dc']['creator'];
1993 - elseif (isset($this->feed->channel['dc']['contributor'])) :
1994 - $author['name'] = $this->feed->channel['dc']['contributor'];
1995 - elseif (isset($this->feed->channel['author_name'])) :
1996 - $author['name'] = $this->feed->channel['author_name'];
1997 - elseif ($this->feed->is_rss() and isset($this->item['author'])) :
1998 - // The author element in RSS is allegedly an
1999 - // e-mail address, but lots of people don't use
2000 - // it that way. So let's make of it what we can.
2001 - $author = parse_email_with_realname($this->item['author']);
2002 -
2003 - if (!isset($author['name'])) :
2004 - if (isset($author['email'])) :
2005 - $author['name'] = $author['email'];
2006 - else :
2007 - $author['name'] = $this->feed->channel['title'];
2008 - endif;
2009 - endif;
2010 - else :
2011 - $author['name'] = $this->feed->channel['title'];
2012 - endif;
2013 -
2014 - if (isset($this->item['author_email'])):
2015 - $author['email'] = $this->item['author_email'];
2016 - elseif (isset($this->feed->channel['author_email'])) :
2017 - $author['email'] = $this->feed->channel['author_email'];
2018 - endif;
2019 -
2020 - if (isset($this->item['author_url'])):
2021 - $author['uri'] = $this->item['author_url'];
2022 - elseif (isset($this->feed->channel['author_url'])) :
2023 - $author['uri'] = $this->item['author_url'];
2063 + $delta = @$feedwordpress->update($args[1]);
2064 + if (is_null($delta)):
2065 + return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
2024 2066 else:
2025 - $author['uri'] = $this->feed->channel['link'];
2067 + $mesg = array();
2068 + if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
2069 + if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
2070 +
2071 + return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
2026 2072 endif;
2027 -
2028 - return $author;
2029 - } // SyndicatedPost::author()
2030 -
2031 - var $strip_attrs = array (
2032 - array('[a-z]+', 'style'),
2033 - array('[a-z]+', 'target'),
2034 - );
2035 - function sanitize_content ($content, $obj) {
2036 - # FeedWordPress used to resolve URIs relative to the
2037 - # feed URI. It now relies on the xml:base support
2038 - # baked in to the MagpieRSS upgrade. So all we do here
2039 - # now is to sanitize problematic attributes.
2040 - #
2041 - # This kind of sucks. I intend to replace it with
2042 - # lib_filter sometime soon.
2043 - foreach ($obj->strip_attrs as $pair):
2044 - list($tag,$attr) = $pair;
2045 - $content = preg_replace (
2046 - ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i",
2047 - "\\1\\4",
2048 - $content
2049 - );
2050 - endforeach;
2051 - return $content;
2052 2073 }
2053 -} // class SyndicatedPost
2054 -
2055 -# class SyndicatedLink: represents a syndication feed stored within the
2056 -# WordPress database
2057 -#
2058 -# To keep things compact and editable from within WordPress, we use all the
2059 -# links under a particular category in the WordPress "Blogroll" for the list of
2060 -# feeds to syndicate. "Contributors" is the category used by default; you can
2061 -# configure that under Options --> Syndication.
2062 -#
2063 -# Fields used are:
2064 -#
2065 -# * link_rss: the URI of the Atom/RSS feed to syndicate
2066 -#
2067 -# * link_notes: user-configurable options, with keys and values
2068 -# like so:
2069 -#
2070 -# key: value
2071 -# cats: computers\nweb
2072 -# feed/key: value
2073 -#
2074 -# Keys that start with "feed/" are gleaned from the data supplied
2075 -# by the feed itself, and will be overwritten with each update.
2076 -#
2077 -# Values have linebreak characters escaped with C-style
2078 -# backslashes (so, for example, a newline becomes "\n").
2079 -#
2080 -# The value of `cats` is used as a newline-separated list of
2081 -# default categories for any post coming from a particular feed.
2082 -# (In the example above, any posts from this feed will be placed
2083 -# in the "computers" and "web" categories--*in addition to* any
2084 -# categories that may already be applied to the posts.)
2085 -#
2086 -# Values of keys in link_notes are accessible from templates using
2087 -# the function `get_feed_meta($key)` if this plugin is activated.
2088 -
2089 -class SyndicatedLink {
2090 - var $id = null;
2091 - var $link = null;
2092 - var $settings = array ();
2093 - var $magpie = null;
2094 -
2095 - function SyndicatedLink ($link) {
2096 - global $wpdb;
2097 -
2098 - if (is_object($link)) :
2099 - $this->link = $link;
2100 - $this->id = $link->link_id;
2101 - else :
2102 - $this->id = $link;
2103 - if (function_exists('get_bookmark')) : // WP 2.1+
2104 - $this->link = get_bookmark($link);
2105 - else :
2106 - $this->link = $wpdb->get_row("
2107 - SELECT * FROM $wpdb->links
2108 - WHERE (link_id = '".$wpdb->escape($link)."')"
2109 - );
2110 - endif;
2111 - endif;
2112 -
2113 - if (strlen($this->link->link_rss) > 0) :
2114 - // Read off feed settings from link_notes
2115 - $notes = explode("\n", $this->link->link_notes);
2116 - foreach ($notes as $note):
2117 - list($key, $value) = explode(": ", $note, 2);
2118 2074
2119 - if (strlen($key) > 0) :
2120 - // Unescape and trim() off the whitespace.
2121 - // Thanks to Ray Lischner for pointing out the
2122 - // need to trim off whitespace.
2123 - $this->settings[$key] = stripcslashes (trim($value));
2124 - endif;
2125 - endforeach;
2075 + function validate (&$args) {
2076 + global $wp_xmlrpc_server;
2126 2077
2127 - // "Magic" feed settings
2128 - $this->settings['link/uri'] = $this->link->link_rss;
2129 - $this->settings['link/name'] = $this->link->link_name;
2130 - $this->settings['link/id'] = $this->link->link_id;
2131 -
2132 - // `hardcode categories` and `unfamiliar categories` are deprecated in favor of `unfamiliar category`
2133 - if (
2134 - isset($this->settings['unfamiliar categories'])
2135 - and !isset($this->settings['unfamiliar category'])
2136 - ) :
2137 - $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
2138 - endif;
2139 - if (
2140 - FeedWordPress::affirmative($this->settings, 'hardcode categories')
2141 - and !isset($this->settings['unfamiliar category'])
2142 - ) :
2143 - $this->settings['unfamiliar category'] = 'default';
2144 - endif;
2078 + // First two params are username/password
2079 + $username = $wp_xmlrpc_server->escape(array_shift($args));
2080 + $password = $wp_xmlrpc_server->escape(array_shift($args));
2145 2081
2146 - // Set this up automagically for del.icio.us
2147 - $bits = parse_url($this->link->link_rss);
2148 - $tagspacers = array('del.icio.us', 'feeds.delicious.com');
2149 - if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
2150 - $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
2151 - endif;
2152 -
2153 - if (isset($this->settings['cats'])):
2154 - $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
2155 - endif;
2156 - if (isset($this->settings['tags'])):
2157 - $this->settings['tags'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['tags']);
2158 - endif;
2159 -
2160 - if (isset($this->settings['map authors'])) :
2161 - $author_rules = explode("\n\n", $this->settings['map authors']);
2162 - $ma = array();
2163 - foreach ($author_rules as $rule) :
2164 - list($rule_type, $author_name, $author_action) = explode("\n", $rule);
2165 -
2166 - // Normalize for case and whitespace
2167 - $rule_type = strtolower(trim($rule_type));
2168 - $author_name = strtolower(trim($author_name));
2169 - $author_action = strtolower(trim($author_action));
2170 -
2171 - $ma[$rule_type][$author_name] = $author_action;
2172 - endforeach;
2173 - $this->settings['map authors'] = $ma;
2174 - endif;
2082 + $ret = array();
2083 + if ( !$user = $wp_xmlrpc_server->login($username, $password) ) :
2084 + $ret = $wp_xmlrpc_server->error;
2085 + elseif (!current_user_can('manage_links')) :
2086 + $ret = new IXR_Error(401, 'Sorry, you cannot change the subscription list.');
2175 2087 endif;
2176 - } /* SyndicatedLink::SyndicatedLink () */
2177 -
2178 - function found () {
2179 - return is_object($this->link);
2180 - } /* SyndicatedLink::found () */
2181 -
2182 - function stale () {
2183 - $stale = true;
2184 - if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
2185 - $stale = false; // don't update on any timed updates; pings only
2186 - elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
2187 - $stale = true; // update on the next timed update
2188 - elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
2189 - $stale = true; // initial update
2190 - else :
2191 - $after = ((int) $this->settings['update/last'])
2192 - +((int) $this->settings['update/ttl'] * 60);
2193 - $stale = (time() >= $after);
2194 - endif;
2195 - return $stale;
2196 - } /* SyndicatedLink::stale () */
2197 -
2198 - function poll ($crash_ts = NULL) {
2199 - global $wpdb;
2200 -
2201 - $this->magpie = fetch_rss($this->link->link_rss);
2202 - $new_count = NULL;
2203 -
2204 - $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
2205 - if ($resume) :
2206 - // pick up where we left off
2207 - $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
2208 - else :
2209 - // begin at the beginning
2210 - $processed = array();
2211 - endif;
2212 -
2213 - if (is_object($this->magpie)) :
2214 - $new_count = array('new' => 0, 'updated' => 0);
2215 -
2216 - # -- Update Link metadata live from feed
2217 - $channel = $this->magpie->channel;
2218 -
2219 - if (!isset($channel['id'])) :
2220 - $channel['id'] = $this->link->link_rss;
2221 - endif;
2222 -
2223 - $update = array();
2224 - if (!$this->hardcode('url') and isset($channel['link'])) :
2225 - $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
2226 - endif;
2227 -
2228 - if (!$this->hardcode('name') and isset($channel['title'])) :
2229 - $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
2230 - endif;
2231 -
2232 - if (!$this->hardcode('description')) :
2233 - if (isset($channel['tagline'])) :
2234 - $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
2235 - elseif (isset($channel['description'])) :
2236 - $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
2237 - endif;
2238 - endif;
2239 -
2240 - $this->settings = array_merge($this->settings, $this->flatten_array($channel));
2241 -
2242 - $this->settings['update/last'] = time(); $ttl = $this->ttl();
2243 - if (!is_null($ttl)) :
2244 - $this->settings['update/ttl'] = $ttl;
2245 - $this->settings['update/timed'] = 'feed';
2246 - else :
2247 - $this->settings['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates
2248 - $this->settings['update/timed'] = 'automatically';
2249 - endif;
2250 -
2251 - if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
2252 - $this->settings['update/hold'] = 'scheduled';
2253 - endif;
2254 -
2255 - $this->settings['update/unfinished'] = 'yes';
2256 -
2257 - $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
2258 -
2259 - $update_set = implode(',', $update);
2260 -
2261 - // Update the properties of the link from the feed information
2262 - $result = $wpdb->query("
2263 - UPDATE $wpdb->links
2264 - SET $update_set
2265 - WHERE link_id='$this->id'
2266 - ");
2267 -
2268 - # -- Add new posts from feed and update any updated posts
2269 - $crashed = false;
2270 -
2271 - if (is_array($this->magpie->items)) :
2272 - foreach ($this->magpie->items as $item) :
2273 - $post =& new SyndicatedPost($item, $this);
2274 - if (!$resume or !in_array(trim($post->guid()), $processed)) :
2275 - $processed[] = $post->guid();
2276 - if (!$post->filtered()) :
2277 - $new = $post->store();
2278 - if ( $new !== false ) $new_count[$new]++;
2279 - endif;
2280 -
2281 - if (!is_null($crash_ts) and (time() > $crash_ts)) :
2282 - $crashed = true;
2283 - break;
2284 - endif;
2285 - endif;
2286 - endforeach;
2287 - endif;
2288 -
2289 - // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
2290 - $to_notes = $this->settings;
2291 -
2292 - $this->settings['update/processed'] = $processed;
2293 - if (!$crashed) :
2294 - $this->settings['update/unfinished'] = 'no';
2295 - endif;
2296 -
2297 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
2298 -
2299 - // Update the properties of the link from the feed information
2300 - $result = $wpdb->query("
2301 - UPDATE $wpdb->links
2302 - SET $update_set
2303 - WHERE link_id='$this->id'
2304 - ");
2305 - endif;
2306 -
2307 - return $new_count;
2308 - } /* SyndicatedLink::poll() */
2309 -
2310 - function map_name_to_new_user ($name, $newuser_name) {
2311 - global $wpdb;
2312 -
2313 - if (strlen($newuser_name) > 0) :
2314 - $userdata = array();
2315 - $userdata['ID'] = NULL;
2316 -
2317 - $userdata['user_login'] = sanitize_user($newuser_name);
2318 - $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
2319 -
2320 - $userdata['user_nicename'] = sanitize_title($newuser_name);
2321 - $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
2322 -
2323 - $userdata['display_name'] = $wpdb->escape($newuser_name);
2324 -
2325 - $newuser_id = wp_insert_user($userdata);
2326 - if (is_numeric($newuser_id)) :
2327 - if (is_null($name)) : // Unfamiliar author
2328 - $this->settings['unfamiliar author'] = $newuser_id;
2329 - else :
2330 - $this->settings['map authors']['name'][$name] = $newuser_id;
2331 - endif;
2332 - else :
2333 - // TODO: Add some error detection and reporting
2334 - endif;
2335 - else :
2336 - // TODO: Add some error reporting
2337 - endif;
2338 - } /* SyndicatedLink::map_name_to_new_user () */
2339 -
2340 - function settings_to_notes () {
2341 - $to_notes = $this->settings;
2342 -
2343 - unset($to_notes['link/id']); // Magic setting; don't save
2344 - unset($to_notes['link/uri']); // Magic setting; don't save
2345 - unset($to_notes['link/name']); // Magic setting; don't save
2346 - unset($to_notes['hardcode categories']); // Deprecated
2347 - unset($to_notes['unfamiliar categories']); // Deprecated
2348 -
2349 - // Collapse array settings
2350 - if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
2351 - $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
2352 - endif;
2353 -
2354 - if (is_array($to_notes['cats'])) :
2355 - $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
2356 - endif;
2357 - if (is_array($to_notes['tags'])) :
2358 - $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
2359 - endif;
2360 -
2361 - // Collapse the author mapping rule structure back into a flat string
2362 - if (isset($to_notes['map authors'])) :
2363 - $ma = array();
2364 - foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
2365 - foreach ($author_rules as $author_name => $author_action) :
2366 - $ma[] = $rule_type."\n".$author_name."\n".$author_action;
2367 - endforeach;
2368 - endforeach;
2369 - $to_notes['map authors'] = implode("\n\n", $ma);
2370 - endif;
2371 -
2372 - $notes = '';
2373 - foreach ($to_notes as $key => $value) :
2374 - $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
2375 - endforeach;
2376 - return $notes;
2377 - } /* SyndicatedLink::settings_to_notes () */
2378 -
2379 - function uri () {
2380 - return (is_object($this->link) ? $this->link->link_rss : NULL);
2381 - } /* SyndicatedLink::uri () */
2382 -
2383 - function homepage () {
2384 - return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
2385 - } /* SyndicatedLink::homepage () */
2386 -
2387 - function ttl () {
2388 - if (is_object($this->magpie)) :
2389 - $channel = $this->magpie->channel;
2390 - else :
2391 - $channel = array();
2392 - endif;
2393 -
2394 - if (isset($channel['ttl'])) :
2395 - // "ttl stands for time to live. It's a number of
2396 - // minutes that indicates how long a channel can be
2397 - // cached before refreshing from the source."
2398 - // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
2399 - $ret = $channel['ttl'];
2400 - elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
2401 - $period_minutes = array (
2402 - 'hourly' => 60, /* minutes in an hour */
2403 - 'daily' => 1440, /* minutes in a day */
2404 - 'weekly' => 10080, /* minutes in a week */
2405 - 'monthly' => 43200, /* minutes in a month */
2406 - 'yearly' => 525600, /* minutes in a year */
2407 - );
2408 -
2409 - // "sy:updatePeriod: Describes the period over which the
2410 - // channel format is updated. Acceptable values are:
2411 - // hourly, daily, weekly, monthly, yearly. If omitted,
2412 - // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
2413 - if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
2414 - else : $period = 'daily';
2415 - endif;
2416 -
2417 - // "sy:updateFrequency: Used to describe the frequency
2418 - // of updates in relation to the update period. A
2419 - // positive integer indicates how many times in that
2420 - // period the channel is updated. ... If omitted a value
2421 - // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
2422 - if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
2423 - else : $freq = 1;
2424 - endif;
2425 -
2426 - $ret = (int) ($period_minutes[$period] / $freq);
2427 - else :
2428 - $ret = NULL;
2429 - endif;
2430 2088 return $ret;
2431 - } /* SyndicatedLink::ttl() */
2089 + }
2432 2090
2433 - // SyndicatedLink::flatten_array (): flatten an array. Useful for
2434 - // hierarchical and namespaced elements.
2435 - //
2436 - // Given an array which may contain array or object elements in it,
2437 - // return a "flattened" array: a one-dimensional array of scalars
2438 - // containing each of the scalar elements contained within the array
2439 - // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
2440 - // returned array for FeedWordPress::flatten_array($a) will contain a key
2441 - // $a['feed/b/c/d'] with value 'e'.
2442 - function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
2443 - $ret = array ();
2444 - if (is_array($arr)) :
2445 - foreach ($arr as $key => $value) :
2446 - if (is_scalar($value)) :
2447 - $ret[$prefix.$key] = $value;
2091 + function subscribe ($args) {
2092 + $ret = $this->validate($args);
2093 + if (is_array($ret)) : // Success
2094 + // The remaining params are feed URLs
2095 + foreach ($args as $arg) :
2096 + $finder = new FeedFinder($arg, /*verify=*/ false, /*fallbacks=*/ 1);
2097 + $feeds = array_values(array_unique($finder->find()));
2098 +
2099 + if (count($feeds) > 0) :
2100 + $link_id = FeedWordPress::syndicate_link(
2101 + /*title=*/ feedwordpress_display_url($feeds[0]),
2102 + /*homepage=*/ $feeds[0],
2103 + /*feed=*/ $feeds[0]
2104 + );
2105 + $ret[] = array(
2106 + 'added',
2107 + $feeds[0],
2108 + $arg,
2109 + );
2448 2110 else :
2449 - $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
2111 + $ret[] = array(
2112 + 'error',
2113 + $arg
2114 + );
2450 2115 endif;
2451 2116 endforeach;
2452 2117 endif;
2453 2118 return $ret;
2454 - } /* SyndicatedLink::flatten_array () */
2455 -
2456 - function hardcode ($what) {
2457 - $default = get_option("feedwordpress_hardcode_$what");
2458 - if ( $default === 'yes' ) :
2459 - // If the default is to hardcode, then we want the
2460 - // negation of negative(): TRUE by default and FALSE if
2461 - // the setting is explicitly "no"
2462 - $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
2463 - else :
2464 - // If the default is NOT to hardcode, then we want
2465 - // affirmative(): FALSE by default and TRUE if the
2466 - // setting is explicitly "yes"
2467 - $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
2468 - endif;
2469 - return $ret;
2470 - } /* SyndicatedLink::hardcode () */
2471 -
2472 - function syndicated_status ($what, $default) {
2473 - global $wpdb;
2474 -
2475 - $ret = get_option("feedwordpress_syndicated_{$what}_status");
2476 - if ( isset($this->settings["$what status"]) ) :
2477 - $ret = $this->settings["$what status"];
2478 - elseif (!$ret) :
2479 - $ret = $default;
2480 - endif;
2481 - return $wpdb->escape(trim(strtolower($ret)));
2482 - } /* SyndicatedLink:syndicated_status () */
2483 -} // class SyndicatedLink
2484 -
2485 -################################################################################
2486 -## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
2487 -################################################################################
2488 -
2489 -function feedwordpress_xmlrpc_hook ($args = array ()) {
2490 - $args['weblogUpdates.ping'] = 'feedwordpress_pong';
2491 - return $args;
2492 -}
2493 -
2494 -function feedwordpress_pong ($args) {
2495 - $feedwordpress =& new FeedWordPress;
2496 - $delta = @$feedwordpress->update($args[1]);
2497 - if (is_null($delta)):
2498 - return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
2499 - else:
2500 - $mesg = array();
2501 - if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
2502 - if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
2503 -
2504 - return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
2505 - endif;
2506 -}
2507 -
2508 -################################################################################
2509 -## class FeedFinder: find likely feeds using autodetection and/or guesswork ####
2510 -################################################################################
2511 -
2512 -class FeedFinder {
2513 - var $uri = NULL;
2514 - var $_cache_uri = NULL;
2119 + } /* FeedWordPressRPC::subscribe () */
2515 2120
2516 - var $verify = FALSE;
2517 -
2518 - var $_data = NULL;
2519 - var $_error = NULL;
2520 - var $_head = NULL;
2521 -
2522 - # -- Recognition patterns
2523 - var $_feed_types = array(
2524 - 'application/rss+xml',
2525 - 'text/xml',
2526 - 'application/atom+xml',
2527 - 'application/x.atom+xml',
2528 - 'application/x-atom+xml'
2529 - );
2530 - var $_feed_markers = array('\\<feed', '\\<rss', 'xmlns="http://purl.org/rss/1.0');
2531 - var $_html_markers = array('\\<html');
2532 - var $_obvious_feed_url = array('[./]rss', '[./]rdf', '[./]atom', '[./]feed', '\.xml');
2533 - var $_maybe_feed_url = array ('rss', 'rdf', 'atom', 'feed', 'xml');
2534 -
2535 - function FeedFinder ($uri = NULL, $verify = TRUE) {
2536 - $this->uri = $uri; $this->verify = $verify;
2537 - } /* FeedFinder::FeedFinder () */
2538 -
2539 - function find ($uri = NULL) {
2540 - $ret = array ();
2541 - if (!is_null($this->data($uri))) {
2542 - if ($this->is_feed($uri)) {
2543 - $ret = array($this->uri);
2544 - } else {
2545 - // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?)
2546 - // Autodiscovery is the preferred method
2547 - $href = $this->_link_rel_feeds();
2121 + function unsubscribe ($method, $args) {
2122 + $ret = $this->validate($args);
2123 + if (is_array($ret)) : // Success
2124 + // The remaining params are feed URLs
2125 + foreach ($args as $arg) :
2126 + $link_id = FeedWordPress::find_link($arg);
2548 2127
2549 - // ... but we'll also take the little orange buttons
2550 - $href = array_merge($href, $this->_a_href_feeds(TRUE));
2128 + if (!$link_id) :
2129 + $link_id = FeedWordPress::find_link($arg, 'link_url');
2130 + endif;
2551 2131
2552 - // If all that failed, look harder
2553 - if (count($href) == 0) $href = $this->_a_href_feeds(FALSE);
2554 -
2555 - // Verify feeds and resolve relative URIs
2556 - foreach ($href as $u) {
2557 - $the_uri = Relative_URI::resolve($u, $this->uri);
2558 - if ($this->verify) {
2559 - $feed =& new FeedFinder($the_uri);
2560 - if ($feed->is_feed()) $ret[] = $the_uri;
2561 - $feed = NULL;
2562 - } else {
2563 - $ret[] = $the_uri;
2564 - }
2565 - } /* foreach */
2566 - } /* if */
2567 - } /* if */
2568 - return array_unique($ret);
2569 - } /* FeedFinder::find () */
2570 -
2571 - function data ($uri = NULL) {
2572 - $this->_get($uri);
2573 - return $this->_data;
2574 - }
2575 -
2576 - function error () {
2577 - return $this->_error;
2578 - }
2579 -
2580 - function is_feed ($uri = NULL) {
2581 - $data = $this->data($uri);
2582 -
2583 - return (
2584 - preg_match (
2585 - "\007(".implode('|',$this->_feed_markers).")\007i",
2586 - $data
2587 - ) and !preg_match (
2588 - "\007(".implode('|',$this->_html_markers).")\007i",
2589 - $data
2590 - )
2591 - );
2592 - } /* FeedFinder::is_feed () */
2593 -
2594 - # --- Private methods ---
2595 - function _get ($uri = NULL) {
2596 - if ($uri) $this->uri = $uri;
2597 -
2598 - // Is the result not yet cached?
2599 - if ($this->_cache_uri !== $this->uri) :
2600 - $headers['Connection'] = 'close';
2601 - $headers['Accept'] = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*';
2602 - $headers['User-Agent'] = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress';
2603 -
2604 - // Use function provided by MagpieRSS package
2605 - $client = _fetch_remote_file($this->uri, $headers);
2606 - if (isset($client->error)) :
2607 - $this->_error = $client->error;
2608 - else :
2609 - $this->_error = NULL;
2610 - endif;
2611 - $this->_data = $client->results;
2612 -
2613 - // Kilroy was here
2614 - $this->_cache_uri = $this->uri;
2132 + if ($link_id) :
2133 + $link = new SyndicatedLink($link_id);
2134 +
2135 + $link->{$method}();
2136 + $ret[] = array(
2137 + 'deactivated',
2138 + $arg,
2139 + );
2140 + else :
2141 + $ret[] = array(
2142 + 'error',
2143 + $arg,
2144 + );
2145 + endif;
2146 + endforeach;
2615 2147 endif;
2616 - } /* FeedFinder::_get () */
2617 -
2618 - function _link_rel_feeds () {
2619 - $links = $this->_tags('link');
2620 - $link_count = count($links);
2621 -
2622 - // now figure out which one points to the RSS file
2623 - $href = array ();
2624 - for ($n=0; $n<$link_count; $n++) {
2625 - if (strtolower($links[$n]['rel']) == 'alternate') {
2626 - if (in_array(strtolower($links[$n]['type']), $this->_feed_types)) {
2627 - $href[] = $links[$n]['href'];
2628 - } /* if */
2629 - } /* if */
2630 - } /* for */
2631 - return $href;
2632 - }
2633 -
2634 - function _a_href_feeds ($obvious = TRUE) {
2635 - $pattern = ($obvious ? $this->_obvious_feed_url : $this->_maybe_feed_url);
2636 -
2637 - $links = $this->_tags('a');
2638 - $link_count = count($links);
2639 -
2640 - // now figure out which one points to the RSS file
2641 - $href = array ();
2642 - for ($n=0; $n<$link_count; $n++) {
2643 - if (preg_match("\007(".implode('|',$pattern).")\007i", $links[$n]['href'])) {
2644 - $href[] = $links[$n]['href'];
2645 - } /* if */
2646 - } /* for */
2647 - return $href;
2648 - }
2649 -
2650 - function _tags ($tag) {
2651 - $html = $this->data();
2652 -
2653 - // search through the HTML, save all <link> tags
2654 - // and store each link's attributes in an associative array
2655 - preg_match_all('/<'.$tag.'\s+(.*?)\s*\/?>/si', $html, $matches);
2656 - $links = $matches[1];
2657 - $ret = array();
2658 - $link_count = count($links);
2659 - for ($n=0; $n<$link_count; $n++) {
2660 - $attributes = preg_split('/\s+/s', $links[$n]);
2661 - foreach($attributes as $attribute) {
2662 - $att = preg_split('/\s*=\s*/s', $attribute, 2);
2663 - if (isset($att[1])) {
2664 - $att[1] = preg_replace('/([\'"]?)(.*)\1/', '$2', $att[1]);
2665 - $final_link[strtolower($att[0])] = $att[1];
2666 - } /* if */
2667 - } /* foreach */
2668 - $ret[$n] = $final_link;
2669 - } /* for */
2670 2148 return $ret;
2671 - }
2672 -} /* class FeedFinder */
2673 -
2674 -# Relative URI static class: PHP class for resolving relative URLs
2675 -#
2676 -# This class is derived (under the terms of the GPL) from URL Class 0.3 by
2677 -# Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
2678 -# for FeedWordPress's purposes. The class has been stripped down to a single
2679 -# public method: Relative_URI::resolve($url, $base), which resolves the URI in
2680 -# $url relative to the URI in $base
2681 -#
2682 -# The upgraded MagpieRSS also uses this class. So if we have it loaded
2683 -# in, don't load it again.
2684 -if (!class_exists('Relative_URI')) {
2685 -
2686 - class Relative_URI
2687 - {
2688 - // Resolve relative URI in $url against the base URI in $base. If $base
2689 - // is not supplied, then we use the REQUEST_URI of this script.
2690 - //
2691 - // I'm hoping this method reflects RFC 2396 Section 5.2
2692 - function resolve ($url, $base = NULL)
2693 - {
2694 - if (is_null($base)):
2695 - $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
2696 - endif;
2149 + } /* FeedWordPress::unsubscribe () */
2697 2150
2698 - $base = Relative_URI::_encode(trim($base));
2699 - $uri_parts = Relative_URI::_parse_url($base);
2151 + function deactivate ($args) {
2152 + return $this->unsubscribe('deactivate', $args);
2153 + } /* FeedWordPressRPC::deactivate () */
2700 2154
2701 - $url = Relative_URI::_encode(trim($url));
2702 - $parts = Relative_URI::_parse_url($url);
2155 + function delete ($args) {
2156 + return $this->unsubscribe('delete', $args);
2157 + } /* FeedWordPressRPC::delete () */
2703 2158
2704 - $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
2705 - $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
2706 -
2707 - // if path is empty, and scheme, host, and query are undefined,
2708 - // the URL is referring the base URL
2709 -
2710 - if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
2711 - // If the URI is empty or only a fragment, return the base URI
2712 - return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
2713 - } elseif (isset($parts['scheme'])) {
2714 - // If the scheme is set, then the URI is absolute.
2715 - return $url;
2716 - } elseif (isset($parts['host'])) {
2717 - $uri_parts['host'] = $parts['host'];
2718 - $uri_parts['path'] = $parts['path'];
2719 - } else {
2720 - // We have a relative path but not a host.
2721 -
2722 - // start ugly fix:
2723 - // prepend slash to path if base host is set, base path is not set, and url path is not absolute
2724 - if ($uri_parts['host'] && ($uri_parts['path'] == '')
2725 - && (strlen($parts['path']) > 0)
2726 - && (substr($parts['path'], 0, 1) != '/')) {
2727 - $parts['path'] = '/'.$parts['path'];
2728 - } // end ugly fix
2729 -
2730 - if (substr($parts['path'], 0, 1) == '/') {
2731 - $uri_parts['path'] = $parts['path'];
2732 - } else {
2733 - // copy base path excluding any characters after the last (right-most) slash character
2734 - $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
2735 - // append relative path
2736 - $buffer .= $parts['path'];
2737 - // remove "./" where "." is a complete path segment.
2738 - $buffer = str_replace('/./', '/', $buffer);
2739 - if (substr($buffer, 0, 2) == './') {
2740 - $buffer = substr($buffer, 2);
2741 - }
2742 - // if buffer ends with "." as a complete path segment, remove it
2743 - if (substr($buffer, -2) == '/.') {
2744 - $buffer = substr($buffer, 0, -1);
2745 - }
2746 - // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
2747 - $search_finished = false;
2748 - $segment = explode('/', $buffer);
2749 - while (!$search_finished) {
2750 - for ($x=0; $x+1 < count($segment);) {
2751 - if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
2752 - if ($x+2 == count($segment)) $segment[] = '';
2753 - unset($segment[$x], $segment[$x+1]);
2754 - $segment = array_values($segment);
2755 - continue 2;
2756 - } else {
2757 - $x++;
2758 - }
2759 - }
2760 - $search_finished = true;
2761 - }
2762 - $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
2763 - $uri_parts['path'] = $buffer;
2764 -
2765 - }
2766 - }
2767 -
2768 - // If we've gotten to this point, we can try to put the pieces
2769 - // back together.
2770 - $ret = '';
2771 - if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
2772 - if (isset($uri_parts['user'])) {
2773 - $ret .= $uri_parts['user'];
2774 - if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
2775 - $ret .= '@';
2776 - }
2777 - if (isset($uri_parts['host'])) {
2778 - $ret .= '//'.$uri_parts['host'];
2779 - if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
2780 - }
2781 - $ret .= $uri_parts['path'];
2782 - if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
2783 - if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
2784 -
2785 - return $ret;
2786 - }
2787 -
2788 - /**
2789 - * Parse URL
2790 - *
2791 - * Regular expression grabbed from RFC 2396 Appendix B.
2792 - * This is a replacement for PHPs builtin parse_url().
2793 - * @param string $url
2794 - * @access private
2795 - * @return array
2796 - */
2797 - function _parse_url($url)
2798 - {
2799 - // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
2800 - // generates a warning.
2801 - if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
2802 - $parts = array();
2803 - if ($match[1] != '') $parts['scheme'] = $match[2];
2804 - if ($match[3] != '') $parts['auth'] = $match[4];
2805 - // parse auth
2806 - if (isset($parts['auth'])) {
2807 - // store user info
2808 - if (($at_pos = strpos($parts['auth'], '@')) !== false) {
2809 - $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
2810 - $parts['user'] = $userinfo[0];
2811 - if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
2812 - $parts['auth'] = substr($parts['auth'], $at_pos+1);
2813 - }
2814 - // get port number
2815 - if ($port_pos = strrpos($parts['auth'], ':')) {
2816 - $parts['host'] = substr($parts['auth'], 0, $port_pos);
2817 - $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
2818 - if ($parts['port'] < 1) $parts['port'] = null;
2819 - } else {
2820 - $parts['host'] = $parts['auth'];
2821 - }
2822 - }
2823 - unset($parts['auth']);
2824 - $parts['path'] = $match[5];
2825 - if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
2826 - if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
2827 - return $parts;
2828 - }
2829 - // shouldn't reach here
2830 - return array('path'=>'');
2831 - }
2832 -
2833 - function _encode($string)
2834 - {
2835 - static $replace = array();
2836 - if (!count($replace)) {
2837 - $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
2838 - $find = array_merge(range(0, 31), $find);
2839 - $find = array_map('chr', $find);
2840 - foreach ($find as $char) {
2841 - $replace[$char] = '%'.bin2hex($char);
2842 - }
2843 - }
2844 - // escape control characters and a few other characters
2845 - $encoded = strtr($string, $replace);
2846 - // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
2847 - return preg_replace('/[^\x21-\x7e]/', '', $encoded);
2848 - }
2849 - } // class Relative_URI
2850 -}
2159 + function nuke ($args) {
2160 + return $this->unsubscribe('nuke', $args);
2161 + } /* FeedWordPressRPC::nuke () */
2162 +} /* class FeedWordPressRPC */
2851 2163
2852 2164 // take your best guess at the realname and e-mail, given a string
2853 2165 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
2854 2166 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');