PluginProbe
FeedWordPress / 0.991
FeedWordPress v0.991
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.991, at feedwordpress.php

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