# - HTTP Navigator 2 by Keyvan Minoukadeh # - Ultra-Liberal Feed Finder by Mark Pilgrim # according to the terms of the GNU General Public License. # # INSTALLATION: see README.text or # # USAGE: once FeedWordPress is installed, you manage just about everything from # the WordPress Dashboard, under Options --> Syndication or Links --> Syndicated # To ensure that fresh content is added as it becomes available, get your # contributors to put your XML-RPC URI (if WordPress is installed at # , XML-RPC requests should be sent to # ), or see `update-feeds.php` # -- Don't change these unless you know what you're doing... define ('FEEDWORDPRESS_VERSION', '0.991'); define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact'); define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors'); define ('FEEDWORDPRESS_DEBUG', false); define ('FEEDWORDPRESS_CAT_SEPARATOR_PATTERN', '/[:\n]/'); define ('FEEDWORDPRESS_CAT_SEPARATOR', "\n"); define ('FEEDVALIDATOR_URI', 'http://feedvalidator.org/check.cgi'); define ('FEEDWORDPRESS_FRESHNESS_INTERVAL', 10*60); // Every ten minutes define ('FWP_SCHEMA_20', 3308); // Database schema # for WP 2.0 define ('FWP_SCHEMA_21', 4772); // Database schema # for WP 2.1 define ('FWP_SCHEMA_23', 5495); // Database schema # for WP 2.3 if (FEEDWORDPRESS_DEBUG) : // Help us to pick out errors, if any. ini_set('error_reporting', E_ALL & ~E_NOTICE); ini_set('display_errors', true); define('MAGPIE_DEBUG', true); // When testing we don't want cache issues to interfere. But this is // a VERY BAD SETTING for a production server. Webmasters will eat your // face for breakfast if you use it, and the baby Jesus will cry. So // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be // used for more than testing purposes! define('MAGPIE_CACHE_AGE', 1); else : define('MAGPIE_DEBUG', false); endif; // Note that the rss-functions.php that comes prepackaged with WordPress is // old & busted. For the new hotness, drop a copy of rss.php from // this archive into wp-includes/rss.php if (is_readable(ABSPATH . WPINC . '/rss.php')) : require_once (ABSPATH . WPINC . '/rss.php'); else : require_once (ABSPATH . WPINC . '/rss-functions.php'); endif; if (isset($wp_db_version)) : if ($wp_db_version >= FWP_SCHEMA_23) : require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user elseif ($wp_db_version >= FWP_SCHEMA_21) : // WordPress 2.1 and 2.2, but not 2.3 require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category elseif ($wp_db_version >= FWP_SCHEMA_20) : // WordPress 2.0 require_once (ABSPATH . WPINC . '/registration-functions.php'); // for wp_insert_user require_once (ABSPATH . 'wp-admin/admin-db.php'); // for wp_insert_category endif; endif; if (function_exists('wp_enqueue_script')) : wp_enqueue_script( 'ajaxcat' ); // Provides the handy-dandy new category text box endif; // Magic quotes are just about the stupidest thing ever. if (is_array($_POST)) : $fwp_post = stripslashes_deep($_POST); endif; if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe! # Syndicated items are generally received in output-ready (X)HTML and # should not be folded, crumpled, mutilated, or spindled by WordPress # formatting filters. But we don't want to interfere with filters for # any locally-authored posts, either. # # What WordPress should really have is a way for upstream filters to # stop downstream filters from running at all. Since it doesn't, and # since a downstream filter can't access the original copy of the text # that is being filtered, what we will do here is (1) save a copy of the # original text upstream, before any other filters run, and then (2) # retrieve that copy downstream, after all the other filters run, *if* # this is a syndicated post add_filter('the_content', 'feedwordpress_preserve_syndicated_content', -10000); add_filter('the_content', 'feedwordpress_restore_syndicated_content', 10000); # Filter in original permalinks if the user wants that add_filter('post_link', 'syndication_permalink', 1); # Admin menu add_action('admin_menu', 'fwp_add_pages'); # Inbound XML-RPC update methods add_filter('xmlrpc_methods', 'feedwordpress_xmlrpc_hook'); # Outbound XML-RPC ping reform remove_action('publish_post', 'generic_ping'); // WP 1.5.x remove_action('do_pings', 'do_all_pings', 10, 1); // WP 2.1, 2.2 remove_action('publish_post', '_publish_post_hook', 5, 1); // WP 2.3 add_action('publish_post', 'fwp_publish_post_hook', 5, 1); add_action('do_pings', 'fwp_do_pings', 10, 1); add_action('feedwordpress_update', 'fwp_hold_pings'); add_action('feedwordpress_update_complete', 'fwp_release_pings'); # Hook in logging functions only if the logging option is ON $update_logging = get_option('feedwordpress_update_logging'); if ($update_logging == 'yes') : add_action('post_syndicated_item', 'log_feedwordpress_post', 100); add_action('update_syndicated_item', 'log_feedwordpress_update_post', 100); add_action('feedwordpress_update', 'log_feedwordpress_update_feeds', 100); add_action('feedwordpress_check_feed', 'log_feedwordpress_check_feed', 100); add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100); endif; # Cron-less auto-update. Hooray! add_action('init', 'feedwordpress_auto_update'); else : # Hook in the menus, which will just point to the upgrade interface add_action('admin_menu', 'fwp_add_pages'); endif; // if (!FeedWordPress::needs_upgrade()) function feedwordpress_auto_update () { if (FeedWordPress::stale()) : $feedwordpress =& new FeedWordPress; $feedwordpress->update(); endif; if (FeedWordPress::update_requested()) : exit; endif; } /* feedwordpress_auto_update () */ ################################################################################ ## LOGGING FUNCTIONS: log status updates to error_log if you want it ########### ################################################################################ function log_feedwordpress_post ($id) { $post = wp_get_single_post($id); error_log("[".date('Y-m-d H:i:s')."][feedwordpress] posted " ."'{$post->post_title}' ({$post->post_date})"); } function log_feedwordpress_update_post ($id) { $post = wp_get_single_post($id); error_log("[".date('Y-m-d H:i:s')."][feedwordpress] updated " ."'{$post->post_title}' ({$post->post_date})" ." (as of {$post->post_modified})"); } function log_feedwordpress_update_feeds ($uri) { error_log("[".date('Y-m-d H:i:s')."][feedwordpress] update('$uri')"); } function log_feedwordpress_check_feed ($feed) { $uri = $feed['link/uri']; $name = $feed['link/name']; error_log("[".date('Y-m-d H:i:s')."][feedwordpress] Examining $name <$uri>"); } function log_feedwordpress_update_complete ($delta) { $mesg = array(); if (isset($delta['new'])) $mesg[] = 'added '.$delta['new'].' new posts'; if (isset($delta['updated'])) $mesg[] = 'updated '.$delta['updated'].' existing posts'; if (empty($mesg)) $mesg[] = 'nothing changed'; error_log("[".date('Y-m-d H:i:s')."][feedwordpress] " .(is_null($delta) ? "Error: I don't syndicate that URI" : implode(' and ', $mesg))); } ################################################################################ ## LEGACY API: Replicate or mock up functions for legacy support purposes ###### ################################################################################ if (!function_exists('get_option')) { function get_option ($option) { return get_settings($option); } } if (!function_exists('current_user_can')) { $legacy_capability_hack = true; function current_user_can ($task) { global $user_level; $can = false; // This is **not** a full replacement for current_user_can. It // is only for checking the capabilities we care about via the // WordPress 1.5 user levels. switch ($task) { case 'manage_options': $can = ($user_level >= 6); break; case 'manage_links': $can = ($user_level >= 5); break; } /* switch */ return $can; } } else { $legacy_capability_hack = false; } if (!function_exists('sanitize_user')) { function sanitize_user ($text, $strict) { return $text; // Don't munge it if it wasn't munged going in... } } ################################################################################ ## TEMPLATE API: functions to make your templates syndication-aware ############ ################################################################################ function is_syndicated () { return (strlen(get_syndication_feed_id()) > 0); } function the_syndication_source_link () { echo get_syndication_source_link(); } function get_syndication_source_link () { list($n) = get_post_custom_values('syndication_source_uri'); return $n; } function get_syndication_source () { list($n) = get_post_custom_values('syndication_source'); return $n; } function the_syndication_source () { echo get_syndication_source(); } function get_syndication_feed () { list($u) = get_post_custom_values('syndication_feed'); return $u; } function the_syndication_feed () { echo get_syndication_feed (); } function get_syndication_feed_id () { list($u) = get_post_custom_values('syndication_feed_id'); return $u; } function the_syndication_feed_id () { echo get_syndication_feed_id(); } $feedwordpress_linkcache = array (); // only load links from database once function get_feed_meta ($key) { global $wpdb, $feedwordpress_linkcache; $feed_id = get_syndication_feed_id(); $ret = NULL; if (strlen($feed_id) > 0): if (isset($feedwordpress_linkcache[$feed_id])) : $link = $feedwordpress_linkcache[$feed_id]; else : $link =& new SyndicatedLink($feed_id); $feedwordpress_linkcache[$feed_id] = $link; endif; $ret = $link->settings[$key]; endif; return $ret; } /* get_feed_meta() */ function get_syndication_permalink () { list($u) = get_post_custom_values('syndication_permalink'); return $u; } function the_syndication_permalink () { echo get_syndication_permalink(); } ################################################################################ ## FILTERS: syndication-aware handling of post data for templates and feeds #### ################################################################################ $feedwordpress_the_syndicated_content = NULL; function feedwordpress_preserve_syndicated_content ($text) { global $feedwordpress_the_syndicated_content; if ( is_syndicated() ) : $feedwordpress_the_syndicated_content = $text; else : $feedwordpress_the_syndicated_content = NULL; endif; return $text; } function feedwordpress_restore_syndicated_content ($text) { global $feedwordpress_the_syndicated_content; if ( !is_null($feedwordpress_the_syndicated_content) ) : $text = $feedwordpress_the_syndicated_content; endif; return $text; } function syndication_permalink ($permalink = '') { if (get_option('feedwordpress_munge_permalink') != 'no'): $uri = get_syndication_permalink(); return ((strlen($uri) > 0) ? $uri : $permalink); else: return $permalink; endif; } // function syndication_permalink () ################################################################################ ## UPGRADE INTERFACE: Have users upgrade DB from older versions of FWP ######### ################################################################################ function fwp_upgrade_page () { if (isset($GLOBALS['fwp_post']['action']) and $GLOBALS['fwp_post']['action']=='Upgrade') : $ver = get_option('feedwordpress_version'); if (get_option('feedwordpress_version') != FEEDWORDPRESS_VERSION) : echo "
\n"; echo "

Upgrading FeedWordPress...

"; $feedwordpress =& new FeedWordPress; $feedwordpress->upgrade_database(); echo "

Done! Upgraded database to version ".FEEDWORDPRESS_VERSION.".

\n"; echo "
\n"; echo "
"; echo "
\n"; echo "
\n"; return; else : echo "

Already at version ".FEEDWORDPRESS_VERSION."!

"; endif; endif; ?>

Upgrade FeedWordPress

It appears that you have installed FeedWordPress as an upgrade to an existing installation of FeedWordPress. That's no problem, but you will need to take a minute out first to upgrade your database: some necessary changes in how the software keeps track of posts and feeds will cause problems such as duplicate posts and broken templates if we were to continue without the upgrade.

Note that most of FeedWordPress's functionality is temporarily disabled until we have successfully completed the upgrade. Everything should begin working as normal again once the upgrade is complete. There's extraordinarily little chance of any damage as the result of the upgrade, but if you're paranoid like me you may want to back up your database before you proceed.

This may take several minutes for a large installation.

= FWP_SCHEMA_20) : // WordPress 2.x ?>

Place under...

Place under...

Updating ".$feed['link/name']." from <".$feed['link/uri']."> ...\n"; flush(); } function fwp_syndication_manage_page () { global $wpdb; if (FeedWordPress::needs_upgrade()) : fwp_upgrade_page(); return; endif; ?> get_results(" SELECT * FROM $wpdb->links WHERE link_id IN (".implode(",",$_POST['link_ids']).") "); if (is_array($targets)) : foreach ($targets as $target) : $update_set[] = $target->link_rss; endforeach; else : // This should never happen FeedWordPress::critical_bug('fwp_syndication_manage_page::targets', $targets, __LINE__); endif; elseif (isset($_POST['update_uri'])) : $update_set[] = $_POST['update_uri']; endif; if ($fwp_update_invoke != 'get' and count($update_set) > 0) : // Only do things with side-effects for HTTP POST or command line $feedwordpress =& new FeedWordPress; add_action('feedwordpress_check_feed', 'update_feeds_mention'); echo "
\n"; echo "
    \n"; $tdelta = NULL; foreach ($update_set as $uri) : if ($uri == '*') : $uri = NULL; endif; $delta = $feedwordpress->update($uri); if (!is_null($delta)) : if (is_null($tdelta)) : $tdelta = $delta; else : $tdelta['new'] += $delta['new']; $tdelta['updated'] += $delta['updated']; endif; else : echo "
  • Error: There was a problem updating $uri

  • \n"; endif; endforeach; echo "
\n"; if (!is_null($tdelta)) : $mesg = array(); if (isset($delta['new'])) : $mesg[] = ' '.$tdelta['new'].' new posts were syndicated'; endif; if (isset($delta['updated'])) : $mesg[] = ' '.$tdelta['updated'].' existing posts were updated'; endif; echo "

Update complete.".implode(' and', $mesg)."

"; echo "\n"; flush(); endif; echo "
\n"; endif; ?>

Update feeds now

Check currently scheduled feeds for new and updated posts.

Note: Automatic updates are currently turned off. New posts from your feeds will not be syndicated until you manually check for them here. You can turn on automatic updates under Syndication Options.

Syndicated Sites

> link_rss) > 0): $caption='Switch Feed'; $uri_bits = parse_url($link->link_rss); $uri_bits['host'] = preg_replace('/^www\./i', '', $uri_bits['host']); $display_uri = (isset($uri_bits['user'])?$uri_bits['user'].'@':'') .(isset($uri_bits['host'])?$uri_bits['host']:'') .(isset($uri_bits['port'])?':'.$uri_bits['port']:'') .(isset($uri_bits['path'])?$uri_bits['path']:'') .(isset($uri_bits['query'])?'?'.$uri_bits['query']:''); if (strlen($display_uri) > 32) : $display_uri = substr($display_uri, 0, 32).'…'; endif; ?> "; endforeach; else: ?>

There are no websites currently listed for syndication.

link_name, 'both'); ?>

no feed assigned



Add a new syndicated site:

get_row("SELECT * FROM $wpdb->links WHERE link_id='".$wpdb->escape($link_id)."'"); if (is_object($link)): if (is_null($lookup)) $lookup = $link->link_url; $name = wp_specialchars($link->link_name, 'both'); else: die (__("Cheatin' uh ?")); endif; else: $name = "New Syndicated Feed"; $link_id = 0; endif; ?>

Feed Finder:

find(); if (count($feeds) > 0): foreach ($feeds as $key => $f): $rss = fetch_rss($f); if ($rss): $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link']; $feed_link = isset($rss->channel['link'])?$rss->channel['link']:''; ?>
feed_type; ?> feed_version; ?> feed
items) > 0): ?> items[0]; ?>

Sample Item

  • Title:
  • Date:

No Items

FeedWordPress found no posts on this feed.

Feed Information

  • Website: Unknown':$feed_title; ?>
  • Feed URI: (→)
  • Encoding: encoding)?wp_specialchars($rss->encoding, 'both'):"Unknown"; ?>
  • Description: channel['description'])?wp_specialchars($rss->channel['description'], 'both'):"Unknown"; ?>
no feed found

"; endif; ?>

Use another feed

has been added as a contributing site, using the newsfeed at <>.

There was a problem adding the newsfeed. [SQL: ]

query(" UPDATE $wpdb->links SET link_rss = '".$wpdb->escape($_REQUEST['feed'])."' WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."' "); if ($result): $result = $wpdb->get_row(" SELECT link_name, link_url FROM $wpdb->links WHERE link_id = '".$wpdb->escape($_REQUEST['link_id'])."' "); ?>

Feed for link_name, 'both'); ?> updated to <>.

Nothing was changed.

found()) : if (isset($GLOBALS['fwp_post']['save'])) : $alter = array (); $meta = $link->settings; if (isset($meta['cats'])): $meta['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $meta['cats']); endif; // custom feed settings first foreach ($GLOBALS['fwp_post']['notes'] as $mn) : $mn['key0'] = trim($mn['key0']); $mn['key1'] = trim($mn['key1']); if (preg_match("\007^((" .implode(')|(',$special_settings) ."))$\007i", $mn['key1'])) : $mn['key1'] = 'user/'.$mn['key1']; endif; if (strlen($mn['key0']) > 0) : unset($meta[$mn['key0']]); // out with the old endif; if (($mn['action']=='update') and (strlen($mn['key1']) > 0)) : $meta[$mn['key1']] = $mn['value']; // in with the new endif; endforeach; // now stuff through the web form // hardcoded feed info if (isset($GLOBALS['fwp_post']['hardcode_name'])) : $meta['hardcode name'] = $GLOBALS['fwp_post']['hardcode_name']; if (FeedWordPress::affirmative($meta, 'hardcode name')) : $alter[] = "link_name = '".$wpdb->escape($GLOBALS['fwp_post']['name'])."'"; endif; endif; if (isset($GLOBALS['fwp_post']['hardcode_description'])) : $meta['hardcode description'] = $GLOBALS['fwp_post']['hardcode_description']; if (FeedWordPress::affirmative($meta, 'hardcode description')) : $alter[] = "link_description = '".$wpdb->escape($GLOBALS['fwp_post']['description'])."'"; endif; endif; if (isset($GLOBALS['fwp_post']['hardcode_url'])) : $meta['hardcode url'] = $GLOBALS['fwp_post']['hardcode_url']; if (FeedWordPress::affirmative($meta, 'hardcode url')) : $alter[] = "link_url = '".$wpdb->escape($GLOBALS['fwp_post']['linkurl'])."'"; endif; endif; // Update scheduling if (isset($GLOBALS['fwp_post']['update_schedule'])) : $meta['update/hold'] = $GLOBALS['fwp_post']['update_schedule']; endif; // Categories if (isset($GLOBALS['fwp_post']['post_category'])) : $meta['cats'] = array(); foreach ($GLOBALS['fwp_post']['post_category'] as $cat_id) : $meta['cats'][] = '{#'.$cat_id.'}'; endforeach; else : unset($meta['cats']); endif; // Post status, comment status, ping status foreach (array('post', 'comment', 'ping') as $what) : $sfield = "feed_{$what}_status"; if (isset($GLOBALS['fwp_post'][$sfield])) : if ($GLOBALS['fwp_post'][$sfield]=='site-default') : unset($meta["{$what} status"]); else : $meta["{$what} status"] = $GLOBALS['fwp_post'][$sfield]; endif; endif; endforeach; // Unfamiliar author, unfamiliar categories foreach (array("author", "category") as $what) : $sfield = "unfamiliar_{$what}"; if (isset($GLOBALS['fwp_post'][$sfield])) : if ($GLOBALS['fwp_post'][$sfield]=='site-default') : unset($meta["unfamiliar {$what}"]); else : $meta["unfamiliar {$what}"] = $GLOBALS['fwp_post'][$sfield]; endif; endif; endforeach; if (isset($GLOBALS['fwp_post']['cat_split'])) : if (strlen(trim($GLOBALS['fwp_post']['cat_split'])) > 0) : $meta['cat_split'] = trim($GLOBALS['fwp_post']['cat_split']); else : unset($meta['cat_split']); endif; endif; if (is_array($meta['cats'])) : $meta['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $meta['cats']); endif; $notes = ''; foreach ($meta as $key => $value) : $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n"; endforeach; $alter[] = "link_notes = '".$wpdb->escape($notes)."'"; $alter_set = implode(", ", $alter); // issue update query $result = $wpdb->query(" UPDATE $wpdb->links SET $alter_set WHERE link_id='$link_id' "); $updated_link = true; // reload link information from DB $link =& new SyndicatedLink($link_id); else : $updated_link = false; endif; $db_link = $link->link; $link_url = wp_specialchars($db_link->link_url, 1); $link_name = wp_specialchars($db_link->link_name, 1); $link_description = wp_specialchars($db_link->link_description, 'both'); $link_notes = wp_specialchars($db_link->link_notes, 'both'); $link_rss_uri = wp_specialchars($db_link->link_rss, 'both'); $meta = $link->settings; $post_status_global = get_option('feedwordpress_syndicated_post_status'); $comment_status_global = get_option('feedwordpress_syndicated_comment_status'); $ping_status_global = get_option('feedwordpress_syndicated_ping_status'); $status['post'] = array('publish' => '', 'private' => '', 'draft' => '', 'site-default' => ''); $status['comment'] = array('open' => '', 'closed' => '', 'site-default' => ''); $status['ping'] = array('open' => '', 'closed' => '', 'site-default' => ''); foreach (array('post', 'comment', 'ping') as $what) : if (isset($meta["{$what} status"])) : $status[$what][$meta["{$what} status"]] = ' checked="checked"'; else : $status[$what]['site-default'] = ' checked="checked"'; endif; endforeach; $unfamiliar['author'] = array ('create' => '','default' => '','filter' => ''); $unfamiliar['category'] = array ('create'=>'','default'=>'','filter'=>''); foreach (array('author', 'category') as $what) : if (is_string($meta["unfamiliar {$what}"]) and array_key_exists($meta["unfamiliar {$what}"], $unfamiliar[$what])) : $key = $meta["unfamiliar {$what}"]; else: $key = 'site-default'; endif; $unfamiliar[$what][$key] = ' checked="checked"'; endforeach; $dogs = get_nested_categories(-1, 0); if (is_array($meta['cats'])) : $cats = array_map('strtolower', array_map('trim', $meta['cats'] )); else: $cats = array(); endif; foreach ($dogs as $tag => $dog) : $found_by_name = in_array(strtolower(trim($dog['cat_name'])), $cats); if (isset($dog['cat_ID'])) : $dog['cat_id'] = $dog['cat_ID']; endif; $found_by_id = in_array('{#'.trim($dog['cat_id']).'}', $cats); if ($found_by_name or $found_by_id) : $dogs[$tag]['checked'] = true; endif; endforeach; else : die( __('Link not found.') ); endif; ?>

Syndicated feed settings updated.

Edit a syndicated feed:

Basics
→
:
Next update:

Syndicated Posts
Publication:
Comments:
Trackback and Pingback:

Advanced Feed Options
Unfamiliar authors:
Unfamiliar categories:
Multiple categories:
Enter a Perl-compatible regular expression here if the feed provides multiple categories in a single category element. The regular expression should match the characters used to separate one category from the next. If the feed uses spaces (like del.icio.us), use the pattern "\s". If the feed does not provide multiple categories in a single element, leave this blank.

Custom Settings (for use in templates)
$value) : if (!preg_match("\007^((".implode(')|(', $special_settings)."))$\007i", $key)) : ?>
Key Value Action
add new setting...

$what) : $do_it[$what][] = $link_id; endforeach; $alter = array(); if (count($do_it['hide']) > 0) : $hidem = "(".implode(', ', $do_it['hide']).")"; $alter[] = " UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id IN {$hidem} "; endif; if (count($do_it['nuke']) > 0) : $nukem = "(".implode(', ', $do_it['nuke']).")"; // Make a list of the items syndicated from this feed... $post_ids = $wpdb->get_col(" SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'syndication_feed_id' AND meta_value IN {$nukem} "); // ... and kill them all if (count($post_ids) > 0) : foreach ($post_ids as $post_id) : wp_delete_post($post_id); endforeach; endif; $alter[] = " DELETE FROM $wpdb->links WHERE link_id IN {$nukem} "; endif; if (count($do_it['delete']) > 0) : $deletem = "(".implode(', ', $do_it['delete']).")"; // Make the items syndicated from this feed appear to be locally-authored $alter[] = " DELETE FROM $wpdb->postmeta WHERE meta_key = 'syndication_feed_id' AND meta_value IN {$deletem} "; // ... and delete the links themselves. $alter[] = " DELETE FROM $wpdb->links WHERE link_id IN {$deletem} "; endif; $errs = array(); $success = array (); foreach ($alter as $sql) : $result = $wpdb->query($sql); if (!$result): $errs[] = mysql_error(); endif; endforeach; if (count($alter) > 0) : echo "
\n"; if (count($errs) > 0) : echo "There were some problems processing your "; echo "unsubscribe request. [SQL: ".implode('; ', $errs)."]"; else : echo "Your unsubscribe request(s) have been processed."; endif; echo "
\n"; endif; return true; // Continue on to Syndicated Sites listing else : $targets = $wpdb->get_results(" SELECT * FROM $wpdb->links WHERE link_id IN (".implode(",",$link_ids).") "); ?>

Unsubscribe from Syndicated Links:

link_url, 1); $link_name = wp_specialchars($link->link_name, 1); $link_description = wp_specialchars($link->link_description, 'both'); $link_rss = wp_specialchars($link->link_rss, 'both'); ?>
Subscription :
feeds = array (); $links = FeedWordPress::syndicated_links(); if ($links): foreach ($links as $link): $this->feeds[] =& new SyndicatedLink($link); endforeach; endif; } // FeedWordPress::FeedWordPress () # function update (): polls for updates on one or more Contributor feeds # # Arguments: # ---------- # * $uri (string): either the URI of the feed to poll, the URI of the # (human-readable) website whose feed you want to poll, or NULL. # # If $uri is NULL, then FeedWordPress will poll any feeds that are # ready for polling. It will not poll feeds that are marked as # "Invisible" Links (signifying that the subscription has been # de-activated), or feeds that are not yet stale according to their # TTL setting (which is either set in the feed, or else set # randomly within a window of 30 minutes - 2 hours). # # Returns: # -------- # * Normally returns an associative array, with 'new' => the number # of new posts added during the update, and 'updated' => the number # of old posts that were updated during the update. If both numbers # are zero, there was no change since the last poll on that URI. # # * Returns NULL if URI it was passed was not a URI that this # installation of FeedWordPress syndicates. # # Effects: # -------- # * One or more feeds are polled for updates # # * If the feed Link does not have a hardcoded name set, its Link # Name is synchronized with the feed's title element # # * If the feed Link does not have a hardcoded URI set, its Link URI # is synchronized with the feed's human-readable link element # # * If the feed Link does not have a hardcoded description set, its # Link Description is synchronized with the feed's description, # tagline, or subtitle element. # # * The time of polling is recorded in the feed's settings, and the # TTL (time until the feed is next available for polling) is set # either from the feed (if it is supplied in the ttl or syndication # module elements) or else from a randomly-generated time window # (between 30 minutes and 2 hours). # # * New posts from the polled feed are added to the WordPress store. # # * Updates to existing posts since the last poll are mirrored in the # WordPress store. # function update ($uri = null) { global $wpdb; if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off return NULL; endif; if (!is_null($uri)) : $uri = trim($uri); else : // Update all update_option('feedwordpress_last_update_all', time()); endif; do_action('feedwordpress_update', $uri); // Loop through and check for new posts $delta = NULL; foreach ($this->feeds as $feed) : $pinged_that = (is_null($uri) or in_array($uri, array($feed->uri(), $feed->homepage()))); if (!is_null($uri)) : // A site-specific ping always updates $timely = true; else : $timely = $feed->stale(); endif; if ($pinged_that and is_null($delta)) : // If at least one feed was hit for updating... $delta = array('new' => 0, 'updated' => 0); // ... don't return error condition endif; if ($pinged_that and $timely) : do_action('feedwordpress_check_feed', $feed->settings); $added = $feed->poll(); if (isset($added['new'])) : $delta['new'] += $added['new']; endif; if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif; endif; endforeach; do_action('feedwordpress_update_complete', $delta); return $delta; } function stale () { if (get_option('feedwordpress_automatic_updates')) : $last = get_option('feedwordpress_last_update_all'); // If we haven't updated all yet, give it a time window if (false === $last) : $ret = false; update_option('feedwordpress_last_update_all', time()); // Otherwise, check against freshness interval elseif (is_numeric($last)) : // Expect a timestamp $freshness = get_option('feedwordpress_freshness'); if (false === $freshness) : // Use default $freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL; endif; $ret = ( (time() - $last) > $freshness); // This should never happen. else : FeedWordPress::critical_bug('FeedWordPress::stale::last', $last, __LINE__); endif; // Explicit request for an update (e.g. from a cron job). elseif (FeedWordPress::update_requested()) : $ret = true; else : $ret = false; endif; return $ret; } // FeedWordPress::stale() function update_requested () { return (isset($_REQUEST['update_feedwordpress']) and $_REQUEST['update_feedwordpress']); } // FeedWordPress::update_requested() function syndicate_link ($name, $uri, $rss) { global $wpdb; // Get the category ID# $cat_id = FeedWordPress::link_category_id(); // WordPress gets cranky if there's no homepage URI if (!isset($uri) or strlen($uri)<1) : $uri = $rss; endif; if (function_exists('wp_insert_link')) { // WordPress 2.x $link_id = wp_insert_link(array( "link_name" => $name, "link_url" => $uri, "link_category" => array($cat_id), "link_rss" => $rss )); } else { // WordPress 1.5.x $result = $wpdb->query(" INSERT INTO $wpdb->links SET link_name = '".$wpdb->escape($name)."', link_url = '".$wpdb->escape($uri)."', link_category = '".$wpdb->escape($cat_id)."', link_rss = '".$wpdb->escape($rss)."' "); $link_id = $wpdb->insert_id; } // if return $link_id; } // function FeedWordPress::syndicate_link() function on_unfamiliar ($what = 'author', $override = NULL) { $set = array('create', 'default', 'filter'); $ret = strtolower($override); if (!in_array($ret, $set)) : $ret = get_option('feedwordpress_unfamiliar_'.$what); if (!in_array($ret, $set)) : $ret = 'create'; endif; endif; return $ret; } // function FeedWordPress::on_unfamiliar() function syndicated_links () { $contributors = FeedWordPress::link_category_id(); if (function_exists('get_bookmarks')) : $links = get_bookmarks(array("category" => $contributors)); else: $links = get_linkobjects($contributors); // deprecated as of WP 2.1 endif; return $links; } // function FeedWordPress::syndicated_links() function link_category_id () { global $wpdb, $wp_db_version; $cat_id = get_option('feedwordpress_cat_id'); // If we don't yet *have* the category, we'll have to create it if ($cat_id === false) : $cat = $wpdb->escape(DEFAULT_SYNDICATION_CATEGORY); // Look for something with the right name... // ----------------------------------------- // WordPress 2.3 introduces a new taxonomy/term API if (function_exists('is_term')) : $cat_id = is_term($cat, 'link_category'); // WordPress 2.1 and 2.2 use a common table for both link and post categories elseif (isset($wp_db_version) and ($wp_db_version >= FWP_SCHEMA_21 and $wp_db_version < FWP_SCHEMA_23) ) : $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->categories} WHERE cat_name='$cat'"); // WordPress 1.5 and 2.0.x have a separate table for link categories elseif (!isset($wp_db_version) or $wp_db_version < FWP_SCHEMA_21) : $cat_id = $wpdb->get_var("SELECT cat_id FROM {$wpdb->linkcategories} WHERE cat_name='$cat'"); // This should never happen. else : FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__); endif; // If you still can't find anything, make it for yourself. // ------------------------------------------------------- if (!$cat_id) : // WordPress 2.3+ term/taxonomy API if (function_exists('wp_insert_term')) : $term = wp_insert_term($cat, 'link_category'); $cat_id = $term['term_id']; // WordPress 2.1, 2.2 category API. By the way, why the fuck is this API function only available in a wp-admin module? elseif (function_exists('wp_insert_category')) : $cat_id = wp_insert_category(array('cat_name' => $cat)); // WordPress 1.5 and 2.0.x elseif (!isset($wp_db_version) or $wp_db_version < FWP_SCHEMA_21) : $result = $wpdb->query(" INSERT INTO $wpdb->linkcategories SET cat_id = 0, cat_name='$cat', show_images='N', show_description='N', show_rating='N', show_updated='N', sort_order='name' "); $cat_id = $wpdb->insert_id; // This should never happen. else : FeedWordPress::critical_bug('FeedWordPress::link_category_id::wp_db_version', $wp_db_version, __LINE__); endif; endif; update_option('feedwordpress_cat_id', $cat_id); endif; return $cat_id; } // function FeedWordPress::link_category_id() # Upgrades and maintenance... function needs_upgrade () { global $wpdb; $fwp_db_version = get_option('feedwordpress_version'); $ret = false; // innocent until proven guilty if (!$fwp_db_version or $fwp_db_version < FEEDWORDPRESS_VERSION) : // This is an older version or a fresh install. Does it // require a database upgrade or database initialization? if ($fwp_db_version > 0.96) : // No. Just brand it with the new version. update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); else : // Yes. Check to see whether this is a fresh install or an upgrade. $syn = $wpdb->get_col(" SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'syndication_feed' "); if (count($syn) > 0) : // contains at least one syndicated post $ret = true; else : // fresh install; brand it as ours update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); endif; endif; endif; return $ret; } function upgrade_database ($from = NULL) { global $wpdb; if (is_null($from) or $from <= 0.96) : $from = 0.96; endif; switch ($from) : case 0.96: // account for changes to syndication custom values and guid echo "

Upgrading database from {$from} to ".FEEDWORDPRESS_VERSION."...

\n"; $cat_id = FeedWordPress::link_category_id(); // Avoid duplicates $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'"); // Look up all the link IDs $wpdb->query(" CREATE TEMPORARY TABLE tmp_custom_values SELECT NULL AS meta_id, post_id, 'syndication_feed_id' AS meta_key, link_id AS meta_value FROM `{$wpdb->postmeta}`, `{$wpdb->links}` WHERE meta_key='syndication_feed' AND meta_value=link_rss AND link_category = {$cat_id} "); // Now attach them to their posts $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values"); // And clean up after ourselves. $wpdb->query("DROP TABLE tmp_custom_values"); // Now fix the guids to avoid duplicate posts echo "
    "; foreach ($this->feeds as $feed) : echo "
  • Fixing post meta-data for ".$feed['link/name']." … "; flush(); $rss = @fetch_rss($feed['link/uri']); if (is_array($rss->items)) : foreach ($rss->items as $item) : $guid = $wpdb->escape(FeedWordPress::guid($item, $feed)); // new GUID algorithm $link = $wpdb->escape($item['link']); $wpdb->query(" UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}' "); endforeach; endif; echo "complete.
  • \n"; endforeach; echo "
\n"; // Mark the upgrade as successful. update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); endswitch; echo "

Upgrade complete. FeedWordPress is now ready to use again.

"; } /* FeedWordPress::upgrade_database() */ # Utility functions for handling text settings function negative ($f, $setting) { $nego = array ('n', 'no', 'f', 'false'); return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $nego)); } function affirmative ($f, $setting) { $affirmo = array ('y', 'yes', 't', 'true', 1); return (isset($f[$setting]) and in_array(strtolower($f[$setting]), $affirmo)); } # Internal debugging functions function critical_bug ($varname, $var, $line) { global $wp_version; echo '

There may be a bug in FeedWordPress. Please contact the author and paste the following information into your e-mail:

'; echo "\n"; echo "Triggered at line # ".$line."\n"; echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n"; echo "WordPress version: $wp_version\n"; echo "PHP version: ".phpversion()."\n"; echo "\n"; echo $varname.": "; var_dump($var); echo "\n"; die; } } // class FeedWordPress class SyndicatedPost { var $item = null; var $link = null; var $feed = null; var $feedmeta = null; var $post = array (); var $_base = null; var $_freshness = null; var $_wp_id = null; var $strip_attrs = array ( array('[a-z]+', 'style'), array('[a-z]+', 'target'), ); function SyndicatedPost ($item, $link) { global $wpdb; $this->link = $link; $feedmeta = $link->settings; $feed = $link->magpie; # This is ugly as all hell. I'd like to use apply_filters()'s # alleged support for a variable argument count, but this seems # to have been broken in WordPress 1.5. It'll be fixed somehow # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across # the board here. # # Cf.: <http://mosquito.wordpress.org/view.php?id=901> global $fwp_channel, $fwp_feedmeta; $fwp_channel = $feed; $fwp_feedmeta = $feedmeta; $this->feed = $feed; $this->feedmeta = $feedmeta; $this->item = $item; $this->item = apply_filters('syndicated_item', $this->item, $this); # Filters can halt further processing by returning NULL if (is_null($this->item)) : $this->post = NULL; else : # Note that nothing is run through $wpdb->escape() here. # That's deliberate. The escaping is done at the point # of insertion, not here, to avoid double-escaping and # to avoid screwing with syndicated_post filters $this->post['post_title'] = $this->item['title']; // This just gives usan alphanumeric representation of // the author. We will look up (or create) the numeric // ID for the author in SyndicatedPost::add() $this->post['named']['author'] = $this->author(); # Identify content and sanitize it. # --------------------------------- if (isset($this->item['xhtml']['body'])) : $content = $this->item['xhtml']['body']; elseif (isset($this->item['xhtml']['div'])) : $content = $this->item['xhtml']['div']; elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']): $content = $this->item['content']['encoded']; else: $content = $this->item['description']; endif; # FeedWordPress used to resolve URIs relative to the # feed URI. It now relies on the xml:base support # baked in to the MagpieRSS upgrade. So all we do here # now is to sanitize problematic attributes. # # This kind of sucks. I intend to replace it with # lib_filter sometime soon. foreach ($this->strip_attrs as $pair): list($tag,$attr) = $pair; $content = preg_replace ( ":(<$tag [^>]*)($attr=(\"[^\">]*\"|[^>\\s]+))([^>]*>):i", "\\1\\4", $content ); endforeach; # Identify and sanitize excerpt $excerpt = NULL; if ( isset($this->item['description']) and $this->item['description'] ) : $excerpt = $this->item['description']; elseif ( isset($content) and $content ) : $excerpt = strip_tags($content); if (strlen($excerpt) > 255) : $excerpt = substr($excerpt,0,252).'...'; endif; endif; $this->post['post_content'] = $content; if (!is_null($excerpt)): $this->post['post_excerpt'] = $excerpt; endif; // This is unnecessary if we use wp_insert_post if (!$this->use_api('wp_insert_post')) : $this->post['post_name'] = sanitize_title($this->post['post_title']); endif; $this->post['epoch']['issued'] = $this->published(); $this->post['epoch']['created'] = $this->created(); $this->post['epoch']['modified'] = $this->updated(); // Dealing with timestamps in WordPress is so fucking fucked. $offset = (int) get_option('gmt_offset') * 60 * 60; $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset); $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset); $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published()); $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated()); // Use feed-level preferences or the global default. $this->post['post_status'] = $this->link->syndicated_status('post', 'publish'); $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed'); $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed'); // Unique ID (hopefully a unique tag: URI); failing that, the permalink $this->post['guid'] = $this->guid(); // RSS 2.0 / Atom 1.0 enclosure support if ( isset($this->item['enclosure#']) ) : for ($i = 1; $i <= $this->item['enclosure#']; $i++) : $eid = (($i > 1) ? "#{$id}" : ""); $this->post['meta']['enclosure'][] = $this->item["enclosure{$eid}@url"]."\n". $this->item["enclosure{$eid}@length"]."\n". $this->item["enclosure{$eid}@type"]; endfor; endif; // In case you want to point back to the blog this was syndicated from if (isset($this->feed->channel['title'])) : $this->post['meta']['syndication_source'] = $this->feed->channel['title']; endif; if (isset($this->feed->channel['link'])) : $this->post['meta']['syndication_source_uri'] = $this->feed->channel['link']; endif; // Store information on human-readable and machine-readable comment URIs if (isset($this->item['comments'])) : $this->post['meta']['rss:comments'] = $this->item['comments']; endif; if (isset($this->item['wfw']['commentrss'])) : $this->post['meta']['wfw:commentRSS'] = $this->item['wfw']['commentrss']; endif; // Store information to identify the feed that this came from $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri']; $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id']; // In case you want to know the external permalink... $this->post['meta']['syndication_permalink'] = $this->item['link']; // Feed-by-feed options for author and category creation $this->post['named']['unfamiliar']['author'] = $this->feedmeta['unfamiliar author']; $this->post['named']['unfamiliar']['category'] = $this->feedmeta['unfamiliar categories']; // Categories: start with default categories, if any $fc = get_option("feedwordpress_syndication_cats"); if ($fc) : $this->post['named']['preset/category'] = explode("\n", $fc); else : $this->post['named']['preset/category'] = array(); endif; if (is_array($this->feedmeta['cats'])) : $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']); endif; // Now add categories from the post, if we have 'em $this->post['named']['category'] = array(); if ( isset($this->item['category#']) ) : for ($i = 1; $i <= $this->item['category#']; $i++) : $cat_idx = (($i > 1) ? "#{$i}" : ""); $cat = $this->item["category{$cat_idx}"]; if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) : $pcre = "\007".$this->feedmeta['cat_split']."\007"; $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY)); else : $this->post['named']['category'][] = $cat; endif; endfor; endif; endif; } // SyndicatedPost::SyndicatedPost() function filtered () { return is_null($this->post); } function freshness () { global $wpdb; if ($this->filtered()) : // This should never happen. FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__); endif; if (is_null($this->_freshness)) : $guid = $wpdb->escape($this->guid()); $result = $wpdb->get_row(" SELECT id, guid, post_modified_gmt FROM $wpdb->posts WHERE guid='$guid' "); preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref); $updated = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]); if (!$result) : $this->_freshness = 2; // New content elseif ($this->updated() > $updated) : $this->_freshness = 1; // Updated content $this->_wp_id = $result->id; else : $this->_freshness = 0; // Same old, same old $this->_wp_id = $result->id; endif; endif; return $this->_freshness; } function wp_id () { if ($this->filtered()) : // This should never happen. FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__); endif; if (is_null($this->_wp_id) and is_null($this->_freshness)) : $fresh = $this->freshness(); // sets WP DB id in the process endif; return $this->_wp_id; } function store () { global $wpdb; if ($this->filtered()) : // This should never happen. FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__); endif; $freshness = $this->freshness(); if ($freshness > 0) : # -- Look up, or create, numeric ID for author $this->post['post_author'] = $this->author_id ( FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author']) ); if (is_null($this->post['post_author'])) : $this->post = NULL; endif; endif; if (!$this->filtered() and $freshness > 0) : # -- Look up, or create, numeric ID for categories $this->post['post_category'] = $this->category_ids ( $this->post['named']['category'], FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']) ); if (is_null($this->post['post_category'])) : // filter mode on, no matching categories; drop the post $this->post = NULL; else : // filter mode off or at least one match; now add on the feed and global presets $this->post['post_category'] = array_merge ( $this->post['post_category'], $this->category_ids ( $this->post['named']['preset/category'], 'default' ) ); if (count($this->post['post_category']) < 1) : $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else endif; endif; endif; if (!$this->filtered() and $freshness > 0) : unset($this->post['named']); $this->post = apply_filters('syndicated_post', $this->post, $this); endif; if (!$this->filtered() and $freshness == 2) : // The item has not yet been added. So let's add it. $this->insert_new(); $this->add_rss_meta(); do_action('post_syndicated_item', $this->wp_id()); $ret = 'new'; elseif (!$this->filtered() and $freshness == 1) : $this->post['ID'] = $this->wp_id(); $this->update_existing(); $this->add_rss_meta(); do_action('update_syndicated_item', $this->wp_id()); $ret = 'updated'; else : $ret = false; endif; return $ret; } // function SyndicatedPost::store () function insert_new () { global $wpdb, $wp_db_version; // Why the fuck doesn't wp_insert_post already do this? foreach ($this->post as $key => $value) : if (is_string($value)) : $dbpost[$key] = $wpdb->escape($value); else : $dbpost[$key] = $value; endif; endforeach; if ($this->use_api('wp_insert_post')) : $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks $this->_wp_id = wp_insert_post($dbpost); // This should never happen. if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) : FeedWordPress::critical_bug('SyndicatedPost (_wp_id problem)', $this, __LINE__); endif; // Unfortunately, as of WordPress 2.3, wp_insert_post() // *still* offers no way to use a guid of your choice, // and munges your post modified timestamp, too. $result = $wpdb->query(" UPDATE $wpdb->posts SET guid='{$dbpost['guid']}', post_modified='{$dbpost['post_modified']}', post_modified_gmt='{$dbpost['post_modified_gmt']}' WHERE ID='{$this->_wp_id}' "); else : # The right way to do this is the above. But, alas, # in earlier versions of WordPress, wp_insert_post has # too much behavior (mainly related to pings) that can't # be overridden. In WordPress 1.5, it's enough of a # resource hog to make PHP segfault after inserting # 50-100 posts. This can get pretty annoying, especially # if you are trying to update your feeds for the first # time. $result = $wpdb->query(" INSERT INTO $wpdb->posts SET guid = '{$dbpost['guid']}', post_author = '{$dbpost['post_author']}', post_date = '{$dbpost['post_date']}', post_date_gmt = '{$dbpost['post_date_gmt']}', post_content = '{$dbpost['post_content']}'," .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")." post_title = '{$dbpost['post_title']}', post_name = '{$dbpost['post_name']}', post_modified = '{$dbpost['post_modified']}', post_modified_gmt = '{$dbpost['post_modified_gmt']}', comment_status = '{$dbpost['comment_status']}', ping_status = '{$dbpost['ping_status']}', post_status = '{$dbpost['post_status']}' "); $this->_wp_id = $wpdb->insert_id; // This should never happen. if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) : FeedWordPress::critical_bug('SyndicatedPost::_wp_id', $this->_wp_id, __LINE__); endif; // WordPress 1.5.x - 2.0.x wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']); // Since we are not going through official channels, we need to // manually tell WordPress that we've published a new post. // We need to make sure to do this in order for FeedWordPress // to play well with the staticize-reloaded plugin (something // that a large aggregator website is going to *want* to be // able to use). do_action('publish_post', $this->_wp_id); endif; } /* SyndicatedPost::insert_new() */ function update_existing () { global $wpdb; // Why the fuck doesn't wp_insert_post already do this? $dbpost = array(); foreach ($this->post as $key => $value) : if (is_string($value)) : $dbpost[$key] = $wpdb->escape($value); else : $dbpost[$key] = $value; endif; endforeach; if ($this->use_api('wp_insert_post')) : $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks $this->_wp_id = wp_insert_post($dbpost); // This should never happen. if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) : FeedWordPress::critical_bug('SyndicatedPost::_wp_id', $this->_wp_id, __LINE__); endif; // Unfortunately, as of WordPress 2.3, wp_insert_post() // munges your post modified timestamp. $result = $wpdb->query(" UPDATE $wpdb->posts SET post_modified='{$dbpost['post_modified']}', post_modified_gmt='{$dbpost['post_modified_gmt']}' WHERE ID='{$this->_wp_id}' "); else : $result = $wpdb->query(" UPDATE $wpdb->posts SET post_author = '{$dbpost['post_author']}', post_content = '{$dbpost['post_content']}'," .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")." post_title = '{$dbpost['post_title']}', post_name = '{$dbpost['post_name']}', post_modified = '{$dbpost['post_modified']}', post_modified_gmt = '{$dbpost['post_modified_gmt']}' WHERE guid='{$dbpost['guid']}' "); // WordPress 2.1.x and up if (function_exists('wp_set_post_categories')) : wp_set_post_categories($this->wp_id(), $this->post['post_category']); // WordPress 1.5.x - 2.0.x elseif (function_exists('wp_set_post_cats')) : wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']); // This should never happen. else : FeedWordPress::critical_bug('SyndicatedPost::insert_new::wp_version', $GLOBALS['wp_version'], __LINE__); endif; // Since we are not going through official channels, we need to // manually tell WordPress that we've published a new post. // We need to make sure to do this in order for FeedWordPress // to play well with the staticize-reloaded plugin (something // that a large aggregator website is going to *want* to be // able to use). do_action('edit_post', $this->post['ID']); endif; } /* SyndicatedPost::update_existing() */ // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry // using the space for custom keys. The set of keys and values to add is // specified by the keys and values of $post['meta']. This is used to // store anything that the WordPress user might want to access from a // template concerning the post's original source that isn't provided // for by standard WP meta-data (i.e., any interesting data about the // syndicated post other than author, title, timestamp, categories, and // guid). It's also used to hook into WordPress's support for // enclosures. function add_rss_meta () { global $wpdb; if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) : $postId = $this->wp_id(); // Aggregated posts should NOT send out pingbacks. // WordPress 2.1-2.2 claim you can tell them not to // using $post_pingback, but they don't listen, so we // make sure here. $result = $wpdb->query(" DELETE FROM $wpdb->postmeta WHERE post_id='$postId' AND meta_key='_pingme' "); foreach ( $this->post['meta'] as $key => $values ) : $key = $wpdb->escape($key); // If this is an update, clear out the old // values to avoid duplication. $result = $wpdb->query(" DELETE FROM $wpdb->postmeta WHERE post_id='$postId' AND meta_key='$key' "); // Allow for either a single value or an array if (!is_array($values)) $values = array($values); foreach ( $values as $value ) : $value = $wpdb->escape($value); $result = $wpdb->query(" INSERT INTO $wpdb->postmeta SET post_id='$postId', meta_key='$key', meta_value='$value' "); endforeach; endforeach; endif; } /* SyndicatedPost::add_rss_meta () */ // SyndicatedPost::author_id (): get the ID for an author name from // the feed. Create the author if necessary. function author_id ($unfamiliar_author = 'create') { global $wpdb, $wp_db_version; // test for WordPress 2.0 database schema $a = $this->author(); $author = $a['name']; $email = $a['email']; $url = $a['uri']; // Never can be too careful... $login = sanitize_user($author, /*strict=*/ true); $login = apply_filters('pre_user_login', $login); $nice_author = sanitize_title($author); $nice_author = apply_filters('pre_user_nicename', $nice_author); $reg_author = $wpdb->escape(preg_quote($author)); $author = $wpdb->escape($author); $email = $wpdb->escape($email); $url = $wpdb->escape($url); // Look for an existing author record that fits... if (!isset($wp_db_version)) : #-- WordPress 1.5.x $id = $wpdb->get_var( "SELECT ID from $wpdb->users WHERE TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR ( LENGTH(TRIM(LCASE(user_email))) > 0 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$email')) ) OR TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR ( LOWER(user_description) RLIKE CONCAT( '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LCASE('$reg_author'), '( |\\t|\\r)*(\\n|\$)' ) ) "); elseif ($wp_db_version >= 2966) : #-- WordPress 2.0+ # First try the user core data table. $id = $wpdb->get_var( "SELECT ID FROM $wpdb->users WHERE TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR ( LENGTH(TRIM(LCASE(user_email))) > 0 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$email')) ) OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) "); if (is_null($id)) : # Then look for aliases in the user meta data table $id = $wpdb->get_var( "SELECT user_id FROM $wpdb->usermeta WHERE (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author'))) OR ( meta_key = 'description' AND TRIM(LCASE(meta_value)) RLIKE CONCAT( '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', TRIM(LCASE('$reg_author')), '( |\\t|\\r)*(\\n|\$)' ) ) "); endif; endif; // ... if you don't find one, then do what you need to do if (is_null($id)) : if ($unfamiliar_author === 'create') : if (!isset($wp_db_version)) : #-- WordPress 1.5.x $wpdb->query ( "INSERT INTO $wpdb->users SET ID='0', user_login='$login', user_firstname='$author', user_nickname='$author', user_nicename='$nice_author', user_description='$author', user_email='$email', user_url='$url'"); $id = $wpdb->insert_id; elseif ($wp_db_version >= 2966) : #-- WordPress 2.0+ $userdata = array(); #-- user table data $userdata['ID'] = NULL; // new user $userdata['user_login'] = $login; $userdata['user_nicename'] = $nice_author; $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up $userdata['user_email'] = $email; $userdata['user_url'] = $url; $userdata['display_name'] = $author; $id = wp_insert_user($userdata); endif; elseif ($unfamiliar_author === 'default') : $id = 1; endif; endif; return $id; } // function SyndicatedPost::author_id () // look up (and create) category ids from a list of categories function category_ids ($cats, $unfamiliar_category = 'create') { global $wpdb; // We need to normalize whitespace because (1) trailing // whitespace can cause PHP and MySQL not to see eye to eye on // VARCHAR comparisons for some versions of MySQL (cf. // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2) // because I doubt most people want to make a semantic // distinction between 'Computers' and 'Computers ' $cats = array_map('trim', $cats); $cat_ids = array (); foreach ($cats as $cat_name) : if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) : $cat_id = (int) $backref[1]; if (function_exists('is_term') and is_term($cat_id, 'category')) : $cat_ids[] = $cat_id; elseif (get_category($cat_id)) : $cat_ids[] = $cat_id; endif; else : $esc = $wpdb->escape($cat_name); $resc = $wpdb->escape(preg_quote($cat_name)); // WordPress 2.3+ if (function_exists('is_term')) : $cat_id = is_term($cat_name, 'category'); if ($cat_id) : $cat_ids[] = $cat_id['term_id']; // There must be a better way to do this... elseif ($results = $wpdb->get_results( "SELECT term_id FROM $wpdb->term_taxonomy WHERE LOWER(description) RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')" )) : foreach ($results AS $term) : $cat_ids[] = (int) $term->term_id; endforeach; elseif ('create'===$unfamiliar_category) : $term = wp_insert_term($cat_name, 'category'); $cat_ids[] = $term['term_id']; endif; // WordPress 1.5.x - 2.2.x else : $results = $wpdb->get_results( "SELECT cat_ID FROM $wpdb->categories WHERE (LOWER(cat_name) = LOWER('$esc')) OR (LOWER(category_description) RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')) "); if ($results) : foreach ($results as $term) : $cat_ids[] = (int) $term->cat_ID; endforeach; elseif ('create'===$unfamiliar_category) : if (function_exists('wp_insert_category')) : $cat_id = wp_insert_category(array('cat_name' => $cat_name)); // And into the database we go. else : $nice_kitty = sanitize_title($cat_name); $wpdb->query(sprintf(" INSERT INTO $wpdb->categories SET cat_name='%s', category_nicename='%s' ", $wpdb->escape($cat_name), $nice_kitty )); $cat_id = $wpdb->insert_id; endif; $cat_ids[] = $cat_id; endif; endif; endif; endforeach; if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) : $cat_ids = NULL; // Drop the post else : $cat_ids = array_unique($cat_ids); endif; return $cat_ids; } // function SyndicatedPost::category_ids () function use_api ($tag) { global $wp_db_version; if ('wp_insert_post'==$tag) : // Before 2.2, wp_insert_post does too much of the wrong stuff to use it // In 1.5 it was such a resource hog it would make PHP segfault on big updates $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21); endif; return $ret; } // function SyndicatedPost::use_api () #### EXTRACT DATA FROM FEED ITEM #### function created () { $epoch = null; if (isset($this->item['dc']['created'])) : $epoch = @parse_w3cdtf($this->item['dc']['created']); elseif (isset($this->item['dcterms']['created'])) : $epoch = @parse_w3cdtf($this->item['dcterms']['created']); elseif (isset($this->item['created'])): // Atom 0.3 $epoch = @parse_w3cdtf($this->item['created']); endif; return $epoch; } function published ($fallback = true) { $epoch = null; # RSS is a fucking mess. Figure out whether we have a date in # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix # epoch format for reformatting. If we can't find anything, # we'll use the last-updated time. if (isset($this->item['dc']['date'])): // Dublin Core $epoch = @parse_w3cdtf($this->item['dc']['date']); elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions $epoch = @parse_w3cdtf($this->item['dcterms']['issued']); elseif (isset($this->item['published'])) : // Atom 1.0 $epoch = @parse_w3cdtf($this->item['published']); elseif (isset($this->item['issued'])): // Atom 0.3 $epoch = @parse_w3cdtf($this->item['issued']); elseif (isset($this->item['pubdate'])): // RSS 2.0 $epoch = strtotime($this->item['pubdate']); elseif ($fallback) : // Fall back to <updated> / <modified> if present $epoch = $this->updated(/*fallback=*/ false); endif; # If everything failed, then default to the current time. if (is_null($epoch)) : $epoch = time(); endif; return $epoch; } function updated ($fallback = true) { $epoch = null; # As far as I know, only dcterms and Atom have reliable ways to # specify when something was *modified* last. If neither is # available, then we'll try to get the time of publication. if (isset($this->item['dc']['modified'])) : // Not really correct $epoch = @parse_w3cdtf($this->item['dc']['modified']); elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions $epoch = @parse_w3cdtf($this->item['dcterms']['modified']); elseif (isset($this->item['modified'])): // Atom 0.3 $epoch = @parse_w3cdtf($this->item['modified']); elseif (isset($this->item['updated'])): // Atom 1.0 $epoch = @parse_w3cdtf($this->item['updated']); elseif ($fallback) : // Fall back to issued / dc:date $epoch = $this->published(/*fallback=*/ false); endif; # If everything failed, then default to the current time. if (is_null($epoch)) : $epoch = time(); endif; return $epoch; } function guid () { $guid = null; if (isset($this->item['id'])): // Atom 0.3 / 1.0 $guid = $this->item['id']; elseif (isset($this->item['atom']['id'])) : // Namespaced Atom $guid = $this->item['atom']['id']; elseif (isset($this->item['guid'])) : // RSS 2.0 $guid = $this->item['guid']; elseif (isset($this->item['dc']['identifier'])) :// yeah, right $guid = $this->item['dc']['identifier']; else : // The feed does not seem to have provided us with a // unique identifier, so we'll have to cobble together // a tag: URI that might work for us. The base of the // URI will be the host name of the feed source ... $bits = parse_url($this->feedmeta['link/uri']); $guid = 'tag:'.$bits['host']; // If we have a date of creation, then we can use that // to uniquely identify the item. (On the other hand, if // the feed producer was consicentious enough to // generate dates of creation, she probably also was // conscientious enough to generate unique identifiers.) if (!is_null($this->created())) : $guid .= '://post.'.date('YmdHis', $this->created()); // Otherwise, use both the URI of the item, *and* the // item's title. We have to use both because titles are // often not unique, and sometimes links aren't unique // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news, // some podcasts). But it's rare to have *both* the same // title *and* the same link for two different items. So // this is about the best we can do. else : $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']); endif; endif; return $guid; } function author () { $author = array (); if (isset($this->item['author_name'])): $author['name'] = $this->item['author_name']; elseif (isset($this->item['dc']['creator'])): $author['name'] = $this->item['dc']['creator']; elseif (isset($this->item['dc']['contributor'])): $author['name'] = $this->item['dc']['contributor']; elseif (isset($this->feed->channel['dc']['creator'])) : $author['name'] = $this->feed->channel['dc']['creator']; elseif (isset($this->feed->channel['dc']['contributor'])) : $author['name'] = $this->feed->channel['dc']['contributor']; elseif (isset($this->feed->channel['author_name'])) : $author['name'] = $this->feed->channel['author_name']; elseif ($this->feed->is_rss() and isset($this->item['author'])) : // The author element in RSS is allegedly an // e-mail address, but lots of people don't use // it that way. So let's make of it what we can. $author = parse_email_with_realname($this->item['author']); if (!isset($author['name'])) : if (isset($author['email'])) : $author['name'] = $author['email']; else : $author['name'] = $this->feed->channel['title']; endif; endif; else : $author['name'] = $this->feed->channel['title']; endif; if (isset($this->item['author_email'])): $author['email'] = $this->item['author_email']; elseif (isset($this->feed->channel['author_email'])) : $author['email'] = $this->feed->channel['author_email']; endif; if (isset($this->item['author_url'])): $author['uri'] = $this->item['author_url']; elseif (isset($this->feed->channel['author_url'])) : $author['uri'] = $this->item['author_url']; else: $author['uri'] = $this->feed->channel['link']; endif; return $author; } // SyndicatedPost::author() } // class SyndicatedPost # class SyndicatedLink: represents a syndication feed stored within the # WordPress database # # To keep things compact and editable from within WordPress, we use all the # links under a particular category in the WordPress "Blogroll" for the list of # feeds to syndicate. "Contributors" is the category used by default; you can # configure that under Options --> Syndication. # # Fields used are: # # * link_rss: the URI of the Atom/RSS feed to syndicate # # * link_notes: user-configurable options, with keys and values # like so: # # key: value # cats: computers\nweb # feed/key: value # # Keys that start with "feed/" are gleaned from the data supplied # by the feed itself, and will be overwritten with each update. # # Values have linebreak characters escaped with C-style # backslashes (so, for example, a newline becomes "\n"). # # The value of `cats` is used as a newline-separated list of # default categories for any post coming from a particular feed. # (In the example above, any posts from this feed will be placed # in the "computers" and "web" categories--*in addition to* any # categories that may already be applied to the posts.) # # Values of keys in link_notes are accessible from templates using # the function `get_feed_meta($key)` if this plugin is activated. class SyndicatedLink { var $id = null; var $link = null; var $settings = array (); var $magpie = null; function SyndicatedLink ($link) { global $wpdb; if (is_object($link)) : $this->link = $link; $this->id = $link->link_id; else : $this->id = $link; if (function_exists('get_bookmark')) : // WP 2.1+ $this->link = get_bookmark($link); else : $this->link = $wpdb->get_row(" SELECT * FROM $wpdb->links WHERE (link_id = '".$wpdb->escape($link)."')" ); endif; endif; if (strlen($this->link->link_rss) > 0) : // Read off feed settings from link_notes $notes = explode("\n", $this->link->link_notes); foreach ($notes as $note): list($key, $value) = explode(": ", $note, 2); if (strlen($key) > 0) : // Unescape and trim() off the whitespace. // Thanks to Ray Lischner for pointing out the // need to trim off whitespace. $this->settings[$key] = stripcslashes (trim($value)); endif; endforeach; // "Magic" feed settings $this->settings['link/uri'] = $this->link->link_rss; $this->settings['link/name'] = $this->link->link_name; $this->settings['link/id'] = $this->link->link_id; // `hardcode categories` is deprecated in favor of `unfamiliar categories` if ( FeedWordPress::affirmative($this->settings, 'hardcode categories') and !isset($this->settings['unfamiliar categories']) ) : $this->settings['unfamiliar categories'] = 'default'; endif; // Set this up automagically for del.icio.us if (!isset($this->settings['cat_split']) and false !== strpos($this->link->link_rss, 'del.icio.us')) : $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds endif; if (isset($this->settings['cats'])): $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']); endif; endif; } // SyndicatedLink::SyndicatedLink () function found () { return is_object($this->link); } function stale () { $stale = true; if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) : $stale = false; // don't update on any timed updates; pings only elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) : $stale = true; // update on the next timed update elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) : $stale = true; // initial update else : $after = ((int) $this->settings['update/last']) +((int) $this->settings['update/ttl'] * 60); $stale = (time() >= $after); endif; return $stale; } function poll () { global $wpdb; $this->magpie = fetch_rss($this->link->link_rss); $new_count = NULL; if (is_object($this->magpie)) : $new_count = array('new' => 0, 'updated' => 0); # -- Update Link metadata live from feed $channel = $this->magpie->channel; if (!isset($channel['id'])) : $channel['id'] = $this->link->link_rss; endif; $update = array(); if (!$this->hardcode('url') and isset($channel['link'])) : $update[] = "link_url = '".$wpdb->escape($channel['link'])."'"; endif; if (!$this->hardcode('name') and isset($channel['title'])) : $update[] = "link_name = '".$wpdb->escape($channel['title'])."'"; endif; if (!$this->hardcode('description')) : if (isset($channel['tagline'])) : $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'"; elseif (isset($channel['description'])) : $update[] = "link_description = '".$wpdb->escape($channel['description'])."'"; endif; endif; $this->settings = array_merge($this->settings, $this->flatten_array($channel)); $this->settings['update/last'] = time(); $ttl = $this->ttl(); if (!is_null($ttl)) : $this->settings['update/ttl'] = $ttl; $this->settings['update/timed'] = 'feed'; else : $this->settings['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates $this->settings['update/timed'] = 'automatically'; endif; if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') : $this->settings['update/hold'] = 'scheduled'; endif; // Copy back without a few things that we don't want to save in the notes $to_notes = $this->settings; if (is_array($to_notes['cats'])) : $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']); endif; unset($to_notes['link/id']); unset($to_notes['link/uri']); unset($to_notes['link/name']); unset($to_notes['hardcode categories']); // Deprecated $notes = ''; foreach ($to_notes as $key => $value) : $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n"; endforeach; $update[] = "link_notes = '".$wpdb->escape($notes)."'"; $update_set = implode(',', $update); // Update the properties of the link from the feed information $result = $wpdb->query(" UPDATE $wpdb->links SET $update_set WHERE link_id='$this->id' "); # -- Add new posts from feed and update any updated posts if (is_array($this->magpie->items)) : foreach ($this->magpie->items as $item) : $post =& new SyndicatedPost($item, $this); if (!$post->filtered()) : $new = $post->store(); if ( $new !== false ) $new_count[$new]++; endif; endforeach; endif; endif; return $new_count; } /* SyndicatedLink::poll() */ function uri () { return (is_object($this->link) ? $this->link->link_rss : NULL); } function homepage () { return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL); } function ttl () { if (is_object($this->magpie)) : $channel = $this->magpie->channel; else : $channel = array(); endif; if (isset($channel['ttl'])) : // "ttl stands for time to live. It's a number of // minutes that indicates how long a channel can be // cached before refreshing from the source." // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt> $ret = $channel['ttl']; elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) : $period_minutes = array ( 'hourly' => 60, /* minutes in an hour */ 'daily' => 1440, /* minutes in a day */ 'weekly' => 10080, /* minutes in a week */ 'monthly' => 43200, /* minutes in a month */ 'yearly' => 525600, /* minutes in a year */ ); // "sy:updatePeriod: Describes the period over which the // channel format is updated. Acceptable values are: // hourly, daily, weekly, monthly, yearly. If omitted, // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/> if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod']; else : $period = 'daily'; endif; // "sy:updateFrequency: Used to describe the frequency // of updates in relation to the update period. A // positive integer indicates how many times in that // period the channel is updated. ... If omitted a value // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/> if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency']; else : $freq = 1; endif; $ret = (int) ($period_minutes[$period] / $freq); else : $ret = NULL; endif; return $ret; } /* SyndicatedLink::ttl() */ // SyndicatedLink:flatten_array (): flatten an array. Useful for // hierarchical and namespaced elements. // // Given an array which may contain array or object elements in it, // return a "flattened" array: a one-dimensional array of scalars // containing each of the scalar elements contained within the array // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the // returned array for FeedWordPress::flatten_array($a) will contain a key // $a['feed/b/c/d'] with value 'e'. function flatten_array ($arr, $prefix = 'feed/', $separator = '/') { $ret = array (); if (is_array($arr)) : foreach ($arr as $key => $value) : if (is_scalar($value)) : $ret[$prefix.$key] = $value; else : $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator)); endif; endforeach; endif; return $ret; } // function SyndicatedLink::flatten_array () function hardcode ($what) { $default = get_option("feedwordpress_hardcode_$what"); if ( $default === 'yes' ) : // If the default is to hardcode, then we want the // negation of negative(): TRUE by default and FALSE if // the setting is explicitly "no" $ret = !FeedWordPress::negative($this->settings, "hardcode $what"); else : // If the default is NOT to hardcode, then we want // affirmative(): FALSE by default and TRUE if the // setting is explicitly "yes" $ret = FeedWordPress::affirmative($this->settings, "hardcode $what"); endif; return $ret; } // function SyndicatedLink::hardcode () function syndicated_status ($what, $default) { global $wpdb; $ret = get_option("feedwordpress_syndicated_{$what}_status"); if ( isset($this->settings["$what status"]) ) : $ret = $this->settings["$what status"]; elseif (!$ret) : $ret = $default; endif; return $wpdb->escape(trim(strtolower($ret))); } // function SyndicatedLink:syndicated_status () } // class SyndicatedLink ################################################################################ ## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################ ################################################################################ function feedwordpress_xmlrpc_hook ($args = array ()) { $args['weblogUpdates.ping'] = 'feedwordpress_pong'; return $args; } function feedwordpress_pong ($args) { $feedwordpress =& new FeedWordPress; $delta = @$feedwordpress->update($args[1]); if (is_null($delta)): return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>."); else: $mesg = array(); if (isset($delta['new'])) { $mesg[] = ' '.$delta['new'].' new posts were syndicated'; } if (isset($delta['updated'])) { $mesg[] = ' '.$delta['updated'].' existing posts were updated'; } return array('flerror' => false, 'message' => "Thanks for the ping.".implode(' and', $mesg)); endif; } ################################################################################ ## class FeedFinder: find likely feeds using autodetection and/or guesswork #### ################################################################################ class FeedFinder { var $uri = NULL; var $_cache_uri = NULL; var $verify = FALSE; var $_data = NULL; var $_head = NULL; # -- Recognition patterns var $_feed_types = array( 'application/rss+xml', 'text/xml', 'application/atom+xml', 'application/x.atom+xml', 'application/x-atom+xml' ); var $_feed_markers = array('\\<feed', '\\<rss', 'xmlns="http://purl.org/rss/1.0'); var $_html_markers = array('\\<html'); var $_obvious_feed_url = array('[./]rss', '[./]rdf', '[./]atom', '[./]feed', '\.xml'); var $_maybe_feed_url = array ('rss', 'rdf', 'atom', 'feed', 'xml'); function FeedFinder ($uri = NULL, $verify = TRUE) { $this->uri = $uri; $this->verify = $verify; } /* FeedFinder::FeedFinder () */ function find ($uri = NULL) { $ret = array (); if (!is_null($this->data($uri))) { if ($this->is_feed($uri)) { $ret = array($this->uri); } else { // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?) // Autodiscovery is the preferred method $href = $this->_link_rel_feeds(); // ... but we'll also take the little orange buttons $href = array_merge($href, $this->_a_href_feeds(TRUE)); // If all that failed, look harder if (count($href) == 0) $href = $this->_a_href_feeds(FALSE); // Verify feeds and resolve relative URIs foreach ($href as $u) { $the_uri = Relative_URI::resolve($u, $this->uri); if ($this->verify) { $feed =& new FeedFinder($the_uri); if ($feed->is_feed()) $ret[] = $the_uri; $feed = NULL; } else { $ret[] = $the_uri; } } /* foreach */ } /* if */ } /* if */ return array_unique($ret); } /* FeedFinder::find () */ function data ($uri = NULL) { $this->_get($uri); return $this->_data; } function is_feed ($uri = NULL) { $data = $this->data($uri); return ( preg_match ( "\007(".implode('|',$this->_feed_markers).")\007i", $data ) and !preg_match ( "\007(".implode('|',$this->_html_markers).")\007i", $data ) ); } /* FeedFinder::is_feed () */ # --- Private methods --- function _get ($uri = NULL) { if ($uri) $this->uri = $uri; // Is the result not yet cached? if ($this->_cache_uri !== $this->uri) : // Snoopy is an HTTP client in PHP $client = new Snoopy(); // Prepare headers and internal settings $client->rawheaders['Connection'] = 'close'; $client->accept = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*'; $client->agent = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress'; $client->read_timeout = 25; // Fetch the HTML or feed @$client->fetch($this->uri); $this->_data = $client->results; // Kilroy was here $this->_cache_uri = $this->uri; endif; } /* FeedFinder::_get () */ function _link_rel_feeds () { $links = $this->_tags('link'); $link_count = count($links); // now figure out which one points to the RSS file $href = array (); for ($n=0; $n<$link_count; $n++) { if (strtolower($links[$n]['rel']) == 'alternate') { if (in_array(strtolower($links[$n]['type']), $this->_feed_types)) { $href[] = $links[$n]['href']; } /* if */ } /* if */ } /* for */ return $href; } function _a_href_feeds ($obvious = TRUE) { $pattern = ($obvious ? $this->_obvious_feed_url : $this->_maybe_feed_url); $links = $this->_tags('a'); $link_count = count($links); // now figure out which one points to the RSS file $href = array (); for ($n=0; $n<$link_count; $n++) { if (preg_match("\007(".implode('|',$pattern).")\007i", $links[$n]['href'])) { $href[] = $links[$n]['href']; } /* if */ } /* for */ return $href; } function _tags ($tag) { $html = $this->data(); // search through the HTML, save all <link> tags // and store each link's attributes in an associative array preg_match_all('/<'.$tag.'\s+(.*?)\s*\/?>/si', $html, $matches); $links = $matches[1]; $ret = array(); $link_count = count($links); for ($n=0; $n<$link_count; $n++) { $attributes = preg_split('/\s+/s', $links[$n]); foreach($attributes as $attribute) { $att = preg_split('/\s*=\s*/s', $attribute, 2); if (isset($att[1])) { $att[1] = preg_replace('/([\'"]?)(.*)\1/', '$2', $att[1]); $final_link[strtolower($att[0])] = $att[1]; } /* if */ } /* foreach */ $ret[$n] = $final_link; } /* for */ return $ret; } } /* class FeedFinder */ # Relative URI static class: PHP class for resolving relative URLs # # This class is derived (under the terms of the GPL) from URL Class 0.3 by # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need # for FeedWordPress's purposes. The class has been stripped down to a single # public method: Relative_URI::resolve($url, $base), which resolves the URI in # $url relative to the URI in $base # # The upgraded MagpieRSS also uses this class. So if we have it loaded # in, don't load it again. if (!class_exists('Relative_URI')) { class Relative_URI { // Resolve relative URI in $url against the base URI in $base. If $base // is not supplied, then we use the REQUEST_URI of this script. // // I'm hoping this method reflects RFC 2396 Section 5.2 function resolve ($url, $base = NULL) { if (is_null($base)): $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; endif; $base = Relative_URI::_encode(trim($base)); $uri_parts = Relative_URI::_parse_url($base); $url = Relative_URI::_encode(trim($url)); $parts = Relative_URI::_parse_url($url); $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null); $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null); // if path is empty, and scheme, host, and query are undefined, // the URL is referring the base URL if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) { // If the URI is empty or only a fragment, return the base URI return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : ''); } elseif (isset($parts['scheme'])) { // If the scheme is set, then the URI is absolute. return $url; } elseif (isset($parts['host'])) { $uri_parts['host'] = $parts['host']; $uri_parts['path'] = $parts['path']; } else { // We have a relative path but not a host. // start ugly fix: // prepend slash to path if base host is set, base path is not set, and url path is not absolute if ($uri_parts['host'] && ($uri_parts['path'] == '') && (strlen($parts['path']) > 0) && (substr($parts['path'], 0, 1) != '/')) { $parts['path'] = '/'.$parts['path']; } // end ugly fix if (substr($parts['path'], 0, 1) == '/') { $uri_parts['path'] = $parts['path']; } else { // copy base path excluding any characters after the last (right-most) slash character $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1); // append relative path $buffer .= $parts['path']; // remove "./" where "." is a complete path segment. $buffer = str_replace('/./', '/', $buffer); if (substr($buffer, 0, 2) == './') { $buffer = substr($buffer, 2); } // if buffer ends with "." as a complete path segment, remove it if (substr($buffer, -2) == '/.') { $buffer = substr($buffer, 0, -1); } // remove "<segment>/../" where <segment> is a complete path segment not equal to ".." $search_finished = false; $segment = explode('/', $buffer); while (!$search_finished) { for ($x=0; $x+1 < count($segment);) { if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) { if ($x+2 == count($segment)) $segment[] = ''; unset($segment[$x], $segment[$x+1]); $segment = array_values($segment); continue 2; } else { $x++; } } $search_finished = true; } $buffer = (count($segment) == 1) ? '/' : implode('/', $segment); $uri_parts['path'] = $buffer; } } // If we've gotten to this point, we can try to put the pieces // back together. $ret = ''; if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':'; if (isset($uri_parts['user'])) { $ret .= $uri_parts['user']; if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts']; $ret .= '@'; } if (isset($uri_parts['host'])) { $ret .= '//'.$uri_parts['host']; if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port']; } $ret .= $uri_parts['path']; if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query']; if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment']; return $ret; } /** * Parse URL * * Regular expression grabbed from RFC 2396 Appendix B. * This is a replacement for PHPs builtin parse_url(). * @param string $url * @access private * @return array */ function _parse_url($url) { // I'm using this pattern instead of parse_url() as there's a few strings where parse_url() // generates a warning. if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) { $parts = array(); if ($match[1] != '') $parts['scheme'] = $match[2]; if ($match[3] != '') $parts['auth'] = $match[4]; // parse auth if (isset($parts['auth'])) { // store user info if (($at_pos = strpos($parts['auth'], '@')) !== false) { $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2); $parts['user'] = $userinfo[0]; if (isset($userinfo[1])) $parts['pass'] = $userinfo[1]; $parts['auth'] = substr($parts['auth'], $at_pos+1); } // get port number if ($port_pos = strrpos($parts['auth'], ':')) { $parts['host'] = substr($parts['auth'], 0, $port_pos); $parts['port'] = (int)substr($parts['auth'], $port_pos+1); if ($parts['port'] < 1) $parts['port'] = null; } else { $parts['host'] = $parts['auth']; } } unset($parts['auth']); $parts['path'] = $match[5]; if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7]; if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9]; return $parts; } // shouldn't reach here return array('path'=>''); } function _encode($string) { static $replace = array(); if (!count($replace)) { $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127); $find = array_merge(range(0, 31), $find); $find = array_map('chr', $find); foreach ($find as $char) { $replace[$char] = '%'.bin2hex($char); } } // escape control characters and a few other characters $encoded = strtr($string, $replace); // remove any character outside the hex range: 21 - 7E (see www.asciitable.com) return preg_replace('/[^\x21-\x7e]/', '', $encoded); } } // class Relative_URI } // take your best guess at the realname and e-mail, given a string define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)'); define('FWP_REGEX_EMAIL_NAME', '("([^"]*)"|([^"<(]+\S))'); define('FWP_REGEX_EMAIL_POSTFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_ADDY."\s+\(".FWP_REGEX_EMAIL_NAME.'\)\s*$/'); define('FWP_REGEX_EMAIL_PREFIX_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*<'.FWP_REGEX_EMAIL_ADDY.'>\s*$/'); define('FWP_REGEX_EMAIL_JUST_ADDY', '/^\s*'.FWP_REGEX_EMAIL_ADDY.'\s*$/'); define('FWP_REGEX_EMAIL_JUST_NAME', '/^\s*'.FWP_REGEX_EMAIL_NAME.'\s*$/'); function parse_email_with_realname ($email) { if (preg_match(FWP_REGEX_EMAIL_POSTFIX_NAME, $email, $matches)) : ($ret['name'] = $matches[3]) or ($ret['name'] = $matches[2]); $ret['email'] = $matches[1]; elseif (preg_match(FWP_REGEX_EMAIL_PREFIX_NAME, $email, $matches)) : ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]); $ret['email'] = $matches[4]; elseif (preg_match(FWP_REGEX_EMAIL_JUST_ADDY, $email, $matches)) : $ret['name'] = NULL; $ret['email'] = $matches[1]; elseif (preg_match(FWP_REGEX_EMAIL_JUST_NAME, $email, $matches)) : $ret['email'] = NULL; ($ret['name'] = $matches[2]) or ($ret['name'] = $matches[3]); else : $ret['name'] = NULL; $ret['email'] = NULL; endif; return $ret; } ?>