PluginProbe
FeedWordPress / 0.992
FeedWordPress v0.992
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
feedwordpress / feedwordpress.php

feedwordpress.php in FeedWordPress 0.992, at feedwordpress.php

3,706 lines 132.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: FeedWordPress
4 Plugin URI: http://projects.radgeek.com/feedwordpress
5 Description: simple and flexible Atom/RSS syndication for WordPress
6 Version: 0.992
7 Author: Charles Johnson
8 Author URI: http://radgeek.com/
9 License: GPL
10 Last modified: 2008-01-04 18:03 PDT
11 */
12
13 # This uses code derived from:
14 # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
15 # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
16 # - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org>
17 # according to the terms of the GNU General Public License.
18 #
19 # INSTALLATION: see README.text or <http://projects.radgeek.com/install>
20 #
21 # 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
25 # <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`
27
28 # -- Don't change these unless you know what you're doing...
29
30 define ('FEEDWORDPRESS_VERSION', '0.992');
31 define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
32 define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors');
33
34 define ('FEEDWORDPRESS_DEBUG', false);
35
36 define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/');
37 define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n");
38
39 define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi');
40
41 define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes
42
43 define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0
44 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1
45 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3
46
47 if (FEEDWORDPRESS_DEBUG) :
48 // Help us to pick out errors, if any.
49 ini_set('error_reporting', E_ALL & ~E_NOTICE);
50 ini_set('display_errors', true);
51 define('MAGPIE_DEBUG', true);
52
53 // When testing we don't want cache issues to interfere. But this is
54 // a VERY BAD SETTING for a production server. Webmasters will eat your
55 // face for breakfast if you use it, and the baby Jesus will cry. So
56 // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
57 // used for more than testing purposes!
58 define('MAGPIE_CACHE_AGE', 1);
59 else :
60 define('MAGPIE_DEBUG', false);
61 endif;
62
63 // Note that the rss-functions.php that comes prepackaged with WordPress is
64 // old & busted. For the new hotness, drop a copy of rss.php from
65 // this archive into wp-includes/rss.php
66
67 if (is_readable(ABSPATH . WPINC . '/rss.php')) :
68 require_once (ABSPATH . WPINC . '/rss.php');
69 else :
70 require_once (ABSPATH . WPINC . '/rss-functions.php');
71 endif;
72
73 if (isset($wp_db_version)) :
74 if ($wp_db_version >= FWP_SCHEMA_23) :
75 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
76 elseif ($wp_db_version >= FWP_SCHEMA_21) : // WordPress 2.1 and 2.2, but not 2.3
77 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user
78 require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
79 elseif ($wp_db_version >= FWP_SCHEMA_20) : // WordPress 2.0
80 require_once (ABSPATH . WPINC . '/registration-functions.php'); // for wp_insert_user
81 require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category
82 endif;
83 endif;
84
85 if (function_exists('wp_enqueue_script')) :
86 wp_enqueue_script( 'ajaxcat' ); // Provides the handy-dandy new category text box
87 endif;
88
89 // Magic quotes are just about the stupidest thing ever.
90 if (is_array($_POST)) :
91 $fwp_post = stripslashes_deep($_POST);
92 endif;
93
94
95 if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
96
97 # Syndicated items are generally received in output-ready (X)HTML and
98 # should not be folded, crumpled, mutilated, or spindled by WordPress
99 # formatting filters. But we don't want to interfere with filters for
100 # any locally-authored posts, either.
101 #
102 # What WordPress should really have is a way for upstream filters to
103 # stop downstream filters from running at all. Since it doesn't, and
104 # since a downstream filter can't access the original copy of the text
105 # that is being filtered, what we will do here is (1) save a copy of the
106 # original text upstream, before any other filters run, and then (2)
107 # retrieve that copy downstream, after all the other filters run, *if*
108 # this is a syndicated post
109
110 add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000);
111 add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000);
112
113 # Filter in original permalinks if the user wants that
114 add_filter('post_link', 'syndication_permalink', 1);
115
116 # WTF? By default, wp_insert_link runs incoming link_url and link_rss
117 # URIs through default filters that include `wp_kses()`. But `wp_kses()`
118 # just happens to escape any occurrence of & to &amp; -- which just
119 # happens to fuck up any URI with a & to separate GET parameters.
120 remove_filter('pre_link_rss', 'wp_filter_kses');
121 remove_filter('pre_link_url', 'wp_filter_kses');
122
123 # Admin menu
124 add_action('admin_menu', 'fwp_add_pages');
125
126 # Inbound XML-RPC update methods
127 add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook');
128
129 # Outbound XML-RPC ping reform
130 remove_action('publish_post', 'generic_ping'); // WP 1.5.x
131 remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2
132 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3
133
134 add_action('publish_post', 'fwp_publish_post_hook', 5, 1);
135 add_action('do_pings', 'fwp_do_pings', 10, 1);
136 add_action('feedwordpress_update', 'fwp_hold_pings');
137 add_action('feedwordpress_update_complete', 'fwp_release_pings');
138
139 # Hook in logging functions only if the logging option is ON
140 $update_logging = get_option('feedwordpress_update_logging');
141 if ($update_logging == 'yes') :
142 add_action('post_syndicated_item', 'log_feedwordpress_post', 100);
143 add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100);
144 add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100);
145 add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100);
146 add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
147 endif;
148
149 # Cron-less auto-update. Hooray!
150 add_action('init', 'feedwordpress_auto_update');
151 else :
152 # Hook in the menus, which will just point to the upgrade interface
153 add_action('admin_menu', 'fwp_add_pages');
154 endif; // if (!FeedWordPress::needs_upgrade())
155
156 function feedwordpress_auto_update () {
157 if (FeedWordPress::stale()) :
158 $feedwordpress =& new FeedWordPress;
159 $feedwordpress->update();
160 endif;
161
162 if (FeedWordPress::update_requested()) :
163 exit;
164 endif;
165 } /* feedwordpress_auto_update () */
166
167 ################################################################################
168 ## LOGGING FUNCTIONS: log status updates to error_log if you want it ###########
169 ################################################################################
170
171 function log_feedwordpress_post ($id) {
172 $post = wp_get_single_post($id);
173 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted "
174 ."'{$post->post_title}' ({$post->post_date})");
175 }
176
177 function log_feedwordpress_update_post ($id) {
178 $post = wp_get_single_post($id);
179 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated "
180 ."'{$post->post_title}' ({$post->post_date})"
181 ." (as of {$post->post_modified})");
182 }
183
184 function log_feedwordpress_update_feeds ($uri) {
185 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')");
186 }
187
188 function log_feedwordpress_check_feed ($feed) {
189 $uri = $feed['link/uri']; $name = $feed['link/name'];
190 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>");
191 }
192
193 function log_feedwordpress_update_complete ($delta) {
194 $mesg = array();
195 if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts';
196 if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts';
197 if (empty($mesg)) $mesg[] = 'nothing changed';
198
199 error_log("[".date('Y-m-d H:i:s')."][feedwordpress] "
200 .(is_null($delta) ? "Error: I don't syndicate that URI"
201 : implode(' and ', $mesg)));
202 }
203
204 ################################################################################
205 ## LEGACY API: Replicate or mock up functions for legacy support purposes ######
206 ################################################################################
207
208 if (!function_exists('get_option')) {
209 function get_option ($option) {
210 return get_settings($option);
211 }
212 }
213 if (!function_exists('current_user_can')) {
214 $legacy_capability_hack = true;
215 function current_user_can ($task) {
216 global $user_level;
217
218 $can = false;
219
220 // This is **not** a full replacement for current_user_can. It
221 // is only for checking the capabilities we care about via the
222 // WordPress 1.5 user levels.
223 switch ($task) {
224 case 'manage_options':
225 $can = ($user_level >= 6);
226 break;
227 case 'manage_links':
228 $can = ($user_level >= 5);
229 break;
230 } /* switch */
231 return $can;
232 }
233 } else {
234 $legacy_capability_hack = false;
235 }
236 if (!function_exists('sanitize_user')) {
237 function sanitize_user ($text, $strict) {
238 return $text; // Don't munge it if it wasn't munged going in...
239 }
240 }
241 if (!function_exists('wp_insert_user')) {
242 function wp_insert_user ($userdata) {
243 #-- Help WordPress 1.5.x quack like a duck
244 $login = $userdata['user_login'];
245 $author = $userdata['display_name'];
246 $nice_author = $userdata['user_nicename'];
247 $email = $userdata['user_email'];
248 $url = $userdata['user_url'];
249
250 $wpdb->query (
251 "INSERT INTO $wpdb->users
252 SET
253 ID='0',
254 user_login='$login',
255 user_firstname='$author',
256 user_nickname='$author',
257 user_nicename='$nice_author',
258 user_description='$author',
259 user_email='$email',
260 user_url='$url'");
261 $id = $wpdb->insert_id;
262
263 return $id;
264 }
265 }
266 ################################################################################
267 ## TEMPLATE API: functions to make your templates syndication-aware ############
268 ################################################################################
269
270 function is_syndicated () { return (strlen(get_syndication_feed_id()) > 0); }
271
272 function the_syndication_source_link () { echo get_syndication_source_link(); }
273 function get_syndication_source_link () { list($n) = get_post_custom_values('syndication_source_uri'); return $n; }
274
275 function get_syndication_source () { list($n) = get_post_custom_values('syndication_source'); return $n; }
276 function the_syndication_source () { echo get_syndication_source(); }
277
278 function get_syndication_feed () { list($u) = get_post_custom_values('syndication_feed'); return $u; }
279 function the_syndication_feed () { echo get_syndication_feed (); }
280
281 function get_syndication_feed_id () { list($u) = get_post_custom_values('syndication_feed_id'); return $u; }
282 function the_syndication_feed_id () { echo get_syndication_feed_id(); }
283
284 $feedwordpress_linkcache = array (); // only load links from database once
285
286 function get_feed_meta ($key) {
287 global $wpdb, $feedwordpress_linkcache;
288 $feed_id = get_syndication_feed_id();
289
290 $ret = NULL;
291 if (strlen($feed_id) > 0):
292 if (isset($feedwordpress_linkcache[$feed_id])) :
293 $link = $feedwordpress_linkcache[$feed_id];
294 else :
295 $link =& new SyndicatedLink($feed_id);
296 $feedwordpress_linkcache[$feed_id] = $link;
297 endif;
298
299 $ret = $link->settings[$key];
300 endif;
301 return $ret;
302 } /* get_feed_meta() */
303
304 function get_syndication_permalink () {
305 list($u) = get_post_custom_values('syndication_permalink'); return $u;
306 }
307 function the_syndication_permalink () {
308 echo get_syndication_permalink();
309 }
310
311 ################################################################################
312 ## FILTERS: syndication-aware handling of post data for templates and feeds ####
313 ################################################################################
314
315 $feedwordpress_the_syndicated_content = NULL;
316
317 function feedwordpress_preserve_syndicated_content ($text) {
318 global $feedwordpress_the_syndicated_content;
319
320 if ( is_syndicated() ) :
321 $feedwordpress_the_syndicated_content = $text;
322 else :
323 $feedwordpress_the_syndicated_content = NULL;
324 endif;
325 return $text;
326 }
327
328 function feedwordpress_restore_syndicated_content ($text) {
329 global $feedwordpress_the_syndicated_content;
330
331 if ( !is_null($feedwordpress_the_syndicated_content) ) :
332 $text = $feedwordpress_the_syndicated_content;
333 endif;
334
335 return $text;
336 }
337
338 function syndication_permalink ($permalink = '') {
339 if (get_option('feedwordpress_munge_permalink') != 'no'):
340 $uri = get_syndication_permalink();
341 return ((strlen($uri) > 0) ? $uri : $permalink);
342 else:
343 return $permalink;
344 endif;
345 } // function syndication_permalink ()
346
347 ################################################################################
348 ## UPGRADE INTERFACE: Have users upgrade DB from older versions of FWP #########
349 ################################################################################
350
351 function fwp_upgrade_page () {
352 if (isset($GLOBALS['fwp_post']['action']) and $GLOBALS['fwp_post']['action']=='Upgrade') :
353 $ver = get_option('feedwordpress_version');
354 if (get_option('feedwordpress_version') != FEEDWORDPRESS_VERSION) :
355 echo "<div class=\"wrap\">\n";
356 echo "<h2>Upgrading FeedWordPress...</h2>";
357
358 $feedwordpress =& new FeedWordPress;
359 $feedwordpress->upgrade_database();
360 echo "<p><strong>Done!</strong> Upgraded database to version ".FEEDWORDPRESS_VERSION.".</p>\n";
361 echo "<form action=\"\" method=\"get\">\n";
362 echo "<div class=\"submit\"><input type=\"hidden\" name=\"page\" value=\"".basename(__FILE__)."\" />";
363 echo "<input type=\"submit\" value=\"Continue &raquo;\" /></form></div>\n";
364 echo "</div>\n";
365 return;
366 else :
367 echo "<div class=\"updated\"><p>Already at version ".FEEDWORDPRESS_VERSION."!</p></div>";
368 endif;
369 endif;
370 ?>
371 <div class="wrap">
372 <h2>Upgrade FeedWordPress</h2>
373
374 <p>It appears that you have installed FeedWordPress
375 <?php echo FEEDWORDPRESS_VERSION; ?> as an upgrade to an existing installation of
376 FeedWordPress. That's no problem, but you will need to take a minute out first
377 to upgrade your database: some necessary changes in how the software keeps
378 track of posts and feeds will cause problems such as duplicate posts and broken
379 templates if we were to continue without the upgrade.</p>
380
381 <p>Note that most of FeedWordPress's functionality is temporarily disabled
382 until we have successfully completed the upgrade. Everything should begin
383 working as normal again once the upgrade is complete. There's extraordinarily
384 little chance of any damage as the result of the upgrade, but if you're paranoid
385 like me you may want to back up your database before you proceed.</p>
386
387 <p>This may take several minutes for a large installation.</p>
388
389 <form action="" method="post">
390 <div class="submit"><input type="submit" name="action" value="Upgrade" /></div>
391 </form>
392 </div>
393 <?php
394 } // function fwp_upgrade_page ()
395
396 ################################################################################
397 ## ADMIN MENU ADD-ONS: implement Dashboard management pages ####################
398 ################################################################################
399
400 function fwp_add_pages () {
401 global $legacy_capability_hack;
402
403 if ($legacy_capability_hack) :
404 // old & busted: numeric user levels
405 $manage_links = 5;
406 $manage_options = 6;
407 else :
408 // new hotness: named capabilities
409 $manage_links = 'manage_links';
410 $manage_options = 'manage_options';
411 endif;
412
413 //add_submenu_page('plugins.php', 'Akismet Configuration', 'Akismet Configuration', 'manage_options', 'syndication-manage-page', 'fwp_syndication_manage_page');
414 add_menu_page('Syndicated Sites', 'Syndication', $manage_links, 'feedwordpress/'.basename(__FILE__), 'fwp_syndication_manage_page');
415 add_submenu_page('feedwordpress/'.basename(__FILE__), 'Syndication Options', 'Options', $manage_options, 'feedwordpress/syndication-options.php');
416 add_options_page('Syndication Options', 'Syndication', $manage_options, 'feedwordpress/syndication-options.php');
417 } // function fwp_add_pages () */
418
419 function fwp_category_box ($checked, $object) {
420 global $wp_db_version;
421
422 if (isset($wp_db_version) and $wp_db_version >= FWP_SCHEMA_20) : // WordPress 2.x
423 ?>
424 <div id="poststuff">
425
426 <div id="moremeta">
427 <div id="grabit" class="dbx-group">
428 <fieldset id="categorydiv" class="dbx-box">
429 <h3 class="dbx-handle"><?php _e('Categories') ?></h3>
430 <div class="dbx-content">
431 <p style="font-size:smaller;font-style:bold;margin:0">Place <?php print $object; ?> under...</p>
432 <p id="jaxcat"></p>
433 <ul id="categorychecklist"><?php write_nested_categories($checked); ?></ul></div>
434 </fieldset>
435 </div>
436 </div>
437 </div>
438 <?php
439 else : // WordPress 1.5
440 ?>
441 <fieldset id="categorydiv" style="width: 20%; margin-right: 2em">
442 <legend><?php _e('Categories') ?></legend>
443 <p style="font-size:smaller;font-style:bold;margin:0">Place <?php print $object; ?> under...</p>
444 <div style="height: 20em"><?php write_nested_categories($checked); ?></div>
445 </fieldset>
446 <?php
447 endif;
448 }
449
450 function update_feeds_mention ($feed) {
451 echo "<li>Updating <cite>".$feed['link/name']."</cite> from &lt;<a href=\""
452 .$feed['link/uri']."\">".$feed['link/uri']."</a>&gt; ...</li>\n";
453 flush();
454 }
455
456 function fwp_syndication_manage_page () {
457 global $wpdb;
458
459 if (FeedWordPress::needs_upgrade()) :
460 fwp_upgrade_page();
461 return;
462 endif;
463
464 ?>
465 <?php $cont = true;
466 if (isset($_REQUEST['action'])):
467 if ($_REQUEST['action'] == 'feedfinder') : $cont = fwp_feedfinder_page();
468 elseif ($_REQUEST['action'] == 'switchfeed') : $cont = fwp_switchfeed_page();
469 elseif ($_REQUEST['action'] == 'linkedit') : $cont = fwp_linkedit_page();
470 elseif ($_REQUEST['action'] == 'Unsubscribe from Checked Links' or $_REQUEST['action'] == 'Unsubscribe') : $cont = fwp_multidelete_page();
471 endif;
472 endif;
473
474 if ($cont):
475 ?>
476 <?php
477 $links = FeedWordPress::syndicated_links();
478
479 if (isset($_POST['update']) or isset($_POST['action']) or isset($_POST['update_uri'])) :
480 $fwp_update_invoke = 'post';
481 else :
482 $fwp_update_invoke = 'get';
483 endif;
484
485 $update_set = array();
486 if (isset($_POST['link_ids']) and is_array($_POST['link_ids']) and ($_POST['action']=='Update Checked Links')) :
487 $targets = $wpdb->get_results("
488 SELECT * FROM $wpdb->links
489 WHERE link_id IN (".implode(",",$_POST['link_ids']).")
490 ");
491 if (is_array($targets)) :
492 foreach ($targets as $target) :
493 $update_set[] = $target->link_rss;
494 endforeach;
495 else : // This should never happen
496 FeedWordPress::critical_bug('fwp_syndication_manage_page::targets', $targets, __LINE__);
497 endif;
498 elseif (isset($_POST['update_uri'])) :
499 $update_set[] = $_POST['update_uri'];
500 endif;
501
502 if ($fwp_update_invoke != 'get' and count($update_set) > 0) : // Only do things with side-effects for HTTP POST or command line
503 $feedwordpress =& new FeedWordPress;
504 add_action('feedwordpress_check_feed', 'update_feeds_mention');
505
506 echo "<div class=\"updated\">\n";
507 echo "<ul>\n";
508 $tdelta = NULL;
509 foreach ($update_set as $uri) :
510 if ($uri == '*') : $uri = NULL; endif;
511 $delta = $feedwordpress->update($uri);
512 if (!is_null($delta)) :
513 if (is_null($tdelta)) :
514 $tdelta = $delta;
515 else :
516 $tdelta['new'] += $delta['new'];
517 $tdelta['updated'] += $delta['updated'];
518 endif;
519 else :
520 echo "<li><p><strong>Error:</strong> There was a problem updating <a href=\"$uri\">$uri</a></p></li>\n";
521 endif;
522 endforeach;
523 echo "</ul>\n";
524
525 if (!is_null($tdelta)) :
526 $mesg = array();
527 if (isset($delta['new'])) : $mesg[] = ' '.$tdelta['new'].' new posts were syndicated'; endif;
528 if (isset($delta['updated'])) : $mesg[] = ' '.$tdelta['updated'].' existing posts were updated'; endif;
529 echo "<p>Update complete.".implode(' and', $mesg)."</p>";
530 echo "\n"; flush();
531 endif;
532 echo "</div> <!-- class=\"updated\" -->\n";
533 endif;
534
535 ?>
536 <div class="wrap">
537 <form action="" method="POST">
538 <h2>Update feeds now</h2>
539 <p>Check currently scheduled feeds for new and updated posts.</p>
540
541 <?php if (!get_option('feedwordpress_automatic_updates')) : ?>
542 <p><strong>Note:</strong> Automatic updates are currently turned
543 <strong>off</strong>. New posts from your feeds will not be syndicated
544 until you manually check for them here. You can turn on automatic
545 updates under <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication
546 Options</a>.</p>
547 <?php endif; ?>
548
549
550 <div class="submit"><input type="hidden" name="update_uri" value="*" /><input type="submit" name="update" value="Update" /></div>
551 </form>
552 </div> <!-- class="wrap" -->
553
554 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
555 <div class="wrap">
556 <h2>Syndicated Sites</h2>
557 <?php $alt_row = true;
558 if ($links): ?>
559
560 <table width="100%" cellpadding="3" cellspacing="3">
561 <tr>
562 <th width="20%"><?php _e('Name'); ?></th>
563 <th width="40%"><?php _e('Feed'); ?></th>
564 <th colspan="4"><?php _e('Action'); ?></th>
565 </tr>
566
567 <?php foreach ($links as $link):
568 $alt_row = !$alt_row; ?>
569 <tr<?php echo ($alt_row?' class="alternate"':''); ?>>
570 <td><a href="<?php echo wp_specialchars($link->link_url, 'both'); ?>"><?php echo wp_specialchars($link->link_name, 'both'); ?></a></td>
571 <?php
572 if (strlen($link->link_rss) > 0):
573 $caption='Switch Feed';
574 $uri_bits = parse_url($link->link_rss);
575 $uri_bits['host'] = preg_replace('/^www\./i', '', $uri_bits['host']);
576 $display_uri =
577 (isset($uri_bits['user'])?$uri_bits['user'].'@':'')
578 .(isset($uri_bits['host'])?$uri_bits['host']:'')
579 .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
580 .(isset($uri_bits['path'])?$uri_bits['path']:'')
581 .(isset($uri_bits['query'])?'?'.$uri_bits['query']:'');
582 if (strlen($display_uri) > 32) : $display_uri = substr($display_uri, 0, 32).'&#8230;'; endif;
583 ?>
584 <td>
585 <strong><a href="<?php echo wp_specialchars($link->link_rss, 'both'); ?>"><?php echo wp_specialchars($display_uri, 'both'); ?></a></strong></td>
586 <?php
587 else:
588 $caption='Find Feed';
589 ?>
590 <td style="background-color:#FFFFD0"><p><strong>no
591 feed assigned</strong></p></td>
592 <?php
593 endif;
594 ?>
595 <td><a href="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=linkedit" class="edit"><?php _e('Edit')?></a></td>
596 <td><a href="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=feedfinder" class="edit"><?php echo $caption; ?></a></td>
597 <td><a href="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=Unsubscribe" class="delete"><?php _e('Unsubscribe'); ?></a></td>
598 <td><input type="checkbox" name="link_ids[]" value="<?php echo $link->link_id; ?>" /></td>
599 <?php
600 echo "\n\t</tr>";
601 endforeach;
602 else:
603 ?>
604
605 <p>There are no websites currently listed for syndication.</p>
606
607 <?php endif; ?>
608 </table>
609
610 <br/><hr/>
611 <div class="submit"><input type="submit" class="delete" name="action" value="Unsubscribe from Checked Links" />
612 <input type="submit" name="action" value="Update Checked Links" /></div>
613 </div> <!-- class="wrap" -->
614 </form>
615
616 <div class="wrap">
617 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
618 <h2>Add a new syndicated site:</h2>
619 <div>
620 <label for="add-uri">Website or newsfeed:</label>
621 <input type="text" name="lookup" id="add-uri" value="URI" size="64" />
622 <input type="hidden" name="action" value="feedfinder" />
623 </div>
624 <div class="submit"><input type="submit" value="Syndicate &raquo;" /></div>
625 </form>
626 </div> <!-- class="wrap" -->
627 <?php
628 endif;
629 }
630
631 function fwp_feedfinder_page () {
632 global $wpdb;
633
634 $lookup = (isset($_REQUEST['lookup'])?$_REQUEST['lookup']:NULL);
635
636 if (isset($_REQUEST['link_id']) and ($_REQUEST['link_id']!=0)):
637 $link_id = $_REQUEST['link_id'];
638 if (!is_numeric($link_id)) : FeedWordPress::critical_bug('fwp_feedfinder_page::link_id', $link_id, __LINE__); endif;
639
640 $link = $wpdb->get_row("SELECT * FROM $wpdb->links WHERE link_id='".$wpdb->escape($link_id)."'");
641 if (is_object($link)):
642 if (is_null($lookup)) $lookup = $link->link_url;
643 $name = wp_specialchars($link->link_name, 'both');
644 else:
645 die (__("Cheatin' uh ?"));
646 endif;
647 else:
648 $name = "New Syndicated Feed";
649 $link_id = 0;
650 endif;
651 ?>
652 <div class="wrap">
653 <h2>Feed Finder: <?php echo $name; ?></h2>
654 <?php
655 $f =& new FeedFinder($lookup);
656 $feeds = $f->find();
657 if (count($feeds) > 0):
658 foreach ($feeds as $key => $f):
659 $rss = fetch_rss($f);
660 if ($rss):
661 $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link'];
662 $feed_link = isset($rss->channel['link'])?$rss->channel['link']:'';
663 ?>
664 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
665 <fieldset style="clear: both">
666 <legend><?php echo $rss->feed_type; ?> <?php echo $rss->feed_version; ?> feed</legend>
667
668 <?php if ($link_id===0): ?>
669 <input type="hidden" name="feed_title" value="<?php echo wp_specialchars($feed_title, 'both'); ?>" />
670 <input type="hidden" name="feed_link" value="<?php echo wp_specialchars($feed_link, 'both'); ?>" />
671 <?php endif; ?>
672
673 <input type="hidden" name="link_id" value="<?php echo $link_id; ?>" />
674 <input type="hidden" name="feed" value="<?php echo wp_specialchars($f, 'both'); ?>" />
675 <input type="hidden" name="action" value="switchfeed" />
676
677 <div>
678 <div style="float:right; background-color:#D0D0D0; color: black; width:45%; font-size:70%; border-left: 1px dotted #A0A0A0; padding-left: 0.5em; margin-left: 1.0em">
679 <?php if (count($rss->items) > 0): ?>
680 <?php $item = $rss->items[0]; ?>
681 <h3>Sample Item</h3>
682 <ul>
683 <li><strong>Title:</strong> <a href="<?php echo $item['link']; ?>"><?php echo $item['title']; ?></a></li>
684 <li><strong>Date:</strong> <?php echo isset($item['date_timestamp']) ? date('d-M-y g:i:s a', $item['date_timestamp']) : 'unknown'; ?></li>
685 </ul>
686 <div class="entry">
687 <?php echo (isset($item['content']['encoded'])?$item['content']['encoded']:$item['description']); ?>
688 </div>
689 <?php else: ?>
690 <h3>No Items</h3>
691 <p>FeedWordPress found no posts on this feed.</p>
692 <?php endif; ?>
693 </div>
694
695 <div>
696 <h3>Feed Information</h3>
697 <ul>
698 <li><strong>Website:</strong> <a href="<?php echo $feed_link; ?>"><?php echo is_null($feed_title)?'<em>Unknown</em>':$feed_title; ?></a></li>
699 <li><strong>Feed URI:</strong> <a href="<?php echo wp_specialchars($f, 'both'); ?>"><?php echo wp_specialchars($f, 'both'); ?></a> (<a title="Check feed &lt;<?php echo wp_specialchars($f, 'both'); ?>&gt; for validity" href="http://feedvalidator.org/check.cgi?url=<?php echo urlencode($f); ?>">validate</a>)</li>
700 <li><strong>Encoding:</strong> <?php echo isset($rss->encoding)?wp_specialchars($rss->encoding, 'both'):"<em>Unknown</em>"; ?></li>
701 <li><strong>Description:</strong> <?php echo isset($rss->channel['description'])?wp_specialchars($rss->channel['description'], 'both'):"<em>Unknown</em>"; ?></li>
702 </ul>
703 <div class="submit"><input type="submit" name="Use" value="&laquo; Use this feed" /></div>
704 <div class="submit"><input type="submit" name="Cancel" value="&laquo; Cancel" /></div>
705 </div>
706 </div>
707 </fieldset>
708 </form>
709 <?php endif;
710 endforeach;
711 else:
712 echo "<p><strong>no feed found</strong></p>";
713 endif;
714 ?>
715 </div>
716
717 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
718 <div class="wrap">
719 <h2>Use another feed</h2>
720 <div><label>Feed:</label>
721 <input type="text" name="lookup" value="URI" />
722 <input type="hidden" name="link_id" value="<?php echo $link_id; ?>" />
723 <input type="hidden" name="action" value="feedfinder" /></div>
724 <div class="submit"><input type="submit" value="Use this feed &raquo;" /></div>
725 </div>
726 </form>
727 <?php
728 return false; // Don't continue
729 }
730
731 function fwp_switchfeed_page () {
732 global $wpdb, $wp_db_version;
733
734 check_admin_referer();
735 if (!isset($_REQUEST['Cancel'])):
736 if (!current_user_can('manage_links')):
737 die (__("Cheatin' uh ?"));
738 elseif (isset($_REQUEST['link_id']) and ($_REQUEST['link_id']==0)):
739 $link_id = FeedWordPress::syndicate_link($_REQUEST['feed_title'], $_REQUEST['feed_link'], $_REQUEST['feed']);
740 if ($link_id): ?>
741 <div class="updated"><p><a href="<?php echo $_REQUEST['feed_link']; ?>"><?php echo wp_specialchars($_REQUEST['feed_title'], 'both'); ?></a>
742 has been added as a contributing site, using the newsfeed at &lt;<a href="<?php echo $_REQUEST['feed']; ?>"><?php echo wp_specialchars($_REQUEST['feed'], 'both'); ?></a>&gt;.</p></div>
743 <?php else: ?>
744 <div class="updated"><p>There was a problem adding the newsfeed. [SQL: <?php echo wp_specialchars(mysql_error(), 'both'); ?>]</p></div>
745 <?php endif;
746 elseif (isset($_REQUEST['link_id'])):
747 // Update link_rss
748 $result = $wpdb->query("
749 UPDATE $wpdb->links
750 SET
751 link_rss = '".$wpdb->escape($_REQUEST['feed'])."'
752 WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."'
753 ");
754
755 if ($result):
756 $result = $wpdb->get_row("
757 SELECT link_name, link_url FROM $wpdb->links
758 WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."'
759 ");
760 ?>
761 <div class="updated"><p>Feed for <a href="<?php echo $result->link_url; ?>"><?php echo wp_specialchars($result->link_name, 'both'); ?></a>
762 updated to &lt;<a href="<?php echo $_REQUEST['feed']; ?>"><?php echo wp_specialchars($_REQUEST['feed'], 'both'); ?></a>&gt;.</p></div>
763 <?php else: ?>
764 <div class="updated"><p>Nothing was changed.</p></div>
765 <?php endif;
766 endif;
767 endif;
768 return true; // Continue
769 }
770
771 function fwp_linkedit_page () {
772 global $wpdb;
773
774 check_admin_referer(); // Make sure we arrived here from the Dashboard
775
776 $special_settings = array ( /* Regular expression syntax is OK here */
777 'cats',
778 'cat_split',
779 'hardcode name',
780 'hardcode url',
781 'hardcode description',
782 'hardcode categories', /* Deprecated */
783 'post status',
784 'comment status',
785 'ping status',
786 'unfamiliar author',
787 'unfamliar categories',
788 'map authors',
789 'update/.*',
790 'feed/.*',
791 'link/.*',
792 );
793
794 if (!current_user_can('manage_links')) :
795 die (__("Cheatin' uh ?"));
796 elseif (isset($_REQUEST['feedfinder'])) :
797 return fwp_feedfinder_page(); // re-route to Feed Finder page
798 else :
799 $link_id = (int) $_REQUEST['link_id'];
800 $link =& new SyndicatedLink($link_id);
801
802 if ($link->found()) :
803 if (isset($GLOBALS['fwp_post']['save'])) :
804 $alter = array ();
805
806 $meta = $link->settings;
807 //if (isset($meta['cats'])):
808 // $meta['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $meta['cats']);
809 //endif;
810
811 // custom feed settings first
812 foreach ($GLOBALS['fwp_post']['notes'] as $mn) :
813 $mn['key0'] = trim($mn['key0']);
814 $mn['key1'] = trim($mn['key1']);
815 if (preg_match("\007^(("
816 .implode(')|(',$special_settings)
817 ."))$\007i",
818 $mn['key1'])) :
819 $mn['key1'] = 'user/'.$mn['key1'];
820 endif;
821
822 if (strlen($mn['key0']) > 0) :
823 unset($meta[$mn['key0']]); // out with the old
824 endif;
825
826 if (($mn['action']=='update') and (strlen($mn['key1']) > 0)) :
827 $meta[$mn['key1']] = $mn['value']; // in with the new
828 endif;
829 endforeach;
830
831 // now stuff through the web form
832 // hardcoded feed info
833 if (isset($GLOBALS['fwp_post']['hardcode_name'])) :
834 $meta['hardcode name'] = $GLOBALS['fwp_post']['hardcode_name'];
835 if (FeedWordPress::affirmative($meta, 'hardcode name')) :
836 $alter[] = "link_name = '".$wpdb->escape($GLOBALS['fwp_post']['name'])."'";
837 endif;
838 endif;
839 if (isset($GLOBALS['fwp_post']['hardcode_description'])) :
840 $meta['hardcode description'] = $GLOBALS['fwp_post']['hardcode_description'];
841 if (FeedWordPress::affirmative($meta, 'hardcode description')) :
842 $alter[] = "link_description = '".$wpdb->escape($GLOBALS['fwp_post']['description'])."'";
843 endif;
844 endif;
845 if (isset($GLOBALS['fwp_post']['hardcode_url'])) :
846 $meta['hardcode url'] = $GLOBALS['fwp_post']['hardcode_url'];
847 if (FeedWordPress::affirmative($meta, 'hardcode url')) :
848 $alter[] = "link_url = '".$wpdb->escape($GLOBALS['fwp_post']['linkurl'])."'";
849 endif;
850 endif;
851
852 // Update scheduling
853 if (isset($GLOBALS['fwp_post']['update_schedule'])) :
854 $meta['update/hold'] = $GLOBALS['fwp_post']['update_schedule'];
855 endif;
856
857 // Categories
858 if (isset($GLOBALS['fwp_post']['post_category'])) :
859 $meta['cats'] = array();
860 foreach ($GLOBALS['fwp_post']['post_category'] as $cat_id) :
861 $meta['cats'][] = '{#'.$cat_id.'}';
862 endforeach;
863 else :
864 unset($meta['cats']);
865 endif;
866
867 // Post status, comment status, ping status
868 foreach (array('post', 'comment', 'ping') as $what) :
869 $sfield = "feed_{$what}_status";
870 if (isset($GLOBALS['fwp_post'][$sfield])) :
871 if ($GLOBALS['fwp_post'][$sfield]=='site-default') :
872 unset($meta["{$what} status"]);
873 else :
874 $meta["{$what} status"] = $GLOBALS['fwp_post'][$sfield];
875 endif;
876 endif;
877 endforeach;
878
879 // Unfamiliar author, unfamiliar categories
880 foreach (array("author", "category") as $what) :
881 $sfield = "unfamiliar_{$what}";
882 if (isset($GLOBALS['fwp_post'][$sfield])) :
883 if ('site-default'==$GLOBALS['fwp_post'][$sfield]) :
884 unset($meta["unfamiliar {$what}"]);
885 elseif ('newuser'==$GLOBALS['fwp_post'][$sfield]) :
886 $newuser_name = trim($GLOBALS['fwp_post']["{$sfield}_newuser"]);
887 if (strlen($newuser_name) > 0) :
888 $userdata = array();
889 $userdata['ID'] = NULL;
890
891 $userdata['user_login'] = sanitize_user($newuser_name);
892 $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
893
894 $userdata['user_nicename'] = sanitize_title($newuser_name);
895 $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
896
897 $userdata['display_name'] = $wpdb->escape($newuser_name);
898
899 $newuser_id = wp_insert_user($userdata);
900 if (is_numeric($newuser_id)) :
901 $meta["unfamiliar {$what}"] = $newuser_id;
902 else :
903 // TODO: Add some error detection and reporting
904 endif;
905 else :
906 // TODO: Add some error reporting
907 endif;
908 else :
909 $meta["unfamiliar {$what}"] = $GLOBALS['fwp_post'][$sfield];
910 endif;
911 endif;
912 endforeach;
913
914 // Handle author mapping rules
915 if (isset($GLOBALS['fwp_post']['author_rules_name']) and isset($GLOBALS['fwp_post']['author_rules_action'])) :
916 unset($meta['map authors']);
917 foreach ($GLOBALS['fwp_post']['author_rules_name'] as $key => $name) :
918 // Normalize for case and whitespace
919 $name = strtolower(trim($name));
920 $author_action = strtolower(trim($GLOBALS['fwp_post']['author_rules_action'][$key]));
921
922 if (strlen($name) > 0) :
923 if ('newuser' == $author_action) :
924 $newuser_name = trim($GLOBALS['fwp_post']['author_rules_newuser'][$key]);
925 if (strlen($newuser_name) > 0) :
926 $userdata = array();
927 $userdata['ID'] = NULL;
928
929 $userdata['user_login'] = sanitize_user($newuser_name);
930 $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
931
932 $userdata['user_nicename'] = sanitize_title($newuser_name);
933 $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
934
935 $userdata['display_name'] = $wpdb->escape($newuser_name);
936
937 $newuser_id = wp_insert_user($userdata);
938 if (is_numeric($newuser_id)) :
939 $meta['map authors']['name'][$name] = $newuser_id;
940 else :
941 // TODO: Add some error detection and reporting
942 endif;
943 else :
944 // TODO: Add some error reporting
945 endif;
946 else :
947 $meta['map authors']['name'][$name] = $author_action;
948 endif;
949 endif;
950 endforeach;
951 endif;
952
953 if (isset($GLOBALS['fwp_post']['add_author_rule_name']) and isset($GLOBALS['fwp_post']['add_author_rule_action'])) :
954 $name = strtolower(trim($GLOBALS['fwp_post']['add_author_rule_name']));
955 $author_action = strtolower(trim($GLOBALS['fwp_post']['add_author_rule_action']));
956 if (strlen($name) > 0) :
957 if ('newuser' == $author_action) :
958 $newuser_name = trim($GLOBALS['fwp_post']['add_author_rule_newuser']);
959 if (strlen($newuser_name) > 0) :
960 $userdata = array();
961 $userdata['ID'] = NULL;
962
963 $userdata['user_login'] = sanitize_user($newuser_name);
964 $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
965
966 $userdata['user_nicename'] = sanitize_title($newuser_name);
967 $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
968
969 $userdata['display_name'] = $wpdb->escape($newuser_name);
970
971 $newuser_id = wp_insert_user($userdata);
972 if (is_numeric($newuser_id)) :
973 $meta['map authors']['name'][$name] = $newuser_id;
974 else :
975 // TODO: Add some error detection and reporting
976 endif;
977 else :
978 // TODO: Add some error reporting
979 endif;
980 else :
981 $meta['map authors']['name'][$name] = $author_action;
982 endif;
983 endif;
984 endif;
985
986 if (isset($GLOBALS['fwp_post']['cat_split'])) :
987 if (strlen(trim($GLOBALS['fwp_post']['cat_split'])) > 0) :
988 $meta['cat_split'] = trim($GLOBALS['fwp_post']['cat_split']);
989 else :
990 unset($meta['cat_split']);
991 endif;
992 endif;
993
994 if (is_array($meta['cats'])) :
995 $meta['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $meta['cats']);
996 endif;
997
998 // Collapse the author mapping rule structure back into a flat string
999 if (isset($meta['map authors'])) :
1000 $ma = array();
1001 foreach ($meta['map authors'] as $rule_type => $author_rules) :
1002 foreach ($author_rules as $author_name => $author_action) :
1003 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
1004 endforeach;
1005 endforeach;
1006 $meta['map authors'] = implode("\n\n", $ma);
1007 endif;
1008
1009 $notes = '';
1010 foreach ($meta as $key => $value) :
1011 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
1012 endforeach;
1013 $alter[] = "link_notes = '".$wpdb->escape($notes)."'";
1014
1015 $alter_set = implode(", ", $alter);
1016
1017 // issue update query
1018 $result = $wpdb->query("
1019 UPDATE $wpdb->links
1020 SET $alter_set
1021 WHERE link_id='$link_id'
1022 ");
1023 $updated_link = true;
1024
1025 // reload link information from DB
1026 $link =& new SyndicatedLink($link_id);
1027 else :
1028 $updated_link = false;
1029 endif;
1030
1031 $db_link = $link->link;
1032 $link_url = wp_specialchars($db_link->link_url, 1);
1033 $link_name = wp_specialchars($db_link->link_name, 1);
1034 $link_description = wp_specialchars($db_link->link_description, 'both');
1035 $link_notes = wp_specialchars($db_link->link_notes, 'both');
1036 $link_rss_uri = wp_specialchars($db_link->link_rss, 'both');
1037
1038 $meta = $link->settings;
1039 $post_status_global = get_option('feedwordpress_syndicated_post_status');
1040 $comment_status_global = get_option('feedwordpress_syndicated_comment_status');
1041 $ping_status_global = get_option('feedwordpress_syndicated_ping_status');
1042
1043 $status['post'] = array('publish' => '', 'private' => '', 'draft' => '', 'site-default' => '');
1044 $status['comment'] = array('open' => '', 'closed' => '', 'site-default' => '');
1045 $status['ping'] = array('open' => '', 'closed' => '', 'site-default' => '');
1046
1047 foreach (array('post', 'comment', 'ping') as $what) :
1048 if (isset($meta["{$what} status"])) :
1049 $status[$what][$meta["{$what} status"]] = ' checked="checked"';
1050 else :
1051 $status[$what]['site-default'] = ' checked="checked"';
1052 endif;
1053 endforeach;
1054
1055 $unfamiliar['author'] = array ('create' => '','default' => '','filter' => '');
1056 $unfamiliar['category'] = array ('create'=>'','default'=>'','filter'=>'');
1057
1058 foreach (array('author', 'category') as $what) :
1059 if (is_string($meta["unfamiliar {$what}"]) and
1060 array_key_exists($meta["unfamiliar {$what}"], $unfamiliar[$what])) :
1061 $key = $meta["unfamiliar {$what}"];
1062 else:
1063 $key = 'site-default';
1064 endif;
1065 $unfamiliar[$what][$key] = ' checked="checked"';
1066 endforeach;
1067
1068 $dogs = get_nested_categories(-1, 0);
1069 if (is_array($meta['cats'])) :
1070 $cats = array_map('strtolower',
1071 array_map('trim',
1072 $meta['cats']
1073 ));
1074 else:
1075 $cats = array();
1076 endif;
1077
1078 foreach ($dogs as $tag => $dog) :
1079 $found_by_name = in_array(strtolower(trim($dog['cat_name'])), $cats);
1080
1081 if (isset($dog['cat_ID'])) : $dog['cat_id'] = $dog['cat_ID']; endif;
1082 $found_by_id = in_array('{#'.trim($dog['cat_id']).'}', $cats);
1083
1084 if ($found_by_name or $found_by_id) :
1085 $dogs[$tag]['checked'] = true;
1086 endif;
1087 endforeach;
1088 else :
1089 die( __('Link not found.') );
1090 endif;
1091
1092 ?>
1093 <script type="text/javascript">
1094 function flip_hardcode (item) {
1095 ed=document.getElementById('basics-'+item+'-edit');
1096 view=document.getElementById('basics-'+item+'-view');
1097
1098 o = document.getElementById('basics-hardcode-'+item);
1099 if (o.value=='yes') { ed.style.display='inline'; view.style.display='none'; }
1100 else { ed.style.display='none'; view.style.display='inline'; }
1101 }
1102 function flip_newuser (item) {
1103 rollup=document.getElementById(item);
1104 newuser=document.getElementById(item+'-newuser');
1105 sitewide=document.getElementById(item+'-default');
1106 if (rollup) {
1107 if ('newuser'==rollup.value) {
1108 if (newuser) newuser.style.display='block';
1109 if (sitewide) sitewide.style.display='none';
1110 } else if ('site-default'==rollup.value) {
1111 if (newuser) newuser.style.display='none';
1112 if (sitewide) sitewide.style.display='block';
1113 } else {
1114 if (newuser) newuser.style.display='none';
1115 if (sitewide) sitewide.style.display='none';
1116 }
1117 }
1118 }
1119 </script>
1120
1121 <?php if ($updated_link) : ?>
1122 <div class="updated"><p>Syndicated feed settings updated.</p></div>
1123 <?php endif; ?>
1124
1125 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
1126 <div class="wrap">
1127 <input type="hidden" name="link_id" value="<?php echo $link_id; ?>" />
1128 <input type="hidden" name="action" value="linkedit" />
1129 <input type="hidden" name="save" value="link" />
1130
1131 <h2>Edit a syndicated feed:</h2>
1132 <fieldset class="options"><legend>Basics</legend>
1133 <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1134 <tr>
1135 <th scope="row" width="20%"><?php _e('Feed URI:') ?></th>
1136 <td width="60%"><a href="<?php echo wp_specialchars($link_rss_uri, 'both'); ?>"><?php echo $link_rss_uri; ?></a>
1137 (<a href="<?php echo FEEDVALIDATOR_URI; ?>?url=<?php echo urlencode($link_rss_uri); ?>"
1138 title="Check feed &lt;<?php echo wp_specialchars($link_rss_uri, 'both'); ?>&gt; for validity">validate</a>)
1139 </td>
1140 <td width="20%"><input type="submit" name="feedfinder" value="switch &rarr;" style="font-size:smaller" /></td>
1141 </tr>
1142 <tr>
1143 <th scope="row" width="20%"><?php _e('Link Name:') ?></th>
1144 <td width="60%"><input type="text" id="basics-name-edit" name="name"
1145 value="<?php echo $link_name; ?>" style="width: 95%" />
1146 <span id="basics-name-view"><strong><?php echo $link_name; ?></strong></span>
1147 </td>
1148 <td>
1149 <select id="basics-hardcode-name" onchange="flip_hardcode('name')" name="hardcode_name">
1150 <option value="no" <?php echo $link->hardcode('name')?'':'selected="selected"'; ?>>update automatically</option>
1151 <option value="yes" <?php echo $link->hardcode('name')?'selected="selected"':''; ?>>edit manually</option>
1152 </select>
1153 </td>
1154 </tr>
1155 <tr>
1156 <th scope="row" width="20%"><?php _e('Short description:') ?></th>
1157 <td width="60%">
1158 <input id="basics-description-edit" type="text" name="description" value="<?php echo $link_description; ?>" style="width: 95%" />
1159 <span id="basics-description-view"><strong><?php echo $link_description; ?></strong></span>
1160 </td>
1161 <td>
1162 <select id="basics-hardcode-description" onchange="flip_hardcode('description')"
1163 name="hardcode_description">
1164 <option value="no" <?php echo $link->hardcode('description')?'':'selected="selected"'; ?>>update automatically</option>
1165 <option value="yes" <?php echo $link->hardcode('description')?'selected="selected"':''; ?>>edit manually</option>
1166 </select></td>
1167 </tr>
1168 <tr>
1169 <th width="20%" scope="row"><?php _e('Homepage:') ?></th>
1170 <td width="60%">
1171 <input id="basics-url-edit" type="text" name="linkurl" value="<?php echo $link_url; ?>" style="width: 95%;" />
1172 <a id="basics-url-view" href="<?php echo $link_url; ?>"><?php echo $link_url; ?></a></td>
1173 <td>
1174 <select id="basics-hardcode-url" onchange="flip_hardcode('url')" name="hardcode_url">
1175 <option value="no"<?php echo $link->hardcode('url')?'':' selected="selected"'; ?>>update automatically</option>
1176 <option value="yes"<?php echo $link->hardcode('url')?' selected="selected"':''; ?>>edit manually</option>
1177 </select></td></tr>
1178
1179 <tr>
1180 <th width="20%"><?php _e('Last update') ?>:</th>
1181 <td colspan="2"><?php
1182 if (isset($meta['update/last'])) :
1183 echo strftime('%x %X', $meta['update/last'])." ";
1184 else :
1185 echo " none yet";
1186 endif;
1187 ?></td></tr>
1188 <tr><th width="20%">Next update:</th>
1189 <td colspan="2"><?php
1190 $holdem = (isset($meta['update/hold']) ? $meta['update/hold'] : 'scheduled');
1191 ?>
1192 <select name="update_schedule">
1193 <option value="scheduled"<?php echo ($holdem=='scheduled')?' selected="selected"':''; ?>>update on schedule <?php
1194 echo " (";
1195 if (isset($meta['update/ttl']) and is_numeric($meta['update/ttl'])) :
1196 if (isset($meta['update/timed']) and $meta['update/timed']=='automatically') :
1197 echo 'next: ';
1198 $next = $meta['update/last'] + ((int) $meta['update/ttl'] * 60);
1199 if (strftime('%x', time()) != strftime('%x', $next)) :
1200 echo strftime('%x', $next)." ";
1201 endif;
1202 echo strftime('%X', $meta['update/last']+((int) $meta['update/ttl']*60));
1203 else :
1204 echo "every ".$meta['update/ttl']." minute".(($meta['update/ttl']!=1)?"s":"");
1205 endif;
1206 else:
1207 echo "next scheduled update";
1208 endif;
1209 echo ")";
1210 ?></option>
1211 <option value="next"<?php echo ($holdem=='next')?' selected="selected"':''; ?>>update ASAP</option>
1212 <option value="ping"<?php echo ($holdem=='ping')?' selected="selected"':''; ?>>update only when pinged</option>
1213 </select></tr>
1214 </table>
1215 </fieldset>
1216
1217 <script type="text/javascript">
1218 flip_hardcode('name');
1219 flip_hardcode('description');
1220 flip_hardcode('url');
1221 </script>
1222
1223 <p class="submit">
1224 <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1225 </p>
1226
1227 <fieldset class="options">
1228 <legend>Syndicated Posts</legend>
1229
1230 <?php fwp_category_box($dogs, 'all syndicated posts from this feed'); ?>
1231
1232 <table class="editform" width="75%" cellspacing="2" cellpadding="5">
1233 <tr><th width="27%" scope="row" style="vertical-align:top">Publication:</th>
1234 <td width="73%" style="vertical-align:top"><ul style="margin:0; list-style:none">
1235 <li><label><input type="radio" name="feed_post_status" value="site-default"
1236 <?php echo $status['post']['site-default']; ?> /> Use site-wide setting from <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication Options</a>
1237 (currently: <strong><?php echo ($post_status_global ? $post_status_global : 'publish'); ?></strong>)</label></li>
1238 <li><label><input type="radio" name="feed_post_status" value="publish"
1239 <?php echo $status['post']['publish']; ?> /> Publish posts from this feed immediately</label></li>
1240 <li><label><input type="radio" name="feed_post_status" value="private"
1241 <?php echo $status['post']['private']; ?> /> Hold posts from this feed as private posts</label></li>
1242 <li><label><input type="radio" name="feed_post_status" value="draft"
1243 <?php echo $status['post']['draft']; ?> /> Hold posts from this feed as drafts</label></li>
1244 </ul></td>
1245 </tr>
1246
1247 <tr><th width="27%" scope="row" style="vertical-align:top">Comments:</th>
1248 <td width="73%"><ul style="margin:0; list-style:none">
1249 <li><label><input type="radio" name="feed_comment_status" value="site-default"
1250 <?php echo $status['comment']['site-default']; ?> /> Use site-wide setting from <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication Options</a>
1251 (currently: <strong><?php echo ($comment_status_global ? $comment_status_global : 'closed'); ?>)</strong></label></li>
1252 <li><label><input type="radio" name="feed_comment_status" value="open"
1253 <?php echo $status['comment']['open']; ?> /> Allow comments on syndicated posts from this feed</label></li>
1254 <li><label><input type="radio" name="feed_comment_status" value="closed"
1255 <?php echo $status['comment']['closed']; ?> /> Don't allow comments on syndicated posts from this feed</label></li>
1256 </ul></td>
1257 </tr>
1258
1259 <tr><th width="27%" scope="row" style="vertical-align:top">Trackback and Pingback:</th>
1260 <td width="73%"><ul style="margin:0; list-style:none">
1261 <li><label><input type="radio" name="feed_ping_status" value="site-default"
1262 <?php echo $status['ping']['site-default']; ?> /> Use site-wide setting from <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication Options</a>
1263 (currently: <strong><?php echo ($ping_status_global ? $ping_status_global : 'closed'); ?>)</strong></label></li>
1264 <li><label><input type="radio" name="feed_ping_status" value="open"
1265 <?php echo $status['ping']['open']; ?> /> Accept pings on syndicated posts from this feed</label></li>
1266 <li><label><input type="radio" name="feed_ping_status" value="closed"
1267 <?php echo $status['ping']['closed']; ?> /> Don't accept pings on syndicated posts from this feed</label></li>
1268 </ul></td>
1269 </tr>
1270 </table>
1271 </fieldset>
1272
1273 <p class="submit">
1274 <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1275 </p>
1276
1277 <fieldset class="options">
1278 <legend>Syndicated Authors</legend>
1279 <?php $authorlist = fwp_author_list(); ?>
1280
1281 <table>
1282 <tr><th colspan="3" style="text-align: left; padding-top: 1.0em; border-bottom: 1px dotted black;">For posts by specific authors. Blank out a name to delete the rule.</th></tr>
1283
1284 <?php if (isset($meta['map authors'])) : $i=0; foreach ($meta['map authors'] as $author_rules) : foreach ($author_rules as $author_name => $author_action) : $i++; ?>
1285 <tr>
1286 <th style="text-align: left">Posts by <input type="text" name="author_rules_name[]" value="<?php echo htmlspecialchars($author_name); ?>" /></th>
1287 <td>
1288 <select id="author-rules-<?php echo $i; ?>" name="author_rules_action[]" onchange="flip_newuser('author-rules-<?php echo $i; ?>');">
1289 <?php foreach ($authorlist as $local_author_id => $local_author_name) : ?>
1290 <option value="<?php echo $local_author_id; ?>"<?php if ($local_author_id==$author_action) : echo ' selected="selected"'; endif; ?>>are assigned to <?php echo $local_author_name; ?></option>
1291 <?php endforeach; ?>
1292 <option value="newuser">will be assigned to a new user...</option>
1293 <option value="filter"<?php if ('filter'==$author_action) : echo ' selected="selected"'; endif; ?>>get filtered out</option>
1294 </select>
1295 </td>
1296 <td><div id="author-rules-<?php echo $i; ?>-newuser">named <input type="text" name="author_rules_newuser[]" value="" /></div></td>
1297 </tr>
1298 <?php endforeach; endforeach; endif; ?>
1299
1300 <tr><th colspan="3" style="text-align: left; padding-top: 1.0em; border-bottom: 1px dotted black;">Fill in to set up a new rule:</th></tr>
1301
1302 <tr>
1303 <th style="text-align: left">Posts by <input type="text" name="add_author_rule_name" /></th>
1304 <td>
1305 <select id="add-author-rule" name="add_author_rule_action" onchange="flip_newuser('add-author-rule');">
1306 <?php foreach ($authorlist as $author_id => $author_name) : ?>
1307 <option value="<?php echo $author_id; ?>">are assigned to <?php echo $author_name; ?></option>
1308 <?php endforeach; ?>
1309 <option value="newuser">will be assigned to a new user...</option>
1310 <option value="filter">get filtered out</option>
1311 </select>
1312 </td>
1313 <td><div id="add-author-rule-newuser">named <input type="text" name="add_author_rule_newuser" value="" /></div></td>
1314 </tr>
1315
1316 <tr><th colspan="3" style="text-align: left; padding-top: 1.0em; border-bottom: 1px dotted black;">For posts by authors that haven't been syndicated before:</th></tr>
1317 <tr>
1318 <th style="text-align: left">Posts by new authors</th>
1319 <td>
1320 <select id="unfamiliar-author" name="unfamiliar_author" onchange="flip_newuser('unfamiliar-author');">
1321 <option value="site-default"<?php if (!isset($meta['unfamiliar author'])) : ?>selected="selected"<?php endif; ?>>are handled using site-wide settings</option>
1322 <option value="create"<?php if ('create'==$meta['unfamiliar author']) : ?>selected="selected"<?php endif; ?>>create a new author account</option>
1323 <?php foreach ($authorlist as $author_id => $author_name) : ?>
1324 <option value="<?php echo $author_id; ?>"<?php if ($author_id==$meta['unfamiliar author']) : ?>selected="selected"<?php endif; ?>>are assigned to <?php echo $author_name; ?></option>
1325 <?php endforeach; ?>
1326 <option value="newuser">will be assigned to a new user...</option>
1327 <option value="filter"<?php if ('filter'==$meta['unfamiliar author']) : ?>selected="selected"<?php endif; ?>>get filtered out</option>
1328 </select>
1329 </td>
1330 <td>
1331 <div id="unfamiliar-author-default">Site-wide settings can be set in <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication Options</a></div>
1332 <div id="unfamiliar-author-newuser">named <input type="text" name="unfamiliar_author_newuser" value="" /></div>
1333 </td>
1334 </tr>
1335
1336 </table>
1337 </fieldset>
1338
1339 <script>
1340 flip_newuser('unfamiliar-author');
1341 <?php for ($j=1; $j<=$i; $j++) : ?>
1342 flip_newuser('author-rules-<?php echo $j; ?>');
1343 <?php endfor; ?>
1344 flip_newuser('add-author-rule');
1345 </script>
1346
1347 <p class="submit">
1348 <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1349 </p>
1350
1351 <fieldset>
1352 <legend>Advanced Feed Options</legend>
1353 <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1354
1355 <tr>
1356 <th width="20%" scope="row" style="vertical-align:top">Unfamiliar categories:</th>
1357 <td width="80%"><ul style="margin: 0; list-style:none">
1358 <li><label><input type="radio" name="unfamiliar_category" value="site-default"<?php echo $unfamiliar['category']['site-default']; ?> /> use site-wide setting from <a href="admin.php?page=feedwordpress/syndication-options.php">Syndication Options</a>
1359 (currently <strong><?php echo FeedWordPress::on_unfamiliar('category'); ?></strong>)</label></li>
1360 <li><label><input type="radio" name="unfamiliar_category" value="create"<?php echo $unfamiliar['category']['create']; ?> /> create any categories the post is in</label></li>
1361 <li><label><input type="radio" name="unfamiliar_category" value="default"<?php echo $unfamiliar['category']['default']; ?> /> don't create new categories</label></li>
1362 <li><label><input type="radio" name="unfamiliar_category" value="filter"<?php echo $unfamiliar['category']['filter']; ?> /> don't create new categories and don't syndicate posts unless they match at least one familiar category</label></li>
1363 </ul></td>
1364 </tr>
1365
1366 <tr>
1367 <th width="20%" scope="row" style="vertical-align:top">Multiple categories:</th>
1368 <td width="80%">
1369 <input type="text" size="20" id="cat_split" name="cat_split" value="<?php if (isset($meta['cat_split'])) : echo htmlspecialchars($meta['cat_split']); endif; ?>" /><br/>
1370 Enter a <a href="http://us.php.net/manual/en/reference.pcre.pattern.syntax.php">Perl-compatible regular expression</a> here if the feed provides multiple
1371 categories in a single category element. The regular expression should match
1372 the characters used to separate one category from the next. If the feed uses
1373 spaces (like <a href="http://del.icio.us/">del.icio.us</a>), use the pattern "\s".
1374 If the feed does not provide multiple categories in a single element, leave this
1375 blank.</td>
1376 </tr>
1377 </table>
1378 </fieldset>
1379
1380 <p class="submit">
1381 <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1382 </p>
1383
1384 <fieldset id="postcustom">
1385 <legend>Custom Settings (for use in templates)</legend>
1386 <div id="postcustomstuff">
1387 <table id="meta-list" cellpadding="3">
1388 <tr>
1389 <th>Key</th>
1390 <th>Value</th>
1391 <th>Action</th>
1392 </tr>
1393
1394 <?php
1395 $i = 0;
1396 foreach ($meta as $key => $value) :
1397 if (!preg_match("\007^((".implode(')|(', $special_settings)."))$\007i", $key)) :
1398 ?>
1399 <tr style="vertical-align:top">
1400 <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo wp_specialchars($key, 'both'); ?>" />
1401 <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo wp_specialchars($key, 'both'); ?>" /></th>
1402 <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo wp_specialchars($value, 'both'); ?></textarea></td>
1403 <td width="10%"><select name="notes[<?php echo $i; ?>][action]">
1404 <option value="update">save changes</option>
1405 <option value="delete">delete this setting</option>
1406 </select></td>
1407 </tr>
1408 <?php
1409 $i++;
1410 endif;
1411 endforeach;
1412 ?>
1413 <tr>
1414 <th scope="row"><input type="text" size="10" name="notes[<?php echo $i; ?>][key1]" value="" /></th>
1415 <td><textarea name="notes[<?php echo $i; ?>][value]" rows="2" cols="40"></textarea></td>
1416 <td><em>add new setting...</em><input type="hidden" name="notes[<?php echo $i; ?>][action]" value="update" /></td>
1417 </tr>
1418 </table>
1419 </fieldset>
1420
1421 <p class="submit">
1422 <input type="submit" name="submit" value="<?php _e('Save Changes &raquo;') ?>" />
1423 </p>
1424
1425 </div>
1426 <?php
1427 endif;
1428 return false; // Don't continue
1429 }
1430
1431 function fwp_author_list () {
1432 global $wpdb;
1433 $ret = array();
1434 $users = $wpdb->get_results("SELECT * FROM $wpdb->users ORDER BY display_name");
1435 if (is_array($users)) :
1436 foreach ($users as $user) :
1437 $id = (int) $user->ID;
1438 $ret[$id] = $user->display_name;
1439 endforeach;
1440 endif;
1441 return $ret;
1442 }
1443
1444 function fwp_multidelete_page () {
1445 global $wpdb;
1446
1447 check_admin_referer(); // Make sure the referers are kosher
1448
1449 $link_ids = (isset($_REQUEST['link_ids']) ? $_REQUEST['link_ids'] : array());
1450 if (isset($_REQUEST['link_id'])) : array_push($link_ids, $_REQUEST['link_id']); endif;
1451
1452 if (!current_user_can('manage_links')):
1453 die (__("Cheatin' uh ?"));
1454 elseif (isset($GLOBALS['fwp_post']['confirm']) and $GLOBALS['fwp_post']['confirm']=='Delete'):
1455 foreach ($GLOBALS['fwp_post']['link_action'] as $link_id => $what) :
1456 $do_it[$what][] = $link_id;
1457 endforeach;
1458
1459 $alter = array();
1460 if (count($do_it['hide']) > 0) :
1461 $hidem = "(".implode(', ', $do_it['hide']).")";
1462 $alter[] = "
1463 UPDATE $wpdb->links
1464 SET link_visible = 'N'
1465 WHERE link_id IN {$hidem}
1466 ";
1467 endif;
1468
1469 if (count($do_it['nuke']) > 0) :
1470 $nukem = "(".implode(', ', $do_it['nuke']).")";
1471
1472 // Make a list of the items syndicated from this feed...
1473 $post_ids = $wpdb->get_col("
1474 SELECT post_id FROM $wpdb->postmeta
1475 WHERE meta_key = 'syndication_feed_id'
1476 AND meta_value IN {$nukem}
1477 ");
1478
1479 // ... and kill them all
1480 if (count($post_ids) > 0) :
1481 foreach ($post_ids as $post_id) :
1482 wp_delete_post($post_id);
1483 endforeach;
1484 endif;
1485
1486 $alter[] = "
1487 DELETE FROM $wpdb->links
1488 WHERE link_id IN {$nukem}
1489 ";
1490 endif;
1491
1492 if (count($do_it['delete']) > 0) :
1493 $deletem = "(".implode(', ', $do_it['delete']).")";
1494
1495 // Make the items syndicated from this feed appear to be locally-authored
1496 $alter[] = "
1497 DELETE FROM $wpdb->postmeta
1498 WHERE meta_key = 'syndication_feed_id'
1499 AND meta_value IN {$deletem}
1500 ";
1501
1502 // ... and delete the links themselves.
1503 $alter[] = "
1504 DELETE FROM $wpdb->links
1505 WHERE link_id IN {$deletem}
1506 ";
1507 endif;
1508
1509 $errs = array(); $success = array ();
1510 foreach ($alter as $sql) :
1511 $result = $wpdb->query($sql);
1512 if (!$result):
1513 $errs[] = mysql_error();
1514 endif;
1515 endforeach;
1516
1517 if (count($alter) > 0) :
1518 echo "<div class=\"updated\">\n";
1519 if (count($errs) > 0) :
1520 echo "There were some problems processing your ";
1521 echo "unsubscribe request. [SQL: ".implode('; ', $errs)."]";
1522 else :
1523 echo "Your unsubscribe request(s) have been processed.";
1524 endif;
1525 echo "</div>\n";
1526 endif;
1527
1528 return true; // Continue on to Syndicated Sites listing
1529 else :
1530 $targets = $wpdb->get_results("
1531 SELECT * FROM $wpdb->links
1532 WHERE link_id IN (".implode(",",$link_ids).")
1533 ");
1534 ?>
1535 <form action="admin.php?page=feedwordpress/<?php echo basename(__FILE__); ?>" method="post">
1536 <div class="wrap">
1537 <input type="hidden" name="action" value="Unsubscribe" />
1538 <input type="hidden" name="confirm" value="Delete" />
1539
1540 <h2>Unsubscribe from Syndicated Links:</h2>
1541 <?php foreach ($targets as $link) :
1542 $link_url = wp_specialchars($link->link_url, 1);
1543 $link_name = wp_specialchars($link->link_name, 1);
1544 $link_description = wp_specialchars($link->link_description, 'both');
1545 $link_rss = wp_specialchars($link->link_rss, 'both');
1546 ?>
1547 <fieldset>
1548 <legend><?php echo $link_name; ?></legend>
1549 <table class="editform" width="100%" cellspacing="2" cellpadding="5">
1550 <tr><th scope="row" width="20%"><?php _e('Feed URI:') ?></th>
1551 <td width="80%"><a href="<?php echo $link_rss; ?>"><?php echo $link_rss; ?></a></td></tr>
1552 <tr><th scope="row" width="20%"><?php _e('Short description:') ?></th>
1553 <td width="80%"><?php echo $link_description; ?></span></td></tr>
1554 <tr><th width="20%" scope="row"><?php _e('Homepage:') ?></th>
1555 <td width="80%"><a href="<?php echo $link_url; ?>"><?php echo $link_url; ?></a></td></tr>
1556 <tr style="vertical-align:top"><th width="20%" scope="row">Subscription <?php _e('Options') ?>:</th>
1557 <td width="80%"><ul style="margin:0; padding: 0; list-style: none">
1558 <li><input type="radio" id="hide-<?php echo $link->link_id; ?>"
1559 name="link_action[<?php echo $link->link_id; ?>]" value="hide" />
1560 <label for="hide-<?php echo $link->link_id; ?>">Turn off the subscription for this
1561 syndicated link<br/><span style="font-size:smaller">(Keep the feed information
1562 and all the posts from this feed in the database, but don't syndicate any
1563 new posts from the feed.)</span></label></li>
1564 <li><input type="radio" id="nuke-<?php echo $link->link_id; ?>"
1565 name="link_action[<?php echo $link->link_id; ?>]" value="nuke" />
1566 <label for="nuke-<?php echo $link->link_id; ?>">Delete this syndicated link and all the
1567 posts that were syndicated from it</label></li>
1568 <li><input type="radio" id="delete-<?php echo $link->link_id; ?>"
1569 name="link_action[<?php echo $link->link_id; ?>]" value="delete" />
1570 <label for="delete-<?php echo $link->link_id; ?>">Delete this syndicated link, but
1571 <em>keep</em> posts that were syndicated from it (as if they were authored
1572 locally).</label></li>
1573 <li><input type="radio" id="nothing-<?php echo $link->link_id; ?>"
1574 name="link_action[<?php echo $link->link_id; ?>]" value="nothing" />
1575 <label for="nothing-<?php echo $link->link_id; ?>">Keep this feed as it is. I changed
1576 my mind.</label></li>
1577 </ul>
1578 </table>
1579 </fieldset>
1580 <?php endforeach; ?>
1581
1582 <div class="submit">
1583 <input class="delete" type="submit" name="submit" value="<?php _e('Unsubscribe from selected feeds &raquo;') ?>" />
1584 </div>
1585 </div>
1586 <?php
1587 return false; // Don't continue on to Syndicated Sites listing
1588 endif;
1589 }
1590
1591 ################################################################################
1592 ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
1593 ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
1594 ################################################################################
1595
1596 $fwp_held_ping = NULL; // NULL: not holding pings yet
1597
1598 function fwp_hold_pings () {
1599 global $fwp_held_ping;
1600 if (is_null($fwp_held_ping)):
1601 $fwp_held_ping = 0; // 0: ready to hold pings; none yet received
1602 endif;
1603 }
1604
1605 function fwp_release_pings () {
1606 global $fwp_held_ping;
1607 if ($fwp_held_ping):
1608 if (function_exists('wp_schedule_single_event')) :
1609 wp_schedule_single_event(time(), 'do_pings');
1610 else :
1611 generic_ping($fwp_held_ping);
1612 endif;
1613 endif;
1614 $fwp_held_ping = NULL; // NULL: not holding pings anymore
1615 }
1616
1617 function fwp_do_pings () {
1618 if (!is_null($fwp_held_ping) and $post_id) : // Defer until we're done updating
1619 $fwp_held_ping = $post_id;
1620 elseif (function_exists('do_all_pings')) :
1621 do_all_pings();
1622 else :
1623 generic_ping($fwp_held_ping);
1624 endif;
1625 }
1626
1627 function fwp_publish_post_hook ($post_id) {
1628 global $fwp_held_ping;
1629
1630 if (!is_null($fwp_held_ping)) : // Syndicated post. Don't mark with _pingme
1631 if ( defined('XMLRPC_REQUEST') )
1632 do_action('xmlrpc_publish_post', $post_id);
1633 if ( defined('APP_REQUEST') )
1634 do_action('app_publish_post', $post_id);
1635
1636 if ( defined('WP_IMPORTING') )
1637 return;
1638
1639 // Defer sending out pings until we finish updating
1640 $fwp_held_ping = $post_id;
1641 else :
1642 if (function_exists('_publish_post_hook')) : // WordPress 2.3
1643 _publish_post_hook($post_id);
1644 endif;
1645 endif;
1646 }
1647
1648 ################################################################################
1649 ## class FeedWordPress #########################################################
1650 ################################################################################
1651
1652 // class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface
1653 class FeedWordPress {
1654 var $strip_attrs = array (
1655 array('[a-z]+', 'style'),
1656 array('[a-z]+', 'target'),
1657 );
1658 var $uri_attrs = array (
1659 array('a', 'href'),
1660 array('applet', 'codebase'),
1661 array('area', 'href'),
1662 array('blockquote', 'cite'),
1663 array('body', 'background'),
1664 array('del', 'cite'),
1665 array('form', 'action'),
1666 array('frame', 'longdesc'),
1667 array('frame', 'src'),
1668 array('iframe', 'longdesc'),
1669 array('iframe', 'src'),
1670 array('head', 'profile'),
1671 array('img', 'longdesc'),
1672 array('img', 'src'),
1673 array('img', 'usemap'),
1674 array('input', 'src'),
1675 array('input', 'usemap'),
1676 array('ins', 'cite'),
1677 array('link', 'href'),
1678 array('object', 'classid'),
1679 array('object', 'codebase'),
1680 array('object', 'data'),
1681 array('object', 'usemap'),
1682 array('q', 'cite'),
1683 array('script', 'src')
1684 );
1685
1686 var $feeds = NULL;
1687
1688 # function FeedWordPress (): Contructor; retrieve a list of feeds
1689 function FeedWordPress () {
1690 $this->feeds = array ();
1691 $links = FeedWordPress::syndicated_links();
1692 if ($links): foreach ($links as $link):
1693 $this->feeds[] =& new SyndicatedLink($link);
1694 endforeach; endif;
1695 } // FeedWordPress::FeedWordPress ()
1696
1697 # function update (): polls for updates on one or more Contributor feeds
1698 #
1699 # Arguments:
1700 # ----------
1701 # * $uri (string): either the URI of the feed to poll, the URI of the
1702 # (human-readable) website whose feed you want to poll, or NULL.
1703 #
1704 # If $uri is NULL, then FeedWordPress will poll any feeds that are
1705 # ready for polling. It will not poll feeds that are marked as
1706 # "Invisible" Links (signifying that the subscription has been
1707 # de-activated), or feeds that are not yet stale according to their
1708 # TTL setting (which is either set in the feed, or else set
1709 # randomly within a window of 30 minutes - 2 hours).
1710 #
1711 # Returns:
1712 # --------
1713 # * Normally returns an associative array, with 'new' => the number
1714 # of new posts added during the update, and 'updated' => the number
1715 # of old posts that were updated during the update. If both numbers
1716 # are zero, there was no change since the last poll on that URI.
1717 #
1718 # * Returns NULL if URI it was passed was not a URI that this
1719 # installation of FeedWordPress syndicates.
1720 #
1721 # Effects:
1722 # --------
1723 # * One or more feeds are polled for updates
1724 #
1725 # * If the feed Link does not have a hardcoded name set, its Link
1726 # Name is synchronized with the feed's title element
1727 #
1728 # * If the feed Link does not have a hardcoded URI set, its Link URI
1729 # is synchronized with the feed's human-readable link element
1730 #
1731 # * If the feed Link does not have a hardcoded description set, its
1732 # Link Description is synchronized with the feed's description,
1733 # tagline, or subtitle element.
1734 #
1735 # * The time of polling is recorded in the feed's settings, and the
1736 # TTL (time until the feed is next available for polling) is set
1737 # either from the feed (if it is supplied in the ttl or syndication
1738 # module elements) or else from a randomly-generated time window
1739 # (between 30 minutes and 2 hours).
1740 #
1741 # * New posts from the polled feed are added to the WordPress store.
1742 #
1743 # * Updates to existing posts since the last poll are mirrored in the
1744 # WordPress store.
1745 #
1746 function update ($uri = null) {
1747 global $wpdb;
1748
1749 if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off
1750 return NULL;
1751 endif;
1752
1753 if (!is_null($uri)) :
1754 $uri = trim($uri);
1755 else : // Update all
1756 update_option('feedwordpress_last_update_all', time());
1757 endif;
1758
1759 do_action('feedwordpress_update', $uri);
1760
1761 // Loop through and check for new posts
1762 $delta = NULL;
1763 foreach ($this->feeds as $feed) :
1764 $pinged_that = (is_null($uri) or in_array($uri, array($feed->uri(), $feed->homepage())));
1765
1766 if (!is_null($uri)) : // A site-specific ping always updates
1767 $timely = true;
1768 else :
1769 $timely = $feed->stale();
1770 endif;
1771
1772 if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating...
1773 $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition
1774 endif;
1775
1776 if ($pinged_that and $timely) :
1777 do_action('feedwordpress_check_feed', $feed->settings);
1778 $added = $feed->poll();
1779 if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1780 if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1781 endif;
1782 endforeach;
1783
1784 do_action('feedwordpress_update_complete', $delta);
1785
1786 return $delta;
1787 }
1788
1789 function stale () {
1790 if (get_option('feedwordpress_automatic_updates')) :
1791 $last = get_option('feedwordpress_last_update_all');
1792
1793 // If we haven't updated all yet, give it a time window
1794 if (false === $last) :
1795 $ret = false;
1796 update_option('feedwordpress_last_update_all', time());
1797
1798 // Otherwise, check against freshness interval
1799 elseif (is_numeric($last)) : // Expect a timestamp
1800 $freshness = get_option('feedwordpress_freshness');
1801 if (false === $freshness) : // Use default
1802 $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
1803 endif;
1804 $ret = ( (time() - $last) > $freshness);
1805
1806 // This should never happen.
1807 else :
1808 FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__);
1809 endif;
1810
1811 // Explicit request for an update (e.g. from a cron job).
1812 elseif (FeedWordPress::update_requested()) :
1813 $ret = true;
1814
1815 else :
1816 $ret = false;
1817 endif;
1818 return $ret;
1819 } // FeedWordPress::stale()
1820
1821 function update_requested () {
1822 return (isset($_REQUEST['update_feedwordpress']) and $_REQUEST['update_feedwordpress']);
1823 } // FeedWordPress::update_requested()
1824
1825 function syndicate_link ($name, $uri, $rss) {
1826 global $wpdb;
1827
1828 // Get the category ID#
1829 $cat_id = FeedWordPress::link_category_id();
1830
1831 // WordPress gets cranky if there's no homepage URI
1832 if (!isset($uri) or strlen($uri)<1) : $uri = $rss; endif;
1833
1834 if (function_exists('wp_insert_link')) { // WordPress 2.x
1835 $link_id = wp_insert_link(array(
1836 "link_name" => $name,
1837 "link_url" => $uri,
1838 "link_category" => array($cat_id),
1839 "link_rss" => $rss
1840 ));
1841 } else { // WordPress 1.5.x
1842 $result = $wpdb->query("
1843 INSERT INTO $wpdb->links
1844 SET
1845 link_name = '".$wpdb->escape($name)."',
1846 link_url = '".$wpdb->escape($uri)."',
1847 link_category = '".$wpdb->escape($cat_id)."',
1848 link_rss = '".$wpdb->escape($rss)."'
1849 ");
1850 $link_id = $wpdb->insert_id;
1851 } // if
1852 return $link_id;
1853 } // function FeedWordPress::syndicate_link()
1854
1855 function on_unfamiliar ($what = 'author', $override = NULL) {
1856 $set = array('create', 'default', 'filter');
1857
1858 if (is_string($override)) :
1859 $ret = strtolower($override);
1860 endif;
1861
1862 if (!is_numeric($override) and !in_array($ret, $set)) :
1863 $ret = get_option('feedwordpress_unfamiliar_'.$what);
1864 if (!is_numeric($ret) and !in_array($ret, $set)) :
1865 $ret = 'create';
1866 endif;
1867 endif;
1868
1869 return $ret;
1870 } // function FeedWordPress::on_unfamiliar()
1871
1872 function syndicated_links () {
1873 $contributors = FeedWordPress::link_category_id();
1874 if (function_exists('get_bookmarks')) :
1875 $links = get_bookmarks(array("category" => $contributors));
1876 else:
1877 $links = get_linkobjects($contributors); // deprecated as of WP 2.1
1878 endif;
1879 return $links;
1880 } // function FeedWordPress::syndicated_links()
1881
1882 function link_category_id () {
1883 global $wpdb, $wp_db_version;
1884
1885 $cat_id = get_option('feedwordpress_cat_id');
1886
1887 // If we don't yet *have* the category, we'll have to create it
1888 if ($cat_id === false) :
1889 $cat = $wpdb->escape(DEFAULT_SYNDICATION_CATEGORY);
1890
1891 // Look for something with the right name...
1892 // -----------------------------------------
1893
1894 // WordPress 2.3 introduces a new taxonomy/term API
1895 if (function_exists('is_term')) :
1896 $cat_id = is_term($cat, 'link_category');
1897 // WordPress 2.1 and 2.2 use a common table for both link and post categories
1898 elseif (isset($wp_db_version) and ($wp_db_version >= FWP_SCHEMA_21 and $wp_db_version < FWP_SCHEMA_23) ) :
1899 $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->categories} WHERE cat_name='$cat'");
1900 // WordPress 1.5 and 2.0.x have a separate table for link categories
1901 elseif (!isset($wp_db_version) or $wp_db_version < FWP_SCHEMA_21) :
1902 $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->linkcategories} WHERE cat_name='$cat'");
1903 // This should never happen.
1904 else :
1905 FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
1906 endif;
1907
1908 // If you still can't find anything, make it for yourself.
1909 // -------------------------------------------------------
1910 if (!$cat_id) :
1911 // WordPress 2.3+ term/taxonomy API
1912 if (function_exists('wp_insert_term')) :
1913 $term = wp_insert_term($cat, 'link_category');
1914 $cat_id = $term['term_id'];
1915 // WordPress 2.1, 2.2 category API. By the way, why the fuck is this API function only available in a wp-admin module?
1916 elseif (function_exists('wp_insert_category')) :
1917 $cat_id = wp_insert_category(array('cat_name' => $cat));
1918 // WordPress 1.5 and 2.0.x
1919 elseif (!isset($wp_db_version) or $wp_db_version < FWP_SCHEMA_21) :
1920 $result = $wpdb->query("
1921 INSERT INTO $wpdb->linkcategories
1922 SET
1923 cat_id = 0,
1924 cat_name='$cat',
1925 show_images='N',
1926 show_description='N',
1927 show_rating='N',
1928 show_updated='N',
1929 sort_order='name'
1930 ");
1931 $cat_id = $wpdb->insert_id;
1932 // This should never happen.
1933 else :
1934 FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__);
1935 endif;
1936 endif;
1937
1938 update_option('feedwordpress_cat_id', $cat_id);
1939 endif;
1940 return $cat_id;
1941 } // function FeedWordPress::link_category_id()
1942
1943 # Upgrades and maintenance...
1944 function needs_upgrade () {
1945 global $wpdb;
1946 $fwp_db_version = get_option('feedwordpress_version');
1947 $ret = false; // innocent until proven guilty
1948 if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) :
1949 // This is an older version or a fresh install. Does it
1950 // require a database upgrade or database initialization?
1951 if ($fwp_db_version > 0.96) :
1952 // No. Just brand it with the new version.
1953 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1954 else :
1955 // Yes. Check to see whether this is a fresh install or an upgrade.
1956 $syn = $wpdb->get_col("
1957 SELECT post_id
1958 FROM $wpdb->postmeta
1959 WHERE meta_key = 'syndication_feed'
1960 ");
1961 if (count($syn) > 0) : // contains at least one syndicated post
1962 $ret = true;
1963 else : // fresh install; brand it as ours
1964 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1965 endif;
1966 endif;
1967 endif;
1968 return $ret;
1969 }
1970
1971 function upgrade_database ($from = NULL) {
1972 global $wpdb;
1973
1974 if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1975
1976 switch ($from) :
1977 case 0.96: // account for changes to syndication custom values and guid
1978 echo "<p>Upgrading database from {$from} to ".FEEDWORDPRESS_VERSION."...</p>\n";
1979
1980 $cat_id = FeedWordPress::link_category_id();
1981
1982 // Avoid duplicates
1983 $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'");
1984
1985 // Look up all the link IDs
1986 $wpdb->query("
1987 CREATE TEMPORARY TABLE tmp_custom_values
1988 SELECT
1989 NULL AS meta_id,
1990 post_id,
1991 'syndication_feed_id' AS meta_key,
1992 link_id AS meta_value
1993 FROM `{$wpdb->postmeta}`, `{$wpdb->links}`
1994 WHERE
1995 meta_key='syndication_feed'
1996 AND meta_value=link_rss
1997 AND link_category = {$cat_id}
1998 ");
1999
2000 // Now attach them to their posts
2001 $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values");
2002
2003 // And clean up after ourselves.
2004 $wpdb->query("DROP TABLE tmp_custom_values");
2005
2006 // Now fix the guids to avoid duplicate posts
2007 echo "<ul>";
2008 foreach ($this->feeds as $feed) :
2009 echo "<li>Fixing post meta-data for <cite>".$feed['link/name']."</cite> &#8230; "; flush();
2010 $rss = @fetch_rss($feed['link/uri']);
2011 if (is_array($rss->items)) :
2012 foreach ($rss->items as $item) :
2013 $guid = $wpdb->escape(FeedWordPress::guid($item, $feed)); // new GUID algorithm
2014 $link = $wpdb->escape($item['link']);
2015
2016 $wpdb->query("
2017 UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}'
2018 ");
2019 endforeach;
2020 endif;
2021 echo "<strong>complete.</strong></li>\n";
2022 endforeach;
2023 echo "</ul>\n";
2024
2025 // Mark the upgrade as successful.
2026 update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
2027 endswitch;
2028 echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
2029 } /* FeedWordPress::upgrade_database() */
2030
2031 # Utility functions for handling text settings
2032 function negative ($f, $setting) {
2033 $nego = array ('n', 'no', 'f', 'false');
2034 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego));
2035 }
2036
2037 function affirmative ($f, $setting) {
2038 $affirmo = array ('y', 'yes', 't', 'true', 1);
2039 return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo));
2040 }
2041
2042
2043 # Internal debugging functions
2044 function critical_bug ($varname, $var, $line) {
2045 global $wp_version;
2046
2047 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>';
2048 echo "\n<plaintext>";
2049 echo "Triggered at line # ".$line."\n";
2050 echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
2051 echo "WordPress version: $wp_version\n";
2052 echo "PHP version: ".phpversion()."\n";
2053 echo "\n";
2054 echo $varname.": "; var_dump($var); echo "\n";
2055 die;
2056 }
2057 } // class FeedWordPress
2058
2059 class SyndicatedPost {
2060 var $item = null;
2061
2062 var $link = null;
2063 var $feed = null;
2064 var $feedmeta = null;
2065
2066 var $post = array ();
2067 var $_base = null;
2068
2069 var $_freshness = null;
2070 var $_wp_id = null;
2071
2072 var $strip_attrs = array (
2073 array('[a-z]+', 'style'),
2074 array('[a-z]+', 'target'),
2075 );
2076
2077 function SyndicatedPost ($item, $link) {
2078 global $wpdb;
2079
2080 $this->link = $link;
2081 $feedmeta = $link->settings;
2082 $feed = $link->magpie;
2083
2084 # This is ugly as all hell. I'd like to use apply_filters()'s
2085 # alleged support for a variable argument count, but this seems
2086 # to have been broken in WordPress 1.5. It'll be fixed somehow
2087 # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
2088 # the board here.
2089 #
2090 # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
2091 global $fwp_channel, $fwp_feedmeta;
2092 $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
2093
2094 $this->feed = $feed;
2095 $this->feedmeta = $feedmeta;
2096
2097 $this->item = $item;
2098 $this->item = apply_filters('syndicated_item', $this->item, $this);
2099
2100 # Filters can halt further processing by returning NULL
2101 if (is_null($this->item)) :
2102 $this->post = NULL;
2103 else :
2104 # Note that nothing is run through $wpdb->escape() here.
2105 # That's deliberate. The escaping is done at the point
2106 # of insertion, not here, to avoid double-escaping and
2107 # to avoid screwing with syndicated_post filters
2108
2109 $this->post['post_title'] = $this->item['title'];
2110
2111 // This just gives usan alphanumeric representation of
2112 // the author. We will look up (or create) the numeric
2113 // ID for the author in SyndicatedPost::add()
2114 $this->post['named']['author'] = $this->author();
2115
2116 # Identify content and sanitize it.
2117 # ---------------------------------
2118 if (isset($this->item['xhtml']['body'])) :
2119 $content = $this->item['xhtml']['body'];
2120 elseif (isset($this->item['xhtml']['div'])) :
2121 $content = $this->item['xhtml']['div'];
2122 elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
2123 $content = $this->item['content']['encoded'];
2124 else:
2125 $content = $this->item['description'];
2126 endif;
2127
2128 # FeedWordPress used to resolve URIs relative to the
2129 # feed URI. It now relies on the xml:base support
2130 # baked in to the MagpieRSS upgrade. So all we do here
2131 # now is to sanitize problematic attributes.
2132 #
2133 # This kind of sucks. I intend to replace it with
2134 # lib_filter sometime soon.
2135 foreach ($this->strip_attrs as $pair):
2136 list($tag,$attr) = $pair;
2137 $content = preg_replace (
2138 ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i",
2139 "\\1\\4",
2140 $content
2141 );
2142 endforeach;
2143
2144 # Identify and sanitize excerpt
2145 $excerpt = NULL;
2146 if ( isset($this->item['description']) and $this->item['description'] ) :
2147 $excerpt = $this->item['description'];
2148 elseif ( isset($content) and $content ) :
2149 $excerpt = strip_tags($content);
2150 if (strlen($excerpt) > 255) :
2151 $excerpt = substr($excerpt,0,252).'...';
2152 endif;
2153 endif;
2154
2155 $this->post['post_content'] = $content;
2156
2157 if (!is_null($excerpt)):
2158 $this->post['post_excerpt'] = $excerpt;
2159 endif;
2160
2161 // This is unnecessary if we use wp_insert_post
2162 if (!$this->use_api('wp_insert_post')) :
2163 $this->post['post_name'] = sanitize_title($this->post['post_title']);
2164 endif;
2165
2166 $this->post['epoch']['issued'] = $this->published();
2167 $this->post['epoch']['created'] = $this->created();
2168 $this->post['epoch']['modified'] = $this->updated();
2169
2170 // Dealing with timestamps in WordPress is so fucking fucked.
2171 $offset = (int) get_option('gmt_offset') * 60 * 60;
2172 $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
2173 $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
2174 $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
2175 $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
2176
2177 // Use feed-level preferences or the global default.
2178 $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
2179 $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
2180 $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
2181
2182 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
2183 $this->post['guid'] = $this->guid();
2184
2185 // RSS 2.0 / Atom 1.0 enclosure support
2186 if ( isset($this->item['enclosure#']) ) :
2187 for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
2188 $eid = (($i > 1) ? "#{$id}" : "");
2189 $this->post['meta']['enclosure'][] =
2190 $this->item["enclosure{$eid}@url"]."\n".
2191 $this->item["enclosure{$eid}@length"]."\n".
2192 $this->item["enclosure{$eid}@type"];
2193 endfor;
2194 endif;
2195
2196 // In case you want to point back to the blog this was syndicated from
2197 if (isset($this->feed->channel['title'])) :
2198 $this->post['meta']['syndication_source'] = $this->feed->channel['title'];
2199 endif;
2200 if (isset($this->feed->channel['link'])) :
2201 $this->post['meta']['syndication_source_uri'] = $this->feed->channel['link'];
2202 endif;
2203
2204 // Store information on human-readable and machine-readable comment URIs
2205 if (isset($this->item['comments'])) :
2206 $this->post['meta']['rss:comments'] = $this->item['comments'];
2207 endif;
2208 if (isset($this->item['wfw']['commentrss'])) :
2209 $this->post['meta']['wfw:commentRSS'] = $this->item['wfw']['commentrss'];
2210 endif;
2211
2212 // Store information to identify the feed that this came from
2213 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
2214 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
2215
2216 // In case you want to know the external permalink...
2217 $this->post['meta']['syndication_permalink'] = $this->item['link'];
2218
2219 // Feed-by-feed options for author and category creation
2220 $this->post['named']['unfamiliar']['author'] = $this->feedmeta['unfamiliar author'];
2221 $this->post['named']['unfamiliar']['category'] = $this->feedmeta['unfamiliar categories'];
2222
2223 // Categories: start with default categories, if any
2224 $fc = get_option("feedwordpress_syndication_cats");
2225 if ($fc) :
2226 $this->post['named']['preset/category'] = explode("\n", $fc);
2227 else :
2228 $this->post['named']['preset/category'] = array();
2229 endif;
2230
2231 if (is_array($this->feedmeta['cats'])) :
2232 $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
2233 endif;
2234
2235 // Now add categories from the post, if we have 'em
2236 $this->post['named']['category'] = array();
2237 if ( isset($this->item['category#']) ) :
2238 for ($i = 1; $i <= $this->item['category#']; $i++) :
2239 $cat_idx = (($i > 1) ? "#{$i}" : "");
2240 $cat = $this->item["category{$cat_idx}"];
2241
2242 if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
2243 $pcre = "\007".$this->feedmeta['cat_split']."\007";
2244 $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
2245 else :
2246 $this->post['named']['category'][] = $cat;
2247 endif;
2248 endfor;
2249 endif;
2250 endif;
2251 } // SyndicatedPost::SyndicatedPost()
2252
2253 function filtered () {
2254 return is_null($this->post);
2255 }
2256
2257 function freshness () {
2258 global $wpdb;
2259
2260 if ($this->filtered()) : // This should never happen.
2261 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
2262 endif;
2263
2264 if (is_null($this->_freshness)) :
2265 $guid = $wpdb->escape($this->guid());
2266
2267 $result = $wpdb->get_row("
2268 SELECT id, guid, post_modified_gmt
2269 FROM $wpdb->posts WHERE guid='$guid'
2270 ");
2271
2272 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
2273 $updated = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
2274 if (!$result) :
2275 $this->_freshness = 2; // New content
2276 elseif ($this->updated() > $updated) :
2277 $this->_freshness = 1; // Updated content
2278 $this->_wp_id = $result->id;
2279 else :
2280 $this->_freshness = 0; // Same old, same old
2281 $this->_wp_id = $result->id;
2282 endif;
2283 endif;
2284 return $this->_freshness;
2285 }
2286
2287 function wp_id () {
2288 if ($this->filtered()) : // This should never happen.
2289 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
2290 endif;
2291
2292 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
2293 $fresh = $this->freshness(); // sets WP DB id in the process
2294 endif;
2295 return $this->_wp_id;
2296 }
2297
2298 function store () {
2299 global $wpdb;
2300
2301 if ($this->filtered()) : // This should never happen.
2302 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
2303 endif;
2304
2305 $freshness = $this->freshness();
2306 if ($freshness > 0) :
2307 # -- Look up, or create, numeric ID for author
2308 $this->post['post_author'] = $this->author_id (
2309 FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
2310 );
2311
2312 if (is_null($this->post['post_author'])) :
2313 $this->post = NULL;
2314 endif;
2315 endif;
2316
2317 if (!$this->filtered() and $freshness > 0) :
2318 # -- Look up, or create, numeric ID for categories
2319 $this->post['post_category'] = $this->category_ids (
2320 $this->post['named']['category'],
2321 FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category'])
2322 );
2323
2324 if (is_null($this->post['post_category'])) :
2325 // filter mode on, no matching categories; drop the post
2326 $this->post = NULL;
2327 else :
2328 // filter mode off or at least one match; now add on the feed and global presets
2329 $this->post['post_category'] = array_merge (
2330 $this->post['post_category'],
2331 $this->category_ids (
2332 $this->post['named']['preset/category'],
2333 'default'
2334 )
2335 );
2336
2337 if (count($this->post['post_category']) < 1) :
2338 $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
2339 endif;
2340 endif;
2341 endif;
2342
2343 if (!$this->filtered() and $freshness > 0) :
2344 unset($this->post['named']);
2345 $this->post = apply_filters('syndicated_post', $this->post, $this);
2346 endif;
2347
2348 if (!$this->filtered() and $freshness == 2) :
2349 // The item has not yet been added. So let's add it.
2350 $this->insert_new();
2351 $this->add_rss_meta();
2352 do_action('post_syndicated_item', $this->wp_id());
2353
2354 $ret = 'new';
2355 elseif (!$this->filtered() and $freshness == 1) :
2356 $this->post['ID'] = $this->wp_id();
2357 $this->update_existing();
2358 $this->add_rss_meta();
2359 do_action('update_syndicated_item', $this->wp_id());
2360
2361 $ret = 'updated';
2362 else :
2363 $ret = false;
2364 endif;
2365
2366 return $ret;
2367 } // function SyndicatedPost::store ()
2368
2369 function insert_new () {
2370 global $wpdb, $wp_db_version;
2371
2372 // Why the fuck doesn't wp_insert_post already do this?
2373 foreach ($this->post as $key => $value) :
2374 if (is_string($value)) :
2375 $dbpost[$key] = $wpdb->escape($value);
2376 else :
2377 $dbpost[$key] = $value;
2378 endif;
2379 endforeach;
2380
2381 if ($this->use_api('wp_insert_post')) :
2382 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
2383 $this->_wp_id = wp_insert_post($dbpost);
2384
2385 // This should never happen.
2386 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
2387 FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', $this, __LINE__);
2388 endif;
2389
2390 // Unfortunately, as of WordPress 2.3, wp_insert_post()
2391 // *still* offers no way to use a guid of your choice,
2392 // and munges your post modified timestamp, too.
2393 $result = $wpdb->query("
2394 UPDATE $wpdb->posts
2395 SET
2396 guid='{$dbpost['guid']}',
2397 post_modified='{$dbpost['post_modified']}',
2398 post_modified_gmt='{$dbpost['post_modified_gmt']}'
2399 WHERE ID='{$this->_wp_id}'
2400 ");
2401 else :
2402 # The right way to do this is the above. But, alas,
2403 # in earlier versions of WordPress, wp_insert_post has
2404 # too much behavior (mainly related to pings) that can't
2405 # be overridden. In WordPress 1.5, it's enough of a
2406 # resource hog to make PHP segfault after inserting
2407 # 50-100 posts. This can get pretty annoying, especially
2408 # if you are trying to update your feeds for the first
2409 # time.
2410
2411 $result = $wpdb->query("
2412 INSERT INTO $wpdb->posts
2413 SET
2414 guid = '{$dbpost['guid']}',
2415 post_author = '{$dbpost['post_author']}',
2416 post_date = '{$dbpost['post_date']}',
2417 post_date_gmt = '{$dbpost['post_date_gmt']}',
2418 post_content = '{$dbpost['post_content']}',"
2419 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
2420 post_title = '{$dbpost['post_title']}',
2421 post_name = '{$dbpost['post_name']}',
2422 post_modified = '{$dbpost['post_modified']}',
2423 post_modified_gmt = '{$dbpost['post_modified_gmt']}',
2424 comment_status = '{$dbpost['comment_status']}',
2425 ping_status = '{$dbpost['ping_status']}',
2426 post_status = '{$dbpost['post_status']}'
2427 ");
2428 $this->_wp_id = $wpdb->insert_id;
2429
2430 // This should never happen.
2431 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
2432 FeedWordPress::critical_bug('SyndicatedPost::_wp_id', $this->_wp_id, __LINE__);
2433 endif;
2434
2435 // WordPress 1.5.x - 2.0.x
2436 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
2437
2438 // Since we are not going through official channels, we need to
2439 // manually tell WordPress that we've published a new post.
2440 // We need to make sure to do this in order for FeedWordPress
2441 // to play well with the staticize-reloaded plugin (something
2442 // that a large aggregator website is going to *want* to be
2443 // able to use).
2444 do_action('publish_post', $this->_wp_id);
2445 endif;
2446 } /* SyndicatedPost::insert_new() */
2447
2448 function update_existing () {
2449 global $wpdb;
2450
2451 // Why the fuck doesn't wp_insert_post already do this?
2452 $dbpost = array();
2453 foreach ($this->post as $key => $value) :
2454 if (is_string($value)) :
2455 $dbpost[$key] = $wpdb->escape($value);
2456 else :
2457 $dbpost[$key] = $value;
2458 endif;
2459 endforeach;
2460
2461 if ($this->use_api('wp_insert_post')) :
2462 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
2463 $this->_wp_id = wp_insert_post($dbpost);
2464
2465 // This should never happen.
2466 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
2467 FeedWordPress::critical_bug('SyndicatedPost::_wp_id', $this->_wp_id, __LINE__);
2468 endif;
2469
2470 // Unfortunately, as of WordPress 2.3, wp_insert_post()
2471 // munges your post modified timestamp.
2472 $result = $wpdb->query("
2473 UPDATE $wpdb->posts
2474 SET
2475 post_modified='{$dbpost['post_modified']}',
2476 post_modified_gmt='{$dbpost['post_modified_gmt']}'
2477 WHERE ID='{$this->_wp_id}'
2478 ");
2479 else :
2480
2481 $result = $wpdb->query("
2482 UPDATE $wpdb->posts
2483 SET
2484 post_author = '{$dbpost['post_author']}',
2485 post_content = '{$dbpost['post_content']}',"
2486 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
2487 post_title = '{$dbpost['post_title']}',
2488 post_name = '{$dbpost['post_name']}',
2489 post_modified = '{$dbpost['post_modified']}',
2490 post_modified_gmt = '{$dbpost['post_modified_gmt']}'
2491 WHERE guid='{$dbpost['guid']}'
2492 ");
2493
2494 // WordPress 2.1.x and up
2495 if (function_exists('wp_set_post_categories')) :
2496 wp_set_post_categories($this->wp_id(), $this->post['post_category']);
2497 // WordPress 1.5.x - 2.0.x
2498 elseif (function_exists('wp_set_post_cats')) :
2499 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
2500 // This should never happen.
2501 else :
2502 FeedWordPress::critical_bug('SyndicatedPost::insert_new::wp_version', $GLOBALS['wp_version'], __LINE__);
2503 endif;
2504
2505 // Since we are not going through official channels, we need to
2506 // manually tell WordPress that we've published a new post.
2507 // We need to make sure to do this in order for FeedWordPress
2508 // to play well with the staticize-reloaded plugin (something
2509 // that a large aggregator website is going to *want* to be
2510 // able to use).
2511 do_action('edit_post', $this->post['ID']);
2512 endif;
2513 } /* SyndicatedPost::update_existing() */
2514
2515 // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
2516 // using the space for custom keys. The set of keys and values to add is
2517 // specified by the keys and values of $post['meta']. This is used to
2518 // store anything that the WordPress user might want to access from a
2519 // template concerning the post's original source that isn't provided
2520 // for by standard WP meta-data (i.e., any interesting data about the
2521 // syndicated post other than author, title, timestamp, categories, and
2522 // guid). It's also used to hook into WordPress's support for
2523 // enclosures.
2524 function add_rss_meta () {
2525 global $wpdb;
2526 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
2527 $postId = $this->wp_id();
2528
2529 // Aggregated posts should NOT send out pingbacks.
2530 // WordPress 2.1-2.2 claim you can tell them not to
2531 // using $post_pingback, but they don't listen, so we
2532 // make sure here.
2533 $result = $wpdb->query("
2534 DELETE FROM $wpdb->postmeta
2535 WHERE post_id='$postId' AND meta_key='_pingme'
2536 ");
2537
2538 foreach ( $this->post['meta'] as $key => $values ) :
2539
2540 $key = $wpdb->escape($key);
2541
2542 // If this is an update, clear out the old
2543 // values to avoid duplication.
2544 $result = $wpdb->query("
2545 DELETE FROM $wpdb->postmeta
2546 WHERE post_id='$postId' AND meta_key='$key'
2547 ");
2548
2549 // Allow for either a single value or an array
2550 if (!is_array($values)) $values = array($values);
2551 foreach ( $values as $value ) :
2552 $value = $wpdb->escape($value);
2553 $result = $wpdb->query("
2554 INSERT INTO $wpdb->postmeta
2555 SET
2556 post_id='$postId',
2557 meta_key='$key',
2558 meta_value='$value'
2559 ");
2560 endforeach;
2561 endforeach;
2562 endif;
2563 } /* SyndicatedPost::add_rss_meta () */
2564
2565 // SyndicatedPost::author_id (): get the ID for an author name from
2566 // the feed. Create the author if necessary.
2567 function author_id ($unfamiliar_author = 'create') {
2568 global $wpdb, $wp_db_version; // test for WordPress 2.0 database schema
2569
2570 $a = $this->author();
2571 $author = $a['name'];
2572 $email = $a['email'];
2573 $url = $a['uri'];
2574
2575 // Never can be too careful...
2576 $login = sanitize_user($author, /*strict=*/ true);
2577 $login = apply_filters('pre_user_login', $login);
2578
2579 $nice_author = sanitize_title($author);
2580 $nice_author = apply_filters('pre_user_nicename', $nice_author);
2581
2582 $reg_author = $wpdb->escape(preg_quote($author));
2583 $author = $wpdb->escape($author);
2584 $email = $wpdb->escape($email);
2585 $url = $wpdb->escape($url);
2586
2587 // Check for an existing author rule....
2588 if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
2589 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
2590 else :
2591 $author_rule = NULL;
2592 endif;
2593
2594 // User name is mapped to a particular author. If that author ID exists, use it.
2595 if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
2596 $id = (int) $author_rule;
2597
2598 // User name is filtered out
2599 elseif ('filter' == $author_rule) :
2600 $id = NULL;
2601
2602 else :
2603 // Check the database for an existing author record that might fit
2604
2605 #-- WordPress 1.5.x
2606 if (!isset($wp_db_version)) :
2607 $id = $wpdb->get_var(
2608 "SELECT ID from $wpdb->users
2609 WHERE
2610 TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
2611 (
2612 LENGTH(TRIM(LCASE(user_email))) > 0
2613 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$email'))
2614 ) OR
2615 TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
2616 TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
2617 TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
2618 TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
2619 (
2620 LOWER(user_description)
2621 RLIKE CONCAT(
2622 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
2623 LCASE('$reg_author'),
2624 '( |\\t|\\r)*(\\n|\$)'
2625 )
2626 )
2627 ");
2628
2629 #-- WordPress 2.0+
2630 elseif ($wp_db_version >= 2966) :
2631
2632 // First try the user core data table.
2633 $id = $wpdb->get_var(
2634 "SELECT ID FROM $wpdb->users
2635 WHERE
2636 TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
2637 OR (
2638 LENGTH(TRIM(LCASE(user_email))) > 0
2639 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$email'))
2640 )
2641 OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
2642 ");
2643
2644 // If that fails, look for aliases in the user meta data table
2645 if (is_null($id)) :
2646 $id = $wpdb->get_var(
2647 "SELECT user_id FROM $wpdb->usermeta
2648 WHERE
2649 (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
2650 OR (
2651 meta_key = 'description'
2652 AND TRIM(LCASE(meta_value))
2653 RLIKE CONCAT(
2654 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
2655 TRIM(LCASE('$reg_author')),
2656 '( |\\t|\\r)*(\\n|\$)'
2657 )
2658 )
2659 ");
2660 endif;
2661 endif;
2662
2663 // ... if you don't find one, then do what you need to do
2664 if (is_null($id)) :
2665 if ($unfamiliar_author === 'create') :
2666 $userdata = array();
2667
2668 #-- user table data
2669 $userdata['ID'] = NULL; // new user
2670 $userdata['user_login'] = $login;
2671 $userdata['user_nicename'] = $nice_author;
2672 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
2673 $userdata['user_email'] = $email;
2674 $userdata['user_url'] = $url;
2675 $userdata['display_name'] = $author;
2676
2677 $id = wp_insert_user($userdata);
2678 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
2679 $id = (int) $unfamiliar_author;
2680 elseif ($unfamiliar_author === 'default') :
2681 $id = 1;
2682 endif;
2683 endif;
2684 endif;
2685
2686 if ($id) :
2687 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
2688 endif;
2689 return $id;
2690 } // function SyndicatedPost::author_id ()
2691
2692 // look up (and create) category ids from a list of categories
2693 function category_ids ($cats, $unfamiliar_category = 'create') {
2694 global $wpdb;
2695
2696 // We need to normalize whitespace because (1) trailing
2697 // whitespace can cause PHP and MySQL not to see eye to eye on
2698 // VARCHAR comparisons for some versions of MySQL (cf.
2699 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
2700 // because I doubt most people want to make a semantic
2701 // distinction between 'Computers' and 'Computers '
2702 $cats = array_map('trim', $cats);
2703
2704 $cat_ids = array ();
2705 foreach ($cats as $cat_name) :
2706 if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
2707 $cat_id = (int) $backref[1];
2708 if (function_exists('is_term') and is_term($cat_id, 'category')) :
2709 $cat_ids[] = $cat_id;
2710 elseif (get_category($cat_id)) :
2711 $cat_ids[] = $cat_id;
2712 endif;
2713 else :
2714 $esc = $wpdb->escape($cat_name);
2715 $resc = $wpdb->escape(preg_quote($cat_name));
2716
2717 // WordPress 2.3+
2718 if (function_exists('is_term')) :
2719 $cat_id = is_term($cat_name, 'category');
2720 if ($cat_id) :
2721 $cat_ids[] = $cat_id['term_id'];
2722 // There must be a better way to do this...
2723 elseif ($results = $wpdb->get_results(
2724 "SELECT term_id
2725 FROM $wpdb->term_taxonomy
2726 WHERE
2727 LOWER(description) RLIKE
2728 CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
2729 )) :
2730 foreach ($results AS $term) :
2731 $cat_ids[] = (int) $term->term_id;
2732 endforeach;
2733 elseif ('create'===$unfamiliar_category) :
2734 $term = wp_insert_term($cat_name, 'category');
2735 $cat_ids[] = $term['term_id'];
2736 endif;
2737
2738 // WordPress 1.5.x - 2.2.x
2739 else :
2740 $results = $wpdb->get_results(
2741 "SELECT cat_ID
2742 FROM $wpdb->categories
2743 WHERE
2744 (LOWER(cat_name) = LOWER('$esc'))
2745 OR (LOWER(category_description)
2746 RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
2747 ");
2748 if ($results) :
2749 foreach ($results as $term) :
2750 $cat_ids[] = (int) $term->cat_ID;
2751 endforeach;
2752 elseif ('create'===$unfamiliar_category) :
2753 if (function_exists('wp_insert_category')) :
2754 $cat_id = wp_insert_category(array('cat_name' => $cat_name));
2755 // And into the database we go.
2756 else :
2757 $nice_kitty = sanitize_title($cat_name);
2758 $wpdb->query(sprintf("
2759 INSERT INTO $wpdb->categories
2760 SET
2761 cat_name='%s',
2762 category_nicename='%s'
2763 ", $wpdb->escape($cat_name), $nice_kitty
2764 ));
2765 $cat_id = $wpdb->insert_id;
2766 endif;
2767 $cat_ids[] = $cat_id;
2768 endif;
2769 endif;
2770 endif;
2771 endforeach;
2772
2773 if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
2774 $cat_ids = NULL; // Drop the post
2775 else :
2776 $cat_ids = array_unique($cat_ids);
2777 endif;
2778 return $cat_ids;
2779 } // function SyndicatedPost::category_ids ()
2780
2781 function use_api ($tag) {
2782 global $wp_db_version;
2783 if ('wp_insert_post'==$tag) :
2784 // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
2785 // In 1.5 it was such a resource hog it would make PHP segfault on big updates
2786 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
2787 endif;
2788 return $ret;
2789 } // function SyndicatedPost::use_api ()
2790
2791 #### EXTRACT DATA FROM FEED ITEM ####
2792
2793 function created () {
2794 $epoch = null;
2795 if (isset($this->item['dc']['created'])) :
2796 $epoch = @parse_w3cdtf($this->item['dc']['created']);
2797 elseif (isset($this->item['dcterms']['created'])) :
2798 $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
2799 elseif (isset($this->item['created'])): // Atom 0.3
2800 $epoch = @parse_w3cdtf($this->item['created']);
2801 endif;
2802 return $epoch;
2803 }
2804 function published ($fallback = true) {
2805 $epoch = null;
2806
2807 # RSS is a fucking mess. Figure out whether we have a date in
2808 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
2809 # epoch format for reformatting. If we can't find anything,
2810 # we'll use the last-updated time.
2811 if (isset($this->item['dc']['date'])): // Dublin Core
2812 $epoch = @parse_w3cdtf($this->item['dc']['date']);
2813 elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
2814 $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
2815 elseif (isset($this->item['published'])) : // Atom 1.0
2816 $epoch = @parse_w3cdtf($this->item['published']);
2817 elseif (isset($this->item['issued'])): // Atom 0.3
2818 $epoch = @parse_w3cdtf($this->item['issued']);
2819 elseif (isset($this->item['pubdate'])): // RSS 2.0
2820 $epoch = strtotime($this->item['pubdate']);
2821 elseif ($fallback) : // Fall back to <updated> / <modified> if present
2822 $epoch = $this->updated(/*fallback=*/ false);
2823 endif;
2824
2825 # If everything failed, then default to the current time.
2826 if (is_null($epoch)) :
2827 $epoch = time();
2828 endif;
2829
2830 return $epoch;
2831 }
2832 function updated ($fallback = true) {
2833 $epoch = null;
2834
2835 # As far as I know, only dcterms and Atom have reliable ways to
2836 # specify when something was *modified* last. If neither is
2837 # available, then we'll try to get the time of publication.
2838 if (isset($this->item['dc']['modified'])) : // Not really correct
2839 $epoch = @parse_w3cdtf($this->item['dc']['modified']);
2840 elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
2841 $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
2842 elseif (isset($this->item['modified'])): // Atom 0.3
2843 $epoch = @parse_w3cdtf($this->item['modified']);
2844 elseif (isset($this->item['updated'])): // Atom 1.0
2845 $epoch = @parse_w3cdtf($this->item['updated']);
2846 elseif ($fallback) : // Fall back to issued / dc:date
2847 $epoch = $this->published(/*fallback=*/ false);
2848 endif;
2849
2850 # If everything failed, then default to the current time.
2851 if (is_null($epoch)) :
2852 $epoch = time();
2853 endif;
2854
2855 return $epoch;
2856 }
2857
2858 function guid () {
2859 $guid = null;
2860 if (isset($this->item['id'])): // Atom 0.3 / 1.0
2861 $guid = $this->item['id'];
2862 elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
2863 $guid = $this->item['atom']['id'];
2864 elseif (isset($this->item['guid'])) : // RSS 2.0
2865 $guid = $this->item['guid'];
2866 elseif (isset($this->item['dc']['identifier'])) :// yeah, right
2867 $guid = $this->item['dc']['identifier'];
2868 else :
2869 // The feed does not seem to have provided us with a
2870 // unique identifier, so we'll have to cobble together
2871 // a tag: URI that might work for us. The base of the
2872 // URI will be the host name of the feed source ...
2873 $bits = parse_url($this->feedmeta['link/uri']);
2874 $guid = 'tag:'.$bits['host'];
2875
2876 // If we have a date of creation, then we can use that
2877 // to uniquely identify the item. (On the other hand, if
2878 // the feed producer was consicentious enough to
2879 // generate dates of creation, she probably also was
2880 // conscientious enough to generate unique identifiers.)
2881 if (!is_null($this->created())) :
2882 $guid .= '://post.'.date('YmdHis', $this->created());
2883
2884 // Otherwise, use both the URI of the item, *and* the
2885 // item's title. We have to use both because titles are
2886 // often not unique, and sometimes links aren't unique
2887 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
2888 // some podcasts). But it's rare to have *both* the same
2889 // title *and* the same link for two different items. So
2890 // this is about the best we can do.
2891 else :
2892 $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
2893 endif;
2894 endif;
2895 return $guid;
2896 }
2897
2898 function author () {
2899 $author = array ();
2900
2901 if (isset($this->item['author_name'])):
2902 $author['name'] = $this->item['author_name'];
2903 elseif (isset($this->item['dc']['creator'])):
2904 $author['name'] = $this->item['dc']['creator'];
2905 elseif (isset($this->item['dc']['contributor'])):
2906 $author['name'] = $this->item['dc']['contributor'];
2907 elseif (isset($this->feed->channel['dc']['creator'])) :
2908 $author['name'] = $this->feed->channel['dc']['creator'];
2909 elseif (isset($this->feed->channel['dc']['contributor'])) :
2910 $author['name'] = $this->feed->channel['dc']['contributor'];
2911 elseif (isset($this->feed->channel['author_name'])) :
2912 $author['name'] = $this->feed->channel['author_name'];
2913 elseif ($this->feed->is_rss() and isset($this->item['author'])) :
2914 // The author element in RSS is allegedly an
2915 // e-mail address, but lots of people don't use
2916 // it that way. So let's make of it what we can.
2917 $author = parse_email_with_realname($this->item['author']);
2918
2919 if (!isset($author['name'])) :
2920 if (isset($author['email'])) :
2921 $author['name'] = $author['email'];
2922 else :
2923 $author['name'] = $this->feed->channel['title'];
2924 endif;
2925 endif;
2926 else :
2927 $author['name'] = $this->feed->channel['title'];
2928 endif;
2929
2930 if (isset($this->item['author_email'])):
2931 $author['email'] = $this->item['author_email'];
2932 elseif (isset($this->feed->channel['author_email'])) :
2933 $author['email'] = $this->feed->channel['author_email'];
2934 endif;
2935
2936 if (isset($this->item['author_url'])):
2937 $author['uri'] = $this->item['author_url'];
2938 elseif (isset($this->feed->channel['author_url'])) :
2939 $author['uri'] = $this->item['author_url'];
2940 else:
2941 $author['uri'] = $this->feed->channel['link'];
2942 endif;
2943
2944 return $author;
2945 } // SyndicatedPost::author()
2946 } // class SyndicatedPost
2947
2948 # class SyndicatedLink: represents a syndication feed stored within the
2949 # WordPress database
2950 #
2951 # To keep things compact and editable from within WordPress, we use all the
2952 # links under a particular category in the WordPress "Blogroll" for the list of
2953 # feeds to syndicate. "Contributors" is the category used by default; you can
2954 # configure that under Options --> Syndication.
2955 #
2956 # Fields used are:
2957 #
2958 # * link_rss: the URI of the Atom/RSS feed to syndicate
2959 #
2960 # * link_notes: user-configurable options, with keys and values
2961 # like so:
2962 #
2963 # key: value
2964 # cats: computers\nweb
2965 # feed/key: value
2966 #
2967 # Keys that start with "feed/" are gleaned from the data supplied
2968 # by the feed itself, and will be overwritten with each update.
2969 #
2970 # Values have linebreak characters escaped with C-style
2971 # backslashes (so, for example, a newline becomes "\n").
2972 #
2973 # The value of `cats` is used as a newline-separated list of
2974 # default categories for any post coming from a particular feed.
2975 # (In the example above, any posts from this feed will be placed
2976 # in the "computers" and "web" categories--*in addition to* any
2977 # categories that may already be applied to the posts.)
2978 #
2979 # Values of keys in link_notes are accessible from templates using
2980 # the function `get_feed_meta($key)` if this plugin is activated.
2981
2982 class SyndicatedLink {
2983 var $id = null;
2984 var $link = null;
2985 var $settings = array ();
2986 var $magpie = null;
2987
2988 function SyndicatedLink ($link) {
2989 global $wpdb;
2990
2991 if (is_object($link)) :
2992 $this->link = $link;
2993 $this->id = $link->link_id;
2994 else :
2995 $this->id = $link;
2996 if (function_exists('get_bookmark')) : // WP 2.1+
2997 $this->link = get_bookmark($link);
2998 else :
2999 $this->link = $wpdb->get_row("
3000 SELECT * FROM $wpdb->links
3001 WHERE (link_id = '".$wpdb->escape($link)."')"
3002 );
3003 endif;
3004 endif;
3005
3006 if (strlen($this->link->link_rss) > 0) :
3007 // Read off feed settings from link_notes
3008 $notes = explode("\n", $this->link->link_notes);
3009 foreach ($notes as $note):
3010 list($key, $value) = explode(": ", $note, 2);
3011
3012 if (strlen($key) > 0) :
3013 // Unescape and trim() off the whitespace.
3014 // Thanks to Ray Lischner for pointing out the
3015 // need to trim off whitespace.
3016 $this->settings[$key] = stripcslashes (trim($value));
3017 endif;
3018 endforeach;
3019
3020 // "Magic" feed settings
3021 $this->settings['link/uri'] = $this->link->link_rss;
3022 $this->settings['link/name'] = $this->link->link_name;
3023 $this->settings['link/id'] = $this->link->link_id;
3024
3025 // `hardcode categories` is deprecated in favor of `unfamiliar categories`
3026 if (
3027 FeedWordPress::affirmative($this->settings, 'hardcode categories')
3028 and !isset($this->settings['unfamiliar categories'])
3029 ) :
3030 $this->settings['unfamiliar categories'] = 'default';
3031 endif;
3032
3033 // Set this up automagically for del.icio.us
3034 if (!isset($this->settings['cat_split']) and false !== strpos($this->link->link_rss, 'del.icio.us')) :
3035 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
3036 endif;
3037
3038 if (isset($this->settings['cats'])):
3039 $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
3040 endif;
3041
3042 if (isset($this->settings['map authors'])) :
3043 $author_rules = explode("\n\n", $this->settings['map authors']);
3044 $ma = array();
3045 foreach ($author_rules as $rule) :
3046 list($rule_type, $author_name, $author_action) = explode("\n", $rule);
3047
3048 // Normalize for case and whitespace
3049 $rule_type = strtolower(trim($rule_type));
3050 $author_name = strtolower(trim($author_name));
3051 $author_action = strtolower(trim($author_action));
3052
3053 $ma[$rule_type][$author_name] = $author_action;
3054 endforeach;
3055 $this->settings['map authors'] = $ma;
3056 endif;
3057 endif;
3058 } // SyndicatedLink::SyndicatedLink ()
3059
3060 function found () {
3061 return is_object($this->link);
3062 }
3063
3064 function stale () {
3065 $stale = true;
3066 if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
3067 $stale = false; // don't update on any timed updates; pings only
3068 elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
3069 $stale = true; // update on the next timed update
3070 elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
3071 $stale = true; // initial update
3072 else :
3073 $after = ((int) $this->settings['update/last'])
3074 +((int) $this->settings['update/ttl'] * 60);
3075 $stale = (time() >= $after);
3076 endif;
3077 return $stale;
3078 }
3079
3080 function poll () {
3081 global $wpdb;
3082
3083 $this->magpie = fetch_rss($this->link->link_rss);
3084 $new_count = NULL;
3085
3086 if (is_object($this->magpie)) :
3087 $new_count = array('new' => 0, 'updated' => 0);
3088
3089 # -- Update Link metadata live from feed
3090 $channel = $this->magpie->channel;
3091
3092 if (!isset($channel['id'])) :
3093 $channel['id'] = $this->link->link_rss;
3094 endif;
3095
3096 $update = array();
3097 if (!$this->hardcode('url') and isset($channel['link'])) :
3098 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
3099 endif;
3100
3101 if (!$this->hardcode('name') and isset($channel['title'])) :
3102 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
3103 endif;
3104
3105 if (!$this->hardcode('description')) :
3106 if (isset($channel['tagline'])) :
3107 $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
3108 elseif (isset($channel['description'])) :
3109 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
3110 endif;
3111 endif;
3112
3113 $this->settings = array_merge($this->settings, $this->flatten_array($channel));
3114
3115 $this->settings['update/last'] = time(); $ttl = $this->ttl();
3116 if (!is_null($ttl)) :
3117 $this->settings['update/ttl'] = $ttl;
3118 $this->settings['update/timed'] = 'feed';
3119 else :
3120 $this->settings['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates
3121 $this->settings['update/timed'] = 'automatically';
3122 endif;
3123
3124 if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
3125 $this->settings['update/hold'] = 'scheduled';
3126 endif;
3127
3128 // Copy back without a few things that we don't want to save in the notes
3129 $to_notes = $this->settings;
3130
3131 if (is_array($to_notes['cats'])) :
3132 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
3133 endif;
3134
3135 if (isset($to_notes['map authors'])) :
3136 $ma = array();
3137 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
3138 foreach ($author_rules as $author_name => $author_action) :
3139 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
3140 endforeach;
3141 endforeach;
3142 $to_notes['map authors'] = implode("\n\n", $ma);
3143 endif;
3144
3145 unset($to_notes['link/id']); unset($to_notes['link/uri']);
3146 unset($to_notes['link/name']);
3147 unset($to_notes['hardcode categories']); // Deprecated
3148
3149 $notes = '';
3150 foreach ($to_notes as $key => $value) :
3151 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
3152 endforeach;
3153 $update[] = "link_notes = '".$wpdb->escape($notes)."'";
3154
3155 $update_set = implode(',', $update);
3156
3157 // Update the properties of the link from the feed information
3158 $result = $wpdb->query("
3159 UPDATE $wpdb->links
3160 SET $update_set
3161 WHERE link_id='$this->id'
3162 ");
3163
3164 # -- Add new posts from feed and update any updated posts
3165 if (is_array($this->magpie->items)) :
3166 foreach ($this->magpie->items as $item) :
3167 $post =& new SyndicatedPost($item, $this);
3168 if (!$post->filtered()) :
3169 $new = $post->store();
3170 if ( $new !== false ) $new_count[$new]++;
3171 endif;
3172 endforeach;
3173 endif;
3174
3175 // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
3176 $to_notes = $this->settings;
3177
3178 if (is_array($to_notes['cats'])) :
3179 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
3180 endif;
3181
3182 if (isset($to_notes['map authors'])) :
3183 $ma = array();
3184 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
3185 foreach ($author_rules as $author_name => $author_action) :
3186 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
3187 endforeach;
3188 endforeach;
3189 $to_notes['map authors'] = implode("\n\n", $ma);
3190 endif;
3191
3192 unset($to_notes['link/id']); unset($to_notes['link/uri']);
3193 unset($to_notes['link/name']);
3194 unset($to_notes['hardcode categories']); // Deprecated
3195
3196 $notes = '';
3197 foreach ($to_notes as $key => $value) :
3198 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
3199 endforeach;
3200
3201 $update_set = "link_notes = '".$wpdb->escape($notes)."'";
3202
3203 // Update the properties of the link from the feed information
3204 $result = $wpdb->query("
3205 UPDATE $wpdb->links
3206 SET $update_set
3207 WHERE link_id='$this->id'
3208 ");
3209 endif;
3210 return $new_count;
3211 } /* SyndicatedLink::poll() */
3212
3213 function uri () {
3214 return (is_object($this->link) ? $this->link->link_rss : NULL);
3215 }
3216 function homepage () {
3217 return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
3218 }
3219
3220 function ttl () {
3221 if (is_object($this->magpie)) :
3222 $channel = $this->magpie->channel;
3223 else :
3224 $channel = array();
3225 endif;
3226
3227 if (isset($channel['ttl'])) :
3228 // "ttl stands for time to live. It's a number of
3229 // minutes that indicates how long a channel can be
3230 // cached before refreshing from the source."
3231 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
3232 $ret = $channel['ttl'];
3233 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
3234 $period_minutes = array (
3235 'hourly' => 60, /* minutes in an hour */
3236 'daily' => 1440, /* minutes in a day */
3237 'weekly' => 10080, /* minutes in a week */
3238 'monthly' => 43200, /* minutes in a month */
3239 'yearly' => 525600, /* minutes in a year */
3240 );
3241
3242 // "sy:updatePeriod: Describes the period over which the
3243 // channel format is updated. Acceptable values are:
3244 // hourly, daily, weekly, monthly, yearly. If omitted,
3245 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
3246 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
3247 else : $period = 'daily';
3248 endif;
3249
3250 // "sy:updateFrequency: Used to describe the frequency
3251 // of updates in relation to the update period. A
3252 // positive integer indicates how many times in that
3253 // period the channel is updated. ... If omitted a value
3254 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
3255 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
3256 else : $freq = 1;
3257 endif;
3258
3259 $ret = (int) ($period_minutes[$period] / $freq);
3260 else :
3261 $ret = NULL;
3262 endif;
3263 return $ret;
3264 } /* SyndicatedLink::ttl() */
3265
3266 // SyndicatedLink::flatten_array (): flatten an array. Useful for
3267 // hierarchical and namespaced elements.
3268 //
3269 // Given an array which may contain array or object elements in it,
3270 // return a "flattened" array: a one-dimensional array of scalars
3271 // containing each of the scalar elements contained within the array
3272 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
3273 // returned array for FeedWordPress::flatten_array($a) will contain a key
3274 // $a['feed/b/c/d'] with value 'e'.
3275 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
3276 $ret = array ();
3277 if (is_array($arr)) :
3278 foreach ($arr as $key => $value) :
3279 if (is_scalar($value)) :
3280 $ret[$prefix.$key] = $value;
3281 else :
3282 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
3283 endif;
3284 endforeach;
3285 endif;
3286 return $ret;
3287 } // function SyndicatedLink::flatten_array ()
3288
3289 function hardcode ($what) {
3290 $default = get_option("feedwordpress_hardcode_$what");
3291 if ( $default === 'yes' ) :
3292 // If the default is to hardcode, then we want the
3293 // negation of negative(): TRUE by default and FALSE if
3294 // the setting is explicitly "no"
3295 $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
3296 else :
3297 // If the default is NOT to hardcode, then we want
3298 // affirmative(): FALSE by default and TRUE if the
3299 // setting is explicitly "yes"
3300 $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
3301 endif;
3302 return $ret;
3303 } // function SyndicatedLink::hardcode ()
3304
3305 function syndicated_status ($what, $default) {
3306 global $wpdb;
3307
3308 $ret = get_option("feedwordpress_syndicated_{$what}_status");
3309 if ( isset($this->settings["$what status"]) ) :
3310 $ret = $this->settings["$what status"];
3311 elseif (!$ret) :
3312 $ret = $default;
3313 endif;
3314 return $wpdb->escape(trim(strtolower($ret)));
3315 } // function SyndicatedLink:syndicated_status ()
3316 } // class SyndicatedLink
3317
3318 ################################################################################
3319 ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################
3320 ################################################################################
3321
3322 function feedwordpress_xmlrpc_hook ($args = array ()) {
3323 $args['weblogUpdates.ping'] = 'feedwordpress_pong';
3324 return $args;
3325 }
3326
3327 function feedwordpress_pong ($args) {
3328 $feedwordpress =& new FeedWordPress;
3329 $delta = @$feedwordpress->update($args[1]);
3330 if (is_null($delta)):
3331 return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
3332 else:
3333 $mesg = array();
3334 if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; }
3335 if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; }
3336
3337 return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg));
3338 endif;
3339 }
3340
3341 ################################################################################
3342 ## class FeedFinder: find likely feeds using autodetection and/or guesswork ####
3343 ################################################################################
3344
3345 class FeedFinder {
3346 var $uri = NULL;
3347 var $_cache_uri = NULL;
3348
3349 var $verify = FALSE;
3350
3351 var $_data = NULL;
3352 var $_head = NULL;
3353
3354 # -- Recognition patterns
3355 var $_feed_types = array(
3356 'application/rss+xml',
3357 'text/xml',
3358 'application/atom+xml',
3359 'application/x.atom+xml',
3360 'application/x-atom+xml'
3361 );
3362 var $_feed_markers = array('\\<feed', '\\<rss', 'xmlns="http://purl.org/rss/1.0');
3363 var $_html_markers = array('\\<html');
3364 var $_obvious_feed_url = array('[./]rss', '[./]rdf', '[./]atom', '[./]feed', '\.xml');
3365 var $_maybe_feed_url = array ('rss', 'rdf', 'atom', 'feed', 'xml');
3366
3367 function FeedFinder ($uri = NULL, $verify = TRUE) {
3368 $this->uri = $uri; $this->verify = $verify;
3369 } /* FeedFinder::FeedFinder () */
3370
3371 function find ($uri = NULL) {
3372 $ret = array ();
3373 if (!is_null($this->data($uri))) {
3374 if ($this->is_feed($uri)) {
3375 $ret = array($this->uri);
3376 } else {
3377 // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?)
3378 // Autodiscovery is the preferred method
3379 $href = $this->_link_rel_feeds();
3380
3381 // ... but we'll also take the little orange buttons
3382 $href = array_merge($href, $this->_a_href_feeds(TRUE));
3383
3384 // If all that failed, look harder
3385 if (count($href) == 0) $href = $this->_a_href_feeds(FALSE);
3386
3387 // Verify feeds and resolve relative URIs
3388 foreach ($href as $u) {
3389 $the_uri = Relative_URI::resolve($u, $this->uri);
3390 if ($this->verify) {
3391 $feed =& new FeedFinder($the_uri);
3392 if ($feed->is_feed()) $ret[] = $the_uri;
3393 $feed = NULL;
3394 } else {
3395 $ret[] = $the_uri;
3396 }
3397 } /* foreach */
3398 } /* if */
3399 } /* if */
3400 return array_unique($ret);
3401 } /* FeedFinder::find () */
3402
3403 function data ($uri = NULL) {
3404 $this->_get($uri);
3405 return $this->_data;
3406 }
3407
3408 function is_feed ($uri = NULL) {
3409 $data = $this->data($uri);
3410
3411 return (
3412 preg_match (
3413 "\007(".implode('|',$this->_feed_markers).")\007i",
3414 $data
3415 ) and !preg_match (
3416 "\007(".implode('|',$this->_html_markers).")\007i",
3417 $data
3418 )
3419 );
3420 } /* FeedFinder::is_feed () */
3421
3422 # --- Private methods ---
3423 function _get ($uri = NULL) {
3424 if ($uri) $this->uri = $uri;
3425
3426 // Is the result not yet cached?
3427 if ($this->_cache_uri !== $this->uri) :
3428 // Snoopy is an HTTP client in PHP
3429 $client = new Snoopy();
3430
3431 // Prepare headers and internal settings
3432 $client->rawheaders['Connection'] = 'close';
3433 $client->accept = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*';
3434 $client->agent = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress';
3435 $client->read_timeout = 25;
3436
3437 // Fetch the HTML or feed
3438 @$client->fetch($this->uri);
3439 $this->_data = $client->results;
3440
3441 // Kilroy was here
3442 $this->_cache_uri = $this->uri;
3443 endif;
3444 } /* FeedFinder::_get () */
3445
3446 function _link_rel_feeds () {
3447 $links = $this->_tags('link');
3448 $link_count = count($links);
3449
3450 // now figure out which one points to the RSS file
3451 $href = array ();
3452 for ($n=0; $n<$link_count; $n++) {
3453 if (strtolower($links[$n]['rel']) == 'alternate') {
3454 if (in_array(strtolower($links[$n]['type']), $this->_feed_types)) {
3455 $href[] = $links[$n]['href'];
3456 } /* if */
3457 } /* if */
3458 } /* for */
3459 return $href;
3460 }
3461
3462 function _a_href_feeds ($obvious = TRUE) {
3463 $pattern = ($obvious ? $this->_obvious_feed_url : $this->_maybe_feed_url);
3464
3465 $links = $this->_tags('a');
3466 $link_count = count($links);
3467
3468 // now figure out which one points to the RSS file
3469 $href = array ();
3470 for ($n=0; $n<$link_count; $n++) {
3471 if (preg_match("\007(".implode('|',$pattern).")\007i", $links[$n]['href'])) {
3472 $href[] = $links[$n]['href'];
3473 } /* if */
3474 } /* for */
3475 return $href;
3476 }
3477
3478 function _tags ($tag) {
3479 $html = $this->data();
3480
3481 // search through the HTML, save all <link> tags
3482 // and store each link's attributes in an associative array
3483 preg_match_all('/<'.$tag.'\s+(.*?)\s*\/?>/si', $html, $matches);
3484 $links = $matches[1];
3485 $ret = array();
3486 $link_count = count($links);
3487 for ($n=0; $n<$link_count; $n++) {
3488 $attributes = preg_split('/\s+/s', $links[$n]);
3489 foreach($attributes as $attribute) {
3490 $att = preg_split('/\s*=\s*/s', $attribute, 2);
3491 if (isset($att[1])) {
3492 $att[1] = preg_replace('/([\'"]?)(.*)\1/', '$2', $att[1]);
3493 $final_link[strtolower($att[0])] = $att[1];
3494 } /* if */
3495 } /* foreach */
3496 $ret[$n] = $final_link;
3497 } /* for */
3498 return $ret;
3499 }
3500 } /* class FeedFinder */
3501
3502 # Relative URI static class: PHP class for resolving relative URLs
3503 #
3504 # This class is derived (under the terms of the GPL) from URL Class 0.3 by
3505 # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
3506 # for FeedWordPress's purposes. The class has been stripped down to a single
3507 # public method: Relative_URI::resolve($url, $base), which resolves the URI in
3508 # $url relative to the URI in $base
3509 #
3510 # The upgraded MagpieRSS also uses this class. So if we have it loaded
3511 # in, don't load it again.
3512 if (!class_exists('Relative_URI')) {
3513
3514 class Relative_URI
3515 {
3516 // Resolve relative URI in $url against the base URI in $base. If $base
3517 // is not supplied, then we use the REQUEST_URI of this script.
3518 //
3519 // I'm hoping this method reflects RFC 2396 Section 5.2
3520 function resolve ($url, $base = NULL)
3521 {
3522 if (is_null($base)):
3523 $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
3524 endif;
3525
3526 $base = Relative_URI::_encode(trim($base));
3527 $uri_parts = Relative_URI::_parse_url($base);
3528
3529 $url = Relative_URI::_encode(trim($url));
3530 $parts = Relative_URI::_parse_url($url);
3531
3532 $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
3533 $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
3534
3535 // if path is empty, and scheme, host, and query are undefined,
3536 // the URL is referring the base URL
3537
3538 if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
3539 // If the URI is empty or only a fragment, return the base URI
3540 return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
3541 } elseif (isset($parts['scheme'])) {
3542 // If the scheme is set, then the URI is absolute.
3543 return $url;
3544 } elseif (isset($parts['host'])) {
3545 $uri_parts['host'] = $parts['host'];
3546 $uri_parts['path'] = $parts['path'];
3547 } else {
3548 // We have a relative path but not a host.
3549
3550 // start ugly fix:
3551 // prepend slash to path if base host is set, base path is not set, and url path is not absolute
3552 if ($uri_parts['host'] && ($uri_parts['path'] == '')
3553 && (strlen($parts['path']) > 0)
3554 && (substr($parts['path'], 0, 1) != '/')) {
3555 $parts['path'] = '/'.$parts['path'];
3556 } // end ugly fix
3557
3558 if (substr($parts['path'], 0, 1) == '/') {
3559 $uri_parts['path'] = $parts['path'];
3560 } else {
3561 // copy base path excluding any characters after the last (right-most) slash character
3562 $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
3563 // append relative path
3564 $buffer .= $parts['path'];
3565 // remove "./" where "." is a complete path segment.
3566 $buffer = str_replace('/./', '/', $buffer);
3567 if (substr($buffer, 0, 2) == './') {
3568 $buffer = substr($buffer, 2);
3569 }
3570 // if buffer ends with "." as a complete path segment, remove it
3571 if (substr($buffer, -2) == '/.') {
3572 $buffer = substr($buffer, 0, -1);
3573 }
3574 // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
3575 $search_finished = false;
3576 $segment = explode('/', $buffer);
3577 while (!$search_finished) {
3578 for ($x=0; $x+1 < count($segment);) {
3579 if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
3580 if ($x+2 == count($segment)) $segment[] = '';
3581 unset($segment[$x], $segment[$x+1]);
3582 $segment = array_values($segment);
3583 continue 2;
3584 } else {
3585 $x++;
3586 }
3587 }
3588 $search_finished = true;
3589 }
3590 $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
3591 $uri_parts['path'] = $buffer;
3592
3593 }
3594 }
3595
3596 // If we've gotten to this point, we can try to put the pieces
3597 // back together.
3598 $ret = '';
3599 if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
3600 if (isset($uri_parts['user'])) {
3601 $ret .= $uri_parts['user'];
3602 if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
3603 $ret .= '@';
3604 }
3605 if (isset($uri_parts['host'])) {
3606 $ret .= '//'.$uri_parts['host'];
3607 if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
3608 }
3609 $ret .= $uri_parts['path'];
3610 if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
3611 if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
3612
3613 return $ret;
3614 }
3615
3616 /**
3617 * Parse URL
3618 *
3619 * Regular expression grabbed from RFC 2396 Appendix B.
3620 * This is a replacement for PHPs builtin parse_url().
3621 * @param string $url
3622 * @access private
3623 * @return array
3624 */
3625 function _parse_url($url)
3626 {
3627 // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
3628 // generates a warning.
3629 if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
3630 $parts = array();
3631 if ($match[1] != '') $parts['scheme'] = $match[2];
3632 if ($match[3] != '') $parts['auth'] = $match[4];
3633 // parse auth
3634 if (isset($parts['auth'])) {
3635 // store user info
3636 if (($at_pos = strpos($parts['auth'], '@')) !== false) {
3637 $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
3638 $parts['user'] = $userinfo[0];
3639 if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
3640 $parts['auth'] = substr($parts['auth'], $at_pos+1);
3641 }
3642 // get port number
3643 if ($port_pos = strrpos($parts['auth'], ':')) {
3644 $parts['host'] = substr($parts['auth'], 0, $port_pos);
3645 $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
3646 if ($parts['port'] < 1) $parts['port'] = null;
3647 } else {
3648 $parts['host'] = $parts['auth'];
3649 }
3650 }
3651 unset($parts['auth']);
3652 $parts['path'] = $match[5];
3653 if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
3654 if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
3655 return $parts;
3656 }
3657 // shouldn't reach here
3658 return array('path'=>'');
3659 }
3660
3661 function _encode($string)
3662 {
3663 static $replace = array();
3664 if (!count($replace)) {
3665 $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
3666 $find = array_merge(range(0, 31), $find);
3667 $find = array_map('chr', $find);
3668 foreach ($find as $char) {
3669 $replace[$char] = '%'.bin2hex($char);
3670 }
3671 }
3672 // escape control characters and a few other characters
3673 $encoded = strtr($string, $replace);
3674 // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
3675 return preg_replace('/[^\x21-\x7e]/', '', $encoded);
3676 }
3677 } // class Relative_URI
3678 }
3679
3680 // take your best guess at the realname and e-mail, given a string
3681 define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
3682 define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))');
3683 define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/');
3684 define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/');
3685 define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/');
3686 define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/');
3687
3688 function parse_email_with_realname ($email) {
3689 if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) :
3690 ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]);
3691 $ret['email'] = $matches[1];
3692 elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) :
3693 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
3694 $ret['email'] = $matches[4];
3695 elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) :
3696 $ret['name'] = NULL; $ret['email'] = $matches[1];
3697 elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) :
3698 $ret['email'] = NULL;
3699 ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]);
3700 else :
3701 $ret['name'] = NULL; $ret['email'] = NULL;
3702 endif;
3703 return $ret;
3704 }
3705 ?>
3706