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