diff --git a/wp-content/plugins/feedwordpress/admin-ui.php b/wp-content/plugins/feedwordpress/admin-ui.php new file mode 100644 index 0000000000000000000000000000000000000000..ed633f58b59770030131de3cb6dff496329656ee --- /dev/null +++ b/wp-content/plugins/feedwordpress/admin-ui.php @@ -0,0 +1,1169 @@ +<?php +class FeedWordPressAdminPage { + var $context; + var $updated = false; + var $mesg = NULL; + + var $link = NULL; + var $dispatch = NULL; + var $filename = NULL; + var $pagenames = array(); + + /** + * Construct the admin page object. + * + * @param mixed $link An object of class {@link SyndicatedLink} if created for one feed's settings, NULL if created for global default settings + */ + function FeedWordPressAdminPage ($page = 'feedwordpressadmin', $link = NULL) { + $this->link = $link; + + // Set meta-box context name + $this->context = $page; + if ($this->for_feed_settings()) : + $this->context .= 'forfeed'; + endif; + } /* FeedWordPressAdminPage constructor */ + + function pageslug () { + $slug = preg_replace('/FeedWordPress(.*)Page/', '$1', get_class($this)); + return strtolower($slug); + } + + function pagename ($context = NULL) { + if (is_null($context)) : + $context = 'default'; + endif; + + if (isset($this->pagenames[$context])) : + $name = $this->pagenames[$context]; + elseif (isset($tis->pagenames['default'])) : + $name = $this->pagenames['default']; + else : + $name = $this->pageslug(); + endif; + return __($name); + } /* FeedWordPressAdminPage::pagename () */ + + function accept_POST ($post) { + if ($this->for_feed_settings() and $this->update_requested_in($post)) : + $this->update_feed(); + elseif ($this->save_requested_in($post)) : // User mashed Save Changes + $this->save_settings($post); + endif; + do_action($this->dispatch.'_post', &$post, &$this); + } + + function update_feed () { + global $feedwordpress; + + add_action('feedwordpress_check_feed', 'update_feeds_mention'); + add_action('feedwordpress_check_feed_complete', 'update_feeds_finish', 10, 3); + + print '<div class="updated">'; + print "<ul>"; + $delta = $feedwordpress->update($this->link->uri()); + print "</ul>"; + + if (!is_null($delta)) : + $mesg = array(); + if (isset($delta['new'])) : $mesg[] = ' '.$delta['new'].' new posts were syndicated'; endif; + if (isset($delta['updated'])) : $mesg[] = ' '.$delta['updated'].' existing posts were updated'; endif; + echo "<p><strong>Update complete.</strong>".implode(' and', $mesg)."</p>"; + echo "\n"; flush(); + else : + echo "<p><strong>Error:</strong> There was a problem updating <a href=\"$uri\">$uri</a></p>\n"; + endif; + print "</div>\n"; + remove_action('feedwordpress_check_feed', 'update_feeds_mention'); + remove_action('feedwordpress_check_feed_complete', 'update_feeds_finish', 10, 3); + } + + function save_settings ($post) { + do_action($this->dispatch.'_save', &$post, &$this); + + if ($this->for_feed_settings()) : + // Save settings + $this->link->save_settings(/*reload=*/ true); + $this->updated = true; + + // Reset, reload + $link_id = $this->link->id; + unset($this->link); + $this->link = new SyndicatedLink($link_id); + else : + $this->updated = true; + endif; + } /* FeedWordPressAdminPage::save_settings () */ + + function for_feed_settings () { return (is_object($this->link) and method_exists($this->link, 'found') and $this->link->found()); } + function for_default_settings () { return !$this->for_feed_settings(); } + + function setting ($names, $fallback_value = NULL, $default = 'default') { + if (is_string($names)) : + $feed_name = $names; + $global_name = 'feedwordpress_'.preg_replace('![\s/]+!', '_', $names); + else : + $feed_name = $names['feed']; + $global_name = 'feedwordpress_'.$names['global']; + endif; + + if ($this->for_feed_settings()) : // Check feed-specific setting first; fall back to global + $ret = $this->link->setting($feed_name, $global_name, $fallback_value); + else : // Check global setting + $ret = get_option($global_name, $fallback_value); + endif; + return $ret; + } + + function update_setting ($names, $value, $default = 'default') { + if (is_string($names)) : + $feed_name = $names; + $global_name = 'feedwordpress_'.preg_replace('![\s/]+!', '_', $names); + else : + $feed_name = $names['feed']; + $global_name = 'feedwordpress_'.$names['global']; + endif; + + if ($this->for_feed_settings()) : // Update feed-specific setting + $this->link->update_setting($feed_name, $value, $default); + else : // Update global setting + update_option($global_name, $value); + endif; + } /* FeedWordPressAdminPage::update_setting () */ + + function save_requested_in ($post) { + return (isset($post['save']) or isset($post['submit'])); + } + function update_requested_in ($post) { + return (isset($post['update']) and (strlen($post['update']) > 0)); + } + + /*static*/ function submitted_link_id () { + global $fwp_post; + + // Presume global unless we get a specific link ID + $link_id = NULL; + + $submit_buttons = array( + 'save', + 'submit', + 'fix_mismatch', + 'feedfinder', + ); + foreach ($submit_buttons as $field) : + if (isset($fwp_post[$field])) : + $link_id = $_REQUEST['save_link_id']; + endif; + endforeach; + + if (is_null($link_id) and isset($_REQUEST['link_id'])) : + $link_id = $_REQUEST['link_id']; + endif; + + return $link_id; + } /* FeedWordPressAdminPage::submitted_link_id() */ + + /*static*/ function submitted_link () { + $link_id = FeedWordPressAdminPage::submitted_link_id(); + if (is_numeric($link_id) and $link_id) : + $link = new SyndicatedLink($link_id); + else : + $link = NULL; + endif; + return $link; + } /* FeedWordPressAdminPage::submitted_link () */ + + function stamp_link_id ($field = null) { + if (is_null($field)) : $field = 'save_link_id'; endif; + ?> + <input type="hidden" name="<?php print esc_html($field); ?>" value="<?php print ($this->for_feed_settings() ? $this->link->id : '*'); ?>" /> + <?php + } /* FeedWordPressAdminPage::stamp_link_id () */ + + function these_posts_phrase () { + if ($this->for_feed_settings()) : + $phrase = __('posts from this feed'); + else : + $phrase = __('syndicated posts'); + endif; + return $phrase; + } /* FeedWordPressAdminPage::these_posts_phrase() */ + + /** + * Provides a uniquely identifying name for the interface context for + * use with add_meta_box() and do_meta_boxes(), + * + * @return string the context name + * + * @see add_meta_box() + * @see do_meta_boxes() + */ + function meta_box_context () { + return $this->context; + } /* FeedWordPressAdminPage::meta_box_context () */ + + /** + * Outputs JavaScript to fix AJAX toggles settings. + * + * @uses FeedWordPressAdminPage::meta_box_context() + */ + function fix_toggles () { + FeedWordPressSettingsUI::fix_toggles_js($this->meta_box_context()); + } /* FeedWordPressAdminPage::fix_toggles() */ + + function ajax_interface_js () { +?> + function contextual_appearance (item, appear, disappear, value, visibleStyle, checkbox) { + if (typeof(visibleStyle)=='undefined') visibleStyle = 'block'; + + var rollup=document.getElementById(item); + if (rollup) { + if ((checkbox && rollup.checked) || (!checkbox && value==rollup.value)) { + jQuery('#'+disappear).hide(); + jQuery('#'+appear).show(600); + } else { + jQuery('#'+appear).hide(); + jQuery('#'+disappear).show(600); + } + } + } +<?php + } /* FeedWordPressAdminPage::ajax_interface_js () */ + + function display_feed_settings_page_links ($params = array()) { + global $fwp_path; + + $params = wp_parse_args($params, array( + 'before' => '', + 'between' => ' | ', + 'after' => '', + 'long' => false, + 'subscription' => $this->link, + )); + $sub = $params['subscription']; + + $links = array( + "Feed" => array('page' => 'feeds-page.php', 'long' => 'Feeds & Updates'), + "Posts" => array('page' => 'posts-page.php', 'long' => 'Posts & Links'), + "Authors" => array('page' => 'authors-page.php', 'long' => 'Authors'), + 'Categories' => array('page' => 'categories-page.php', 'long' => 'Categories'.FEEDWORDPRESS_AND_TAGS), + ); + + $hrefPrefix = 'admin.php?'; + + $link_id = NULL; + if (is_object($sub)) : + if (method_exists($sub, 'found')) : + if ($sub->found()) : + $link_id = $sub->link->link_id; + endif; + else : + $link_id = $sub->link_id; + endif; + endif; + + if (!is_null($link_id)) : + $urlParam = "link_id={$link_id}"; + $hrefPrefix .= $urlParam."&"; + $urlSuffix = "&".$urlParam; + else : + $urlParam = ''; + endif; + $hrefPrefix .= "page=${fwp_path}/"; + + print $params['before']; $first = true; + foreach ($links as $label => $link) : + if (!$first) : print $params['between']; endif; + + if (isset($link['url'])) : $url = $link['url'].$urlSuffix; + else : $url = $hrefPrefix.$link['page']; + endif; + $url = esc_html($url); + + if ($link['page']==basename($this->filename)) : + print "<strong>"; + else : + print "<a href=\"${url}\">"; + endif; + + if ($params['long']) : print esc_html(__($link['long'])); + else : print esc_html(__($label)); + endif; + + if ($link['page']==basename($this->filename)) : + print "</strong>"; + else : + print "</a>"; + endif; + + $first = false; + endforeach; + print $params['after']; + } /* FeedWordPressAdminPage::display_feed_settings_page_links */ + + function display_feed_select_dropdown() { + $links = FeedWordPress::syndicated_links(); + + ?> + <div id="fwpfs-container"><ul class="subsubsub"> + <li><select name="link_id" class="fwpfs" style="max-width: 20.0em;"> + <option value="*"<?php if ($this->for_default_settings()) : ?> selected="selected"<?php endif; ?>>- defaults for all feeds -</option> + <?php if ($links) : foreach ($links as $ddlink) : ?> + <option value="<?php print (int) $ddlink->link_id; ?>"<?php if (!is_null($this->link) and ($this->link->id==$ddlink->link_id)) : ?> selected="selected"<?php endif; ?>><?php print esc_html($ddlink->link_name); ?></option> + <?php endforeach; endif; ?> + </select> + <input id="fwpfs-button" class="button" type="submit" name="go" value="<?php _e('Go') ?> »" /></li> + + <?php + $this->display_feed_settings_page_links(array( + 'before' => '<li>', + 'between' => "</li>\n<li>", + 'after' => '</li>', + 'subscription' => $this->link, + )); + + if ($this->for_feed_settings()) : + ?> + <li><input class="button" type="submit" name="update" value="Update Now" /></li> + <?php + endif; + ?> + </ul> + </div> + <?php + } /* FeedWordPressAdminPage::display_feed_select_dropdown() */ + + function display_sheet_header ($pagename = 'Syndication', $all = false) { + ?> + <div class="icon32"><img src="<?php print esc_html(WP_PLUGIN_URL.'/'.$GLOBALS['fwp_path'].'/feedwordpress.png'); ?>" alt="" /></div> + <h2><?php print esc_html(__($pagename.($all ? '' : ' Settings'))); ?><?php if ($this->for_feed_settings()) : ?>: <?php echo esc_html($this->link->name(/*from feed=*/ false)); ?><?php endif; ?></h2> + <?php + } + + function display_update_notice_if_updated ($pagename = 'Syndication', $mesg = NULL) { + if (!is_null($mesg)) : + $this->mesg = $mesg; + endif; + + if ($this->updated) : + if ($this->updated === true) : + $this->mesg = $pagename . ' settings updated.'; + else : + $this->mesg = $this->updated; + endif; + endif; + + if (!is_null($this->mesg)) : + ?> + <div class="updated"> + <p><?php print esc_html($this->mesg); ?></p> + </div> + <?php + endif; + } /* FeedWordPressAdminPage::display_update_notice_if_updated() */ + + function display_settings_scope_message () { + if ($this->for_feed_settings()) : + ?> + <p>These settings only affect posts syndicated from + <strong><?php echo esc_html($this->link->link->link_name); ?></strong>.</p> + <?php + else : + ?> + <p>These settings affect posts syndicated from any feed unless they are overridden + by settings for that specific feed.</p> + <?php + endif; + } /* FeedWordPressAdminPage::display_settings_scope_message () */ + + /*static*/ function has_link () { return true; } + + function form_action ($filename = NULL) { + global $fwp_path; + + if (is_null($filename)) : + $filename = basename($this->filename); + endif; + return "admin.php?page=${fwp_path}/".$filename; + } /* FeedWordPressAdminPage::form_action () */ + + function update_message () { + return $this->mesg; + } + + function display () { + if (FeedWordPress::needs_upgrade()) : + fwp_upgrade_page(); + return; + endif; + + FeedWordPressCompatibility::validate_http_request(/*action=*/ $this->dispatch, /*capability=*/ 'manage_links'); + + //////////////////////////////////////////////// + // Process POST request, if any //////////////// + //////////////////////////////////////////////// + if (strtoupper($_SERVER['REQUEST_METHOD'])=='POST') : + $this->accept_POST($GLOBALS['fwp_post']); + else : + $this->updated = false; + endif; + + //////////////////////////////////////////////// + // Prepare settings page /////////////////////// + //////////////////////////////////////////////// + + $this->display_update_notice_if_updated( + $this->pagename('settings-update'), + $this->update_message() + ); + + $this->open_sheet($this->pagename('open-sheet')); + ?> + <div id="post-body"> + <?php + foreach ($this->boxes_by_methods as $method => $row) : + if (is_array($row)) : + $id = $row['id']; + $title = $row['title']; + else : + $id = 'feedwordpress_'.$method; + $title = $row; + endif; + + add_meta_box( + /*id=*/ $id, + /*title=*/ $title, + /*callback=*/ array(&$this, $method), + /*page=*/ $this->meta_box_context(), + /*context=*/ $this->meta_box_context() + ); + endforeach; + do_action($this->dispatch.'_meta_boxes', $this); + ?> + <div class="metabox-holder"> + <?php + fwp_do_meta_boxes($this->meta_box_context(), $this->meta_box_context(), $this); + ?> + </div> <!-- class="metabox-holder" --> + </div> <!-- id="post-body" --> + <?php $this->close_sheet(); ?> + <?php + } + + function open_sheet ($header) { + // Set up prepatory AJAX stuff + ?> + <script type="text/javascript"> + <?php + $this->ajax_interface_js(); + ?> + </script> + + <?php + add_action( + FeedWordPressCompatibility::bottom_script_hook($this->filename), + /*callback=*/ array($this, 'fix_toggles'), + /*priority=*/ 10000 + ); + FeedWordPressSettingsUI::ajax_nonce_fields(); + + ?> + <div class="wrap feedwordpress-admin" id="feedwordpress-admin-<?php print $this->pageslug(); ?>"> + <?php + if (!is_null($header)) : + $this->display_sheet_header($header); + endif; + + if (!is_null($this->dispatch)) : + ?> + <form action="<?php print $this->form_action(); ?>" method="post"> + <div><?php + FeedWordPressCompatibility::stamp_nonce($this->dispatch); + $this->stamp_link_id(); + ?></div> + <?php + endif; + + if ($this->has_link()) : + $this->display_settings_scope_message(); + endif; + + ?><div class="tablenav"><?php + if (!is_null($this->dispatch)) : + ?><div class="alignright"><?php + $this->save_button(); + ?></div><?php + endif; + + if ($this->has_link()) : + $this->display_feed_select_dropdown(); + endif; + ?> + </div> + + <div id="poststuff"> + <?php + } /* FeedWordPressAdminPage::open_sheet () */ + + function close_sheet () { + ?> + + </div> <!-- id="poststuff" --> + <?php + if (!is_null($this->dispatch)) : + $this->save_button(); + print "</form>\n"; + endif; + ?> + </div> <!-- class="wrap" --> + + <?php + } /* FeedWordPressAdminPage::close_sheet () */ + + function setting_radio_control ($localName, $globalName, $options, $params = array()) { + global $fwp_path; + + if (isset($params['filename'])) : $filename = $params['filename']; + else : $filename = basename($this->filename); + endif; + + if (isset($params['site-wide-url'])) : $href = $params['site-wide-url']; + else : $href = "admin.php?page=${fwp_path}/${filename}"; + endif; + + if (isset($params['setting-default'])) : $settingDefault = $params['setting-default']; + else : $settingDefault = NULL; + endif; + + if (isset($params['global-setting-default'])) : $globalSettingDefault = $params['global-setting-default']; + else : $globalSettingDefault = $settingDefault; + endif; + + $globalSetting = get_option('feedwordpress_'.$globalName, $globalSettingDefault); + if ($this->for_feed_settings()) : + $setting = $this->link->setting($localName, NULL, $settingDefault); + else : + $setting = $globalSetting; + endif; + + if (isset($params['offer-site-wide'])) : $offerSiteWide = $params['offer-site-wide']; + else : $offerSiteWide = $this->for_feed_settings(); + endif; + + // This allows us to provide an alternative set of human-readable + // labels for each potential value. For use in Currently: line. + if (isset($params['labels'])) : $labels = $params['labels']; + else : $labels = $options; + endif; + + if (isset($params['input-name'])) : $inputName = $params['input-name']; + else : $inputName = $globalName; + endif; + + if (isset($params['default-input-id'])) : $defaultInputId = $params['default-input-id']; + else : $defaultInputId = NULL; + endif; + + if (isset($params['default-input-id-no'])) : $defaultInputIdNo = $params['default-input-id-no']; + elseif (!is_null($defaultInputId)) : $defaultInputIdNo = $defaultInputId.'-no'; + else : $defaultInputIdNo = NULL; + endif; + + // This allows us to either include the site-default setting as + // one of the options within the radio box, or else as a simple + // yes/no toggle that controls whether or not to check another + // set of inputs. + if (isset($params['default-input-name'])) : $defaultInputName = $params['default-input-name']; + else : $defaultInputName = $inputName; + endif; + + if ($defaultInputName != $inputName) : + $defaultInputValue = 'yes'; + else : + $defaultInputValue = ( + isset($params['default-input-value']) + ? $params['default-input-value'] + : 'site-default' + ); + endif; + + $settingDefaulted = (is_null($setting) or ($settingDefault === $setting)); + + if (!is_callable($options)) : + $checked = array(); + if ($settingDefaulted) : + $checked[$defaultInputValue] = ' checked="checked"'; + endif; + + foreach ($options as $value => $label) : + if ($setting == $value) : + $checked[$value] = ' checked="checked"'; + else : + $checked[$value] = ''; + endif; + endforeach; + endif; + + $defaulted = array(); + if ($defaultInputName != $inputName) : + $defaulted['yes'] = ($settingDefaulted ? ' checked="checked"' : ''); + $defaulted['no'] = ($settingDefaulted ? '' : ' checked="checked"'); + else : + $defaulted['yes'] = (isset($checked[$defaultInputValue]) ? $checked[$defaultInputValue] : ''); + endif; + + if (isset($params['defaulted'])) : + $defaulted['yes'] = ($params['defaulted'] ? ' checked="checked"' : ''); + $defaulted['no'] = ($params['defaulted'] ? '' : ' checked="checked"'); + endif; + + if ($offerSiteWide) : + ?> + <table class="twofer"> + <tbody> + <tr><td class="equals first inactive"> + <ul class="options"> + <li><label><input type="radio" + name="<?php print $defaultInputName; ?>" + value="<?php print $defaultInputValue; ?>" + <?php if (!is_null($defaultInputId)) : ?>id="<?php print $defaultInputId; ?>" <?php endif; ?> + <?php print $defaulted['yes']; ?> /> + Use the site-wide setting</label> + <span class="current-setting">Currently: + <strong><?php if (is_callable($labels)) : + print call_user_func($labels, $globalSetting, $defaulted, $params); + else : + print $labels[$globalSetting]; + endif; ?></strong> (<a href="<?php print $href; ?>">change</a>)</span></li> + </ul></td> + + <td class="equals second inactive"> + <?php if ($defaultInputName != $inputName) : ?> + <ul class="options"> + <li><label><input type="radio" + name="<?php print $defaultInputName; ?>" + value="no" + <?php if (!is_null($defaultInputIdNo)) : ?>id="<?php print $defaultInputIdNo; ?>" <?php endif; ?> + <?php print $defaulted['no']; ?> /> + <?php _e('Do something different with this feed.'); ?></label> + <?php endif; + endif; + + // Let's spit out the controls here. + if (is_callable($options)) : + // Method call to print out options list + call_user_func($options, $setting, $defaulted, $params); + else : + ?> + <ul class="options"> + <?php foreach ($options as $value => $label) : ?> + <li><label><input type="radio" name="<?php print $inputName; ?>" + value="<?php print $value; ?>" + <?php print $checked[$value]; ?> /> + <?php print $label; ?></label></li> + <?php endforeach; ?> + </ul> <!-- class="options" --> + <?php + endif; + + if ($offerSiteWide) : + if ($defaultInputName != $inputName) : + // Close the <li> and <ul class="options"> we opened above + ?> + </li> + </ul> <!-- class="options" --> + <?php + endif; + + // Close off the twofer table that we opened up above. + ?> + </td></tr> + </tbody> + </table> + <?php + endif; + } /* FeedWordPressAdminPage::setting_radio_control () */ + + function save_button ($caption = NULL) { + if (is_null($caption)) : $caption = __('Save Changes'); endif; + ?> +<p class="submit"> +<input class="button-primary" type="submit" name="save" value="<?php print $caption; ?>" /> +</p> + <?php + } +} /* class FeedWordPressAdminPage */ + +function fwp_authors_single_submit ($link = NULL) { +?> +<div class="submitbox" id="submitlink"> +<div id="previewview"> +</div> +<div class="inside"> +</div> + +<p class="submit"> +<input type="submit" name="save" value="<?php _e('Save') ?>" /> +</p> +</div> +<?php +} + +function fwp_option_box_opener ($legend, $id, $class = "stuffbox") { +?> +<div id="<?php print $id; ?>" class="<?php print $class; ?>"> +<h3><?php print htmlspecialchars($legend); ?></h3> +<div class="inside"> +<?php +} + +function fwp_option_box_closer () { + global $wp_db_version; + if (isset($wp_db_version) and $wp_db_version >= FWP_SCHEMA_25) : +?> + </div> <!-- class="inside" --> + </div> <!-- class="stuffbox" --> +<?php + else : +?> + </div> <!-- class="wrap" --> +<?php + endif; +} + +function fwp_tags_box ($tags, $object, $params = array()) { + if (!is_array($tags)) : $tags = array(); endif; + $tax_name = (isset($params['taxonomy']) ? $params['taxonomy'] : 'post_tag'); + $desc = "<p style=\"font-size:smaller;font-style:bold;margin:0\">Tag $object as...</p>"; + + print $desc; + $helps = __('Separate tags with commas.'); + $box['title'] = __('Tags'); + ?> + <div class="tagsdiv" id="<?php echo $tax_name; ?>"> + <div class="jaxtag"> + <div class="nojs-tags hide-if-js"> + <p><?php _e('Add or remove tags'); ?></p> + <textarea name="<?php echo "tax_input[$tax_name]"; ?>" class="the-tags" id="tax-input[<?php echo $tax_name; ?>]"><?php echo esc_attr(implode(",", $tags)); ?></textarea></div> + + <div class="ajaxtag hide-if-no-js"> + <label class="screen-reader-text" for="new-tag-<?php echo $tax_name; ?>"><?php echo $box['title']; ?></label> + <div class="taghint"><?php _e('Add new tag'); ?></div> + <p> + <input type="text" id="new-tag-<?php echo $tax_name; ?>" name="newtag[<?php echo $tax_name; ?>]" class="newtag form-input-tip" size="16" autocomplete="off" value="" /> + <input type="button" class="button tagadd" value="<?php esc_attr_e('Add'); ?>" tabindex="3" /> + </p> + </div></div> + <p class="howto"><?php echo $helps; ?></p> + <div class="tagchecklist"></div> + </div> + <p class="hide-if-no-js"><a href="#titlediv" class="tagcloud-link" id="link-<?php echo $tax_name; ?>"><?php printf( __('Choose from the most used tags in %s'), $box['title'] ); ?></a></p> + <?php +} + +function fwp_category_box ($checked, $object, $tags = array(), $params = array()) { + global $wp_db_version; + + if (is_string($params)) : + $prefix = $params; + $taxonomy = 'category'; + elseif (is_array($params)) : + $prefix = (isset($params['prefix']) ? $params['prefix'] : ''); + $taxonomy = (isset($params['taxonomy']) ? $params['taxonomy'] : 'category'); + endif; + $tax = get_taxonomy($taxonomy); + + if (strlen($prefix) > 0) : + $idPrefix = $prefix.'-'; + $idSuffix = "-".$prefix; + $namePrefix = $prefix . '_'; + else : + $idPrefix = 'feedwordpress-'; + $idSuffix = "-feedwordpress"; + $namePrefix = 'feedwordpress_'; + endif; + +?> +<div id="<?php print $idPrefix; ?>taxonomy-<?php print $taxonomy; ?>" class="feedwordpress-category-div"> + <ul id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-tabs" class="category-tabs"> + <li class="ui-tabs-selected tabs"><a href="#<?php print $idPrefix; ?><?php print $taxonomy; ?>-all" tabindex="3"><?php _e( 'All posts' ); ?></a> + <p style="font-size:smaller;font-style:bold;margin:0">Give <?php print $object; ?> these <?php print $tax->labels->name; ?></p> + </li> + </ul> + +<div id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-all" class="tabs-panel"> + <input type="hidden" value="0" name="tax_input[<?php print $taxonomy; ?>][]" /> + <ul id="<?php print $idPrefix; ?><?php print $taxonomy; ?>checklist" class="list:<?php print $taxonomy; ?> categorychecklist form-no-clear"> + <?php fwp_category_checklist(NULL, false, $checked, $params) ?> + </ul> +</div> + +<div id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-adder" class="<?php print $taxonomy; ?>-adder wp-hidden-children"> + <h4><a id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-add-toggle" class="category-add-toggle" href="#<?php print $idPrefix; ?><?php print $taxonomy; ?>-add" class="hide-if-no-js" tabindex="3"><?php _e( '+ Add New Category' ); ?></a></h4> + <p id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-add" class="category-add wp-hidden-child"> + <?php + $newcat = 'new'.$taxonomy; + + // Well, thank God they added "egory" before WP 3.0 came out. + if ('newcategory'==$newcat + and !FeedWordPressCompatibility::test_version(FWP_SCHEMA_30)) : + $newcat = 'newcat'; + endif; + ?> + <label class="screen-reader-text" for="<?php print $idPrefix; ?>new<?php print $taxonomy; ?>"><?php _e('Add New Category'); ?></label> + <input + id="<?php print $idPrefix; ?>new<?php print $taxonomy; ?>" + class="new<?php print $taxonomy; ?> form-required form-input-tip" + aria-required="true" + tabindex="3" + type="text" name="<?php print $newcat; ?>" + value="<?php _e( 'New category name' ); ?>" + /> + <label class="screen-reader-text" for="<?php print $idPrefix; ?>new<?php print $taxonomy; ?>-parent"><?php _e('Parent Category:'); ?></label> + <?php wp_dropdown_categories( array( + 'taxonomy' => $taxonomy, + 'hide_empty' => 0, + 'id' => $idPrefix.'new'.$taxonomy.'-parent', + 'class' => 'new'.$taxonomy.'-parent', + 'name' => $newcat.'_parent', + 'orderby' => 'name', + 'hierarchical' => 1, + 'show_option_none' => __('Parent category'), + 'tab_index' => 3, + ) ); ?> + <input type="button" id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-add-sumbit" class="add:<?php print $idPrefix; ?><?php print $taxonomy; ?>checklist:<?php print $taxonomy; ?>-add add-categorychecklist-category-add button category-add-submit" value="<?php _e( 'Add' ); ?>" tabindex="3" /> + <?php /* wp_nonce_field currently doesn't let us set an id different from name, but we need a non-unique name and a unique id */ ?> + <input type="hidden" id="_ajax_nonce<?php print esc_html($idSuffix); ?>" name="_ajax_nonce" value="<?php print wp_create_nonce('add-'.$taxonomy); ?>" /> + <input type="hidden" id="_ajax_nonce-add-<?php print $taxonomy; ?><?php print esc_html($idSuffix); ?>" name="_ajax_nonce-add-<?php print $taxonomy; ?>" value="<?php print wp_create_nonce('add-'.$taxonomy); ?>" /> + <span id="<?php print $idPrefix; ?><?php print $taxonomy; ?>-ajax-response" class="<?php print $taxonomy; ?>-ajax-response"></span> + </p> +</div> + +</div> +<?php +} + +function update_feeds_mention ($feed) { + echo "<li>Updating <cite>".$feed['link/name']."</cite> from <<a href=\"" + .$feed['link/uri']."\">".$feed['link/uri']."</a>> ..."; + flush(); +} +function update_feeds_finish ($feed, $added, $dt) { + if (is_wp_error($added)) : + $mesgs = $added->get_error_messages(); + foreach ($mesgs as $mesg) : + echo "<br/><strong>Feed error:</strong> <code>$mesg</code>"; + endforeach; + echo "</li>\n"; + else : + echo " completed in $dt second".(($dt==1)?'':'s')."</li>\n"; + endif; + flush(); +} + +function fwp_author_list () { + global $wpdb; + $ret = array(); + + $users = $wpdb->get_results("SELECT * FROM $wpdb->users ORDER BY display_name"); + if (is_array($users)) : + foreach ($users as $user) : + $id = (int) $user->ID; + $ret[$id] = $user->display_name; + if (strlen(trim($ret[$id])) == 0) : + $ret[$id] = $user->user_login; + endif; + endforeach; + endif; + return $ret; +} + +class FeedWordPressSettingsUI { + function is_admin () { + global $fwp_path; + + $admin_page = false; // Innocent until proven guilty + if (isset($_REQUEST['page'])) : + $admin_page = ( + is_admin() + and preg_match("|^{$fwp_path}/|", $_REQUEST['page']) + ); + endif; + return $admin_page; + } + + function admin_scripts () { + global $fwp_path; + + wp_enqueue_script('post'); // for magic tag and category boxes + if (!FeedWordPressCompatibility::test_version(FWP_SCHEMA_29)) : // < 2.9 + wp_enqueue_script('thickbox'); // for fold-up boxes + endif; + wp_enqueue_script('admin-forms'); // for checkbox selection + + wp_register_script('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.js'); + wp_enqueue_script('feedwordpress-elements'); + } + + function admin_styles () { + ?> + <style type="text/css"> + .fwpfs { + background-image: url(<?php print admin_url('images/fav.png'); ?>); + background-repeat: repeat-x; + background-position: left center; + background-attachment: scroll; + } + .fwpfs.slide-down { + background-image:url(<?php print admin_url('images/fav-top.png'); ?>); + background-position:0 top; + background-repeat:repeat-x; + } + </style> + <?php + } /* FeedWordPressSettingsUI::admin_styles () */ + + /*static*/ function ajax_nonce_fields () { + if (function_exists('wp_nonce_field')) : + echo "<form style='display: none' method='get' action=''>\n<p>\n"; + wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); + wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); + echo "</p>\n</form>\n"; + endif; + } /* FeedWordPressSettingsUI::ajax_nonce_fields () */ + + /*static*/ function fix_toggles_js ($context) { + ?> + <script type="text/javascript"> + jQuery(document).ready( function($) { + // In case someone got here first... + $('.postbox h3, .postbox .handlediv').unbind('click'); + $('.postbox h3 a').unbind('click'); + $('.hide-postbox-tog').unbind('click'); + $('.columns-prefs input[type="radio"]').unbind('click'); + $('.meta-box-sortables').sortable('destroy'); + + postboxes.add_postbox_toggles('<?php print $context; ?>'); + } ); + </script> + <?php + } /* FeedWordPressSettingsUI::fix_toggles_js () */ + + function magic_input_tip_js ($id) { + ?> + <script type="text/javascript"> + jQuery(document).ready( function () { + var inputBox = jQuery("#<?php print $id; ?>"); + var boxEl = inputBox.get(0); + if (boxEl.value==boxEl.defaultValue) { inputBox.addClass('form-input-tip'); } + inputBox.focus(function() { + if ( this.value == this.defaultValue ) + jQuery(this).val( '' ).removeClass( 'form-input-tip' ); + }); + inputBox.blur(function() { + if ( this.value == '' ) + jQuery(this).val( this.defaultValue ).addClass( 'form-input-tip' ); + }); + } ); + </script> + <?php + } /* FeedWordPressSettingsUI::magic_input_tip_js () */ +} /* class FeedWordPressSettingsUI */ + +function fwp_insert_new_user ($newuser_name) { + global $wpdb; + + $ret = null; + if (strlen($newuser_name) > 0) : + $userdata = array(); + $userdata['ID'] = NULL; + $userdata['user_login'] = apply_filters('pre_user_login', sanitize_user($newuser_name)); + $userdata['user_nicename'] = apply_filters('pre_user_nicename', sanitize_title($newuser_name)); + $userdata['display_name'] = $newuser_name; + $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up + + $blahUrl = get_bloginfo('url'); $url = parse_url($blahUrl); + $userdata['user_email'] = substr(md5(uniqid(microtime())), 0, 6).'@'.$url['host']; + + $newuser_id = wp_insert_user($userdata); + $ret = $newuser_id; // Either a numeric ID or a WP_Error object + else : + // TODO: Add some error reporting + endif; + return $ret; +} /* fwp_insert_new_user () */ + +/** + * fwp_add_meta_box + * + * This function is no longer necessary, since no versions of WordPress that FWP + * still supports lack add_meta_box(). But I've left it in place for the time + * being for add-on modules that may have used it in setting up their UI. + */ +function fwp_add_meta_box ($id, $title, $callback, $page, $context = 'advanced', $priority = 'default', $callback_args = null) { + return add_meta_box($id, $title, $callback, $page, $context, $priority, $callback_args); +} /* function fwp_add_meta_box () */ + +function fwp_do_meta_boxes($page, $context, $object) { + $ret = do_meta_boxes($page, $context, $object); + + // Avoid JavaScript error from WordPress 2.5 bug +?> + <div style="display: none"> + <div id="tags-input"></div> <!-- avoid JS error from WP 2.5 bug --> + </div> +<?php + return $ret; +} /* function fwp_do_meta_boxes() */ + +function fwp_remove_meta_box($id, $page, $context) { + return remove_meta_box($id, $page, $context); +} /* function fwp_remove_meta_box() */ + +function fwp_syndication_manage_page_links_table_rows ($links, $page, $visible = 'Y') { + global $fwp_path; + + $subscribed = ('Y' == strtoupper($visible)); + if ($subscribed or (count($links) > 0)) : + ?> + <table class="widefat<?php if (!$subscribed) : ?> unsubscribed<?php endif; ?>"> + <thead> + <tr> + <th class="check-column" scope="col"><input type="checkbox" /></th> + <th scope="col"><?php _e('Name'); ?></th> + <th scope="col"><?php _e('Feed'); ?></th> + <th scope="col"><?php _e('Updated'); ?></th> + </tr> + </thead> + + <tbody> +<?php + $alt_row = true; + if (count($links) > 0): + foreach ($links as $link): + $trClass = array(); + + // Prep: Get last updated timestamp + $sLink = new SyndicatedLink($link->link_id); + if (!is_null($sLink->setting('update/last'))) : + $lastUpdated = fwp_time_elapsed($sLink->setting('update/last')); + else : + $lastUpdated = __('None yet'); + endif; + + // Prep: get last error timestamp, if any + if (is_null($sLink->setting('update/error'))) : + $errorsSince = ''; + else : + $trClass[] = 'feed-error'; + + $theError = unserialize($sLink->setting('update/error')); + + $errorsSince = "<div class=\"returning-errors\">" + ."<p><strong>Returning errors</strong> since " + .fwp_time_elapsed($theError['since']) + ."</p>" + ."<p>Most recent (" + .fwp_time_elapsed($theError['ts']) + ."):<br/><code>" + .implode("</code><br/><code>", $theError['object']->get_error_messages()) + ."</code></p>" + ."</div>\n"; + endif; + + $nextUpdate = "<div style='font-style:italic;size:0.9em'>Ready for next update "; + if (isset($sLink->settings['update/ttl']) and is_numeric($sLink->settings['update/ttl'])) : + if (isset($sLink->settings['update/timed']) and $sLink->settings['update/timed']=='automatically') : + $next = $sLink->settings['update/last'] + ((int) $sLink->settings['update/ttl'] * 60); + $nextUpdate .= fwp_time_elapsed($next); + if (FEEDWORDPRESS_DEBUG) : $nextUpdate .= " [".(($next-time())/60)." minutes]"; endif; + else : + $nextUpdate .= "every ".$sLink->settings['update/ttl']." minute".(($sLink->settings['update/ttl']!=1)?"s":""); + endif; + else: + $nextUpdate .= "as soon as possible"; + endif; + $nextUpdate .= "</div>"; + + unset($sLink); + + $alt_row = !$alt_row; + + if ($alt_row) : + $trClass[] = 'alternate'; + endif; + ?> + <tr<?php echo ((count($trClass) > 0) ? ' class="'.implode(" ", $trClass).'"':''); ?>> + <th class="check-column" scope="row"><input type="checkbox" name="link_ids[]" value="<?php echo $link->link_id; ?>" /></th> + <?php + $hrefPrefix = "admin.php?link_id={$link->link_id}&page=${fwp_path}/"; + $caption = ( + (strlen($link->link_rss) > 0) + ? __('Switch Feed') + : $caption=__('Find Feed') + ); + ?> + <td> + <strong><a href="<?php print $hrefPrefix; ?>feeds-page.php"><?php print esc_html($link->link_name); ?></a></strong> + <div class="row-actions"><?php if ($subscribed) : + $page->display_feed_settings_page_links(array( + 'before' => '<div><strong>Settings ></strong> ', + 'after' => '</div>', + 'subscription' => $link, + )); + endif; ?> + + <div><strong>Actions ></strong> + <?php if ($subscribed) : ?> + <a href="<?php print $hrefPrefix; ?>syndication.php&action=feedfinder"><?php echo $caption; ?></a> + <?php else : ?> + <a href="<?php print $hrefPrefix; ?>syndication.php&action=<?php print FWP_RESUB_CHECKED; ?>"><?php _e('Re-subscribe'); ?></a> + <?php endif; ?> + | <a href="<?php print $hrefPrefix; ?>syndication.php&action=Unsubscribe"><?php _e(($subscribed ? 'Unsubscribe' : 'Delete permanently')); ?></a> + | <a href="<?php print esc_html($link->link_url); ?>"><?php _e('View')?></a></div> + </div> + </td> + <?php if (strlen($link->link_rss) > 0): ?> + <td><a href="<?php echo esc_html($link->link_rss); ?>"><?php echo esc_html(feedwordpress_display_url($link->link_rss, 32)); ?></a></td> + <?php else: ?> + <td class="feed-missing"><p><strong>no feed assigned</strong></p></td> + <?php endif; ?> + + <td><div style="float: right; padding-left: 10px"> + <input type="submit" class="button" name="update_uri[<?php print esc_html($link->link_rss); ?>]" value="<?php _e('Update Now'); ?>" /> + </div> + <?php print $lastUpdated; ?> + <?php print $errorsSince; ?> + <?php print $nextUpdate; ?> + </td> + </tr> + <?php + endforeach; + else : +?> +<tr><td colspan="4"><p>There are no websites currently listed for syndication.</p></td></tr> +<?php + endif; +?> +</tbody> +</table> + <?php + endif; +} /* function fwp_syndication_manage_page_links_table_rows () */ + +function fwp_syndication_manage_page_links_subsubsub ($sources, $showInactive) { + global $fwp_path; + $hrefPrefix = "admin.php?page=${fwp_path}/syndication.php"; + ?> + <ul class="subsubsub"> + <li><a <?php if (!$showInactive) : ?>class="current" <?php endif; ?>href="<?php print $hrefPrefix; ?>&visibility=Y">Subscribed + <span class="count">(<?php print count($sources['Y']); ?>)</span></a></li> + <?php if ($showInactive or (count($sources['N']) > 0)) : ?> + <li><a <?php if ($showInactive) : ?>class="current" <?php endif; ?>href="<?php print $hrefPrefix; ?>&visibility=N">Inactive</a> + <span class="count">(<?php print count($sources['N']); ?>)</span></a></li> + <?php endif; ?> + + </ul> <!-- class="subsubsub" --> + <?php +} + diff --git a/wp-content/plugins/feedwordpress/authors-page.php b/wp-content/plugins/feedwordpress/authors-page.php new file mode 100644 index 0000000000000000000000000000000000000000..8a36068207b9dcdfbba5d445471424a10641ac8a --- /dev/null +++ b/wp-content/plugins/feedwordpress/authors-page.php @@ -0,0 +1,392 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); + +class FeedWordPressAuthorsPage extends FeedWordPressAdminPage { + var $authorlist = NULL; + var $rule_count = 0; + + function FeedWordPressAuthorsPage ($link = -1) { + if (is_numeric($link) and -1 == $link) : + $link = FeedWordPressAdminPage::submitted_link(); + endif; + + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpressauthors', $link); + $this->refresh_author_list(); + $this->dispatch = 'feedwordpress_author_settings'; + $this->filename = __FILE__; + + $this->pagenames = array( + 'default' => 'Authors', + 'settings-update' => 'Syndicated Authors', + 'open-sheet' => 'Syndicated Author', + ); + } + + function refresh_author_list () { + $this->authorlist = fwp_author_list(); + } + + /*static*/ function syndicated_authors_box ($page, $box = NULL) { + $link = $page->link; + $unfamiliar = array ('create' => '','default' => '','filter' => ''); + + if ($page->for_feed_settings()) : + $key = $this->link->setting('unfamiliar author', NULL, 'site-default'); + $unfamiliar['site-default'] = ''; + else : + $key = FeedWordPress::on_unfamiliar('author'); + endif; + $unfamiliar[$key] = ' selected="selected"'; + + $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email")); + $null_emails = FeedWordPress::null_email_set(); + + // Hey ho, let's go... + ?> +<table class="form-table"> +<?php +if ($page->for_feed_settings()) : + $map = $this->link->setting('map authors', NULL, array()); +?> +<tr><th>Matching authors</th> +<td><p>How should FeedWordPress attribute posts from this feed to WordPress +authors?</p> +<ul class="settings"> +<li><p><input type="radio" name="author_rules_name[all]" value="*" +<?php if (isset($map['name']['*'])) : $author_action = $map['name']['*']; ?> + checked="checked" +<?php else : $author_action = NULL; ?> +<?php endif; ?> + /> All posts syndicated +from this feed <select class="author-rules" id="author-rules-all" +name="author_rules_action[all]" onchange="contextual_appearance('author-rules-all', 'author-rules-all-newuser', 'author-rules-all-default', 'newuser', 'inline');"> + <?php foreach ($page->authorlist as $local_author_id => $local_author_name) : ?> + <option value="<?php echo $local_author_id; ?>"<?php if ($local_author_id==$author_action) : echo ' selected="selected"'; endif; ?>>are assigned to <?php echo $local_author_name; ?></option> + <?php endforeach; ?> + <option value="newuser">will be assigned to a new user...</option> + <option value="filter"<?php if ('filter'==$author_action) : echo ' selected="selected"'; endif; ?>>get filtered out</option> + </select> + <span class="author-rules-newuser" id="author-rules-all-newuser">named + <input type="text" name="author_rules_newuser[all]" value="" /></span></p></li> +<li><p><input type="radio" name="author_rules_name[all]" value="" +<?php if (!isset($map['name']['*'])) : ?> + checked="checked" +<?php endif; ?> +/> Attribute posts to authors based on automatic mapping rules. (Blank out a +name to delete the rule. Fill in a new name at the bottom to create a new rule.)</p> + +<table style="width: 100%"> +<?php + if (isset($this->link->settings['map authors'])) : +?> +<?php + $page->rule_count=0; + foreach ($this->link->settings['map authors'] as $author_rules) : + foreach ($author_rules as $author_name => $author_action) : + if ($author_name != '*') : + $page->rule_count++; +?> +<tr> +<th style="text-align: left; width: 15.0em">Posts by <input type="text" name="author_rules_name[]" value="<?php echo htmlspecialchars($author_name); ?>" size="11" /></th> + <td> + <select class="author-rules" id="author-rules-<?php echo $page->rule_count; ?>" name="author_rules_action[]" onchange="contextual_appearance('author-rules-<?php echo $page->rule_count; ?>', 'author-rules-<?php echo $page->rule_count; ?>-newuser', 'author-rules-<?php echo $page->rule_count; ?>-default', 'newuser', 'inline');"> + <?php foreach ($page->authorlist as $local_author_id => $local_author_name) : ?> + <option value="<?php echo $local_author_id; ?>"<?php if ($local_author_id==$author_action) : echo ' selected="selected"'; endif; ?>>are assigned to <?php echo $local_author_name; ?></option> + <?php endforeach; ?> + <option value="newuser">will be assigned to a new user...</option> + <option value="filter"<?php if ('filter'==$author_action) : echo ' selected="selected"'; endif; ?>>get filtered out</option> + </select> + + <span class="author-rules-newuser" id="author-rules-<?php echo $page->rule_count; ?>-newuser">named <input type="text" name="author_rules_newuser[]" value="" /></span> + </td> +</tr> +<?php + endif; + endforeach; + endforeach; + endif; +?> + +<tr> +<th style="text-align: left; width: 15.0em">Posts by <input type="text" name="add_author_rule_name" size="11" /></th> + <td> + <select id="add-author-rule" name="add_author_rule_action" onchange="contextual_appearance('add-author-rule', 'add-author-rule-newuser', 'add-author-rule-default', 'newuser', 'inline');"> + <?php foreach ($page->authorlist as $author_id => $author_name) : ?> + <option value="<?php echo $author_id; ?>">are assigned to <?php echo $author_name; ?></option> + <?php endforeach; ?> + <option value="newuser">will be assigned to a new user...</option> + <option value="filter">get filtered out</option> + </select> + + <span id="add-author-rule-newuser">named <input type="text" name="add_author_rule_newuser" value="" /></span> + </td> +</tr> + +<tr> +<th style="text-align: left; width: 15.0em">Unmatched authors</th> +<td> +<span>Authors who haven't been syndicated before</span> + <select style="max-width: 27.0em" id="unfamiliar-author" name="unfamiliar_author" onchange="contextual_appearance('unfamiliar-author', 'unfamiliar-author-newuser', 'unfamiliar-author-default', 'newuser', 'inline');"> +<?php if ($page->for_feed_settings()) : ?> + <option value="site-default"<?php print $unfamiliar['site-default']; ?>>are handled according to the default for all feeds</option> +<?php endif; ?> + <option value="create"<?php print $unfamiliar['create']; ?>>will have a new author account created for them</option> + <?php foreach ($page->authorlist as $author_id => $author_name) : ?> + <option value="<?php echo $author_id; ?>"<?php print (isset($unfamiliar[$author_id]) ? $unfamiliar[$author_id] : ''); ?>>will have their posts attributed to <?php echo $author_name; ?></option> + <?php endforeach; ?> + <option value="newuser">will have their posts attributed to a user named ...</option> + <option value="filter"<?php print $unfamiliar['filter'] ?>>get filtered out</option> + </select> + + <span id="unfamiliar-author-newuser"><input type="text" name="unfamiliar_author_newuser" value="" /></span></p> +</td> +</tr> +</table></li> +</ul> + +</td> +</tr> +<?php endif; ?> + +<?php if ($page->for_default_settings()) : ?> +<tr> +<th scope="row">Matching Authors</th> +<td><ul style="list-style: none; margin: 0; padding: 0;"> +<li><div><label><input id="match-author-by-email" type="checkbox" name="match_author_by_email" value="yes" <?php if ($match_author_by_email) : ?>checked="checked" <?php endif; ?>onchange="contextual_appearance('match-author-by-email', 'unless-null-email', null, 'yes', 'block', /*checkbox=*/ true);" /> Treat syndicated authors with the same e-mail address as the same author.</label></div> +<div id="unless-null-email"> +<p>Unless the e-mail address is one of the following anonymous e-mail addresses:</p> +<textarea name="null_emails" rows="3" style="width: 100%"> +<?php print implode("\n", $null_emails); ?> +</textarea> +</div></li> +</ul></td> +</tr> +<?php endif; ?> +</tbody> +</table> + <?php + } /* FeedWordPressAuthorsPage::syndicated_authors_box () */ + + /*static*/ function fix_authors_box ($page, $box = NULL) { + ?> + <table class="form-table"> + <tbody> + <tr> + <th scope="row">Fixing mis-matched authors:</th> + <td><p style="margin: 0.5em 0px">Take all the posts from this feed attributed to + <select name="fix_mismatch_from"> + <?php foreach ($page->authorlist as $author_id => $author_name) : ?> + <option value="<?php echo $author_id; ?>"><?php echo $author_name; ?></option> + <?php endforeach; ?> + </select> + and instead + <select id="fix-mismatch-to" name="fix_mismatch_to" onchange="contextual_appearance('fix-mismatch-to', 'fix-mismatch-to-newuser', null, 'newuser', 'inline');"> + <?php foreach ($page->authorlist as $author_id => $author_name) : ?> + <option value="<?php echo $author_id; ?>">re-assign them to <?php echo $author_name; ?></option> + <?php endforeach; ?> + <option value="newuser">re-assign them to a new user...</option> + <option value="filter">delete them</option> + </select> + + <span id="fix-mismatch-to-newuser">named <input type="text" name="fix_mismatch_to_newuser" value="" /></span> + <input type="submit" class="button" name="fix_mismatch" value="Fix it!" /> + </td> + </tr> + </tbody> + </table> + <?php + } /* FeedWordPressAuthorsPage::fix_authors_box () */ + + function display () { + $this->boxes_by_methods = array( + 'syndicated_authors_box' => __('Syndicated Authors'), + 'fix_authors_box' => __('Reassign Authors'), + ); + if ($this->for_default_settings()) : + unset($this->boxes_by_methods['fix_authors_box']); + endif; + + parent::display(); + ?> +<script type="text/javascript"> + contextual_appearance('unfamiliar-author', 'unfamiliar-author-newuser', 'unfamiliar-author-default', 'newuser', 'inline'); +</script> + +<?php if ($this->for_feed_settings()) : ?> +<script type="text/javascript"> + jQuery('.author-rules').each ( function () { + contextual_appearance(this.id, this.id+'-newuser', this.id+'-default', 'newuser', 'inline'); + } ); + + contextual_appearance('add-author-rule', 'add-author-rule-newuser', 'add-author-rule-default', 'newuser', 'inline'); + contextual_appearance('fix-mismatch-to', 'fix-mismatch-to-newuser', null, 'newuser', 'inline'); +</script> +<?php else : ?> +<script type="text/javascript"> + contextual_appearance('match-author-by-email', 'unless-null-email', null, 'yes', 'block', /*checkbox=*/ true); +</script> +<?php endif; + } /* FeedWordPressAuthorsPage::display () */ + + function accept_POST ($post) { + if (isset($post['fix_mismatch']) and (strlen($post['fix_mismatch']) > 0)) : + $this->fix_mismatch($post); + else : + parent::accept_POST($post); + endif; + } + + function fix_mismatch ($post) { + global $wpdb; + + if ('newuser'==$post['fix_mismatch_to']) : + $newuser_name = trim($post['fix_mismatch_to_newuser']); + $to = fwp_insert_new_user($newuser_name); + else : + $to = $post['fix_mismatch_to']; + endif; + + $from = (int) $post['fix_mismatch_from']; + if (is_numeric($from)) : + // Make a list of all the items by this author + // syndicated from this feed... + $post_ids = $wpdb->get_col(" + SELECT {$wpdb->posts}.id + FROM {$wpdb->posts}, {$wpdb->postmeta} + WHERE ({$wpdb->posts}.id = {$wpdb->postmeta}.post_id) + AND {$wpdb->postmeta}.meta_key = 'syndication_feed_id' + AND {$wpdb->postmeta}.meta_value = '{$this->link->id}' + AND {$wpdb->posts}.post_author = '{$from}' + "); + + if (count($post_ids) > 0) : + $N = count($post_ids); + $posts = 'post'.(($N==1) ? '' : 's'); + + // Re-assign them all to the correct author + if (is_numeric($to)) : // re-assign to a particular user + $post_set = "(".implode(",", $post_ids).")"; + + // Getting the revisions too, if there are any + $parent_in_clause = "OR {$wpdb->posts}.post_parent IN $post_set"; + + $wpdb->query(" + UPDATE {$wpdb->posts} + SET post_author='{$to}' + WHERE ({$wpdb->posts}.id IN $post_set + $parent_in_clause) + "); + $this->mesg = sprintf(__("Re-assigned %d ${posts}."), $N); + + // ... and kill them all + elseif ('filter'==$to) : + foreach ($post_ids as $post_id) : + wp_delete_post($post_id); + endforeach; + + $this->mesg = sprintf(__("Deleted %d ${posts}."), $N); + endif; + else : + $this->mesg = __("Couldn't find any posts that matched your criteria."); + endif; + endif; + $this->updated = false; + } + + function save_settings ($post) { + + if ($this->for_feed_settings()) : + $alter = array (); + + // Unfamiliar author rule + if (isset($post["unfamiliar_author"])) : + if ('newuser'==$post['unfamiliar_author']) : + $new_name = trim($post["unfamiliar_author_newuser"]); + $this->link->map_name_to_new_user(/*name=*/ NULL, $new_name); + else : + $this->link->update_setting( + "unfamiliar author", + $post['unfamiliar_author'], + 'site-default' + ); + endif; + endif; + + // Handle author mapping rules + if (isset($post['author_rules_name']) + and isset($post['author_rules_action'])) : + if (isset($post['author_rules_name']['all'])) : + if (strlen($post['author_rules_name']['all']) > 0) : + $post['author_rules_name'] = array( + 'all' => $post['author_rules_name']['all'], + ); + + // Erase all the rest. + endif; + endif; + + unset($this->link->settings['map authors']); + foreach ($post['author_rules_name'] as $key => $name) : + // Normalize for case and whitespace + $name = strtolower(trim($name)); + $author_action = strtolower(trim($post['author_rules_action'][$key])); + + if (strlen($name) > 0) : + if ('newuser' == $author_action) : + $new_name = trim($post['author_rules_newuser'][$key]); + $this->link->map_name_to_new_user($name, $new_name); + else : + $this->link->settings['map authors']['name'][$name] = $author_action; + endif; + endif; + endforeach; + endif; + + if (isset($post['add_author_rule_name']) + and isset($post['add_author_rule_action'])) : + $name = strtolower(trim($post['add_author_rule_name'])); + $author_action = strtolower(trim($post['add_author_rule_action'])); + + if (strlen($name) > 0) : + if ('newuser' == $author_action) : + $new_name = trim($post['add_author_rule_newuser']); + $this->link->map_name_to_new_user($name, $new_name); + else : + $this->link->settings['map authors']['name'][$name] = $author_action; + endif; + endif; + endif; + else : + if ('newuser'==$post['unfamiliar_author']) : + $new_name = trim($post['unfamiliar_author_newuser']); + $new_id = fwp_insert_new_user($new_name); + if (is_numeric($new_id)) : + update_option('feedwordpress_unfamiliar_author', $new_id); + else : + // TODO: Add some error detection and reporting + // Put WP_Error stuff into $this->mesg ? + endif; + else : + update_option('feedwordpress_unfamiliar_author', $post['unfamiliar_author']); + endif; + + update_option('feedwordpress_do_not_match_author_by_email', + (isset($post['match_author_by_email']) + and 'yes'==$post['match_author_by_email']) + ? 'no' + : 'yes' + ); + + if (isset($post['null_emails'])) : + update_option('feedwordpress_null_email_set', $post['null_emails']); + endif; + endif; + + parent::save_settings($post); + $this->refresh_author_list(); + } +} /* class FeedWordPressAuthorsPage */ + + $authorsPage = new FeedWordPressAuthorsPage; + $authorsPage->display(); + diff --git a/wp-content/plugins/feedwordpress/categories-page.php b/wp-content/plugins/feedwordpress/categories-page.php new file mode 100644 index 0000000000000000000000000000000000000000..1c7787c182edee6c9f54d3c4b3f3737971ce9dfc --- /dev/null +++ b/wp-content/plugins/feedwordpress/categories-page.php @@ -0,0 +1,617 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); + +class FeedWordPressCategoriesPage extends FeedWordPressAdminPage { + function FeedWordPressCategoriesPage ($link = -1) { + if (is_numeric($link) and -1 == $link) : + $link = $this->submitted_link(); + endif; + + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpresscategories', $link); + $this->dispatch = 'feedwordpress_admin_page_categories'; + $this->pagenames = array( + 'default' => 'Categories'.FEEDWORDPRESS_AND_TAGS, + 'settings-update' => 'Syndicated Categories'.FEEDWORDPRESS_AND_TAGS, + 'open-sheet' => 'Categories'.FEEDWORDPRESS_AND_TAGS, + ); + $this->filename = __FILE__; + } + + function unfamiliar_category_label ($name) { + if (preg_match('/^create:(.*)$/', $name, $refs)) : + $tax = get_taxonomy($refs[1]); + $name = sprintf(__('Create new %s to match them'), $tax->labels->name); + endif; + return $name; + } + + + function feed_categories_box ($page, $box = NULL) { + $link = $page->link; + + $globalPostType = get_option('feedwordpress_syndicated_post_type', 'post'); + if ($this->for_feed_settings()) : + $post_type = $link->setting('syndicated post type', 'syndicated_post_type', 'post'); + else : + $post_type = $globalPostType; + endif; + $taxonomies = get_object_taxonomies(array('object_type' => $post_type), 'names'); + + $unmatched = array('category' => array(), 'post_tag' => array()); + $matchUl = array('cats' => array(), 'tags' => array(), 'filter' => array()); + $tagLikeTaxonomies = array(); + foreach ($taxonomies as $tax) : + $taxonomy = get_taxonomy($tax); + + if (!$taxonomy->hierarchical) : + $tagLikeTaxonomies[] = $tax; + endif; + + $name = 'create:'.$tax; + foreach (array('category', 'post_tag') as $what) : + $unmatched[$what][$name] = array( + 'label' => $this->unfamiliar_category_label($name), + ); + $unmatchedRadio[$what][$name] = ''; + endforeach; + + foreach (array('cats', 'tags', 'filter') as $what) : + $matchUl[$what][$tax] = array( + 'checked' => '', + 'labels' => $taxonomy->labels, + ); + endforeach; + endforeach; + + foreach ($unmatched as $what => $um) : + $unmatched[$what]['null'] = array('label' => __('Don\'t create any matching terms')); + $unmatchedRadio[$what]['null'] = ''; + endforeach; + + $globalUnmatched = array( + 'category' => FeedWordPress::on_unfamiliar('category'), + 'post_tag' => FeedWordPress::on_unfamiliar('post_tag'), + ); + foreach ($globalUnmatched as $what => $value) : + if ($value=='create') : $value = 'create:category'; endif; + if ($value=='tag') : $value = 'create:post_tag'; endif; + $globalUnmatched[$what] = $value; + endforeach; + + $globalMatch['cats'] = get_option('feedwordpress_match_cats', $taxonomies); + $globalMatch['tags'] = get_option('feedwordpress_match_tags', $tagLikeTaxonomies); + $globalMatch['filter'] = get_option('feedwordpress_match_filter', array()); + + $globalMatchLabels = array(); + $nothingDoing = array('cats' => "won't try to match", 'tags' => "won't try to match", "filter" => "won't filter"); + + foreach ($globalMatch as $what => $domain) : + $labels = array(); $domain = array_filter($domain, 'remove_dummy_zero'); + foreach ($domain as $tax) : + $tax = get_taxonomy($tax); + $labels[] = $tax->labels->name; + endforeach; + + if (count($labels) > 0) : + $globalMatchLabels[$what] = implode(", ", $labels); + else : + $globalMatchLabels[$what] = $nothingDoing[$what]; + endif; + endforeach; + + if ($this->for_feed_settings()) : + $href = "admin.php?page={$GLOBALS['fwp_path']}/".basename(__FILE__); + + foreach ($unmatched as $what => $um) : + // Is the global default setting appropriate to this post type? + $GUC = $globalUnmatched[$what]; + if (isset($um[$GUC])) : + // Yup. Let's add a site-default option + $currently = $um[$GUC]['label']; + $defaultLi = array( + 'site-default' => array( + 'label' => sprintf( + __('Use the <a href="%s">site-wide setting</a> <span class="current-setting">Currently: <strong>%s</strong></span>'), + $href, + $currently + ), + ), ); + $unmatchedColumns[$what] = array( + $defaultLi, + ); + $unmatchedDefault[$what] = 'site-default'; + $unmatchedRadio[$what]['site-default'] = ''; + else : + $opts = array_keys($unmatched[$what]); + $unmatchedDefault[$what] = $opts[0]; + $unmatchedColumns[$what] = array(); + endif; + + $ucKey[$what] = $link->setting("unfamiliar $what", NULL, NULL); + endforeach; + + $match['cats'] = $this->link->setting('match/cats', NULL, NULL); + $match['tags'] = $this->link->setting('match/tags', NULL, NULL); + $match['filter'] = $this->link->setting('match/filter', NULL, NULL); + else : + foreach ($unmatched as $what => $um) : + $ucKey[$what] = FeedWordPress::on_unfamiliar($what); + endforeach; + + $match = $globalMatch; + endif; + + foreach ($ucKey as $what => $uck) : + if ($uck == 'tag') : $uck = 'create:post_tag'; endif; + if ($uck == 'create') : $uck = 'create:category'; endif; + + if (!is_string($uck)) : + $uck = $unmatchedDefault[$what]; + endif; + $ucKey[$what] = $uck; + + if (!array_key_exists($uck, $unmatchedRadio[$what])) : + $obsoleteLi = array( + $uck => array( + 'label' => ' <span style="font-style: italic; color: #777;">'.$this->unfamiliar_category_label($uck).'</span> <span style="background-color: #ffff90; color: black;">(This setting is no longer applicable to the type of post syndicated from this feed!)</span><p>Please change this one of the following settings:</p>', + ), + ); + $unmatched[$what] = array_merge($obsoleteLi, $unmatched[$what]); + $unmatchedRadio[$what][$uck] = ' disabled="disabled"'; + endif; + + $unmatchedRadio[$what][$uck] .= ' checked="checked"'; + + $unmatchedColumns[$what][] = $unmatched[$what]; + endforeach; + + $defaulted = array(); + foreach ($match as $what => $set) : + $defaulted[$what] = false; + if (is_null($set) or (count($set) < 1)) : + $defaulted[$what] = true; + if ($this->for_feed_settings()) : + $set = $globalMatch[$what]; + $match[$what] = $globalMatch[$what]; + endif; + endif; + + if (!$defaulted[$what] or $this->for_feed_settings()) : + foreach ($set as $against) : + if (array_key_exists($against, $matchUl[$what])) : + $matchUl[$what][$against]['checked'] = ' checked="checked"'; + endif; + endforeach; + endif; + endforeach; + + // Hey ho, let's go... + $offerSiteWideSettings = ($page->for_feed_settings() and ($post_type==$globalPostType)); + ?> +<table class="edit-form narrow"> +<tr> +<th scope="row">Match feed categories:</th> +<td><input type="hidden" name="match_categories[cats][]" value="0" /> +<?php if ($offerSiteWideSettings) : ?> + <table class="twofer"> + <tbody> + <tr><td class="equals first <?php if ($defaulted['cats']) : ?>active<?php else: ?>inactive<?php endif; ?>"><p><label><input type="radio" name="match_default[cats]" +value="yes" <?php if ($defaulted['cats']) : ?> checked="checked"<?php endif; ?> /> +Use the <a href="<?php print $href; ?>">site-wide setting</a> +<span class="current-setting">Currently: <strong><?php print $globalMatchLabels['cats']; ?></strong></span></label></p></td> + <td class="equals second <?php if ($defaulted['cats']) : ?>inactive<?php else: ?>active<?php endif; ?>"><p><label><input type="radio" name="match_default[cats]" +value="no" <?php if (!$defaulted['cats']) : ?> checked="checked"<?php endif; ?> /> +Do something different with this feed.</label> +<?php else : ?> + <p> +<?php endif; ?> +When a feed provides categories for a post, try to match those categories +locally with:</p> +<ul class="options compact"> +<?php foreach ($matchUl['cats'] as $name => $li) : ?> + <li><label><input type="checkbox" + name="match_categories[cats][]" value="<?php print $name; ?>" + <?php print $li['checked']; ?> /> <?php $l = $li['labels']; print $l->name; ?></label></li> +<?php endforeach; ?> +</ul> +<?php if ($offerSiteWideSettings) : ?> + </td></tr> + </tbody> + </table> +<?php endif; ?> +</td> +</tr> + +<tr> +<th scope="row">Unmatched categories:</th> +<td><p>When <?php print $this->these_posts_phrase(); ?> have categories on +the feed that don't have any local matches yet...</p> + +<?php if (count($unmatchedColumns['category']) > 1) : ?> + <table class="twofer"> +<?php else : ?> + <table style="width: 100%"> +<?php endif; ?> + <tbody> + <tr> + <?php foreach ($unmatchedColumns['category'] as $index => $column) : ?> + <td class="equals <?php print (($index == 0) ? 'first' : 'second'); ?> inactive"><ul class="options"> + <?php foreach ($column as $name => $li) : ?> + <li><label><input type="radio" name="unfamiliar_category" value="<?php print $name; ?>"<?php print $unmatchedRadio['category'][$name]; ?> /> <?php print $li['label']; ?></label></li> + <?php endforeach; ?> + </ul></td> + <?php endforeach; ?> + </tr> + </tbody> + </table> +</td></tr> + +<tr> +<th scope="row">Match inline tags: +<p class="setting-description">Applies only to inline tags marked +as links in the text of syndicated posts, using the +<code><a rel="tag">...</a></code> microformat. +Most feeds with "tags" just treat them as normal feed categories, +like those handled above.</p> +</th> +<td><input type="hidden" name="match_categories[tags][]" value="0" /> +<?php if ($offerSiteWideSettings) : ?> + <table class="twofer"> + <tbody> + <tr><td class="equals first <?php if ($defaulted['tags']) : ?>active<?php else: ?>inactive<?php endif; ?>"><p><label><input type="radio" name="match_default[tags]" +value="yes" <?php if ($defaulted['tags']) : ?> checked="checked"<?php endif; ?> /> +Use the <a href="<?php print $href; ?>">site-wide setting</a> +<span class="current-setting">Currently: <strong><?php print $globalMatchLabels['tags']; ?></strong></span></label></p> +</td> + <td class="equals second <?php if ($defaulted['tags']) : ?>inactive<?php else: ?>active<?php endif; ?>"><p><label><input type="radio" name="match_default[tags]" +value="no" <?php if (!$defaulted['tags']) : ?> checked="checked"<?php endif; ?> /> +Do something different with this feed.</label> +<?php else : ?> + <p> +<?php endif; ?> +When a feed provides tags inline in a post, try to match those tags +locally with:</p> +<ul class="options compact"> +<?php foreach ($matchUl['tags'] as $name => $li) : ?> + <li><label><input type="checkbox" + name="match_categories[tags][]" value="<?php print $name; ?>" + <?php print $li['checked']; ?> /> <?php $l = $li['labels']; print $l->name; ?></label></li> +<?php endforeach; ?> +</ul> +<?php if ($offerSiteWideSettings) : ?> + </td></tr> + </tbody> + </table> +<?php endif; ?> +</td> +</tr> + +<tr> +<th scope="row">Unmatched inline tags:</th> +<td><p>When the text of <?php print $this->these_posts_phrase(); ?> contains +inline tags that don't have any local matches yet...</p> + +<?php if (count($unmatchedColumns['post_tag']) > 1) : ?> + <table class="twofer"> +<?php else : ?> + <table style="width: 100%"> +<?php endif; ?> + <tbody> + <tr> + <?php foreach ($unmatchedColumns['post_tag'] as $index => $column) : ?> + <td class="equals <?php print (($index == 0) ? 'first' : 'second'); ?> inactive"><ul class="options"> + <?php foreach ($column as $name => $li) : ?> + <li><label><input type="radio" name="unfamiliar_post_tag" value="<?php print $name; ?>"<?php print $unmatchedRadio['post_tag'][$name]; ?> /> <?php print $li['label']; ?></label></li> + <?php endforeach; ?> + </ul></td> + <?php endforeach; ?> + </tr> + </tbody> + </table> + +</td></tr> + +<tr> +<th scope="row">Filter:</th> +<td><input type="hidden" name="match_categories[filter][]" value="0" /> +<?php if ($offerSiteWideSettings) : ?> + <table class="twofer"> + <tbody> + <tr> + <td class="equals first <?php if ($defaulted['filter']) : ?>active<?php else: ?>inactive<?php endif; ?>"> + <p><label><input type="radio" name="match_default[filter]" +value="yes" <?php if ($defaulted['filter']) : ?> checked="checked"<?php endif; ?> /> +Use the <a href="<?php print $href; ?>">site-wide setting</a> +<span class="current-setting">Currently: <strong><?php print $globalMatchLabels['filter']; ?></strong></span></label></p> + </td> + <td class="equals second <?php if ($defaulted['filter']) : ?>inactive<?php else: ?>active<?php endif; ?>"> + <p><label><input type="radio" name="match_default[filter]" +value="no" <?php if (!$defaulted['filter']) : ?> checked="checked"<?php endif; ?> /> +Do something different with this feed:</label></p> +<div style="margin-left: 3.0em;"> +<?php endif; ?> + +<ul class="options"> +<?php foreach ($matchUl['filter'] as $tax => $li) : ?> +<li><label><input type="checkbox" name="match_categories[filter][]" value="<?php print $tax; ?>" +<?php print $li['checked']; ?> /> Don't syndicate posts unless they match at +least one local <strong><?php $l = $li['labels']; print $l->singular_name; ?></strong></label></li> +<?php endforeach; ?> +</ul> + +<?php if ($offerSiteWideSettings) : ?> + </div> + </td></tr> + </tbody> + </table> +<?php endif; ?> +</td> +</tr> +<?php if ($page->for_feed_settings()) : ?> +<tr> +<th scope="row">Multiple categories:</th> +<td> +<input type="text" size="20" id="cat_split" name="cat_split" value="<?php if (isset($link->settings['cat_split'])) : echo htmlspecialchars($link->settings['cat_split']); endif; ?>" /> +<p class="setting-description">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 +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 <a href="http://del.icio.us/">del.icio.us</a>), use the pattern "\s". +If the feed does not provide multiple categories in a single element, leave this +blank.</p></td> +</tr> +<?php endif; ?> +</table> + <?php + } /* FeedWordPressCategoriesPage::feed_categories_box() */ + + function term_option_map () { + return array( + 'category' => 'feedwordpress_syndication_cats', + 'post_tag' => 'feedwordpress_syndication_tags', + ); + } + function term_setting_map () { + return array( + 'category' => 'cats', + 'post_tag' => 'tags', + ); + } + + function categories_box ($page, $box = NULL) { + $link = $page->link; + + if ($this->for_feed_settings()) : + $post_type = $link->setting('syndicated post type', 'syndicated_post_type', 'post'); + else : + $post_type = get_option('feedwordpress_syndicated_post_type', 'post'); + endif; + $taxonomies = get_object_taxonomies(array('object_type' => $post_type), 'names'); + + $option_map = $this->term_option_map(); + $setting_map = $this->term_setting_map(); + $globalTax = get_option('feedwordpress_syndication_terms', array()); + if ($page->for_feed_settings()) : + $terms = $link->setting('terms', NULL, array()); + endif; + + ?> + <table class="edit-form narrow"> + <tbody> + <?php + foreach ($taxonomies as $tax) : + $taxonomy = get_taxonomy($tax); + ?> + <tr><th><?php print $taxonomy->labels->name; ?></th> + <td><?php + if (isset($option_map[$tax])) : + $option = $option_map[$tax]; + $globalCats = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, get_option($option)); + elseif (isset($globalTax[$tax])) : + $globalCats = $globalTax[$tax]; + else : + $globalCats = array(); + endif; + $globalCats = array_map('trim', $globalCats); + + if ($page->for_feed_settings()) : + $add_global_categories = $link->setting("add/$tax", NULL, 'yes'); + $checked = array('yes' => '', 'no' => ''); + $checked[$add_global_categories] = ' checked="checked"'; + + if (isset($setting_map[$tax])) : + $setting = $setting_map[$tax]; + $cats = $link->setting($setting, NULL, NULL); + if (is_null($cats)) : $cats = array(); endif; + elseif (isset($terms[$tax])) : + $cats = $terms[$tax]; + else : + $cats = array(); + endif; + else : + $cats = $globalCats; + endif; + + if ($page->for_feed_settings()) : + ?> + <table class="twofer"> + <tbody> + <tr> + <td class="primary"> + <?php + endif; + + $dogs = SyndicatedPost::category_ids($cats, /*unfamiliar=*/ NULL, /*taxonomies=*/ array($tax)); + + if ($taxonomy->hierarchical) : // Use a category-style checkbox + fwp_category_box($dogs, 'all '.$page->these_posts_phrase(), /*tags=*/ array(), /*params=*/ array('taxonomy' => $tax)); + else : // Use a tag-style edit box + fwp_tags_box($cats, 'all '.$page->these_posts_phrase(), /*params=*/ array('taxonomy' => $tax)); + endif; + + $globalDogs = SyndicatedPost::category_ids($globalCats, /*unfamiliar=*/ 'create:'.$tax, /*taxonomies=*/ array($tax)); + + $siteWideHref = 'admin.php?page='.$GLOBALS['fwp_path'].'/'.basename(__FILE__); + if ($page->for_feed_settings()) : + ?> + </td> + <td class="secondary"> + <h4>Site-wide <?php print $taxonomy->labels->name; ?></h4> + <?php if (count($globalCats) > 0) : ?> + <ul class="current-setting"> + <?php + foreach ($globalDogs as $dog) : + ?> + <li><?php $cat = get_term($dog, $tax); print $cat->name; ?></li> + <?php endforeach; ?> + </ul> + </div> + <p> + <?php else : ?> + <p>Site-wide settings may also assign categories to syndicated + posts. + <?php endif; ?> + Should <?php print $page->these_posts_phrase(); ?> be assigned + these <?php print $taxonomy->labels->name; ?> from the <a href="<?php print esc_html($siteWideHref); ?>">site-wide settings</a>, in + addition to the feed-specific <?php print $taxonomy->labels->name; ?> you set up here?</p> + + <ul class="settings"> + <li><p><label><input type="radio" name="add_global[<?php print $tax; ?>]" value="yes" <?php print $checked['yes']; ?> /> Yes. Place <?php print $page->these_posts_phrase(); ?> under all these categories.</label></p></li> + <li><p><label><input type="radio" name="add_global[<?php print $tax; ?>]" value="no" <?php print $checked['no']; ?> /> No. Only use the categories I set up on the left. Do not ise the global defaults for <?php print $page->these_posts_phrase(); ?></label></p></li> + </ul> + </td> + </tr> + </tbody> + </table> + <?php + endif; + ?> + </td> + </tr> + <?php + endforeach; + ?> + </tbody> + </table> + <?php + } /* FeedWordPressCategoriesPage::categories_box () */ + + function save_settings ($post) { + if (isset($post['match_categories'])) : + foreach ($post['match_categories'] as $what => $set) : + // Defaulting is controlled by a separate radio button + if ($this->for_feed_settings() + and isset($post['match_default']) + and isset($post['match_default'][$what]) + and $post['match_default'][$what]=='yes') : + $set = NULL; // Defaulted! + endif; + + $this->update_setting("match/$what", $set, NULL); + endforeach; + endif; + $optionMap = $this->term_option_map(); + $settingMap = $this->term_setting_map(); + + $saveTerms = array(); $separateSaveTerms = array('category' => array(), 'post_tag' => array()); + + if (!isset($post['tax_input'])) : $post['tax_input'] = array(); endif; + + // Merge in data from older-notation category check boxes + if (isset($post['post_category'])) : + // Just merging in for processing below. + $post['tax_input']['category'] = array_merge( + (isset($post['tax_input']['category']) ? $post['tax_input']['category'] : array()), + $post['post_category'] + ); + endif; + + // Process data from term tag boxes and check boxes + foreach ($post['tax_input'] as $tax => $terms) : + $saveTerms[$tax] = array(); + if (is_array($terms)) : // Numeric IDs from checklist + foreach ($terms as $term) : + if ($term) : + $saveTerms[$tax][] = '{'.$tax.'#'.$term.'}'; + endif; + endforeach; + else : // String from tag input + $saveTerms[$tax] = explode(",", $terms); + endif; + $saveTerms[$tax] = array_map('trim', $saveTerms[$tax]); + + if (isset($optionMap[$tax])) : + $separateSaveTerms[$tax] = $saveTerms[$tax]; + unset($saveTerms[$tax]); + endif; + endforeach; + + if (isset($post['post_category'])) : + foreach ($post['post_category'] as $cat) : + $separateSaveTerms['category'][] = '{category#'.$cat.'}'; + endforeach; + endif; + + // Unmatched categories and tags + foreach (array('category', 'post_tag') as $what) : + if (isset($post["unfamiliar_{$what}"])) : + $this->update_setting( + "unfamiliar {$what}", + $post["unfamiliar_{$what}"], + 'site-default' + ); + endif; + endforeach; + + // Categories and Tags + foreach ($separateSaveTerms as $tax => $terms) : + if ($this->for_feed_settings()) : + $this->link->update_setting($settingMap[$tax], $terms, array()); + else : + if (!empty($terms)) : + update_option($optionMap[$tax], implode(FEEDWORDPRESS_CAT_SEPARATOR, $terms)); + else : + delete_option($optionMap[$tax]); + endif; + endif; + endforeach; + + // Other terms + $this->update_setting(array('feed'=>'terms', 'global'=>'syndication_terms'), $saveTerms, array()); + + if ($this->for_feed_settings()) : + // Category splitting regex + if (isset($post['cat_split'])) : + $this->link->update_setting('cat_split', trim($post['cat_split']), ''); + endif; + + // Treat global terms (cats, tags, etc.) as additional, + // or as defaults to be overridden and replaced? + if (isset($post['add_global'])) : + foreach ($post['add_global'] as $what => $value) : + $this->link->update_setting("add/$what", $value); + endforeach; + endif; + endif; + parent::save_settings($post); + } /* FeedWordPressCategoriesPage::save_settings() */ + + function display () { + //////////////////////////////////////////////// + // Display settings boxes ////////////////////// + //////////////////////////////////////////////// + + $this->boxes_by_methods = array( + 'feed_categories_box' => __('Feed Categories'.FEEDWORDPRESS_AND_TAGS), + 'categories_box' => array('title' => __('Categories'), 'id' => 'categorydiv'), + ); + if (!FeedWordPressCompatibility::post_tags()) : + unset($this->boxes_by_methods['tags_box']); + endif; + + parent::display(); + } +} + + $categoriesPage = new FeedWordPressCategoriesPage; + $categoriesPage->display(); + diff --git a/wp-content/plugins/feedwordpress/compatability.php b/wp-content/plugins/feedwordpress/compatability.php new file mode 100644 index 0000000000000000000000000000000000000000..1717949e00f6330c9b3d73f8bd4a930a93f83041 --- /dev/null +++ b/wp-content/plugins/feedwordpress/compatability.php @@ -0,0 +1,222 @@ +<?php +################################################################################ +## LEGACY API: Replicate or mock up functions for legacy support purposes ###### +################################################################################ + +class FeedWordPressCompatibility { + // version testing based on database schema version + /*static*/ function test_version ($floor, $ceiling = null) { + global $wp_db_version; + + $ver = (isset($wp_db_version) ? $wp_db_version : 0); + $good = ($ver >= $floor); + if (!is_null($ceiling)) : + $good = ($good and ($ver < $ceiling)); + endif; + return $good; + } /* FeedWordPressCompatibility::test_version() */ + + /*static*/ function insert_link_category ($name) { + global $wpdb; + + $name = $wpdb->escape($name); + + // WordPress 2.3+ term/taxonomy API + $term = wp_insert_term($name, 'link_category'); + $cat_id = $term['term_id']; + + // Return newly-created category ID + return $cat_id; + } /* FeedWordPressCompatibility::insert_link_category () */ + + /*static*/ function link_category_id ($value, $key = 'cat_name') { + global $wpdb; + + $cat_id = NULL; + + $the_term = term_exists($value, 'link_category'); + + // Sometimes, in some versions, we get a row + if (is_array($the_term)) : + $cat_id = $the_term['term_id']; + + // other times we get an integer result + else : + $cat_id = $the_term; + endif; + + return $cat_id; + } /* FeedWordPressCompatibility::link_category_id () */ + + /*static*/ function post_tags () { + return FeedWordPressCompatibility::test_version(FWP_SCHEMA_23); + } /* FeedWordPressCompatibility::post_tags () */ + + /*static*/ function validate_http_request ($action = -1, $capability = null) { + // Only worry about this if we're using a method with significant side-effects + if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') : + // Limit post by user capabilities + if (!is_null($capability) and !current_user_can($capability)) : + wp_die(__('Cheatin’ uh?')); + endif; + + // If check_admin_referer() checks a nonce. + if (function_exists('wp_verify_nonce')) : + check_admin_referer($action); + + // No nonces means no checking nonces. + else : + check_admin_referer(); + endif; + endif; + } /* FeedWordPressCompatibility::validate_http_request() */ + + /*static*/ function stamp_nonce ($action = -1) { + // stamp form with hidden fields for a nonce in WP 2.0.3 & later + if (function_exists('wp_nonce_field')) : + wp_nonce_field($action); + endif; + } /* FeedWordPressCompatibility::stamp_nonce() */ + + /*static*/ function bottom_script_hook ($filename) { + global $fwp_path; + + $hook = 'admin_footer'; + if (FeedWordPressCompatibility::test_version(FWP_SCHEMA_28)) : // WordPress 2.8+ + $hook = $hook . '-' . $fwp_path . '/' . basename($filename); + endif; + return $hook; + } /* FeedWordPressCompatibility::bottom_script_hook() */ +} /* class FeedWordPressCompatibility */ + +define('FEEDWORDPRESS_AND_TAGS', (FeedWordPressCompatibility::post_tags() ? ' & Tags' : '')); + +if (!function_exists('stripslashes_deep')) { + function stripslashes_deep($value) { + $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); + return $value; + } +} + +if (!function_exists('sanitize_user')) { + function sanitize_user ($text, $strict = false) { + return $text; // Don't munge it if it wasn't munged going in... + } +} + +if (!function_exists('disabled')) { + /** + * Outputs the html disabled attribute. + * + * Compares the first two arguments and if identical marks as disabled + * + * @since 3.0.0 + * + * @param mixed $disabled One of the values to compare + * @param mixed $current (true) The other value to compare if not just true + * @param bool $echo Whether to echo or just return the string + * @return string html attribute or empty string + */ + function disabled( $disabled, $current = true, $echo = true ) { + return __checked_selected_helper( $disabled, $current, $echo, 'disabled' ); + } +} /* if */ + +if (!function_exists('term_exists')) { + // Fucking WordPress 3.0 wordsmithing. + function term_exists ( $term, $taxonomy = '', $parent = 0 ) { + return is_term($term, $taxonomy, $parent); + } +} /* if */ + +require_once(dirname(__FILE__).'/feedwordpress-walker-category-checklist.class.php'); + +function fwp_category_checklist ($post_id = 0, $descendents_and_self = 0, $selected_cats = false, $params = array()) { + if (is_string($params)) : + $prefix = $params; + $taxonomy = 'category'; + elseif (is_array($params)) : + $prefix = (isset($params['prefix']) ? $params['prefix'] : ''); + $taxonomy = (isset($params['taxonomy']) ? $params['taxonomy'] : 'category'); + endif; + + $walker = new FeedWordPress_Walker_Category_Checklist; + $walker->set_prefix($prefix); + $walker->set_taxonomy($taxonomy); + wp_terms_checklist(/*post_id=*/ $post_id, array( + 'taxonomy' => $taxonomy, + 'descendents_and_self' => $descendents_and_self, + 'selected_cats' => $selected_cats, + 'popular_cats' => false, + 'walker' => $walker, + 'checked_ontop' => true, + )); +} + +function fwp_time_elapsed ($ts) { + if (function_exists('human_time_diff')) : + if ($ts >= time()) : + $ret = __(human_time_diff($ts)." from now"); + else : + $ret = __(human_time_diff($ts)." ago"); + endif; + else : + $ret = strftime('%x %X', $ts); + endif; + return $ret; +} + +################################################################################ +## 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 "<div class=\"wrap\">\n"; + echo "<h2>Upgrading FeedWordPress...</h2>"; + + $feedwordpress = new FeedWordPress; + $feedwordpress->upgrade_database($ver); + echo "<p><strong>Done!</strong> Upgraded database to version ".FEEDWORDPRESS_VERSION.".</p>\n"; + echo "<form action=\"\" method=\"get\">\n"; + echo "<div class=\"submit\"><input type=\"hidden\" name=\"page\" value=\"syndication.php\" />"; + echo "<input type=\"submit\" value=\"Continue »\" /></form></div>\n"; + echo "</div>\n"; + return; + else : + echo "<div class=\"updated\"><p>Already at version ".FEEDWORDPRESS_VERSION."!</p></div>"; + endif; + endif; +?> +<div class="wrap"> +<h2>Upgrade FeedWordPress</h2> + +<p>It appears that you have installed FeedWordPress +<?php echo FEEDWORDPRESS_VERSION; ?> 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.</p> + +<p>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.</p> + +<p>This may take several minutes for a large installation.</p> + +<form action="" method="post"> +<?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_upgrade'); ?> +<div class="submit"><input type="submit" name="action" value="Upgrade" /></div> +</form> +</div> +<?php +} // function fwp_upgrade_page () + +function remove_dummy_zero ($var) { + return !(is_numeric($var) and ((int) $var == 0)); +} + diff --git a/wp-content/plugins/feedwordpress/diagnostics-page.php b/wp-content/plugins/feedwordpress/diagnostics-page.php new file mode 100644 index 0000000000000000000000000000000000000000..4e7b845479863347ebe36dab45f2ad17bdc793d6 --- /dev/null +++ b/wp-content/plugins/feedwordpress/diagnostics-page.php @@ -0,0 +1,225 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); + +class FeedWordPressDiagnosticsPage extends FeedWordPressAdminPage { + function FeedWordPressDiagnosticsPage () { + // Set meta-box context name + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpressdiagnosticspage'); + $this->dispatch = 'feedwordpress_diagnostics'; + $this->filename = __FILE__; + } + + function has_link () { return false; } + + function display () { + global $wpdb, $wp_db_version, $fwp_path; + global $fwp_post; + + if (FeedWordPress::needs_upgrade()) : + fwp_upgrade_page(); + return; + endif; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_diagnostics', /*capability=*/ 'manage_options'); + + if (strtoupper($_SERVER['REQUEST_METHOD'])=='POST') : + $this->accept_POST($fwp_post); + do_action('feedwordpress_admin_page_diagnostics_save', $GLOBALS['fwp_post'], $this); + endif; + + //////////////////////////////////////////////// + // Prepare settings page /////////////////////// + //////////////////////////////////////////////// + + $this->display_update_notice_if_updated('Diagnostics'); + + $this->open_sheet('FeedWordPress Diagnostics'); + ?> + <div id="post-body"> + <?php + $boxes_by_methods = array( + 'diagnostics_box' => __('Diagnostics'), + 'updates_box' => __('Updates'), + ); + + foreach ($boxes_by_methods as $method => $title) : + add_meta_box( + /*id=*/ 'feedwordpress_'.$method, + /*title=*/ $title, + /*callback=*/ array('FeedWordPressDiagnosticsPage', $method), + /*page=*/ $this->meta_box_context(), + /*context=*/ $this->meta_box_context() + ); + endforeach; + do_action('feedwordpress_admin_page_diagnostics_meta_boxes', $this); + ?> + <div class="metabox-holder"> + <?php + fwp_do_meta_boxes($this->meta_box_context(), $this->meta_box_context(), $this); + ?> + </div> <!-- class="metabox-holder" --> + </div> <!-- id="post-body" --> + + <?php + $this->close_sheet(); + } /* FeedWordPressDiagnosticsPage::display () */ + + function accept_POST ($post) { + if (isset($post['submit']) + or isset($post['save'])) : + update_option('feedwordpress_debug', $post['feedwordpress_debug']); + + if (!isset($post['diagnostics_output']) + or !is_array($post['diagnostics_output'])) : + $post['diagnostics_output'] = array(); + endif; + update_option('feedwordpress_diagnostics_output', $post['diagnostics_output']); + + if (!isset($post['diagnostics_show']) + or !is_array($post['diagnostics_show'])) : + $post['diagnostics_show'] = array(); + endif; + update_option('feedwordpress_diagnostics_show', $post['diagnostics_show']); + + if ($post['diagnostics_show'] + and in_array('updated_feeds:errors:persistent', $post['diagnostics_show'])) : + update_option('feedwordpress_diagnostics_persistent_errors_hours', (int) $post['diagnostics_persistent_error_hours']); + else : + delete_option('feedwordpress_diagnostics_persistent_errors_hours'); + endif; + + if (in_array('email', $post['diagnostics_output'])) : + $ded = $post['diagnostics_email_destination']; + if ('mailto'==$ded) : + $ded .= ':'.$post['diagnostics_email_destination_address']; + endif; + + update_option('feedwordpress_diagnostics_email_destination', $ded); + else : + delete_option('feedwordpress_diagnostics_email_destination'); + endif; + + $this->updated = true; // Default update message + endif; + } /* FeedWordPressDiagnosticsPage::accept_POST () */ + + /*static*/ function diagnostics_box ($page, $box = NULL) { + $settings = array(); + $settings['debug'] = (get_option('feedwordpress_debug')=='yes'); + + $diagnostics_output = get_option('feedwordpress_diagnostics_output', array()); + + $users = fwp_author_list(); + + $ded = get_option('feedwordpress_diagnostics_email_destination', 'admins'); + + if (preg_match('/^mailto:(.*)$/', $ded, $ref)) : + $ded_addy = $ref[1]; + else : + $ded_addy = NULL; + endif; + + // Hey ho, let's go... + ?> +<table class="edit-form"> +<tr style="vertical-align: top"> +<th scope="row">Debugging mode:</th> +<td><select name="feedwordpress_debug" size="1"> +<option value="yes"<?php echo ($settings['debug'] ? ' selected="selected"' : ''); ?>>on</option> +<option value="no"<?php echo ($settings['debug'] ? '' : ' selected="selected"'); ?>>off</option> +</select> + +<p>When debugging mode is <strong>ON</strong>, FeedWordPress displays many +diagnostic error messages, warnings, and notices that are ordinarily suppressed, +and turns off all caching of feeds. Use with caution: this setting is useful for +testing but absolutely inappropriate for a production server.</p> + +</td> +</tr> +<tr> +<th scope="row">Diagnostics output:</th> +<td><ul class="options"> +<li><input type="checkbox" name="diagnostics_output[]" value="error_log" <?php print (in_array('error_log', $diagnostics_output) ? ' checked="checked"' : ''); ?> /> Log in PHP error logs</label></li> +<li><input type="checkbox" name="diagnostics_output[]" value="admin_footer" <?php print (in_array('admin_footer', $diagnostics_output) ? ' checked="checked"' : ''); ?> /> Display in WordPress admin footer</label></li> +<li><input type="checkbox" name="diagnostics_output[]" value="echo" <?php print (in_array('echo', $diagnostics_output) ? ' checked="checked"' : ''); ?> /> Echo in web browser as they are issued</label></li> +<li><input type="checkbox" name="diagnostics_output[]" value="echo_in_cronjob" <?php print (in_array('echo_in_cronjob', $diagnostics_output) ? ' checked="checked"' : ''); ?> /> Echo to output when they are issued during an update cron job</label></li> +<li><input type="checkbox" name="diagnostics_output[]" value="email" <?php print (in_array('email', $diagnostics_output) ? ' checked="checked"' : ''); ?> /> Send a daily email digest to:</label> <select name="diagnostics_email_destination" id="diagnostics-email-destination" size="1"> +<option value="admins"<?php if ('admins'==$ded) : ?> selected="selected"<?php endif; ?>>the site administrators</option> +<?php foreach ($users as $id => $name) : ?> +<option value="user:<?php print (int) $id; ?>"<?php if (sprintf('user:%d', (int) $id)==$ded) : ?> selected="selected"<?php endif; ?>><?php print esc_html($name); ?></option> +<?php endforeach; ?> +<option value="mailto"<?php if (!is_null($ded_addy)) : ?> selected="selected"<?php endif; ?>>another e-mail address...</option> +</select> +<input type="email" id="diagnostics-email-destination-address" name="diagnostics_email_destination_address" value="<?php print $ded_addy; ?>" placeholder="email address" /></li> +</ul></td> +</tr> +</table> + +<script type="text/javascript"> + contextual_appearance( + 'diagnostics-email-destination', + 'diagnostics-email-destination-address', + 'diagnostics-email-destination-default', + 'mailto', + 'inline' + ); + jQuery('#diagnostics-email-destination').change ( function () { + contextual_appearance( + 'diagnostics-email-destination', + 'diagnostics-email-destination-address', + 'diagnostics-email-destination-default', + 'mailto', + 'inline' + ); + } ); +</script> + <?php + } /* FeedWordPressDiagnosticsPage::diagnostics_box () */ + + /*static*/ function updates_box ($page, $box = NULL) { + $checked = array( + 'updated_feeds' => '', 'updated_feeds:errors' => '', + 'updated_feeds:errors:persistent' => '', + "syndicated_posts" => '', 'syndicated_posts:meta_data' => '', + 'feed_items' => '', + 'memory_usage' => '', + ); + + $diagnostics_show = get_option('feedwordpress_diagnostics_show', array()); + if (is_array($diagnostics_show)) : foreach ($diagnostics_show as $thingy) : + $checked[$thingy] = ' checked="checked"'; + endforeach; endif; + + $hours = get_option('feedwordpress_diagnostics_persistent_errors_hours', 2); + + // Hey ho, let's go... + ?> +<table class="edit-form"> +<tr> +<th scope="row">Update diagnostics:</th> +<td><p>Show a diagnostic message...</p> +<ul class="options"> +<li><label><input type="checkbox" name="diagnostics_show[]" value="updated_feeds" <?php print $checked['updated_feeds']; ?> /> as each feed checked for updates</label></li> +<li><label><input type="checkbox" name="diagnostics_show[]" value="updated_feeds:errors:persistent" <?php print $checked['updated_feeds:errors:persistent'] ?> /> when attempts to update a feed have resulted in errors</label> <label>for at least <input type="number" min="1" max="360" step="1" name="diagnostics_persistent_error_hours" value="<?php print $hours; ?>" /> hours</label></li> +<li><label><input type="checkbox" name="diagnostics_show[]" value="updated_feeds:errors" <?php print $checked['updated_feeds:errors']; ?> /> any time FeedWordPress encounters any errors while checking a feed for updates</label></li> +<li><label><input type="checkbox" name="diagnostics_show[]" value="syndicated_posts" <?php print $checked['syndicated_posts']; ?> /> as each syndicated post is added to the database</label></li> +<li><label><input type="checkbox" name="diagnostics_show[]" value="feed_items" <?php print $checked['feed_items']; ?> /> as each syndicated item is considered on the feed</label></li> +<li><label><input type="checkbox" name="diagnostics_show[]" value="memory_usage" <?php print $checked['memory_usage']; ?> /> indicating how much memory was used</label></li> +</ul></td> +</tr> +<tr> +<th>Syndicated post details:</th> +<td><p>Show a diagnostic message...</p> +<ul class="options"> +<li><label><input type="checkbox" name="diagnostics_show[]" value="syndicated_posts:meta_data" <?php print $checked['syndicated_posts:meta_data']; ?> /> as syndication meta-data is added on the post</label></li> +</ul></td> +</tr> +</table> + <?php + } /* FeedWordPressDiagnosticsPage::updates_box () */ +} /* class FeedWordPressDiagnosticsPage */ + + $diagnosticsPage = new FeedWordPressDiagnosticsPage; + $diagnosticsPage->display(); + diff --git a/wp-content/plugins/feedwordpress/down.png b/wp-content/plugins/feedwordpress/down.png new file mode 100644 index 0000000000000000000000000000000000000000..fb62ec46f13eea15c0366f76582689455baa3670 Binary files /dev/null and b/wp-content/plugins/feedwordpress/down.png differ diff --git a/wp-content/plugins/feedwordpress/feedfinder.class.php b/wp-content/plugins/feedwordpress/feedfinder.class.php new file mode 100644 index 0000000000000000000000000000000000000000..aef508dc098447c67e195977429ed88280c305e9 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedfinder.class.php @@ -0,0 +1,312 @@ +<?php +/** + * class FeedFinder: find likely feeds using autodetection and/or guesswork + * @version 2010.0622 + * @uses SimplePie_Misc + */ + +if (!class_exists('SimplePie')) : + require_once(ABSPATH . WPINC . '/class-simplepie.php'); +endif; +require_once(dirname(__FILE__).'/feedwordpresshtml.class.php'); + +class FeedFinder { + var $uri = NULL; + var $_cache_uri = NULL; + + var $verify = FALSE; + var $fallbacks = 3; + + var $_response = NULL; + var $_data = NULL; + var $_error = 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 $_opml_markers = array('\\<opml', '\\<outline'); + 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, $fallbacks = 3) { + $this->uri = $uri; $this->verify = $verify; + $this->fallbacks = $fallbacks; + } /* FeedFinder::FeedFinder () */ + + function find ($uri = NULL) { + $ret = array (); + if (!is_null($this->data($uri))) : + if ($this->is_opml($uri)) : + $href = $this->_opml_rss_uris(); + else : + if ($this->is_feed($uri)) : + $href = 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 + if ($this->fallbacks > 0) : + $href = array_merge($href, $this->_a_href_feeds(TRUE)); + endif; + + // If all that failed, look harder + if ($this->fallbacks > 1) : + if (count($href) == 0) : + $href = $this->_a_href_feeds(FALSE); + endif; + endif; + + // Our search may turn up duplicate URIs. We only need to do any given URI once. + // Props to Camilo <http://projects.radgeek.com/2008/12/14/feedwordpress-20081214/#comment-20090122160414> + $href = array_unique($href); + endif; + + // Try some clever URL little tricks before we go + if ($this->fallbacks > 2) : + $href = array_merge($href, $this->_url_manipulation_feeds()); + endif; + endif; + + $href = array_unique($href); + + // Verify feeds and resolve relative URIs + foreach ($href as $u) : + $the_uri = SimplePie_Misc::absolutize_url($u, $this->uri); + if ($this->verify and ($u != $this->uri and $the_uri != $this->uri)) : + $feed = new FeedFinder($the_uri); + if ($feed->is_feed()) : $ret[] = $the_uri; endif; + unset($feed); + else : + $ret[] = $the_uri; + endif; + endforeach; + endif; + + return array_values($ret); + } /* FeedFinder::find () */ + + function data ($uri = NULL) { + $this->_get($uri); + return $this->_data; + } /* FeedFinder::data () */ + + function upload_data ($data) { + $this->uri = 'tag:localhost'; + $this->_data = $data; + } /* FeedFinder::upload_data () */ + + function status ($uri = NULL) { + $this->_get($uri); + + if (!is_wp_error($this->_response) and isset($this->_response['response']['code'])) : + $ret = $this->_response['response']['code']; + else : + $ret = NULL; + endif; + return $ret; + } + + function error ($index = NULL) { + $message = NULL; + if (count($this->_error) > 0) : + if (is_scalar($index) and !is_null($index)) : + $message = $this->_error[$index]; + else : + $message = implode(" / ", $this->_error)."\n"; + endif; + endif; + return $message; + } + + 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 () */ + + function is_opml ($uri = NULL) { + $data = $this->data($uri); + return ( + preg_match ( + "\007(".implode('|',$this->_opml_markers).")\007i", + $data + ) + ); + } /* FeedFinder::is_opml () */ + + # --- Private methods --- + function _get ($uri = NULL) { + if ($uri) $this->uri = $uri; + + // Is the result not yet cached? + if ($this->uri != 'tag:localhost' and $this->_cache_uri !== $this->uri) : + $headers['Connection'] = 'close'; + $headers['Accept'] = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*'; + $headers['User-Agent'] = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress'; + + // Use WordPress API function + $client = wp_remote_request($this->uri, array( + 'headers' => $headers, + 'timeout' => FEEDWORDPRESS_FETCH_TIME_OUT, + )); + + $this->_response = $client; + if (is_wp_error($client)) : + $this->_data = NULL; + $this->_error = $client->get_error_messages(); + else : + $this->_data = $client['body']; + $this->_error = NULL; + endif; + + // Kilroy was here + $this->_cache_uri = $this->uri; + endif; + } /* FeedFinder::_get () */ + + function _opml_rss_uris () { + // Really we should parse the XML and use the structure to + // return something intelligent to programs that want to use it + // Oh babe! maybe some day... + + $opml = $this->data(); + + $rx = FeedWordPressHTML::attributeRegex('outline', 'xmlUrl'); + if (preg_match_all($rx, $opml, $matches, PREG_SET_ORDER)) : + foreach ($matches as $m) : + $match = FeedWordPressHTML::attributeMatch($m); + $r[] = $match['value']; + endforeach; + endif; + return $r; + } /* FeedFinder::_opml_rss_uris () */ + + 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; + } /* FeedFinder::_link_rel_feeds () */ + + 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; + } /* FeedFinder::_link_a_href_feeds () */ + + function _url_manipulation_feeds () { + $href = array(); + + // check for HTTP GET parameters that look feed-like. + $bits = parse_url($this->uri); + foreach (array('rss', 'rss2', 'atom', 'rdf') as $format) : + if (isset($bits['query']) and (strlen($bits['query']) > 0)) : + $newQuery = preg_replace('/([?&=])(rss2?|atom|rdf)/i', '$1'.$format, $bits['query']); + else : + $newQuery = NULL; + endif; + + if (isset($bits['path']) and (strlen($bits['path']) > 0)) : + $newPath = preg_replace('!([/.])(rss2?|atom|rdf)!i', '$1'.$format, $bits['path']); + else : + $newPath = NULL; + endif; + + // Reassemble, check and return + $credentials = ''; + if (isset($bits['user'])) : + $credentials = $bits['user']; + if (isset($bits['pass'])) : + $credentials .= ':'.$bits['pass']; + endif; + $credentials .= '@'; + endif; + + // Variations on a theme + $newUrl[0] = ('' + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') + .(!is_null($newPath) ? $newPath : '') + .(!is_null($newQuery) ? '?'.$newQuery : '') + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') + ); + $newUrl[1] = ('' + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') + .(!is_null($newPath) ? $newPath : '') + .(isset($bits['query']) ? '?'.$bits['query'] : '') + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') + ); + $newUrl[2] = ('' + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') + .(isset($bits['path']) ? $bits['path'] : '') + .(!is_null($newQuery) ? '?'.$newQuery : '') + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') + ); + $href = array_merge($href, $newUrl); + endforeach; + return array_unique($href); + } /* FeedFinder::_url_manipulation_feeds () */ + + 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; + } /* FeedFinder::_tags () */ +} /* class FeedFinder */ + diff --git a/wp-content/plugins/feedwordpress/feeds-page.php b/wp-content/plugins/feedwordpress/feeds-page.php new file mode 100644 index 0000000000000000000000000000000000000000..953ce4d6f80d0c27300b3a7d5e25145b9cc335fa --- /dev/null +++ b/wp-content/plugins/feedwordpress/feeds-page.php @@ -0,0 +1,816 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); +require_once(dirname(__FILE__) . '/magpiemocklink.class.php'); +require_once(dirname(__FILE__) . '/feedfinder.class.php'); +require_once(dirname(__FILE__) . '/updatedpostscontrol.class.php'); + +class FeedWordPressFeedsPage extends FeedWordPressAdminPage { + var $HTTPStatusMessages = array ( + 200 => 'OK. FeedWordPress had no problems retrieving the content at this URL but the content does not seem to be a feed, and does not seem to include links to any feeds.', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized. This URL probably needs a username and password for you to access it.', + 402 => 'Payment Required', + 403 => 'Forbidden. The URL is not made available for the machine that FeedWordPress is running on.', + 404 => 'Not Found. There is nothing at this URL. Have you checked the address for typos?', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone. This URL is no longer available on this server and no forwarding address is known.', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 500 => 'Internal Server Error. Something unexpected went wrong with the configuration of the server that hosts this URL. You might try again later to see if this issue has been resolved.', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable. The server is currently unable to handle the request due to a temporary overloading or maintenance of the server that hosts this URL. This is probably a temporary condition and you should try again later to see if the issue has been resolved.', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + var $updatedPosts = NULL; + + /** + * Constructs the Feeds page object + * + * @param mixed $link An object of class {@link SyndicatedLink} if created for one feed's settings, NULL if created for global default settings + */ + function FeedWordPressFeedsPage ($link = -1) { + if (is_numeric($link) and -1 == $link) : + $link = FeedWordPressAdminPage::submitted_link(); + endif; + + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpressfeeds', $link); + + $this->dispatch = 'feedwordpress_admin_page_feeds'; + $this->pagenames = array( + 'default' => 'Feeds', + 'settings-update' => 'Syndicated feed', + 'open-sheet' => 'Feed and Update', + ); + $this->filename = __FILE__; + $this->updatedPosts = new UpdatedPostsControl($this); + } /* FeedWordPressFeedsPage constructor */ + + var $special_settings = array ( /* Regular expression syntax is OK here */ + 'cats', + 'cat_split', + 'freeze updates', + 'hardcode name', + 'hardcode url', + 'hardcode description', + 'hardcode categories', /* Deprecated */ + 'comment status', + 'terms', + 'map authors', + 'munge permalink', + 'ping status', + 'post status', + 'postmeta', + 'resolve relative', + 'syndicated post type', + 'tags', + 'unfamiliar author', + 'unfamliar categories', /* Deprecated */ + 'unfamiliar category', + 'unfamiliar post_tag', + 'update/.*', + 'feed/.*', + 'link/.*', + 'match/.*', + ); + + function display () { + global $fwp_post; + global $post_source; + + $this->boxes_by_methods = array( + 'feed_information_box' => __('Feed Information'), + 'global_feeds_box' => __('Update Scheduling'), + 'updated_posts_box' => __('Updated Posts'), + 'custom_settings_box' => __('Custom Feed Settings (for use in templates)'), + ); + if ($this->for_default_settings()) : + unset($this->boxes_by_methods['custom_settings_box']); + endif; + + // Allow overriding of normal source for FeedFinder, which may + // be called from multiple points. + if (isset($post_source) and !is_null($post_source)) : + $source = $post_source; + else : + $source = $this->dispatch; + endif; + + if (isset($_REQUEST['feedfinder']) + or (isset($_REQUEST['action']) and $_REQUEST['action']=='feedfinder') + or (isset($_REQUEST['action']) and $_REQUEST['action']==FWP_SYNDICATE_NEW)) : + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ $source, /*capability=*/ 'manage_links'); + + return $this->display_feedfinder(); // re-route to Feed Finder page + endif; + + parent::display(); + return false; // Don't continue + } /* FeedWordPressFeedsPage::display() */ + + function ajax_interface_js () { + FeedWordPressAdminPage::ajax_interface_js(); + ?> + + jQuery(document).ready( function () { + contextual_appearance('automatic-updates-selector', 'cron-job-explanation', null, 'no'); + contextual_appearance('time-limit', 'time-limit-box', null, 'yes'); + contextual_appearance('use-default-update-window-no', 'update-scheduling-note', null, null, 'block', true); + jQuery('#use-default-update-window-yes, #use-default-update-window-no').click( function () { + contextual_appearance('use-default-update-window-no', 'update-scheduling-note', null, null, 'block', true); + } ); + + var els = ['name', 'description', 'url']; + for (var i = 0; i < els.length; i++) { + contextual_appearance( + /*item=*/ 'basics-hardcode-'+els[i], + /*appear=*/ 'basics-'+els[i]+'-view', + /*disappear=*/ 'basics-'+els[i]+'-edit', + /*value=*/ 'no', + /*visibleStyle=*/ 'block', + /*checkbox=*/ true + ); + } /* for */ + } ); + + <?php + } + + /*static*/ function updated_posts_box ($page, $box = NULL) { + ?> + <table class="edit-form"> + <?php $page->updatedPosts->display(); ?> + </table> + <?php + } /* FeedWordPressFeedsPage::updated_posts_box() */ + + /*static*/ function global_feeds_box ($page, $box = NULL) { + $automatic_updates = get_option('feedwordpress_automatic_updates'); + $update_time_limit = (int) get_option('feedwordpress_update_time_limit'); + + // Hey, ho, let's go... + ?> + + <table class="edit-form"> + <?php if ($page->for_default_settings()) : ?> + + <tr> + <th scope="row">Updates:</th> + <td><select id="automatic-updates-selector" name="automatic_updates" size="1" onchange="contextual_appearance('automatic-updates-selector', 'cron-job-explanation', null, 'no');"> + <option value="shutdown"<?php echo ($automatic_updates=='shutdown')?' selected="selected"':''; ?>>automatically check for updates after pages load</option> + <option value="init"<?php echo ($automatic_updates=='init')?' selected="selected"':''; ?>>automatically check for updates before pages load</option> + <option value="no"<?php echo (!$automatic_updates)?' selected="selected"':''; ?>>cron job or manual updates</option> + </select> + <div id="cron-job-explanation" class="setting-description"> + <p>If you want to use a cron job, + you can perform scheduled updates by sending regularly-scheduled + requests to <a href="<?php bloginfo('home'); ?>?update_feedwordpress=1"><code><?php bloginfo('url') ?>?update_feedwordpress=1</code></a> + For example, inserting the following line in your crontab:</p> + <pre style="font-size: 0.80em"><code>*/10 * * * * /usr/bin/curl --silent <?php bloginfo('url'); ?>?update_feedwordpress=1</code></pre> + <p class="setting-description">will check in every 10 minutes + and check for updates on any feeds that are ready to be polled for updates.</p> + </div> + </td> + </tr> + + <?php else : /* Feed-specific settings */ ?> + + <tr> + <th scope="row"><?php _e('Last update') ?>:</th> + <td><?php + if (isset($page->link->settings['update/last'])) : + echo fwp_time_elapsed($page->link->settings['update/last'])." "; + else : + echo " none yet"; + endif; + ?></td></tr> + + <tr><th><?php _e('Next update') ?>:</th> + <td><?php + $holdem = (isset($page->link->settings['update/hold']) ? $page->link->settings['update/hold'] : 'scheduled'); + ?> + <select name="update_schedule"> + <option value="scheduled"<?php echo ($holdem=='scheduled')?' selected="selected"':''; ?>>update on schedule <?php + echo " ("; + if (isset($page->link->settings['update/ttl']) and is_numeric($page->link->settings['update/ttl'])) : + if (isset($page->link->settings['update/timed']) and $page->link->settings['update/timed']=='automatically') : + echo 'next: '; + $next = $page->link->settings['update/last'] + ((int) $page->link->settings['update/ttl'] * 60); + if (strftime('%x', time()) != strftime('%x', $next)) : + echo strftime('%x', $next)." "; + endif; + echo strftime('%X', $page->link->settings['update/last']+((int) $page->link->settings['update/ttl']*60)); + else : + echo "every ".$page->link->settings['update/ttl']." minute".(($page->link->settings['update/ttl']!=1)?"s":""); + endif; + else: + echo "next scheduled update"; + endif; + echo ")"; + ?></option> + <option value="next"<?php echo ($holdem=='next')?' selected="selected"':''; ?>>update ASAP</option> + <option value="ping"<?php echo ($holdem=='ping')?' selected="selected"':''; ?>>update only when pinged</option> + </select></td></tr> + + <?php endif; ?> + + <tr> + <th scope="row"><?php print __('Update scheduling:') ?></th> + <td><p style="margin-top:0px">How long should FeedWordPress wait between updates before it considers this feed ready to be polled for updates again?</p> + <?php + + $this->setting_radio_control( + 'update/window', 'update_window', + array(&$this, 'update_window_edit_box'), + array( + 'global-setting-default' => DEFAULT_UPDATE_PERIOD, + 'default-input-name' => 'use_default_update_window', + 'default-input-id' => 'use-default-update-window-yes', + 'default-input-id-no' => 'use-default-update-window-no', + 'labels' => array(&$this, 'update_window_currently'), + ) + ); + ?></td> + </tr> + + <?php if ($this->for_default_settings()) : ?> + + <tr> + <th scope="row"><?php print __('Time limit on updates'); ?>:</th> + <td><select id="time-limit" name="update_time_limit" size="1" onchange="contextual_appearance('time-limit', 'time-limit-box', null, 'yes');"> + <option value="no"<?php echo ($update_time_limit>0)?'':' selected="selected"'; ?>>no time limit on updates</option> + <option value="yes"<?php echo ($update_time_limit>0)?' selected="selected"':''; ?>>limit updates to no more than...</option> + </select> + <span id="time-limit-box"><label><input type="text" name="time_limit_seconds" value="<?php print $update_time_limit; ?>" size="5" /> seconds</label></span> + </tr> + + <?php endif; ?> + + </table> + + <?php + } /* FeedWordPressFeedsPage::global_feeds_box() */ + + function update_window_edit_box ($updateWindow, $defaulted, $params) { + if (!is_numeric($updateWindow)) : + $updateWindow = DEFAULT_UPDATE_PERIOD; + endif; + ?> + <p>Wait <input type="text" name="update_window" value="<?php print $updateWindow; ?>" size="4" /> minutes between polling.</p> + <div class="setting-description" id="update-scheduling-note"> + <p<?php if ($updateWindow<50) : ?> style="color: white; background-color: #703030; padding: 1.0em;"<?php endif; ?>><strong>Recommendation.</strong> Unless you are positive that you have the webmaster's permission, you generally should not set FeedWordPress to poll feeds more frequently than once every 60 minutes. Many webmasters consider more frequent automated polling to be abusive, and may complain to your web host, or ban your IP address, as retaliation for hammering their servers too hard.</p> + <p><strong>Note.</strong> This is a default setting that FeedWordPress uses to schedule updates when the feed does not provide any scheduling requests. If this feed does provide update scheduling information (through elements such as <code><rss:ttl></code> or <code><sy:updateFrequency></code>), FeedWordPress will respect the feed's request.</p> + </div> + <?php + } /* FeedWordPressFeedsPage::update_window_edit_box () */ + + function update_window_currently ($updateWindow, $defaulted, $params) { + $updateWindow = (int) $updateWindow; + if (1==$updateWindow) : + $caption = 'wait %d minute between polling'; + else : + $caption = 'wait %d minutes between polling'; + endif; + return sprintf(__($caption), $updateWindow); + } /* FeedWordPressFeedsPage::update_window_currently () */ + + function feed_information_box ($page, $box = NULL) { + global $wpdb; + if ($page->for_feed_settings()) : + $info['name'] = esc_html($page->link->link->link_name); + $info['description'] = esc_html($page->link->link->link_description); + $info['url'] = esc_html($page->link->link->link_url); + $rss_url = $page->link->link->link_rss; + + $hardcode['name'] = $page->link->hardcode('name'); + $hardcode['description'] = $page->link->hardcode('description'); + $hardcode['url'] = $page->link->hardcode('url'); + else : + $cat_id = FeedWordPress::link_category_id(); + + $params = array(); + if (FeedWordPressCompatibility::test_version(FWP_SCHEMA_USES_ARGS_TAXONOMY)) : + $params['taxonomy'] = 'link_category'; + else : + $params['type'] = 'link'; + endif; + $params['hide_empty'] = false; + $results = get_categories($params); + + // Guarantee that the Contributors category will be in the drop-down chooser, even if it is empty. + $found_link_category_id = false; + foreach ($results as $row) : + // Normalize case + if (!isset($row->cat_id)) : $row->cat_id = $row->cat_ID; endif; + + if ($row->cat_id == $cat_id) : $found_link_category_id = true; endif; + endforeach; + + if (!$found_link_category_id) : + $results[] = get_category($cat_id); + endif; + + $info = array(); + $rss_url = null; + + $hardcode['name'] = get_option('feedwordpress_hardcode_name'); + $hardcode['description'] = get_option('feedwordpress_hardcode_description'); + $hardcode['url'] = get_option('feedwordpress_hardcode_url'); + endif; + + // Hey ho, let's go + ?> + <table class="edit-form"> + + <?php if ($page->for_feed_settings()) : ?> + + <tr> + <th scope="row"><?php _e('Feed URL:') ?></th> + <td><a href="<?php echo esc_html($rss_url); ?>"><?php echo esc_html($rss_url); ?></a> + (<a href="<?php echo FEEDVALIDATOR_URI; ?>?url=<?php echo urlencode($rss_url); ?>" + title="Check feed <<?php echo esc_html($rss_url); ?>> for validity">validate</a>) + <input type="submit" name="feedfinder" value="switch →" style="font-size:smaller" /></td> + </tr> + + <?php + $rows = array( + "name" => __('Link Name'), + "description" => __('Short Description'), + "url" => __('Homepage'), + ); + foreach ($rows as $what => $label) : + ?> + <tr> + <th scope="row"><?php print $label ?></th> + <td> + <div id="basics-<?php print $what; ?>-edit"><input type="text" name="link<?php print $what; ?>" + value="<?php echo $info[$what]; ?>" style="width: 95%" /></div> + <div id="basics-<?php print $what; ?>-view"> + <?php if ($what=='url') : ?><a href="<?php print $info[$what]; ?>"><?php else : ?><strong><?php endif; ?> + <?php print (strlen(trim($info[$what])) > 0) ? $info[$what] : '(none provided)'; ?> + <?php if ($what=='url') : ?></a><?php else : ?></strong><?php endif; ?></div> + + <div> + <label><input id="basics-hardcode-<?php print $what; ?>" + type="radio" name="hardcode_<?php print $what; ?>" value="no" + <?php echo (($hardcode[$what]=='yes')?'':' checked="checked"');?> + onchange="contextual_appearance('basics-hardcode-<?php print $what; ?>', 'basics-<?php print $what; ?>-view', 'basics-<?php print $what; ?>-edit', 'no', 'block', /*checkbox=*/ true)" + /> Update automatically from feed</label> + <label><input type="radio" name="hardcode_<?php print $what; ?>" value="yes" + <?php echo (($hardcode[$what]!='yes')?'':' checked="checked"');?> + onchange="contextual_appearance('basics-hardcode-<?php print $what; ?>', 'basics-<?php print $what; ?>-view', 'basics-<?php print $what; ?>-edit', 'no', 'block', /*checkbox=*/ true)" + /> Edit manually</label> + </div> + </td> + </tr> + <?php + endforeach; + ?> + + <?php else : ?> + + <tr> + <th scope="row">Syndicated Link category:</th> + <td><p><select name="syndication_category" size="1"> + <?php + foreach ($results as $row) : + // Normalize case + if (!isset($row->cat_id)) : $row->cat_id = $row->cat_ID; endif; + + echo "\n\t<option value=\"$row->cat_id\""; + if ($row->cat_id == $cat_id) : + echo " selected='selected'"; + endif; + echo ">$row->cat_id: ".esc_html($row->cat_name); + echo "</option>\n"; + endforeach; + ?></select></p> + <p class="setting-description">FeedWordPress will syndicate the + links placed under this link category.</p> + </td> + </tr> + + <tr> + <th scope="row">Link Names:</th> + <td><label><input type="checkbox" name="hardcode_name" value="no"<?php echo (($hardcode['name']=='yes')?'':' checked="checked"');?>/> Update contributor titles automatically when the feed title changes</label></td> + </tr> + + <tr> + <th scope="row">Short descriptions:</th> + <td><label><input type="checkbox" name="hardcode_description" value="no"<?php echo (($hardcode['description']=='yes')?'':' checked="checked"');?>/> Update contributor descriptions automatically when the feed tagline changes</label></td> + </tr> + + <tr> + <th scope="row">Homepages:</th> + <td><label><input type="checkbox" name="hardcode_url" value="no"<?php echo (($hardcode['url']=='yes')?'':' checked="checked"');?>/> Update contributor homepages automatically when the feed link changes</label></td> + </tr> + + <?php endif; ?> + + </table> + <?php + } /* FeedWordPressFeedsPage::feed_information_box() */ + + function custom_settings_box ($page, $box = NULL) { + ?> + <p class="setting-description">These custom settings are special fields for the <strong>feed</strong> you are + syndicating, to be retrieved in templates using the <code>get_feed_meta()</code> function. They do not create + custom fields on syndicated <strong>posts</strong>. If you want to create custom fields that are applied to each + individual post from this feed, set up the settings in <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/posts-page.php&link_id=<?php print $page->link->id; ?>">Syndicated Posts</a>.</p> + + <div id="postcustomstuff"> + <table id="meta-list" cellpadding="3"> + <tr> + <th>Key</th> + <th>Value</th> + <th>Action</th> + </tr> + + <?php + $i = 0; + foreach ($page->link->settings as $key => $value) : + if (!preg_match("\007^((".implode(')|(', $page->special_settings)."))$\007i", $key)) : + ?> + <tr style="vertical-align:top"> + <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo esc_html($key); ?>" /> + <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo esc_html($key); ?>" /></th> + <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo esc_html($value); ?></textarea></td> + <td width="10%"><select name="notes[<?php echo $i; ?>][action]"> + <option value="update">save changes</option> + <option value="delete">delete this setting</option> + </select></td> + </tr> + <?php + $i++; + endif; + endforeach; + ?> + <tr> + <th scope="row"><input type="text" size="10" name="notes[<?php echo $i; ?>][key1]" value="" /></th> + <td><textarea name="notes[<?php echo $i; ?>][value]" rows="2" cols="40"></textarea></td> + <td><em>add new setting...</em><input type="hidden" name="notes[<?php echo $i; ?>][action]" value="update" /></td> + </tr> + </table> + </div> <!-- id="postcustomstuff" --> + <?php + } + + function display_feedfinder () { + global $wpdb; + + $lookup = (isset($_REQUEST['lookup']) ? $_REQUEST['lookup'] : NULL); + + $feeds = array(); $feedSwitch = false; $current = null; + if ($this->for_feed_settings()) : // Existing feed? + $feedSwitch = true; + if (is_null($lookup)) : + // Switch Feed without a specific feed yet suggested + // Go to the human-readable homepage to look for + // auto-detection links + $lookup = $this->link->link->link_url; + + // Guarantee that you at least have the option to + // stick with what works. + $current = $this->link->link->link_rss; + $feeds[] = $current; + endif; + $name = esc_html($this->link->link->link_name); + else: // Or a new subscription to add? + $name = "Subscribe to <code>".esc_html(feedwordpress_display_url($lookup))."</code>"; + endif; + ?> + <div class="wrap" id="feed-finder"> + <h2>Feed Finder: <?php echo $name; ?></h2> + + <?php + if ($feedSwitch) : + $this->display_alt_feed_box($lookup); + endif; + + $finder = array(); + if (!is_null($current)) : + $finder[$current] = new FeedFinder($current); + endif; + $finder[$lookup] = new FeedFinder($lookup); + + foreach ($finder as $url => $ff) : + $feeds = array_merge($feeds, $ff->find()); + endforeach; + + $feeds = array_values( // Renumber from 0..(N-1) + array_unique( // Eliminate duplicates + $feeds + ) + ); + + if (count($feeds) > 0): + if ($feedSwitch) : + ?> + <h3>Feeds Found</h3> + <?php + endif; + + if (count($feeds) > 1) : + $option_template = 'Option %d: '; + $form_class = ' class="multi"'; + ?> + <p><strong>This web page provides at least <?php print count($feeds); ?> different feeds.</strong> These feeds may provide the same information + in different formats, or may track different items. (You can check the Feed Information and the + Sample Item for each feed to get an idea of what the feed provides.) Please select the feed that you'd like to subscribe to.</p> + <?php + else : + $option_template = ''; + $form_class = ''; + endif; + + foreach ($feeds as $key => $f): + $pie = FeedWordPress::fetch($f); + $rss = (is_wp_error($pie) ? $pie : new MagpieFromSimplePie($pie)); + + if ($rss and !is_wp_error($rss)): + $feed_link = (isset($rss->channel['link'])?$rss->channel['link']:''); + $feed_title = (isset($rss->channel['title'])?$rss->channel['title']:$feed_link); + $feed_type = ($rss->feed_type ? $rss->feed_type : 'Unknown'); + $feed_version_template = '%.1f'; + $feed_version = $rss->feed_version; + else : + // Give us some sucky defaults + $feed_title = feedwordpress_display_url($lookup); + $feed_link = $lookup; + $feed_type = 'Unknown'; + $feed_version_template = ''; + $feed_version = ''; + endif; + ?> + <form<?php print $form_class; ?> action="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/syndication.php" method="post"> + <div class="inside"><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_switchfeed'); ?> + + <?php + $classes = array('feed-found'); $currentFeed = ''; + if (!is_null($current) and $current==$f) : + $classes[] = 'current'; + $currentFeed = ' (currently subscribed)'; + endif; + if ($key%2) : + $classes[] = 'alt'; + endif; + ?> + <fieldset class="<?php print implode(" ", $classes); ?>"> + <legend><?php printf($option_template, ($key+1)); print $feed_type." "; printf($feed_version_template, $feed_version); ?> feed<?php print $currentFeed; ?></legend> + + <?php + $this->stamp_link_id(); + + // No feed specified = add new feed; we + // need to pass along a starting title + // and homepage URL for the new Link. + if (!$this->for_feed_settings()): + ?> + <input type="hidden" name="feed_title" value="<?php echo esc_html($feed_title); ?>" /> + <input type="hidden" name="feed_link" value="<?php echo esc_html($feed_link); ?>" /> + <?php + endif; + ?> + + <input type="hidden" name="feed" value="<?php echo esc_html($f); ?>" /> + <input type="hidden" name="action" value="switchfeed" /> + + <div> + <div class="feed-sample"> + <?php + $link = NULL; + $post = NULL; + if (!is_wp_error($rss) and count($rss->items) > 0): + // Prepare to display Sample Item + $link = new MagpieMockLink(array('simplepie' => $pie, 'magpie' => $rss), $f); + $post = new SyndicatedPost(array('simplepie' => $rss->originals[0], 'magpie' => $rss->items[0]), $link); + ?> + <h3>Sample Item</h3> + <ul> + <li><strong>Title:</strong> <a href="<?php echo $post->post['meta']['syndication_permalink']; ?>"><?php echo $post->post['post_title']; ?></a></li> + <li><strong>Date:</strong> <?php print date('d-M-y g:i:s a', $post->published()); ?></li> + </ul> + <div class="entry"> + <?php print $post->post['post_content']; ?> + </div> + <?php + do_action('feedwordpress_feed_finder_sample_item', $f, $post, $link); + else: + if (is_wp_error($rss)) : + print '<div class="feed-problem">'; + print "<h3>Problem:</h3>\n"; + print "<p>FeedWordPress encountered the following error + when trying to retrieve this feed:</p>"; + print '<p style="margin: 1.0em 3.0em"><code>'.$rss->get_error_message().'</code></p>'; + print "<p>If you think this is a temporary problem, you can still force FeedWordPress to add the subscription. FeedWordPress will not be able to find any syndicated posts until this problem is resolved.</p>"; + print "</div>"; + endif; + ?> + <h3>No Items</h3> + <p>FeedWordPress found no posts on this feed.</p> + <?php + endif; + ?> + </div> + + <div> + <h3>Feed Information</h3> + <ul> + <li><strong>Homepage:</strong> <a href="<?php echo $feed_link; ?>"><?php echo is_null($feed_title)?'<em>Unknown</em>':$feed_title; ?></a></li> + <li><strong>Feed URL:</strong> <a title="<?php echo esc_html($f); ?>" href="<?php echo esc_html($f); ?>"><?php echo esc_html(feedwordpress_display_url($f, 40, 10)); ?></a> (<a title="Check feed <<?php echo esc_html($f); ?>> for validity" href="http://feedvalidator.org/check.cgi?url=<?php echo urlencode($f); ?>">validate</a>)</li> + <li><strong>Encoding:</strong> <?php echo isset($rss->encoding)?esc_html($rss->encoding):"<em>Unknown</em>"; ?></li> + <li><strong>Description:</strong> <?php echo isset($rss->channel['description'])?esc_html($rss->channel['description']):"<em>Unknown</em>"; ?></li> + </ul> + <?php do_action('feedwordpress_feedfinder_form', $f, $post, $link, $this->for_feed_settings()); ?> + <div class="submit"><input type="submit" class="button-primary" name="Use" value="« Use this feed" /> + <input type="submit" class="button" name="Cancel" value="× Cancel" /></div> + </div> + </div> + </fieldset> + </div> <!-- class="inside" --> + </form> + <?php + unset($link); + unset($post); + endforeach; + else: + foreach ($finder as $url => $ff) : + $url = esc_html($url); + print "<h3>Searched for feeds at ${url}</h3>\n"; + print "<p><strong>".__('Error').":</strong> ".__("FeedWordPress couldn't find any feeds at").' <code><a href="'.htmlspecialchars($lookup).'">'.htmlspecialchars($lookup).'</a></code>'; + print ". ".__('Try another URL').".</p>"; + + // Diagnostics + print "<div class=\"updated\" style=\"margin-left: 3.0em; margin-right: 3.0em;\">\n"; + print "<h3>".__('Diagnostic information')."</h3>\n"; + if (!is_null($ff->error()) and strlen($ff->error()) > 0) : + print "<h4>".__('HTTP request failure')."</h4>\n"; + print "<p>".$ff->error()."</p>\n"; + else : + print "<h4>".__('HTTP request completed')."</h4>\n"; + print "<p><strong>Status ".$ff->status().":</strong> ".$this->HTTPStatusMessages[(int) $ff->status()]."</p>\n"; + endif; + + // Do some more diagnostics if the API for it is available. + if (function_exists('_wp_http_get_object')) : + $httpObject = _wp_http_get_object(); + $transports = $httpObject->_getTransport(); + + print "<h4>".__('HTTP Transports available').":</h4>\n"; + print "<ol>\n"; + print "<li>".implode("</li>\n<li>", array_map('get_class', $transports))."</li>\n"; + print "</ol>\n"; + print "</div>\n"; + endif; + endforeach; + endif; + + if (!$feedSwitch) : + $this->display_alt_feed_box($lookup, /*alt=*/ true); + endif; + ?> + </div> <!-- class="wrap" --> + <?php + return false; // Don't continue + } /* FeedWordPressFeedsPage::display_feedfinder() */ + + function display_alt_feed_box ($lookup, $alt = false) { + global $fwp_post; + ?> + <form action="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/<?php echo basename(__FILE__); ?>" method="post"> + <div class="inside"><?php + FeedWordPressCompatibility::stamp_nonce($this->dispatch); + ?> + <fieldset class="alt" + <?php if (!$alt): ?>style="margin: 1.0em 3.0em; font-size: smaller;"<?php endif; ?>> + <legend><?php if ($alt) : ?>Alternative feeds<?php else: ?>Find feeds<?php endif; ?></legend> + <?php if ($alt) : ?><h3>Use a different feed</h3><?php endif; ?> + <div><label>Address: + <input type="text" name="lookup" id="use-another-feed" + placeholder="URL" + <?php if (is_null($lookup)) : ?> + value="URL" + <?php else : ?> + value="<?php print esc_html($lookup); ?>" + <?php endif; ?> + size="64" style="max-width: 80%" /></label> + <?php if (is_null($lookup)) : ?> + <?php FeedWordPressSettingsUI::magic_input_tip_js('use-another-feed'); ?> + <?php endif; ?> + <?php $this->stamp_link_id('link_id'); ?> + <input type="hidden" name="action" value="feedfinder" /> + <input type="submit" class="button<?php if ($alt): ?>-primary<?php endif; ?>" value="Check »" /></div> + <p>This can be the address of a feed, or of a website. FeedWordPress + will try to automatically detect any feeds associated with a + website.</p> + </div> <!-- class="inside" --> + </fieldset></form> + + <?php + } /* FeedWordPressFeedsPage::display_alt_feed_box() */ + + function save_settings ($post) { + if ($this->for_feed_settings()) : + // custom feed settings first + foreach ($post['notes'] as $mn) : + $mn['key0'] = (isset($mn['key0']) ? trim($mn['key0']) : NULL); + $mn['key1'] = trim($mn['key1']); + if (preg_match("\007^((" + .implode(')|(',$this->special_settings) + ."))$\007i", + $mn['key1'])) : + $mn['key1'] = 'user/'.$mn['key1']; + endif; + + if (strlen($mn['key0']) > 0) : + unset($this->link->settings[$mn['key0']]); // out with the old + endif; + + if (($mn['action']=='update') and (strlen($mn['key1']) > 0)) : + $this->link->settings[$mn['key1']] = $mn['value']; // in with the new + endif; + endforeach; + + // now stuff through the web form + // hardcoded feed info + + foreach (array('name', 'description', 'url') as $what) : + // We have a checkbox for "No," so if it's unchecked, mark as "Yes." + $this->link->settings["hardcode {$what}"] = (isset($post["hardcode_{$what}"]) ? $post["hardcode_{$what}"] : 'yes'); + if (FeedWordPress::affirmative($this->link->settings, "hardcode {$what}")) : + $this->link->link->{'link_'.$what} = $post['link'.$what]; + endif; + endforeach; + + // Update scheduling + if (isset($post['update_schedule'])) : + $this->link->settings['update/hold'] = $post['update_schedule']; + endif; + + if (isset($post['use_default_update_window']) and strtolower($post['use_default_update_window'])=='yes') : + unset($this->link->settings['update/window']); + elseif (isset($post['update_window'])): + if ((int) $post['update_window'] > 0) : + $this->link->settings['update/window'] = (int) $post['update_window']; + endif; + endif; + + else : + // Global + update_option('feedwordpress_cat_id', $post['syndication_category']); + + if (!isset($post['automatic_updates']) or !in_array($post['automatic_updates'], array('init', 'shutdown'))) : + $automatic_updates = false; + else : + $automatic_updates = $post['automatic_updates']; + endif; + update_option('feedwordpress_automatic_updates', $automatic_updates); + + if (isset($post['update_window'])): + if ((int) $post['update_window'] > 0) : + update_option('feedwordpress_update_window', (int) $post['update_window']); + endif; + endif; + + update_option('feedwordpress_update_time_limit', ($post['update_time_limit']=='yes')?(int) $post['time_limit_seconds']:0); + + foreach (array('name', 'description', 'url') as $what) : + // We have a checkbox for "No," so if it's unchecked, mark as "Yes." + $hardcode = (isset($post["hardcode_{$what}"]) ? $post["hardcode_{$what}"] : 'yes'); + update_option("feedwordpress_hardcode_{$what}", $hardcode); + endforeach; + endif; + + $this->updatedPosts->accept_POST($post); + parent::save_settings($post); + } /* FeedWordPressFeedsPage::save_settings() */ + +} /* class FeedWordPressFeedsPage */ + + $feedsPage = new FeedWordPressFeedsPage; + $feedsPage->display(); + diff --git a/wp-content/plugins/feedwordpress/feedtime.class.php b/wp-content/plugins/feedwordpress/feedtime.class.php new file mode 100644 index 0000000000000000000000000000000000000000..2a33d540cbb9043aaab7dea5eb7ae47f03a30fb4 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedtime.class.php @@ -0,0 +1,126 @@ +<?php +/** + * class FeedTime: handle common date-time formats used in feeds. + * + */ +class FeedTime { + var $rep; + var $ts; + + function FeedTime ($time) { + $this->set($time); + } /* FeedTime constructor */ + + function set ($time) { + $this->rep = $time; + $this->ts = NULL; + if (is_numeric($time)) : // Presumably a Unix-epoch timestamp + $this->ts = $this->rep; + elseif (is_string($time)) : + // First, try to parse it as a W3C date-time + $this->ts = $this->parse_w3cdtf(); + + if ($this->failed()) : + // In some versions of PHP, strtotime() does not support + // the UT timezone. Since UT is by definition within 1 + // second of UTC, we'll just convert it here to avoid + // problems. + $time = preg_replace( + '/(\s)UT$/', + '$1UTC', + $time + ); + $this->ts = strtotime($time); + endif; + endif; + } /* FeedTime::set() */ + + function timestamp () { + $unix = NULL; + if (!$this->failed()) : + $unix = $this->ts; + endif; + return $unix; + } /* FeedTime::timestamp() */ + + function failed () { + return (!is_numeric($this->ts) or !$this->ts or ($this->ts <= 0)); + } /* FeedTime::failed() */ + + /** + * FeedTime::parse_w3cdtf() parses a W3C date-time format date into a + * Unix epoch timestamp. Derived from the parse_w3cdtf function included + * with MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net>, with + * modifications and bugfixes by Charles Johnson + * <technophilia@radgeek.com>, under the terms of the GPL. + */ + function parse_w3cdtf () { + $unix = NULL; // Failure + + # regex to match wc3dtf + $pat = "/^\s* + (\d{4}) + (- + (\d{2}) + (- + (\d{2}) + (T + (\d{2}) + :(\d{2}) + (: + (\d{2}) + (\.\d+)? + )? + (?:([-+])(\d{2}):?(\d{2})|(Z))? + )? + )? + )? + \s*\$ + /x"; + + if ( preg_match( $pat, $this->rep, $match ) ) : + $year = (isset($match[1]) ? $match[1] : NULL); + $month = (isset($match[3]) ? $match[3] : NULL); + $day = (isset($match[5]) ? $match[5] : NULL); + $hours = (isset($match[7]) ? $match[7] : NULL); + $minutes = (isset($match[8]) ? $match[8] : NULL); + $seconds = (isset($match[10]) ? $match[10] : NULL); + + # W3C dates can omit the time, the day of the month, or even the month. + # Fill in any blanks using information from the present moment. --CWJ + $default['hr'] = (int) gmdate('H'); + $default['day'] = (int) gmdate('d'); + $default['month'] = (int) gmdate('m'); + + if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif; + if (is_null($day)) : $day = $default['day']; endif; + if (is_null($month)) : $month = $default['month']; endif; + + # calc epoch for current date assuming GMT + $unix = gmmktime( $hours, $minutes, $seconds, $month, $day, $year); + + $offset = 0; + if ( isset($match[15]) and $match[15] == 'Z' ) : + # zulu time, aka GMT + else : + $tz_mod = $match[12]; + $tz_hour = $match[13]; + $tz_min = $match[14]; + + # zero out the variables + if ( ! $tz_hour ) { $tz_hour = 0; } + if ( ! $tz_min ) { $tz_min = 0; } + + $offset_secs = (($tz_hour*60)+$tz_min)*60; + + # is timezone ahead of GMT? then subtract offset + if ( $tz_mod == '+' ) : + $offset_secs = $offset_secs * -1; + endif; + $offset = $offset_secs; + endif; + $unix = $unix + $offset; + endif; + return $unix; + } /* FeedTime::parse_w3cdtf () */ +} /* class FeedTime */ diff --git a/wp-content/plugins/feedwordpress/feedwordpress-content-type-sniffer.class.php b/wp-content/plugins/feedwordpress/feedwordpress-content-type-sniffer.class.php new file mode 100644 index 0000000000000000000000000000000000000000..02cb19e854821d7843e948c3bd0637930049b096 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpress-content-type-sniffer.class.php @@ -0,0 +1,58 @@ +<?php +require_once(ABSPATH . WPINC . '/class-feed.php'); + +class FeedWordPress_Content_Type_Sniffer extends SimplePie_Content_Type_Sniffer { + /** + * Get the Content-Type of the specified file + * + * @access public + * @return string Filtered content type + */ + function get_type () { + $contentType = null; + $charset = null; + if (isset($this->file->headers['content-type'])) : + if (!is_array($this->file->headers['content-type'])) : + $this->file->headers['content-type'] = array($this->file->headers['content-type']); + endif; + + foreach ($this->file->headers['content-type'] as $type) : + $parts = array_map('trim', split(";", $type, 2)); + if (isset($parts[1])) : + $type = $parts[0]; + $charset = $parts[1]; + endif; + + if (preg_match( + '!(application|text)/((atom|rss|rdf)\+)?xml!', + $type, + $ref + )) : + $contentType = $ref[0]; + endif; + endforeach; + + $outHeader = array(); + if (!is_null($contentType)) : + $outHeader[] = $contentType; + else : + $outHeader[] = 'text/xml'; // Generic + endif; + if (!is_null($charset)) : + $outHeader[] = $charset; + endif; + + $this->file->headers['content-type'] = implode("; ", $outHeader); + else : + // The default SimplePie behavior seems to be to return + // text/plain if it can't find a Content-Type header. + // The default SimplePie behavior sucks. Particularly + // since SimplePie gets so draconian about Content-Type. + // And since the WP SimplePie seems to drop Content-Type + // from cached copies for some unfortunate reason. + $this->file->headers['content-type'] = 'text/xml'; // Generic + endif; + return parent::get_type(); + } /* FeedWordPress_Content_Type_Sniffer::get_type() */ +} /* class FeedWordPress_Content_Type_Sniffer */ + diff --git a/wp-content/plugins/feedwordpress/feedwordpress-elements.css b/wp-content/plugins/feedwordpress/feedwordpress-elements.css new file mode 100644 index 0000000000000000000000000000000000000000..9d5f78475b52c47bb174a343b4c0b8653e02b200 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpress-elements.css @@ -0,0 +1,381 @@ +/* Category widget */ +.feedwordpress-category-div .tabs-panel { + height: 200px; + overflow: auto; + border: 1px solid #DFDFDF; + margin-left: 126px; +} +.feedwordpress-category-div ul.category-tabs { + float: left; + margin: 0 -120px 0 5px; + padding: 0; + text-align: right; + width: 120px; +} +.feedwordpress-category-div ul.category-tabs li.tabs { + -moz-border-radius: 3px 0 0 3px; + -webkit-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; + border-color: #DFDFDF; + border-style: solid none solid solid; + border-width: 1px 0 1px 1px; + margin-right: -1px; + padding: 8px; + background-color: #F1F1F1; +} + +.feedwordpress-category-div ul.categorychecklist ul { + margin-left: 18px; +} +.feedwordpress-category-div ul.category-tabs a { + color: #333333; + font-weight: bold; + text-decoration: none; +} + +/* Common settings page elements */ +#fwpfs-container { + margin:11px 12px 0; + min-width: 130px; + position:relative; +} +#fwpfs-container .subsubsub { + float: none !important; +} +.tablenav p.submit { + margin: 0px; + padding: 0px; +} +.fwpfs { + color: #dddddd !important; background-color: #333 !important; + background-color: #797979; + border-color:#777777 #777777 #666666 !important; + border-radius: 12px; + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + -khtml-border-radius: 12px; + border-style:solid; + border-width:1px; + line-height:15px; + padding:3px 30px 4px 12px; +} +.fwpfs.slide-down { + border-bottom-color: #626262; + border-top-left-radius: 12px; border-top-right-radius: 12px; + border-bottom-left-radius: 0; border-bottom-right-radius: 0; + -webkit-border-top-left-radius: 12px; -webkit-border-top-right-radius: 12px; + -webkit-border-bottom-left-radius: 0; -webkit-border-bottom-right-radius: 0; + -khtml-border-top-left-radius: 12px; -khtml-border-top-right-radius: 12px; + -khtml-border-bottom-left-radius: 0; -khtml-border-bottom-right-radius: 0; + -moz-border-radius-topleft:12px; -moz-border-radius-topright:12px; + -moz-border-radius-bottomleft:0; -moz-border-radius-bottomright:0; + border-bottom-style:solid; + border-bottom-width:1px; +} + +.current-setting { + display: block; + font-size: smaller; + margin-left: 2.0em; + font-size: smaller; +} +.twofer tr td h4 { + margin-top: 0px; + margin-bottom: 3px; +} +ul.current-setting { + list-style: disc outside; + padding-left: 2.0em; +} + +/* Switchfeed interface elements */ + +#feed-finder form { + padding-bottom: 2.0em; + border-bottom: 1px dashed #333; + margin-bottom: 2.0em; +} +#feed-finder form.multi .inside { + margin-left: 1.0em; + border-left: 3px solid #333; + padding-left: 2.0em; +} + +#feed-finder form fieldset { + clear: both; + border: 1px solid #777; + padding: 5px 25px; + background-color: #fff; + border-radius: 5px; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; + -webkit-border-radius: 5px; +} +#feed-finder form fieldset.alt { + background-color: #eee; +} +#feed-finder form fieldset.current { + background-color: #efe; +} +#feed-finder form fieldset.current legend { + background-color: #7f7; +} +#feed-finder form fieldset legend { + border-left: 1px solid #777; + border-right: 1px solid #777; + border-top: 1px solid #777; + border-bottom: 1px solid #777; + border-radius: 5px; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; + -webkit-border-radius: 5px; + background-color: #7FF; + padding: 3px 10px; + font-weight: bold; +} +#feed-finder form fieldset label { + font-weight: bold; +} +#feed-finder form fieldset div.submit { + clear: both; + text-align: left; +} +#feed-finder form fieldset div.submit input { + margin-right: 3.0em; +} + +#feed-finder .feed-sample { + float: right; + background-color: #D0D0D0; + color: black; + width: 45%; + font-size: 70%; + border-left: 1px dotted #A0A0A0; + margin-left: 1.0em; + padding: 3px; + max-height: 200px; + overflow: auto; +} +#feed-finder .feed-sample p, .feed-sample h3 { + padding-left: 0.5em; + padding-right: 0.5em; +} +#feed-finder .feed-sample .feed-problem { + background-color: #ffd0d0; + border-bottom: 1px dotted black; + padding-bottom: 0.5em; + margin-bottom: 0.5em; +} + +table.edit-form { width: 100%; } +table.edit-form th { width: 27%; vertical-align: top; text-align: right; padding: 0.5em; } +table.edit-form th .setting-description { font-weight: normal; font-size: smaller; font-style: italic; } + +table.edit-form td { width: 73%; vertical-align: top; padding: 0.5em; } +table.edit-form td ul.options { margin: 0; padding: 0; list-style: none; } +table.edit-form td ul.options.compact li { display: inline; margin-left: 3.0em; white-space: nowrap; } +table.edit-form.narrow th { width: 15%; } +table.edit-form.narrow td { width: 85%; } +table.edit-form td p { margin-top: 0px; } + +table.twofer { width: 100%; border: none; } +table.twofer tr { vertical-align: top; } +table.twofer td.equals.first { width: 49% !important; border-right: 1px dotted #777; padding-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 10px; } +table.twofer td.equals.second { width: 49% !important; padding-top: 0px; padding-bottom: 0px; padding-left: 10px; padding-right: 0px; } + +table.twofer td.active { color: black; background-color: #cfc; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -khtml-border-radius: 5px; } +table.twofer td.inactive { color: #333; } +table.twofer td.equals.first.active { border-right: none; } +table.twofer td.equals.first.inactive { border-right: none; } + +table.twofer td.primary { width: 69%; border-right: 1px dotted #777; padding-right: 10px; } +table.twofer td.secondary { padding-left: 10px; width: 30%; } + +#add-multiple-uri, #upload-opml { + float: right; + position: relative; + width: 50%; + background-color: #cfc; + padding-left: 1.0em; padding-right: 1.0em; + padding-top: 0.5em; padding-bottom: 0.5em; + right: 0; + top: auto; +} +#add-multiple-uri textarea { + width: 100%; + margin-top: 0.5em; +} +#add-multiple-uri h4 { + margin-bottom: 2px; + margin-top: 0px; + border-bottom: 1px dotted black; +} + +#dashboard-widgets #feedwordpress_dashboard h4 { + color: #777777; + font-family: Georgia, "Times New Roman", "Bitstream Charter", Times, serif; + font-size: 13px; + font-style: italic; + font-weight: normal; + + margin: 0px; + + border-bottom: 1px solid #ececec; + + padding-left: 15px; + padding-top: 10px; + padding-bottom: 5px; + padding-right: 0px; +} + +#feedwordpress_dashboard .feedwordpress-stats { + width: 49%; +} +#feedwordpress_dashboard .feedwordpress-stats table { + margin-top: 5px; + margin-left: 15px; +} +#feedwordpress_dashboard .feedwordpress-actions ul.options { + margin-left: 15px !important; + margin-top: 5px !important; +} + +#feedwordpress_dashboard .feedwordpress-actions { + width: 49%; + float: right; +} +#feedwordpress_dashboard td.b { + padding-right: 12px; +} +#feedwordpress_dashboard td.b a { + font-size: 18px; +} + +#feedwordpress_dashboard td.t.active a:link, +#feedwordpress_dashboard td.t.active a:visited { + color: green; +} +#feedwordpress_dashboard td.t.inactive a:link, +#feedwordpress_dashboard td.t.inactive a:visited { + color: red; +} + +#feedwordpress_dashboard #add-single-uri { + clear: both; + margin-top: 15px; +} + +#feedwordpress_dashboard #add-single-uri #syndicated-links { + border-top: 1px solid #ececec; + width: 49%; +} +#feedwordpress_dashboard #add-single-uri #syndicated-links .container { + margin-top: 5px; + margin-left: 15px; +} + +#feedwordpress_dashboard #add-single-uri #check-for-updates { + border-top: 1px solid #ececec; + float: right; + text-align: left; + width: 49%; +} +#feedwordpress_dashboard #add-single-uri #check-for-updates .container { + margin-top: 5px; + margin-left: 15px; +} +#multiadd-status { + position: absolute; + padding: 10px; + background-color: #ecec70; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + opacity: 0.85; + top: auto; + left: 45%; + right: auto; + bottom: -5px; +} +#multiadd-form { + position: relative; + padding-bottom: 3.0em; +} +#multiadd-status img { + vertical-align: middle; +} +#multiadd-list { + border-top: 1px dotted black; + padding-top: 5px; + padding-bottom: 5px; + border-bottom: 1px dotted black; +} +#multiadd-buttons { + position: absolute; + bottom: 0px; + margin-top: 15px; + margin-left: 15px; +} + +.feedwordpress-admin { + position: relative; +} +.feedwordpress-admin .submit { position: relative; z-index: 40; } + +#feedwordpress-admin-syndication .heads-up { + background-color: #d0d0d0; + color: black; + padding: 1.0em; + margin: 0.5em 4.0em !important; +} +#feedwordpress-admin-syndication .update-form.with-donation { + margin-right: 50%; + min-height: 255px; +} +#feedwordpress-admin-syndication .donation-form, +#feedwordpress-admin-syndication .donation-thanks { + background-color: #ffffcc; + text-align: left; + padding: 0.5em 0.5em; + border-left: thin dashed #777777; + font-size: 70%; + position: absolute; + top: 0; bottom: 0; right: 0; left: auto; + width: 50%; +} +#feedwordpress-admin-syndication .donation-thanks { + background-color: #ccffcc; +} +#feedwordpress-admin-syndication .donation-thanks .signature { + text-align: right; + font-style: italic; +} +#feedwordpress-admin-syndication .donation-form h4, +#feedwordpress-admin-syndication .donation-thanks h4 { + font-size: 10px; + text-align: center; + border-bottom: 1px solid #777777; + margin: 0px; +} +#feedwordpress-admin-syndication .donation-form .donate { + text-align: center; +} +#feedwordpress-admin-syndication .donation-form .sod-off { + padding-top: 0.5em; + margin-top: 0.5em; + border-top: thin solid #777777; +} +#feedwordpress-admin-syndication .feed-missing { + background-color:#FFFFD0; +} +#feedwordpress-admin-syndication .unsubscribed tr { + background-color: #FFE0E0; +} +#feedwordpress-admin-syndication .unsubscribed tr.alternate { + background-color: #FFF0F0; +} +#feedwordpress-admin-syndication tr.feed-error { + background-color: #FFFFD0; +} + diff --git a/wp-content/plugins/feedwordpress/feedwordpress-elements.js b/wp-content/plugins/feedwordpress/feedwordpress-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..838ba66cfc41b73215e13773d681e02e1099e062 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpress-elements.js @@ -0,0 +1,489 @@ +(function($) { +var fs = {add:'ajaxAdd',del:'ajaxDel',dim:'ajaxDim',process:'process',recolor:'recolor'}, fwpList; + +fwpList = { + settings: { + url: ajaxurl, type: 'POST', + response: 'ajax-response', + + what: '', + alt: 'alternate', altOffset: 0, + addColor: null, delColor: null, dimAddColor: null, dimDelColor: null, + + confirm: null, + addBefore: null, addAfter: null, + delBefore: null, delAfter: null, + dimBefore: null, dimAfter: null + }, + + nonce: function(e,s) { + var url = wpAjax.unserialize(e.attr('href')); + return s.nonce || url._ajax_nonce || $('#' + s.elementbox + ' input[name=_ajax_nonce]').val() || url._wpnonce || $('#' + s.element + ' input[name=_wpnonce]').val() || 0; + }, + + parseClass: function(e,t) { + var c = [], cl; + try { + cl = $(e).attr('class') || ''; + cl = cl.match(new RegExp(t+':[\\S]+')); + if ( cl ) { c = cl[0].split(':'); } + } catch(r) {} + return c; + }, + + pre: function(e,s,a) { + var bg, r; + s = $.extend( {}, this.fwpList.settings, { + element: null, + nonce: 0, + target: e.get(0) + }, s || {} ); + if ( $.isFunction( s.confirm ) ) { + if ( 'add' != a ) { + bg = $('#' + s.element).css('backgroundColor'); + $('#' + s.element).css('backgroundColor', '#FF9966'); + } + r = s.confirm.call(this,e,s,a,bg); + if ( 'add' != a ) { $('#' + s.element).css('backgroundColor', bg ); } + if ( !r ) { return false; } + } + return s; + }, + + ajaxAdd: function( e, s ) { + e = $(e); + s = s || {}; + var list = this, cls = fwpList.parseClass(e,'add'), es, valid, formData; + s = fwpList.pre.call( list, e, s, 'add' ); + + s.element = cls[2] || e.attr( 'id' ) || s.element || null; + if ( cls[3] ) { s.addColor = '#' + cls[3]; } + else { s.addColor = s.addColor || '#FFFF33'; } + + if ( !s ) { return false; } + + if ( !e.is("[class^=add:" + list.id + ":]") ) { return !fwpList.add.call( list, e, s ); } + + if ( !s.element ) { return true; } + + s.action = 'add-' + s.what; + + s.nonce = fwpList.nonce(e,s); + + es = $('#' + s.elementbox + ' :input').not('[name=_ajax_nonce], [name=_wpnonce], [name=action]'); + valid = wpAjax.validateForm( '#' + s.element ); + if ( !valid ) { return false; } + + s.data = $.param( $.extend( { _ajax_nonce: s.nonce, action: s.action }, wpAjax.unserialize( cls[4] || '' ) ) ); + formData = $.isFunction(es.fieldSerialize) ? es.fieldSerialize() : es.serialize(); + if ( formData ) { s.data += '&' + formData; } + + if ( $.isFunction(s.addBefore) ) { + s = s.addBefore( s ); + if ( !s ) { return true; } + } + if ( !s.data.match(/_ajax_nonce=[a-f0-9]+/) ) { return true; } + + s.success = function(r) { + var res = wpAjax.parseAjaxResponse(r, s.response, s.element), o; + if ( !res || res.errors ) { return false; } + + if ( true === res ) { return true; } + + jQuery.each( res.responses, function() { + fwpList.add.call( list, this.data, $.extend( {}, s, { // this.firstChild.nodevalue + pos: this.position || 0, + id: this.id || 0, + oldId: this.oldId || null + } ) ); + } ); + + if ( $.isFunction(s.addAfter) ) { + o = this.complete; + this.complete = function(x,st) { + var _s = $.extend( { xml: x, status: st, parsed: res }, s ); + s.addAfter( r, _s ); + if ( $.isFunction(o) ) { o(x,st); } + }; + } + list.fwpList.recolor(); + $(list).trigger( 'fwpListAddEnd', [ s, list.fwpList ] ); + fwpList.clear.call(list,'#' + s.element); + }; + + $.ajax( s ); + return false; + }, + + ajaxDel: function( e, s ) { + e = $(e); s = s || {}; + var list = this, cls = fwpList.parseClass(e,'delete'), element; + s = fwpList.pre.call( list, e, s, 'delete' ); + + s.element = cls[2] || s.element || null; + if ( cls[3] ) { s.delColor = '#' + cls[3]; } + else { s.delColor = s.delColor || '#faa'; } + + if ( !s || !s.element ) { return false; } + + s.action = 'delete-' + s.what; + + s.nonce = fwpList.nonce(e,s); + + s.data = $.extend( + { action: s.action, id: s.element.split('-').pop(), _ajax_nonce: s.nonce }, + wpAjax.unserialize( cls[4] || '' ) + ); + + if ( $.isFunction(s.delBefore) ) { + s = s.delBefore( s, list ); + if ( !s ) { return true; } + } + if ( !s.data._ajax_nonce ) { return true; } + + element = $('#' + s.element); + + if ( 'none' != s.delColor ) { + element.css( 'backgroundColor', s.delColor ).fadeOut( 350, function(){ + list.fwpList.recolor(); + $(list).trigger( 'fwpListDelEnd', [ s, list.fwpList ] ); + }); + } else { + list.fwpList.recolor(); + $(list).trigger( 'fwpListDelEnd', [ s, list.fwpList ] ); + } + + s.success = function(r) { + var res = wpAjax.parseAjaxResponse(r, s.response, s.element), o; + if ( !res || res.errors ) { + element.stop().stop().css( 'backgroundColor', '#faa' ).show().queue( function() { list.fwpList.recolor(); $(this).dequeue(); } ); + return false; + } + if ( $.isFunction(s.delAfter) ) { + o = this.complete; + this.complete = function(x,st) { + element.queue( function() { + var _s = $.extend( { xml: x, status: st, parsed: res }, s ); + s.delAfter( r, _s ); + if ( $.isFunction(o) ) { o(x,st); } + } ).dequeue(); + }; + } + }; + $.ajax( s ); + return false; + }, + + ajaxDim: function( e, s ) { + if ( $(e).parent().css('display') == 'none' ) // Prevent hidden links from being clicked by hotkeys + return false; + e = $(e); s = s || {}; + var list = this, cls = fwpList.parseClass(e,'dim'), element, isClass, color, dimColor; + s = fwpList.pre.call( list, e, s, 'dim' ); + + s.element = cls[2] || s.element || null; + s.dimClass = cls[3] || s.dimClass || null; + if ( cls[4] ) { s.dimAddColor = '#' + cls[4]; } + else { s.dimAddColor = s.dimAddColor || '#FFFF33'; } + if ( cls[5] ) { s.dimDelColor = '#' + cls[5]; } + else { s.dimDelColor = s.dimDelColor || '#FF3333'; } + + if ( !s || !s.element || !s.dimClass ) { return true; } + + s.action = 'dim-' + s.what; + + s.nonce = fwpList.nonce(e,s); + + s.data = $.extend( + { action: s.action, id: s.element.split('-').pop(), dimClass: s.dimClass, _ajax_nonce : s.nonce }, + wpAjax.unserialize( cls[6] || '' ) + ); + + if ( $.isFunction(s.dimBefore) ) { + s = s.dimBefore( s ); + if ( !s ) { return true; } + } + + element = $('#' + s.element); + isClass = element.toggleClass(s.dimClass).is('.' + s.dimClass); + color = fwpList.getColor( element ); + element.toggleClass( s.dimClass ) + dimColor = isClass ? s.dimAddColor : s.dimDelColor; + if ( 'none' != dimColor ) { + element + .animate( { backgroundColor: dimColor }, 'fast' ) + .queue( function() { element.toggleClass(s.dimClass); $(this).dequeue(); } ) + .animate( { backgroundColor: color }, { complete: function() { $(this).css( 'backgroundColor', '' ); $(list).trigger( 'fwpListDimEnd', [ s, list.fwpList ] ); } } ); + } else { + $(list).trigger( 'fwpListDimEnd', [ s, list.fwpList ] ); + } + + if ( !s.data._ajax_nonce ) { return true; } + + s.success = function(r) { + var res = wpAjax.parseAjaxResponse(r, s.response, s.element), o; + if ( !res || res.errors ) { + element.stop().stop().css( 'backgroundColor', '#FF3333' )[isClass?'removeClass':'addClass'](s.dimClass).show().queue( function() { list.fwpList.recolor(); $(this).dequeue(); } ); + return false; + } + if ( $.isFunction(s.dimAfter) ) { + o = this.complete; + this.complete = function(x,st) { + element.queue( function() { + var _s = $.extend( { xml: x, status: st, parsed: res }, s ); + s.dimAfter( r, _s ); + if ( $.isFunction(o) ) { o(x,st); } + } ).dequeue(); + }; + } + }; + + $.ajax( s ); + return false; + }, + + // From jquery.color.js: jQuery Color Animation by John Resig + getColor: function( el ) { + if ( el.constructor == Object ) + el = el.get(0); + var elem = el, color, rgbaTrans = new RegExp( "rgba\\(\\s*0,\\s*0,\\s*0,\\s*0\\s*\\)", "i" ); + do { + color = jQuery.curCSS(elem, 'backgroundColor'); + if ( color != '' && color != 'transparent' && !color.match(rgbaTrans) || jQuery.nodeName(elem, "body") ) + break; + } while ( elem = elem.parentNode ); + return color || '#ffffff'; + }, + + add: function( e, s ) { + e = $(e); + var list = $(this), + old = false, + _s = { pos: 0, id: 0, oldId: null }, + ba, ref, color; + + if ( 'string' == typeof s ) { + s = { what: s }; + } + + s = $.extend(_s, this.fwpList.settings, s); + if ( !e.size() || !s.what ) { return false; } + if ( s.oldId ) { old = $('#' + s.what + '-' + s.oldId); } + if ( s.id && ( s.id != s.oldId || !old || !old.size() ) ) { $('#' + s.what + '-' + s.id).remove(); } + + if ( old && old.size() ) { + old.before(e); + old.remove(); + } else if ( isNaN(s.pos) ) { + ba = 'after'; + if ( '-' == s.pos.substr(0,1) ) { + s.pos = s.pos.substr(1); + ba = 'before'; + } + ref = list.find( '#' + s.pos ); + if ( 1 === ref.size() ) { ref[ba](e); } + else { list.append(e); } + } else if ( s.pos < 0 ) { + list.prepend(e); + } else { + list.append(e); + } + + if ( s.alt ) { + if ( ( list.children(':visible').index( e[0] ) + s.altOffset ) % 2 ) { e.removeClass( s.alt ); } + else { e.addClass( s.alt ); } + } + + if ( 'none' != s.addColor ) { + color = fwpList.getColor( e ); + e.css( 'backgroundColor', s.addColor ).animate( { backgroundColor: color }, { complete: function() { $(this).css( 'backgroundColor', '' ); } } ); + } + + list.each( function() { this.fwpList.process( e ); } ); + return e; + }, + + clear: function(e) { + var list = this, t, tag; + e = $(e); + if ( list.fwpList && e.parents( '#' + list.id ).size() ) { return; } + e.find(':input').each( function() { + if ( $(this).parents('.form-no-clear').size() ) + return; + t = this.type.toLowerCase(); + tag = this.tagName.toLowerCase(); + if ( 'text' == t || 'password' == t || 'textarea' == tag ) { this.value = ''; } + else if ( 'checkbox' == t || 'radio' == t ) { this.checked = false; } + else if ( 'select' == tag ) { this.selectedIndex = null; } + }); + }, + + process: function(el) { + var list = this; + + $("[class^=add:" + list.id + ":]", el || null) + .filter('form').submit( function() { return list.fwpList.add(this); } ).end() + .not('form').click( function() { return list.fwpList.add(this); } ); + $("[class^=delete:" + list.id + ":]", el || null).click( function() { return list.fwpList.del(this); } ); + $("[class^=dim:" + list.id + ":]", el || null).click( function() { return list.fwpList.dim(this); } ); + }, + + recolor: function() { + var list = this, items, eo; + if ( !list.fwpList.settings.alt ) { return; } + items = $('.list-item:visible', list); + if ( !items.size() ) { items = $(list).children(':visible'); } + eo = [':even',':odd']; + if ( list.fwpList.settings.altOffset % 2 ) { eo.reverse(); } + items.filter(eo[0]).addClass(list.fwpList.settings.alt).end().filter(eo[1]).removeClass(list.fwpList.settings.alt); + }, + + init: function() { + var lists = this; + + lists.fwpList.process = function(a) { + lists.each( function() { + this.fwpList.process(a); + } ); + }; + lists.fwpList.recolor = function() { + lists.each( function() { + this.fwpList.recolor(); + } ); + }; + } +}; + +$.fn.fwpList = function( settings ) { + this.each( function() { + var _this = this; + this.fwpList = { settings: $.extend( {}, fwpList.settings, { what: fwpList.parseClass(this,'list')[1] || '' }, settings ) }; + $.each( fs, function(i,f) { _this.fwpList[i] = function( e, s ) { return fwpList[f].call( _this, e, s ); }; } ); + } ); + fwpList.init.call(this); + this.fwpList.process(); + return this; +}; + +})(jQuery); + +jQuery(document).ready( function($) { + // Category boxes + $('.feedwordpress-category-div').each( function () { + var this_id = $(this).attr('id'); + var catAddBefore, catAddAfter; + var taxonomyParts, taxonomy, settingName; + + taxonomyParts = this_id.split('-'); + taxonomyParts.shift(); taxonomyParts.shift(); + taxonomy = taxonomyParts.join('-'); + + settingName = taxonomy + '_tab'; + if ( taxonomy == 'category' ) + settingName = 'cats'; + + // No need to worry about tab stuff for our purposes + + // Ajax Cat + var containerId = $(this).attr('id'); + var checkboxId = $(this).find('.categorychecklist').attr('id'); + var newCatId = $(this).find('.new'+taxonomy).attr('id'); + var responseId = $(this).find('.'+taxonomy+'-ajax-response').attr('id'); + var taxAdderId = $(this).find('.'+taxonomy+'-adder').attr('id'); + + $(this).find('.new'+taxonomy).one( 'focus', function () { $(this).val('').removeClass('form-input-tip'); } ); + $(this).find('.add-categorychecklist-category-add').click( function() { + $(this).parent().children('.new'+taxonomy).focus(); + } ); + + catAddBefore = function (s) { + if ( !$('#'+newCatId).val() ) + return false; + s.data += '&' + $( ':checked', '#'+checkboxId ).serialize(); + return s; + } + catAddAfter = function (r, s) { + // Clear out input box + $('.new' + taxonomy, '#'+this_id).val(''); + + // Clear out parent dropbox + var sup, drop = $('.new' + taxonomy + '-parent', '#'+this_id); + var keep = $('.new' + taxonomy, '#'+this_id); + + if ( 'undefined' != s.parsed.responses[0] && (sup = s.parsed.responses[0].supplemental.newcat_parent) ) { + sup = sup.replace(/id=['"]new[^'"]*_parent['"]/g, 'id="' + keep.attr('id') + '-parent"'); + drop.before(sup); + $('#'+keep.attr('id')+'-parent').addClass('new' + taxonomy + '-parent'); + drop.remove(); + } + }; + + $('#' + checkboxId).fwpList({ + alt: '', + elementbox: taxAdderId, + response: responseId, + addBefore: catAddBefore, + addAfter: catAddAfter + }); + + $(this).find('.category-add-toggle').click( function () { + $('#' + taxAdderId).toggleClass('wp-hidden-children'); + $('#' + newCatId).focus(); + return false; + } ); /* $(this).find('.category-add-toggle').click() */ + + } ); /* $('.feedwordpress-category-div').each() */ +} ); /* jQuery(document).ready() */ + +jQuery(document).ready(function($){ + if ( $('.jaxtag').length ) { + tagBox.init(); + } + + $('.fwpfs').toggle( + function(){$('.fwpfs').removeClass('slideUp').addClass('slideDown'); setTimeout(function(){if ( $('.fwpfs').hasClass('slideDown') ) { $('.fwpfs').addClass('slide-down'); }}, 10) }, + function(){$('.fwpfs').removeClass('slideDown').addClass('slideUp'); setTimeout(function(){if ( $('.fwpfs').hasClass('slideUp') ) { $('.fwpfs').removeClass('slide-down'); }}, 10) } + ); + $('.fwpfs').bind( + 'change', + function () { this.form.submit(); } + ); + $('#fwpfs-button').css( 'display', 'none' ); + + $('table.twofer td.active input[type="radio"], table.twofer td.inactive input[type="radio"]').each( function () { + $(this).click( function () { + var name = $(this).attr('name'); + var table = $(this).closest('table'); + table.find('td').removeClass('active').addClass('inactive'); + table.find('td:has(input[name="'+name+'"]:checked)').removeClass('inactive').addClass('active'); + } ); + + var name = $(this).attr('name'); + var table = $(this).closest('table'); + table.find('td').removeClass('active').addClass('inactive'); + table.find('td:has(input[name="'+name+'"]:checked)').removeClass('inactive').addClass('active'); + } ); + + $('#turn-on-multiple-sources').click ( function () { + $('#add-single-uri').hide(); + $('#add-multiple-uri').show(600); + return false; +; + } ); + $('#turn-off-multiple-sources').click ( function () { + $('#add-multiple-uri').hide(600); + $('#add-single-uri').show(); + return false; + } ); + $('#turn-on-opml-upload').click ( function () { + $('#add-single-uri').hide(); + $('#upload-opml').show(600); + return false; + } ); + $('#turn-off-opml-upload').click ( function () { + $('#upload-opml').hide(600); + $('#add-single-uri').show(); + return false; + } ); +}); + diff --git a/wp-content/plugins/feedwordpress/feedwordpress-tiny.png b/wp-content/plugins/feedwordpress/feedwordpress-tiny.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7675a7ee8f3f44b0909c944cfbcf70b6ffd174 Binary files /dev/null and b/wp-content/plugins/feedwordpress/feedwordpress-tiny.png differ diff --git a/wp-content/plugins/feedwordpress/feedwordpress-walker-category-checklist.class.php b/wp-content/plugins/feedwordpress/feedwordpress-walker-category-checklist.class.php new file mode 100644 index 0000000000000000000000000000000000000000..b645ca46c0d166a54e96e157d5a7d93616cca544 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpress-walker-category-checklist.class.php @@ -0,0 +1,49 @@ +<?php +/** + * FeedWordPress_Walker_Category_Checklist + * + * @version 2010.0531 + * + * This is the fucking stupidest thing ever. + */ + +require_once(ABSPATH.'/wp-admin/includes/template.php'); +// Fucking fuck. + +class FeedWordPress_Walker_Category_Checklist extends Walker_Category_Checklist { + var $prefix = ''; var $taxonomy = 'category'; + function FeedWordPress_Walker_Category_Checklist () { + $this->set_taxonomy('category'); + } + + function set_prefix ($prefix) { + $this->prefix = $prefix; + } + function set_taxonomy ($taxonomy) { + $this->taxonomy = $taxonomy; + } + + function start_el (&$output, $category, $depth, $args) { + extract($args); + if ( empty($taxonomy) ) : + $taxonomy = 'category'; + endif; + + if ($taxonomy=='category') : + $name = 'post_category'; + else : + $name = 'tax_input['.$taxonomy.']'; + endif; + + $unit = array(); + if (strlen($this->prefix) > 0) : + $unit[] = $this->prefix; + endif; + $unit[] = $taxonomy; + $unit[] = $category->term_id; + $unitId = implode("-", $unit); + + $class = in_array( $category->term_id, $popular_cats ) ? ' class="popular-category category-checkbox"' : ' class="category-checkbox"'; + $output .= "\n<li id='{$unitId}'$class>" . '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="'.$name.'[]" id="in-'.$unitId. '"' . checked( in_array( $category->term_id, $selected_cats ), true, false ) . disabled( empty( $args['disabled'] ), false, false ) . ' /> ' . esc_html( apply_filters('the_category', $category->name )) . '</label>'; + } /* FeedWordPress_Walker_Category_Checklist::start_el() */ +} /* FeedWordPress_Walker_Category_Checklist */ diff --git a/wp-content/plugins/feedwordpress/feedwordpress.php b/wp-content/plugins/feedwordpress/feedwordpress.php new file mode 100644 index 0000000000000000000000000000000000000000..57b7d8caca4faa1faf27ffa41310c59634574bf1 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpress.php @@ -0,0 +1,2091 @@ +<?php +/* +Plugin Name: FeedWordPress +Plugin URI: http://feedwordpress.radgeek.com/ +Description: simple and flexible Atom/RSS syndication for WordPress +Version: 2010.1007 +Author: Charles Johnson +Author URI: http://radgeek.com/ +License: GPL +*/ + +/** + * @package FeedWordPress + * @version 2010.1007 + */ + +# This uses code derived from: +# - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net> +# - MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net> +# - Ultra-Liberal Feed Finder by Mark Pilgrim <mark@diveintomark.org> +# - WordPress Blog Tool and Publishing Platform <http://wordpress.org/> +# according to the terms of the GNU General Public License. +# +# INSTALLATION: see readme.txt or <http://projects.radgeek.com/install> +# +# USAGE: once FeedWordPress is installed, you manage just about everything from +# the WordPress Dashboard, under the Syndication menu. To ensure that fresh +# content is added as it becomes available, you can convince your contributors +# to put your XML-RPC URI (if WordPress is installed at +# <http://www.zyx.com/blog>, XML-RPC requests should be sent to +# <http://www.zyx.com/blog/xmlrpc.php>), or update manually under the +# Syndication menu, or set up automatic updates under Syndication --> Settings, +# or use a cron job. + +# -- Don't change these unless you know what you're doing... + +define ('FEEDWORDPRESS_VERSION', '2010.1007'); +define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact'); + +if (!defined('FEEDWORDPRESS_BLEG')) : + define ('FEEDWORDPRESS_BLEG', true); +endif; + +// Defaults +define ('DEFAULT_SYNDICATION_CATEGORY', 'Contributors'); +define ('DEFAULT_UPDATE_PERIOD', 60); // value in minutes + +if (isset($_REQUEST['feedwordpress_debug'])) : + $feedwordpress_debug = $_REQUEST['feedwordpress_debug']; +else : + $feedwordpress_debug = get_option('feedwordpress_debug'); + if (is_string($feedwordpress_debug)) : + $feedwordpress_debug = ($feedwordpress_debug == 'yes'); + endif; +endif; +define ('FEEDWORDPRESS_DEBUG', $feedwordpress_debug); + +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_HAS_USERMETA', 2966); +define ('FWP_SCHEMA_USES_ARGS_TAXONOMY', 12694); // Revision # for using $args['taxonomy'] to get link categories +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 +define ('FWP_SCHEMA_25', 7558); // Database schema # for WP 2.5 +define ('FWP_SCHEMA_26', 8201); // Database schema # for WP 2.6 +define ('FWP_SCHEMA_27', 9872); // Database schema # for WP 2.7 +define ('FWP_SCHEMA_28', 11548); // Database schema # for WP 2.8 +define ('FWP_SCHEMA_29', 12329); // Database schema # for WP 2.9 +define ('FWP_SCHEMA_30', 12694); // Database schema # for WP 3.0 + +if (FEEDWORDPRESS_DEBUG) : + // Help us to pick out errors, if any. + ini_set('error_reporting', E_ALL & ~E_NOTICE); + ini_set('display_errors', 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('FEEDWORDPRESS_CACHE_AGE', 1); + define('FEEDWORDPRESS_CACHE_LIFETIME', 1); + define('FEEDWORDPRESS_FETCH_TIME_OUT', 60); +else : + // Hold onto data all day for conditional GET purposes, + // but consider it stale after 1 min (requiring a conditional GET) + define('FEEDWORDPRESS_CACHE_LIFETIME', 24*60*60); + define('FEEDWORDPRESS_CACHE_AGE', 1*60); + define('FEEDWORDPRESS_FETCH_TIME_OUT', 10); +endif; + +// Use our the cache settings that we want. +add_filter('wp_feed_cache_transient_lifetime', array('FeedWordPress', 'cache_lifetime')); + +// Ensure that we have SimplePie loaded up and ready to go. +// We no longer need a MagpieRSS upgrade module. Hallelujah! +if (!class_exists('SimplePie')) : + require_once(ABSPATH . WPINC . '/class-simplepie.php'); +endif; +require_once(ABSPATH . WPINC . '/class-feed.php'); + +require_once (ABSPATH . WPINC . '/registration.php'); // for wp_insert_user + +require_once(dirname(__FILE__) . '/admin-ui.php'); +require_once(dirname(__FILE__) . '/feedwordpresssyndicationpage.class.php'); +require_once(dirname(__FILE__) . '/compatability.php'); // LEGACY API: Replicate or mock up functions for legacy support purposes + +require_once(dirname(__FILE__) . '/syndicatedpost.class.php'); +require_once(dirname(__FILE__) . '/syndicatedlink.class.php'); +require_once(dirname(__FILE__) . '/feedwordpresshtml.class.php'); +require_once(dirname(__FILE__) . '/feedwordpress-content-type-sniffer.class.php'); + +// Magic quotes are just about the stupidest thing ever. +if (is_array($_POST)) : + $fwp_post = stripslashes_deep($_POST); +endif; + +// Get the path relative to the plugins directory in which FWP is stored +preg_match ( + '|'.preg_quote(WP_PLUGIN_DIR).'/(.+)$|', + dirname(__FILE__), + $ref +); + +if (isset($ref[1])) : + $fwp_path = $ref[1]; +else : // Something went wrong. Let's just guess. + $fwp_path = 'feedwordpress'; +endif; + +// If this is a FeedWordPress admin page, queue up scripts for AJAX functions that FWP uses +// If it is a display page or a non-FeedWordPress admin page, don't. +wp_register_style('feedwordpress-elements', WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-elements.css'); +if (FeedWordPressSettingsUI::is_admin()) : + // For JavaScript that needs to be generated dynamically + add_action('admin_print_scripts', array('FeedWordPressSettingsUI', 'admin_scripts')); + + // For CSS that needs to be generated dynamically. + add_action('admin_print_styles', array('FeedWordPressSettingsUI', 'admin_styles')); + + wp_enqueue_style('dashboard'); + wp_enqueue_style('feedwordpress-elements'); + + if (function_exists('wp_admin_css')) : + wp_admin_css('css/dashboard'); + endif; +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); + + add_action('atom_entry', 'feedwordpress_item_feed_data'); + + # Filter in original permalinks if the user wants that + add_filter('post_link', 'syndication_permalink', /*priority=*/ 1, /*arguments=*/ 3); + + # When foreign URLs are used for permalinks in feeds or display + # contexts, they need to be escaped properly. + add_filter('the_permalink', 'syndication_permalink_escaped'); + add_filter('the_permalink_rss', 'syndication_permalink_escaped'); + + add_filter('post_comments_feed_link', 'syndication_comments_feed_link'); + + # WTF? By default, wp_insert_link runs incoming link_url and link_rss + # URIs through default filters that include `wp_kses()`. But `wp_kses()` + # just happens to escape any occurrence of & to & -- which just + # happens to fuck up any URI with a & to separate GET parameters. + remove_filter('pre_link_rss', 'wp_filter_kses'); + remove_filter('pre_link_url', 'wp_filter_kses'); + + # Admin menu + add_action('admin_menu', 'fwp_add_pages'); + add_action('admin_notices', 'fwp_check_debug'); + + add_action('admin_menu', 'feedwordpress_add_post_edit_controls'); + add_action('save_post', 'feedwordpress_save_post_edit_controls'); + + add_action('admin_footer', array('FeedWordPress', 'admin_footer')); + + # Inbound XML-RPC update methods + $feedwordpressRPC = new FeedWordPressRPC; + + # 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'); + + add_action('syndicated_feed_error', array('FeedWordPressDiagnostic', 'feed_error'), 100, 3); + + add_action('wp_footer', 'debug_out_feedwordpress_footer', -100); + add_action('admin_footer', 'debug_out_feedwordpress_footer', -100); + + $feedwordpress = new FeedWordPress; + + # Cron-less auto-update. Hooray! + $autoUpdateHook = get_option('feedwordpress_automatic_updates'); + if ($autoUpdateHook != 'init') : + $autoUpdateHook = 'shutdown'; + endif; + add_action($autoUpdateHook, array(&$feedwordpress, 'auto_update')); + add_action('init', array(&$feedwordpress, 'init')); + add_action('shutdown', array(&$feedwordpress, 'email_diagnostic_log')); + add_action('wp_dashboard_setup', array(&$feedwordpress, 'dashboard_setup')); + + # Default sanitizers + add_filter('syndicated_item_content', array('SyndicatedPost', 'resolve_relative_uris'), 0, 2); + add_filter('syndicated_item_content', array('SyndicatedPost', 'sanitize_content'), 0, 2); + +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()) + +################################################################################ +## LOGGING FUNCTIONS: log status updates to error_log if you want it ########### +################################################################################ + +class FeedWordPressDiagnostic { + function feed_error ($error, $old, $link) { + $wpError = $error['object']; + $url = $link->uri(); + + $mesgs = $wpError->get_error_messages(); + foreach ($mesgs as $mesg) : + $mesg = esc_html($mesg); + FeedWordPress::diagnostic( + 'updated_feeds:errors', + "Feed Error: [${url}] update returned error: $mesg" + ); + + $hours = get_option('feedwordpress_diagnostics_persistent_errors_hours', 2); + $span = ($error['ts'] - $error['since']); + + if ($span >= ($hours * 60 * 60)) : + $since = date('r', $error['since']); + $mostRecent = date('r', $error['ts']); + FeedWordPress::diagnostic( + 'updated_feeds:errors:persistent', + "Feed Update Error: [${url}] returning errors" + ." since ${since}:<br/><code>$mesg</code>", + $url, $error['since'], $error['ts'] + ); + endif; + endforeach; + } + + function admin_emails ($id = '') { + $users = get_users_of_blog($id); + $recipients = array(); + foreach ($users as $user) : + $dude = new WP_User($user->user_id); + if ($dude->has_cap('administrator')) : + if ($dude->user_email) : + $recipients[] = $dude->user_email; + endif; + endif; + endforeach; + return $recipients; + } +} /* class FeedWordPressDiagnostic */ + +function debug_out_human_readable_bytes ($quantity) { + $quantity = (int) $quantity; + $magnitude = 'B'; + $orders = array('KB', 'MB', 'GB', 'TB'); + while (($quantity > (1024*100)) and (count($orders) > 0)) : + $quantity = floor($quantity / 1024); + $magnitude = array_shift($orders); + endwhile; + return "${quantity} ${magnitude}"; +} + +function debug_out_feedwordpress_footer () { + if (FeedWordPress::diagnostic_on('memory_usage')) : + if (function_exists('memory_get_usage')) : + FeedWordPress::diagnostic ('memory_usage', "Memory: Current usage: ".debug_out_human_readable_bytes(memory_get_usage())); + endif; + if (function_exists('memory_get_peak_usage')) : + FeedWordPress::diagnostic ('memory_usage', "Memory: Peak usage: ".debug_out_human_readable_bytes(memory_get_peak_usage())); + endif; + endif; +} /* debug_out_feedwordpress_footer() */ + +################################################################################ +## TEMPLATE API: functions to make your templates syndication-aware ############ +################################################################################ + +/** + * is_syndicated: Tests whether the current post in a Loop context, or a post + * given by ID number, was syndicated by FeedWordPress. Useful for templates + * to determine whether or not to retrieve syndication-related meta-data in + * displaying a post. + * + * @param int $id The post to check for syndicated status. Defaults to the current post in a Loop context. + * @return bool TRUE if the post's meta-data indicates it was syndicated; FALSE otherwise + */ +function is_syndicated ($id = NULL) { + return (strlen(get_syndication_feed_id($id)) > 0); +} /* function is_syndicated() */ + +function feedwordpress_display_url ($url, $before = 60, $after = 0) { + $bits = parse_url($url); + + // Strip out crufty subdomains + if (isset($bits['host'])) : + $bits['host'] = preg_replace('/^www[0-9]*\./i', '', $bits['host']); + endif; + + // Reassemble bit-by-bit with minimum of crufty elements + $url = (isset($bits['user'])?$bits['user'].'@':'') + .(isset($bits['host'])?$bits['host']:'') + .(isset($bits['path'])?$bits['path']:'') + .(isset($uri_bits['port'])?':'.$uri_bits['port']:'') + .(isset($bits['query'])?'?'.$bits['query']:''); + + if (strlen($url) > ($before+$after)) : + $url = substr($url, 0, $before).'…'.substr($url, 0 - $after, $after); + endif; + + return $url; +} /* feedwordpress_display_url () */ + +function get_syndication_source_property ($original, $id, $local, $remote = NULL) { + $ret = NULL; + if (is_null($original)) : + $original = FeedWordPress::use_aggregator_source_data(); + endif; + + if (is_null($remote)) : + $remote = $local . '_original'; + endif; + + $vals = ($original ? get_post_custom_values($remote, $id) : array()); + if (count($vals) < 1) : + $vals = get_post_custom_values($local, $id); + endif; + + if (count($vals) > 0) : + $ret = $vals[0]; + endif; + return $ret; +} /* function get_syndication_source_property () */ + +function the_syndication_source_link ($original = NULL, $id = NULL) { echo get_syndication_source_link($original, $id); } +function get_syndication_source_link ($original = NULL, $id = NULL) { + return get_syndication_source_property($original, $id, 'syndication_source_uri'); +} /* function get_syndication_source_link() */ + +function the_syndication_source ($original = NULL, $id = NULL) { echo get_syndication_source($original, $id); } +function get_syndication_source ($original = NULL, $id = NULL) { + $ret = get_syndication_source_property($original, $id, 'syndication_source'); + if (is_null($ret) or strlen(trim($ret)) == 0) : // Fall back to URL of blog + $ret = feedwordpress_display_url(get_syndication_source_link()); + endif; + return $ret; +} /* function get_syndication_source() */ + +function the_syndication_feed ($original = NULL, $id = NULL) { echo get_syndication_feed($original, $id); } +function get_syndication_feed ($original = NULL, $id = NULL) { + return get_syndication_source_property($original, $id, 'syndication_feed'); +} /* function get_syndication_feed() */ + +function the_syndication_feed_guid ($original = NULL, $id = NULL) { echo get_syndication_feed_guid($original, $id); } +function get_syndication_feed_guid ($original = NULL, $id = NULL) { + $ret = get_syndication_source_property($original, $id, 'syndication_source_id'); + if (is_null($ret) or strlen(trim($ret))==0) : // Fall back to URL of feed + $ret = get_syndication_feed(); + endif; + return $ret; +} /* function get_syndication_feed_guid () */ + +function get_syndication_feed_id ($id = NULL) { list($u) = get_post_custom_values('syndication_feed_id', $id); return $u; } +function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); } + +$feedwordpress_linkcache = array (); // only load links from database once +function get_syndication_feed_object ($id = NULL) { + global $feedwordpress_linkcache; + + $link = NULL; + + $feed_id = get_syndication_feed_id($id); + 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; + endif; + return $link; +} + +function get_feed_meta ($key, $id = NULL) { + $ret = NULL; + + $link = get_syndication_feed_object($id); + if (is_object($link) and isset($link->settings[$key])) : + $ret = $link->settings[$key]; + endif; + return $ret; +} /* get_feed_meta() */ + +function get_syndication_permalink ($id = NULL) { + list($u) = get_post_custom_values('syndication_permalink', $id); return $u; +} +function the_syndication_permalink ($id = NULL) { + echo get_syndication_permalink($id); +} + +/** + * get_local_permalink: returns a string containing the internal permalink + * for a post (whether syndicated or not) on your local WordPress installation. + * This may be useful if you want permalinks to point to the original source of + * an article for most purposes, but want to retrieve a URL for the local + * representation of the post for one or two limited purposes (for example, + * linking to a comments page on your local aggregator site). + * + * @param $id The numerical ID of the post to get the permalink for. If empty, + * defaults to the current post in a Loop context. + * @return string The URL of the local permalink for this post. + * + * @uses get_permalink() + * @global $feedwordpress_the_original_permalink + * + * @since 2010.0217 + */ +function get_local_permalink ($id = NULL) { + global $feedwordpress_the_original_permalink; + + // get permalink, and thus activate filter and force global to be filled + // with original URL. + $url = get_permalink($id); + return $feedwordpress_the_original_permalink; +} /* get_local_permalink() */ + +/** + * the_original_permalink: displays the contents of get_original_permalink() + * + * @param $id The numerical ID of the post to get the permalink for. If empty, + * defaults to the current post in a Loop context. + * + * @uses get_local_permalinks() + * @uses apply_filters + * + * @since 2010.0217 + */ +function the_local_permalink ($id = NULL) { + print apply_filters('the_permalink', get_local_permalink($id)); +} /* function the_local_permalink() */ + +################################################################################ +## FILTERS: syndication-aware handling of post data for templates and feeds #### +################################################################################ + +$feedwordpress_the_syndicated_content = NULL; +$feedwordpress_the_original_permalink = NULL; + +function feedwordpress_preserve_syndicated_content ($text) { + global $feedwordpress_the_syndicated_content; + + $globalExpose = (get_option('feedwordpress_formatting_filters') == 'yes'); + $localExpose = get_post_custom_values('_feedwordpress_formatting_filters'); + $expose = ($globalExpose or ((count($localExpose) > 0) and $localExpose[0])); + + if ( is_syndicated() and !$expose ) : + $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 feedwordpress_item_feed_data () { + // In a post context.... + if (is_syndicated()) : +?> +<source> + <title><?php print htmlspecialchars(get_syndication_source()); ?></title> + <link rel="alternate" type="text/html" href="<?php print htmlspecialchars(get_syndication_source_link()); ?>" /> + <link rel="self" href="<?php print htmlspecialchars(get_syndication_feed()); ?>" /> +<?php + $id = get_syndication_feed_guid(); + if (strlen($id) > 0) : +?> + <id><?php print htmlspecialchars($id); ?></id> +<?php + endif; + $updated = get_feed_meta('feed/updated'); + if (strlen($updated) > 0) : ?> + <updated><?php print $updated; ?></updated> +<?php + endif; +?> +</source> +<?php + endif; +} + +/** + * syndication_permalink: Allow WordPress to use the original remote URL of + * syndicated posts as their permalink. Can be turned on or off by by setting in + * Syndication => Posts & Links. Saves the old internal permalink in a global + * variable for later use. + * + * @param string $permalink The internal permalink + * @return string The new permalink. Same as the old if the post is not + * syndicated, or if FWP is set to use internal permalinks, or if the post + * was syndicated, but didn't have a proper permalink recorded. + * + * @uses SyndicatedLink::setting() + * @uses get_syndication_permalink() + * @uses get_syndication_feed_object() + * @uses url_to_postid() + * @global $id + * @global $feedwordpress_the_original_permalink + */ +function syndication_permalink ($permalink = '', $post = null, $leavename = false) { + global $id; + global $feedwordpress_the_original_permalink; + + // Save the local permalink in case we need to retrieve it later. + $feedwordpress_the_original_permalink = $permalink; + + if (is_object($post) and isset($post->ID) and !empty($post->ID)) : + // Use the post ID we've been provided with. + $postId = $post->ID; + elseif (is_string($permalink) and strlen($permalink) > 0) : + // Map this permalink to a post ID so we can get the correct + // permalink even outside of the Post Loop. Props Björn. + $postId = url_to_postid($permalink); + else : + // If the permalink string is empty but Post Loop context + // provides an id. + $postId = $id; + endif; + + $munge = false; + $link = get_syndication_feed_object($postId); + if (is_object($link)) : + $munge = ($link->setting('munge permalink', 'munge_permalink', 'yes') != 'no'); + endif; + + if ($munge): + $uri = get_syndication_permalink($postId); + $permalink = ((strlen($uri) > 0) ? $uri : $permalink); + endif; + return $permalink; +} /* function syndication_permalink () */ + +/** + * syndication_permalink_escaped: Escape XML special characters in syndicated + * permalinks when used in feed contexts and HTML display contexts. + * + * @param string $permalink + * @return string + * + * @uses is_syndicated() + * @uses FeedWordPress::munge_permalinks() + * + */ +function syndication_permalink_escaped ($permalink) { + /* FIXME: This should review link settings not just global settings */ + if (is_syndicated() and FeedWordPress::munge_permalinks()) : + // This is a foreign link; WordPress can't vouch for its not + // having any entities that need to be &-escaped. So we'll do + // it here. + $permalink = esc_html($permalink); + endif; + return $permalink; +} /* function syndication_permalink_escaped() */ + +/** + * syndication_comments_feed_link: Escape XML special characters in comments + * feed links + * + * @param string $link + * @return string + * + * @uses is_syndicated() + * @uses FeedWordPress::munge_permalinks() + */ +function syndication_comments_feed_link ($link) { + global $feedwordpress_the_original_permalink, $id; + + if (is_syndicated() and FeedWordPress::munge_permalinks()) : + // If the source post provided a comment feed URL using + // wfw:commentRss or atom:link/@rel="replies" we can make use of + // that value here. + $source = get_syndication_feed_object(); + $replacement = NULL; + if ($source->setting('munge comments feed links', 'munge_comments_feed_links', 'yes') != 'no') : + $commentFeeds = get_post_custom_values('wfw:commentRSS'); + if ( + is_array($commentFeeds) + and (count($commentFeeds) > 0) + and (strlen($commentFeeds[0]) > 0) + ) : + $replacement = $commentFeeds[0]; + + // This is a foreign link; WordPress can't vouch for its not + // having any entities that need to be &-escaped. So we'll do it + // here. + $replacement = esc_html($replacement); + endif; + endif; + + if (is_null($replacement)) : + // Q: How can we get the proper feed format, since the + // format is, stupidly, not passed to the filter? + // A: Kludge kludge kludge kludge! + $fancy_permalinks = ('' != get_option('permalink_structure')); + if ($fancy_permalinks) : + preg_match('|/feed(/([^/]+))?/?$|', $link, $ref); + + $format = (isset($ref[2]) ? $ref[2] : ''); + if (strlen($format) == 0) : $format = get_default_feed(); endif; + + $replacement = trailingslashit($feedwordpress_the_original_permalink) . 'feed'; + if ($format != get_default_feed()) : + $replacement .= '/'.$format; + endif; + $replacement = user_trailingslashit($replacement, 'single_feed'); + else : + // No fancy permalinks = no problem + // WordPress doesn't call get_permalink() to + // generate the comment feed URL, so the + // comments feed link is never munged by FWP. + endif; + endif; + + if (!is_null($replacement)) : $link = $replacement; endif; + endif; + return $link; +} /* function syndication_comments_feed_link() */ + +################################################################################ +## ADMIN MENU ADD-ONS: register Dashboard management pages ##################### +################################################################################ + +function fwp_add_pages () { + global $fwp_path; + + add_menu_page('Syndicated Sites', 'Syndication', 'manage_links', $fwp_path.'/syndication.php', NULL, WP_PLUGIN_URL.'/'.$fwp_path.'/feedwordpress-tiny.png'); + do_action('feedwordpress_admin_menu_pre_feeds'); + add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Feeds & Updates', 'Feeds & Updates', 'manage_options', $fwp_path.'/feeds-page.php'); + do_action('feedwordpress_admin_menu_pre_posts'); + add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Posts & Links', 'Posts & Links', 'manage_options', $fwp_path.'/posts-page.php'); + do_action('feedwordpress_admin_menu_pre_authors'); + add_submenu_page($fwp_path.'/syndication.php', 'Syndicated Authors', 'Authors', 'manage_options', $fwp_path.'/authors-page.php'); + do_action('feedwordpress_admin_menu_pre_categories'); + add_submenu_page($fwp_path.'/syndication.php', 'Categories'.FEEDWORDPRESS_AND_TAGS, 'Categories'.FEEDWORDPRESS_AND_TAGS, 'manage_options', $fwp_path.'/categories-page.php'); + do_action('feedwordpress_admin_menu_pre_performance'); + add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Performance', 'Performance', 'manage_options', $fwp_path.'/performance-page.php'); + do_action('feedwordpress_admin_menu_pre_diagnostics'); + add_submenu_page($fwp_path.'/syndication.php', 'FeedWordPress Diagnostics', 'Diagnostics', 'manage_options', $fwp_path.'/diagnostics-page.php'); +} /* function fwp_add_pages () */ + +function fwp_check_debug () { + // This is a horrible fucking kludge that I have to do because the + // admin notice code is triggered before the code that updates the + // setting. + if (isset($_POST['feedwordpress_debug'])) : + $feedwordpress_debug = $_POST['feedwordpress_debug']; + else : + $feedwordpress_debug = get_option('feedwordpress_debug'); + endif; + if ($feedwordpress_debug==='yes') : +?> + <div class="error"> +<p><strong>FeedWordPress warning.</strong> Debugging mode is <strong>ON</strong>. +While it remains on, FeedWordPress displays many diagnostic error messages, +warnings, and notices that are ordinarily suppressed, and also turns off all +caching of feeds. Use with caution: this setting is absolutely inappropriate +for a production server.</p> + </div> +<?php + endif; +} /* function fwp_check_debug () */ + +################################################################################ +## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform #### +## ... 'coz it's rude to send 500 pings the first time your aggregator runs #### +################################################################################ + +$fwp_held_ping = NULL; // NULL: not holding pings yet + +function fwp_hold_pings () { + global $fwp_held_ping; + if (is_null($fwp_held_ping)): + $fwp_held_ping = 0; // 0: ready to hold pings; none yet received + endif; +} + +function fwp_release_pings () { + global $fwp_held_ping; + if ($fwp_held_ping): + if (function_exists('wp_schedule_single_event')) : + wp_schedule_single_event(time(), 'do_pings'); + else : + generic_ping($fwp_held_ping); + endif; + endif; + $fwp_held_ping = NULL; // NULL: not holding pings anymore +} + +function fwp_do_pings () { + if (!is_null($fwp_held_ping) and $post_id) : // Defer until we're done updating + $fwp_held_ping = $post_id; + elseif (function_exists('do_all_pings')) : + do_all_pings(); + else : + generic_ping($fwp_held_ping); + endif; +} + +function fwp_publish_post_hook ($post_id) { + global $fwp_held_ping; + + if (!is_null($fwp_held_ping)) : // Syndicated post. Don't mark with _pingme + if ( defined('XMLRPC_REQUEST') ) + do_action('xmlrpc_publish_post', $post_id); + if ( defined('APP_REQUEST') ) + do_action('app_publish_post', $post_id); + + if ( defined('WP_IMPORTING') ) + return; + + // Defer sending out pings until we finish updating + $fwp_held_ping = $post_id; + else : + if (function_exists('_publish_post_hook')) : // WordPress 2.3 + _publish_post_hook($post_id); + endif; + endif; +} + + function feedwordpress_add_post_edit_controls () { + add_meta_box('feedwordpress-post-controls', __('Syndication'), 'feedwordpress_post_edit_controls', 'post', 'side', 'high'); + } // function FeedWordPress::postEditControls + + function feedwordpress_post_edit_controls () { + global $post; + + $frozen_values = get_post_custom_values('_syndication_freeze_updates', $post->ID); + $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]); + + if (is_syndicated($post->ID)) : + ?> + <p>This is a syndicated post, which originally appeared at + <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>. + <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p> + + <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" /> + <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong> + If set, FeedWordPress will not overwrite the changes you make manually + to this post, if the syndicated content is updated on the + feed.</label></p> + <?php + else : + ?> + <p>This post was created locally at this website.</p> + <?php + endif; + } // function feedwordpress_post_edit_controls () */ + + function feedwordpress_save_post_edit_controls ( $post_id ) { + global $post; + + if (!isset($_POST['feedwordpress_noncename']) or !wp_verify_nonce($_POST['feedwordpress_noncename'], plugin_basename(__FILE__))) : + return $post_id; + endif; + + // Verify if this is an auto save routine. If it is our form has + // not been submitted, so we don't want to do anything. + if ( defined('DOING_AUTOSAVE') and DOING_AUTOSAVE ) : + return $post_id; + endif; + + // Check permissions + if ( !current_user_can( 'edit_'.$_POST['post_type'], $post_id) ) : + return $post_id; + endif; + + // OK, we're golden. Now let's save some data. + if (isset($_POST['freeze_updates'])) : + update_post_meta($post_id, '_syndication_freeze_updates', $_POST['freeze_updates']); + $ret = $_POST['freeze_updates']; + else : + delete_post_meta($post_id, '_syndication_freeze_updates'); + $ret = NULL; + endif; + + return $ret; + } // function feedwordpress_save_edit_controls + +################################################################################ +## class FeedWordPress ######################################################### +################################################################################ + +// class FeedWordPress: handles feed updates and plugs in to the XML-RPC interface +class FeedWordPress { + var $strip_attrs = array ( + array('[a-z]+', 'style'), + array('[a-z]+', 'target'), + ); + var $uri_attrs = array ( + array('a', 'href'), + array('applet', 'codebase'), + array('area', 'href'), + array('blockquote', 'cite'), + array('body', 'background'), + array('del', 'cite'), + array('form', 'action'), + array('frame', 'longdesc'), + array('frame', 'src'), + array('iframe', 'longdesc'), + array('iframe', 'src'), + array('head', 'profile'), + array('img', 'longdesc'), + array('img', 'src'), + array('img', 'usemap'), + array('input', 'src'), + array('input', 'usemap'), + array('ins', 'cite'), + array('link', 'href'), + array('object', 'classid'), + array('object', 'codebase'), + array('object', 'data'), + array('object', 'usemap'), + array('q', 'cite'), + array('script', 'src') + ); + + var $feeds = NULL; + + # function FeedWordPress (): Contructor; retrieve a list of feeds + function FeedWordPress () { + $this->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, $crash_ts = null) { + global $wpdb; + + if (FeedWordPress::needs_upgrade()) : // Will make duplicate posts if we don't hold off + return NULL; + endif; + + if (!is_null($uri) and $uri != '*') : + $uri = trim($uri); + else : // Update all + update_option('feedwordpress_last_update_all', time()); + endif; + + do_action('feedwordpress_update', $uri); + + if (is_null($crash_ts)) : + $crash_ts = $this->crash_ts(); + endif; + + // Randomize order for load balancing purposes + $feed_set = $this->feeds; + shuffle($feed_set); + + $feed_set = apply_filters('feedwordpress_update_feeds', $feed_set, $uri); + + // Loop through and check for new posts + $delta = NULL; + foreach ($feed_set as $feed) : + if (!is_null($crash_ts) and (time() > $crash_ts)) : // Check whether we've exceeded the time limit + break; + endif; + + $pinged_that = (is_null($uri) or ($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); + $start_ts = time(); + $added = $feed->poll($crash_ts); + do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts); + + if (is_array($added)) : // Success + if (isset($added['new'])) : $delta['new'] += $added['new']; endif; + if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif; + endif; + endif; + endforeach; + + do_action('feedwordpress_update_complete', $delta); + + return $delta; + } + + function crash_ts ($default = NULL) { + $crash_dt = (int) get_option('feedwordpress_update_time_limit', 0); + if ($crash_dt > 0) : + $crash_ts = time() + $crash_dt; + else : + $crash_ts = $default; + endif; + return $crash_ts; + } + + function stale () { + if (get_option('feedwordpress_automatic_updates')) : + // Do our best to avoid possible simultaneous + // updates by getting up-to-the-minute settings. + + $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; + + else : + $ret = false; + endif; + return $ret; + } // FeedWordPress::stale() + + function init () { + $this->clear_cache_magic_url(); + $this->update_magic_url(); + } /* FeedWordPress::init() */ + + function dashboard_setup () { + // Get the stylesheet + wp_enqueue_style('feedwordpress-elements'); + + $widget_id = 'feedwordpress_dashboard'; + $widget_name = __('Syndicated Sources'); + $column = 'side'; + $priority = 'core'; + + // I would love to use wp_add_dashboard_widget() here and save + // myself some trouble. But WP 3 does not yet have any way to + // push a dashboard widget onto the side, or to give it a default + // location. + add_meta_box( + /*id=*/ $widget_id, + /*title=*/ $widget_name, + /*callback=*/ array(&$this, 'dashboard'), + /*page=*/ 'dashboard', + /*context=*/ $column, + /*priority=*/ $priority + ); + /*control_callback= array(&$this, 'dashboard_control') */ + + // This is kind of rude, I know, but the dashboard widget isn't + // worth much if users don't know that it exists, and I don't + // know of any better way to reorder the boxen. + // + // Gleefully ripped off of codex.wordpress.org/Dashboard_Widgets_API + + // Globalize the metaboxes array, this holds all the widgets for wp-admin + global $wp_meta_boxes; + + // Get the regular dashboard widgets array + // (which has our new widget already but at the end) + + $normal_dashboard = $wp_meta_boxes['dashboard'][$column][$priority]; + + // Backup and delete our new dashbaord widget from the end of the array + if (isset($normal_dashboard[$widget_id])) : + $backup = array(); + $backup[$widget_id] = $normal_dashboard[$widget_id]; + unset($normal_dashboard[$widget_id]); + + // Merge the two arrays together so our widget is at the + // beginning + $sorted_dashboard = array_merge($backup, $normal_dashboard); + + // Save the sorted array back into the original metaboxes + $wp_meta_boxes['dashboard'][$column][$priority] = $sorted_dashboard; + endif; + } /* FeedWordPress::dashboard_setup () */ + + function dashboard () { + $syndicationPage = new FeedWordPressSyndicationPage(dirname(__FILE__).'/syndication.php'); + $syndicationPage->dashboard_box($syndicationPage); + } /* FeedWordPress::dashboard () */ + + function update_magic_url () { + global $wpdb; + + // Explicit update request in the HTTP request (e.g. from a cron job) + if ($this->update_requested()) : + $this->update($this->update_requested_url()); + + if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) : + $mysqlTime = 0.0; + $byTime = array(); + foreach ($wpdb->queries as $query) : + $time = $query[1] * 1000000.0; + $mysqlTime += $query[1]; + if (!isset($byTime[$time])) : $byTime[$time] = array(); endif; + $byTime[$time][] = $query[0]. ' // STACK: ' . $query[2]; + endforeach; + krsort($byTime); + + foreach ($byTime as $time => $querySet) : + foreach ($querySet as $query) : + print "[".(sprintf('%4.4f', $time/1000.0)) . "ms] $query\n"; + endforeach; + endforeach; + echo $this->log_prefix()."$wpdb->num_queries queries. $mysqlTime seconds in MySQL. Total of "; timer_stop(1); print " seconds."; + endif; + + debug_out_feedwordpress_footer(); + + // Magic URL should return nothing but a 200 OK header packet + // when successful. + exit; + endif; + } /* FeedWordPress::magic_update_url () */ + + function clear_cache_magic_url () { + if ($this->clear_cache_requested()) : + $this->clear_cache(); + endif; + } /* FeedWordPress::clear_cache_magic_url() */ + + function clear_cache_requested () { + return ( + isset($_GET['clear_cache']) + and $_GET['clear_cache'] + ); + } /* FeedWordPress::clear_cache_requested() */ + + function update_requested () { + return ( + isset($_REQUEST['update_feedwordpress']) + and $_REQUEST['update_feedwordpress'] + ); + } // FeedWordPress::update_requested() + + function update_requested_url () { + $ret = null; + + if (($_REQUEST['update_feedwordpress']=='*') + or (preg_match('|^http://.*|i', $_REQUEST['update_feedwordpress']))) : + $ret = $_REQUEST['update_feedwordpress']; + endif; + + return $ret; + } // FeedWordPress::update_requested_url() + + function auto_update () { + if ($this->stale()) : + $this->update(); + endif; + } /* FeedWordPress::auto_update () */ + + function find_link ($uri, $field = 'link_rss') { + global $wpdb; + + $unslashed = untrailingslashit($uri); + $slashed = trailingslashit($uri); + $link_id = $wpdb->get_var($wpdb->prepare(" + SELECT link_id FROM $wpdb->links WHERE $field IN ('%s', '%s') + LIMIT 1", $unslashed, $slashed + )); + + return $link_id; + } /* FeedWordPress::find_link () */ + + function syndicate_link ($name, $uri, $rss) { + // Get the category ID# + $cat_id = FeedWordPress::link_category_id(); + + // WordPress gets cranky if there's no homepage URI + if (!is_string($uri) or strlen($uri)<1) : $uri = $rss; endif; + + // Check if this feed URL is already being syndicated. + $link_id = wp_insert_link(array( + "link_id" => FeedWordPress::find_link($rss), // insert if nothing was found; else update + "link_rss" => $rss, + "link_name" => $name, + "link_url" => $uri, + "link_category" => array($cat_id), + "link_visible" => 'Y', // reactivate if inactivated + )); + + return $link_id; + } /* function FeedWordPress::syndicate_link() */ + + /*static*/ function syndicated_status ($what, $default) { + $ret = get_option("feedwordpress_syndicated_{$what}_status"); + if (!$ret) : + $ret = $default; + endif; + return $ret; + } /* FeedWordPress::syndicated_status() */ + + function on_unfamiliar ($what = 'author') { + switch ($what) : + case 'category' : $suffix = ':category'; break; + case 'post_tag' : $suffix = ':post_tag'; break; + default: $suffix = ''; + endswitch; + + return get_option('feedwordpress_unfamiliar_'.$what, 'create'.$suffix); + } // function FeedWordPress::on_unfamiliar() + + function null_email_set () { + $base = get_option('feedwordpress_null_email_set'); + + if ($base===false) : + $ret = array('noreply@blogger.com'); // default + else : + $ret = array_map('strtolower', + array_map('trim', explode("\n", $base))); + endif; + $ret = apply_filters('syndicated_item_author_null_email_set', $ret); + return $ret; + + } /* FeedWordPress::null_email_set () */ + + function is_null_email ($email) { + $ret = in_array(strtolower(trim($email)), FeedWordPress::null_email_set()); + $ret = apply_filters('syndicated_item_author_is_null_email', $ret, $email); + return $ret; + } /* FeedWordPress::is_null_email () */ + + function use_aggregator_source_data () { + $ret = get_option('feedwordpress_use_aggregator_source_data'); + return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes')); + } + + /** + * FeedWordPress::munge_permalinks: check whether or not FeedWordPress + * should rewrite permalinks for syndicated items to reflect their + * original location. + * + * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise + */ + /*static*/ function munge_permalinks () { + return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no'); + } /* FeedWordPress::munge_permalinks() */ + + function syndicated_links ($args = array()) { + $contributors = FeedWordPress::link_category_id(); + $links = get_bookmarks(array_merge( + array("category" => $contributors), + $args + )); + 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 ID stored, search by name + if (!$cat_id) : + $cat_id = FeedWordPressCompatibility::link_category_id(DEFAULT_SYNDICATION_CATEGORY); + + if ($cat_id) : + // We found it; let's stamp it. + update_option('feedwordpress_cat_id', $cat_id); + endif; + + // If we *do* have the category ID stored, verify that it exists + else : + $cat_id = FeedWordPressCompatibility::link_category_id((int) $cat_id, 'cat_id'); + endif; + + // If we could not find an appropriate link category, + // make a new one for ourselves. + if (!$cat_id) : + $cat_id = FeedWordPressCompatibility::insert_link_category(DEFAULT_SYNDICATION_CATEGORY); + + // Stamp it + 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) : + // 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; + elseif ($fwp_db_version < 2009.0707) : + // We need to clear out any busted AJAX crap + $wpdb->query(" + DELETE FROM $wpdb->usermeta + WHERE LOCATE('feedwordpress', meta_key) + AND LOCATE('box', meta_key); + "); + + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); + elseif ($fwp_db_version < 2010.0814) : + // Change in terminology. + if (get_option('feedwordpress_unfamiliar_category', 'create')=='default') : + update_option('feedwordpress_unfamiliar_category', 'null'); + endif; + foreach (FeedWordPress::syndicated_links() as $link) : + $sub = new SyndicatedLink($link); + + $remap_uf = array( + 'default' => 'null', + 'filter' => 'null', + 'create' => 'create:category', + 'tag' => 'create:post_tag' + ); + if (isset($sub->settings['unfamiliar category'])) : + if ($sub->settings['unfamiliar category']=='filter') : + $sub->settings['match/filter'] = array('category'); + endif; + foreach ($remap_uf as $from => $to) : + if ($sub->settings['unfamiliar category']==$from) : + $sub->settings['unfamiliar category'] = $to; + endif; + endforeach; + endif; + + if (isset($sub->settings['add global categories'])) : + $sub->settings['add/category'] = $sub->settings['add global categories']; + unset($sub->settings['add global categories']); + endif; + + $sub->save_settings(/*reload=*/ true); + endforeach; + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); + else : + // No. Just brand it with the new version. + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); + 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: + // Dropping legacy upgrade code. If anyone is still + // using 0.96 and just now decided to upgrade, well, I'm + // sorry about that. You'll just have to cope with a few + // duplicate posts. + + // Mark the upgrade as successful. + update_option('feedwordpress_version', FEEDWORDPRESS_VERSION); + endswitch; + echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>"; + } /* FeedWordPress::upgrade_database() */ + + function has_guid_index () { + global $wpdb; + + $found = false; // Guilty until proven innocent. + + $results = $wpdb->get_results(" + SHOW INDEXES FROM {$wpdb->posts} + "); + if ($results) : + foreach ($results as $index) : + if (isset($index->Column_name) + and ('guid' == $index->Column_name)) : + $found = true; + endif; + endforeach; + endif; + return $found; + } /* FeedWordPress::has_guid_index () */ + + function create_guid_index () { + global $wpdb; + + $wpdb->query(" + CREATE INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}(guid) + "); + } /* FeedWordPress::create_guid_index () */ + + function remove_guid_index () { + global $wpdb; + + $wpdb->query(" + DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts} + "); + } + + /*static*/ function fetch ($url, $force_feed = true) { + $pie_class = apply_filters('feedwordpress_simplepie_class', 'SimplePie'); + $cache_class = apply_filters('feedwordpress_cache_class', 'WP_Feed_Cache'); + $file_class = apply_filters('feedwordpress_file_class', 'FeedWordPress_File'); + $parser_class = apply_filters('feedwordpress_parser_class', 'FeedWordPress_Parser'); + + $sniffer_class = apply_filters('feedwordpress_sniffer_class', 'FeedWordPress_Content_Type_Sniffer'); + + $feed = new $pie_class; + $feed->set_feed_url($url); + $feed->set_cache_class($cache_class); + //$feed->set_file_class('WP_SimplePie_File'); + $feed->set_content_type_sniffer_class($sniffer_class); + $feed->set_file_class($file_class); + $feed->set_parser_class($parser_class); + $feed->force_feed($force_feed); + $feed->set_cache_duration(FeedWordPress::cache_duration()); + $feed->init(); + $feed->handle_content_type(); + + if ($feed->error()) : + $ret = new WP_Error('simplepie-error', $feed->error()); + else : + $ret = $feed; + endif; + return $ret; + } /* FeedWordPress::fetch () */ + + function clear_cache () { + global $wpdb; + + // Just in case, clear out any old MagpieRSS cache records. + $magpies = $wpdb->query(" + DELETE FROM {$wpdb->options} + WHERE option_name LIKE 'rss_%' AND LENGTH(option_name) > 32 + "); + + // The WordPress SimplePie module stores its cached feeds as + // transient records in the options table. The data itself is + // stored in `_transient_feed_{md5 of url}` and the last-modified + // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for + // these records are stored in `_transient_timeout_feed_{md5}`. + // Since the md5 is always 32 characters in length, the + // option_name is always over 32 characters. + $simplepies = $wpdb->query(" + DELETE FROM {$wpdb->options} + WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32 + "); + $simplepies = (int) ($simplepies / 4); // Each transient has 4 rows: the data, the modified timestamp; and the timeouts for each + + return ($magpies + $simplepies); + } /* FeedWordPress::clear_cache () */ + + function cache_duration () { + $duration = NULL; + if (defined('FEEDWORDPRESS_CACHE_AGE')) : + $duration = FEEDWORDPRESS_CACHE_AGE; + endif; + return $duration; + } + function cache_lifetime ($duration) { + // Check for explicit setting of a lifetime duration + if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) : + $duration = FEEDWORDPRESS_CACHE_LIFETIME; + + // Fall back to the cache freshness duration + elseif (defined('FEEDWORDPRESS_CACHE_AGE')) : + $duration = FEEDWORDPRESS_CACHE_AGE; + endif; + + // Fall back to WordPress default + return $duration; + } /* FeedWordPress::cache_lifetime () */ + + # 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 '<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>'; + echo "\n<plaintext>"; + 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; + } + + function noncritical_bug ($varname, $var, $line) { + if (FEEDWORDPRESS_DEBUG) : // halt only when we are doing debugging + FeedWordPress::critical_bug($varname, $var, $line); + endif; + } + + function val ($v, $no_newlines = false) { + ob_start(); + var_dump($v); + $out = ob_get_contents(); ob_end_clean(); + + if ($no_newlines) : + $out = preg_replace('/\s+/', " ", $out); + endif; + return $out; + } /* FeedWordPress::val () */ + + function diagnostic_on ($level) { + $show = get_option('feedwordpress_diagnostics_show', array()); + return (in_array($level, $show)); + } /* FeedWordPress::diagnostic_on () */ + + function diagnostic ($level, $out, $persist = NULL, $since = NULL, $mostRecent = NULL) { + global $feedwordpress_admin_footer; + + $output = get_option('feedwordpress_diagnostics_output', array()); + $dlog = get_option('feedwordpress_diagnostics_log', array()); + + $diagnostic_nesting = count(explode(":", $level)); + + if (FeedWordPress::diagnostic_on($level)) : + foreach ($output as $method) : + switch ($method) : + case 'echo' : + echo "<div><pre><strong>Diag".str_repeat('====', $diagnostic_nesting-1).'|</strong> '.$out."</pre></div>"; + break; + case 'echo_in_cronjob' : + if (FeedWordPress::update_requested()) : + echo FeedWordPress::log_prefix()." ".$out."\n"; + endif; + break; + case 'admin_footer' : + $feedwordpress_admin_footer[] = $out; + break; + case 'error_log' : + error_log(FeedWordPress::log_prefix().' '.$out); + break; + case 'email' : + if (is_null($persist)) : + $sect = 'occurrent'; + $hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0); + $line = array("Time" => time(), "Message" => $out); + else : + $sect = 'persistent'; + $hook = md5($level."\n".$persist); + $line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent); + endif; + + if (!isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif; + if (!isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif; + + $dlog['mesg'][$sect][$hook] = $line; + endswitch; + endforeach; + endif; + + update_option('feedwordpress_diagnostics_log', $dlog); + } /* FeedWordPress::diagnostic () */ + + function email_diagnostic_log () { + $dlog = get_option('feedwordpress_diagnostics_log', array()); + + $ded = get_option('feedwordpress_diagnostics_email_destination', 'admins'); + + // e-mail address + if (preg_match('/^mailto:(.*)$/', $ded, $ref)) : + $recipients = array($ref[1]); + + // userid + elseif (preg_match('/^user:(.*)$/', $ded, $ref)) : + $userdata = get_userdata((int) $ref[1]); + $recipients = array($userdata->user_email); + + // admins + else : + $recipients = FeedWordPressDiagnostic::admin_emails(); + endif; + + if (isset($dlog['schedule']) and isset($dlog['schedule']['last'])) : + if (time() > ($dlog['schedule']['last'] + $dlog['schedule']['freq'])) : + // No news is good news; only send if + // there are some messages to send. + $body = NULL; + foreach ($dlog['mesg'] as $sect => $mesgs) : + if (count($mesgs) > 0) : + if (is_null($body)) : $body = ''; endif; + + $paradigm = reset($mesgs); + $body .= "<h2>".ucfirst($sect)." issues</h2>\n" + ."<table>\n" + ."<thead><tr>\n"; + foreach ($paradigm as $col => $value) : + $body .= '<th scope="col">'.$col."</th>\n"; + endforeach; + $body .= "</tr></thead>\n" + ."<tbody>\n"; + + foreach ($mesgs as $line) : + $body .= "<tr>\n"; + foreach ($line as $col => $cell) : + if (is_numeric($cell)) : + $cell = date('j-M-y, h:i a', $cell); + endif; + $class = strtolower(preg_replace('/\s+/', '-', $col)); + $body .= "<td class=\"$class\">${cell}</td>"; + endforeach; + $body .= "</tr>\n"; + endforeach; + + $body .= "</tbody>\n</table>\n\n"; + endif; + endforeach; + + if (!is_null($body)) : + $home = feedwordpress_display_url(get_bloginfo('url')); + $subj = $home . " syndication issues for ".date('j-M-y', time()); + $agent = 'FeedWordPress '.FEEDWORDPRESS_VERSION; + $body = <<<EOMAIL +<html> +<head> +<title>$subj</title> +<style type="text/css"> + body { background-color: white; color: black; } + table { width: 100%; border: 1px solid black; } + table thead tr th { background-color: #ff7700; color: white; border-bottom: 1px solid black; } + table thead tr { border-bottom: 1px solid black; } + table tr { vertical-align: top; } + table .since { width: 20%; } + table .time { width: 20%; } + table .most-recently { width: 20%; } + table .message { width: auto; } +</style> +</head> +<body> +<h1>Syndication Issues encountered by $agent on $home</h1> +$body +</body> +</html> + +EOMAIL; + foreach ($recipients as $email) : + add_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail')); + wp_mail($email, $subj, $body); + remove_filter('wp_mail_content_type', array('FeedWordPress', 'allow_html_mail')); + endforeach; + endif; + + // Clear the logs + $dlog['mesg']['persistent'] = array(); + $dlog['mesg']['occurrent'] = array(); + + // Set schedule for next update + $dlog['schedule']['last'] = time(); + endif; + else : + $dlog['schedule'] = array( + 'freq' => 24 /*hr*/ * 60 /*min*/ * 60 /*s*/, + 'last' => time(), + ); + endif; + + update_option('feedwordpress_diagnostics_log', $dlog); + } /* FeedWordPress::email_diagnostic_log () */ + + function allow_html_mail () { + return 'text/html'; + } /* FeedWordPress::allow_html_mail () */ + + function admin_footer () { + global $feedwordpress_admin_footer; + foreach ($feedwordpress_admin_footer as $line) : + echo '<div><pre>'.$line.'</pre></div>'; + endforeach; + } /* FeedWordPress::admin_footer () */ + + function log_prefix ($date = false) { + $home = get_bloginfo('url'); + $prefix = '['.feedwordpress_display_url($home).'] [feedwordpress] '; + if ($date) : + $prefix = "[".date('Y-m-d H:i:s')."]".$prefix; + endif; + return $prefix; + } /* FeedWordPress::log_prefix () */ + + function param ($key, $type = 'REQUEST', $default = NULL) { + $where = '_'.strtoupper($type); + $ret = $default; + if (isset($GLOBALS[$where]) and is_array($GLOBALS[$where])) : + if (isset($GLOBALS[$where][$key])) : + $ret = $GLOBALS[$where][$key]; + if (get_magic_quotes_gpc()) : + $ret = stripslashes($ret); + endif; + endif; + endif; + return $ret; + } + function post ($key, $default = NULL) { + return FeedWordPress::param($key, 'POST'); + } +} // class FeedWordPress + +class FeedWordPress_File extends WP_SimplePie_File { + function FeedWordPress_File ($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) { + WP_SimplePie_File::WP_SimplePie_File($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + + // SimplePie makes a strongly typed check against integers with + // this, but WordPress puts a string in. Which causes caching + // to break and fall on its ass when SimplePie is getting a 304, + // but doesn't realize it because this member is "304" instead. + $this->status_code = (int) $this->status_code; + } +} /* class FeedWordPress_File () */ + +class FeedWordPress_Parser extends SimplePie_Parser { + function parse (&$data, $encoding) { + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') + { + $this->encoding = 'UTF-8'; + } + else + { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) + { + $declaration =& new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5)); + if ($declaration->parse()) + { + $data = substr($data, $pos + 2); + $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + } + else + { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } + + $return = true; + + static $xml_is_sane = null; + if ($xml_is_sane === null) + { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); + xml_parser_free($parser_check); + $xml_is_sane = isset($values[0]['value']); + } + + // Create the parser + if ($xml_is_sane) + { + $xml = xml_parser_create_ns($this->encoding, $this->separator); + xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xml, $this); + xml_set_character_data_handler($xml, 'cdata'); + xml_set_element_handler($xml, 'tag_open', 'tag_close'); + xml_set_start_namespace_decl_handler($xml, 'start_xmlns'); + + // Parse! + if (!xml_parse($xml, $data, true)) + { + $this->error_code = xml_get_error_code($xml); + $this->error_string = xml_error_string($this->error_code); + $return = false; + } + + $this->current_line = xml_get_current_line_number($xml); + $this->current_column = xml_get_current_column_number($xml); + $this->current_byte = xml_get_current_byte_index($xml); + xml_parser_free($xml); + + return $return; + } + else + { + libxml_clear_errors(); + $xml =& new XMLReader(); + $xml->xml($data); + while (@$xml->read()) + { + switch ($xml->nodeType) + { + + case constant('XMLReader::END_ELEMENT'): + if ($xml->namespaceURI !== '') + { + $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; + } + else + { + $tagName = $xml->localName; + } + $this->tag_close(null, $tagName); + break; + case constant('XMLReader::ELEMENT'): + $empty = $xml->isEmptyElement; + if ($xml->namespaceURI !== '') + { + $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; + } + else + { + $tagName = $xml->localName; + } + $attributes = array(); + while ($xml->moveToNextAttribute()) + { + if ($xml->namespaceURI !== '') + { + $attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; + } + else + { + $attrName = $xml->localName; + } + $attributes[$attrName] = $xml->value; + } + + foreach ($attributes as $attr => $value) : + list($ns, $local) = $this->split_ns($attr); + if ($ns=='http://www.w3.org/2000/xmlns/') : + if ('xmlns' == $local) : $local = false; endif; + $this->start_xmlns(null, $local, $value); + endif; + endforeach; + + $this->tag_open(null, $tagName, $attributes); + if ($empty) + { + $this->tag_close(null, $tagName); + } + break; + case constant('XMLReader::TEXT'): + + case constant('XMLReader::CDATA'): + $this->cdata(null, $xml->value); + break; + } + } + if ($error = libxml_get_last_error()) + { + $this->error_code = $error->code; + $this->error_string = $error->message; + $this->current_line = $error->line; + $this->current_column = $error->column; + return false; + } + else + { + return true; + } + } + } /* FeedWordPress_Parser::parse() */ + + var $xmlns_stack = array(); + var $xmlns_current = array(); + function tag_open ($parser, $tag, $attributes) { + $ret = parent::tag_open($parser, $tag, $attributes); + if ($this->current_xhtml_construct < 0) : + $this->data['xmlns'] = $this->xmlns_current; + $this->xmlns_stack[] = $this->xmlns_current; + endif; + return $ret; + } + + function tag_close($parser, $tag) { + if ($this->current_xhtml_construct < 0) : + $this->xmlns_current = array_pop($this->xmlns_stack); + endif; + $ret = parent::tag_close($parser, $tag); + return $ret; + } + + function start_xmlns ($parser, $prefix, $uri) { + if (!$prefix) : + $prefix = ''; + endif; + if ($this->current_xhtml_construct < 0) : + $this->xmlns_current[$prefix] = $uri; + endif; + return true; + } /* FeedWordPress_Parser::start_xmlns() */ +} + +$feedwordpress_admin_footer = array(); + +################################################################################ +## XML-RPC HOOKS: accept XML-RPC update pings from Contributors ################ +################################################################################ + +class FeedWordPressRPC { + function FeedWordPressRPC () { + add_filter('xmlrpc_methods', array(&$this, 'xmlrpc_methods')); + } + + function xmlrpc_methods ($args = array()) { + $args['weblogUpdates.ping'] = array(&$this, 'ping'); + $args['feedwordpress.subscribe'] = array(&$this, 'subscribe'); + $args['feedwordpress.deactivate'] = array(&$this, 'deactivate'); + $args['feedwordpress.delete'] = array(&$this, 'delete'); + $args['feedwordpress.nuke'] = array(&$this, 'nuke'); + return $args; + } + + function ping ($args) { + global $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; + } + + function validate (&$args) { + global $wp_xmlrpc_server; + + // First two params are username/password + $username = $wp_xmlrpc_server->escape(array_shift($args)); + $password = $wp_xmlrpc_server->escape(array_shift($args)); + + $ret = array(); + if ( !$user = $wp_xmlrpc_server->login($username, $password) ) : + $ret = $wp_xmlrpc_server->error; + elseif (!current_user_can('manage_links')) : + $ret = new IXR_Error(401, 'Sorry, you cannot change the subscription list.'); + endif; + return $ret; + } + + function subscribe ($args) { + $ret = $this->validate($args); + if (is_array($ret)) : // Success + // The remaining params are feed URLs + foreach ($args as $arg) : + $finder = new FeedFinder($arg, /*verify=*/ false, /*fallbacks=*/ 1); + $feeds = array_values(array_unique($finder->find())); + + if (count($feeds) > 0) : + $link_id = FeedWordPress::syndicate_link( + /*title=*/ feedwordpress_display_url($feeds[0]), + /*homepage=*/ $feeds[0], + /*feed=*/ $feeds[0] + ); + $ret[] = array( + 'added', + $feeds[0], + $arg, + ); + else : + $ret[] = array( + 'error', + $arg + ); + endif; + endforeach; + endif; + return $ret; + } /* FeedWordPressRPC::subscribe () */ + + function unsubscribe ($method, $args) { + $ret = $this->validate($args); + if (is_array($ret)) : // Success + // The remaining params are feed URLs + foreach ($args as $arg) : + $link_id = FeedWordPress::find_link($arg); + + if (!$link_id) : + $link_id = FeedWordPress::find_link($arg, 'link_url'); + endif; + + if ($link_id) : + $link = new SyndicatedLink($link_id); + + $link->{$method}(); + $ret[] = array( + 'deactivated', + $arg, + ); + else : + $ret[] = array( + 'error', + $arg, + ); + endif; + endforeach; + endif; + return $ret; + } /* FeedWordPress::unsubscribe () */ + + function deactivate ($args) { + return $this->unsubscribe('deactivate', $args); + } /* FeedWordPressRPC::deactivate () */ + + function delete ($args) { + return $this->unsubscribe('delete', $args); + } /* FeedWordPressRPC::delete () */ + + function nuke ($args) { + return $this->unsubscribe('nuke', $args); + } /* FeedWordPressRPC::nuke () */ +} /* class FeedWordPressRPC */ + +// 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; +} + diff --git a/wp-content/plugins/feedwordpress/feedwordpress.png b/wp-content/plugins/feedwordpress/feedwordpress.png new file mode 100644 index 0000000000000000000000000000000000000000..ec7844560e8f2cec5d6fbbb529eb94a6333b63a7 Binary files /dev/null and b/wp-content/plugins/feedwordpress/feedwordpress.png differ diff --git a/wp-content/plugins/feedwordpress/feedwordpresshtml.class.php b/wp-content/plugins/feedwordpress/feedwordpresshtml.class.php new file mode 100644 index 0000000000000000000000000000000000000000..1cefd5a5f5f8ec8566257ee2910584daa94d3312 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpresshtml.class.php @@ -0,0 +1,86 @@ +<?php +class FeedWordPressHTML { + function attributeRegex ($tag, $attr) { + return ":( + (<($tag)\s+[^>]*) + ($attr)= + ) + ( + \s*(\"|') + (((?!\\6).)*) + \\6([^>]*>) + | + \s*(((?!/>)[^\s>])*) + ([^>]*>) + ) + :ix"; + } /* function FeedWordPressHTML::attributeRegex () */ + + function attributeMatch ($matches) { + for ($i = 0; $i <= 12; $i++) : + if (!isset($matches[$i])) : + $matches[$i] = ''; + endif; + endfor; + + $suffix = $matches[12].$matches[9]; + $value = $matches[10].$matches[7]; + + return array( + "tag" => $matches[3], + "attribute" => $matches[4], + "value" => $value, + "quote" => $matches[6], + "prefix" => $matches[1].$matches[6], + "suffix" => $matches[6].$suffix, + "before_attribute" => $matches[2], + "after_attribute" => $suffix, + ); + } /* function FeedWordPressHTML::attributeMatch () */ + + function tagWithAttributeRegex ($tag, $attr, $value) { + return ":( + (<($tag)\s+[^>]*) + ($attr)= + ) + ( + \s*(\"|') + ((((?!\\6).)*\s)*($value)(\s((?!\\6).)*)*) + \\6([^>]*>) + | + \s*((?!/>)($value)) + ([^>]*>) + ) + (((?!</($tag)>).)*) + (</($tag)>) + :ix"; + } /* FeedWordPressHTML::tagWithAttributeRegex () */ + + function tagWithAttributeMatch ($matches) { + for ($i = 0; $i <= 21; $i++) : + if (!isset($matches[$i])) : + $matches[$i] = ''; + endif; + endfor; + + $suffix = $matches[16].$matches[13]; + $value = $matches[14].$matches[7]; + + return array( + "full" => $matches[0], + "tag" => $matches[3], + "attribute" => $matches[4], + "value" => $value, + "quote" => $matches[6], + "prefix" => $matches[1].$matches[6], + "suffix" => $matches[6].$suffix, + "before_attribute" => $matches[2], + "after_attribute" => $suffix, + "open_tag" => $matches[1].$matches[6].$value.$matches[6].$suffix, + "content" => $matches[17], + "close_tag" => $matches[20], + ); + + } /* FeedWordPressHTML::tagWithAttributeMatch () */ +} /* class FeedWordPressHTML */ + diff --git a/wp-content/plugins/feedwordpress/feedwordpresssyndicationpage.class.php b/wp-content/plugins/feedwordpress/feedwordpresssyndicationpage.class.php new file mode 100644 index 0000000000000000000000000000000000000000..2d22d1ecdc32a4b11e894ae3da7b221817104df6 --- /dev/null +++ b/wp-content/plugins/feedwordpress/feedwordpresssyndicationpage.class.php @@ -0,0 +1,1152 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); +require_once(dirname(__FILE__) . '/feedfinder.class.php'); + +################################################################################ +## ADMIN MENU ADD-ONS: implement Dashboard management pages #################### +################################################################################ + +define('FWP_UPDATE_CHECKED', 'Update Checked'); +define('FWP_UNSUB_CHECKED', 'Unsubscribe'); +define('FWP_DELETE_CHECKED', 'Delete'); +define('FWP_RESUB_CHECKED', 'Re-subscribe'); +define('FWP_SYNDICATE_NEW', 'Add →'); +define('FWP_UNSUB_FULL', 'Unsubscribe from selected feeds →'); +define('FWP_CANCEL_BUTTON', '× Cancel'); +define('FWP_CHECK_FOR_UPDATES', 'Update'); + +class FeedWordPressSyndicationPage extends FeedWordPressAdminPage { + function FeedWordPressSyndicationPage ($filename = NULL) { + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpresssyndication', /*link=*/ NULL); + + // No over-arching form element + $this->dispatch = NULL; + if (is_null($filename)) : + $this->filename = __FILE__; + else : + $this->filename = $filename; + endif; + } /* FeedWordPressSyndicationPage constructor */ + + function has_link () { return false; } + + var $_sources = NULL; + + function sources ($visibility = 'Y') { + if (is_null($this->_sources)) : + $links = FeedWordPress::syndicated_links(array("hide_invisible" => false)); + $this->_sources = array("Y" => array(), "N" => array()); + foreach ($links as $link) : + $this->_sources[$link->link_visible][] = $link; + endforeach; + endif; + $ret = ( + array_key_exists($visibility, $this->_sources) + ? $this->_sources[$visibility] + : $this->_sources + ); + return $ret; + } /* FeedWordPressSyndicationPage::sources() */ + + function visibility_toggle () { + $sources = $this->sources('*'); + + $defaultVisibility = 'Y'; + if ((count($this->sources('N')) > 0) + and (count($this->sources('Y'))==0)) : + $defaultVisibility = 'N'; + endif; + + $visibility = ( + isset($_REQUEST['visibility']) + ? $_REQUEST['visibility'] + : $defaultVisibility + ); + + return $visibility; + } /* FeedWordPressSyndicationPage::visibility_toggle() */ + + function show_inactive () { + return ($this->visibility_toggle() == 'N'); + } + + function updates_requested () { + global $wpdb; + + if (isset($_POST['update']) or isset($_POST['action']) or isset($_POST['update_uri'])) : + // Only do things with side-effects for HTTP POST or command line + $fwp_update_invoke = 'post'; + else : + $fwp_update_invoke = 'get'; + endif; + + $update_set = array(); + if ($fwp_update_invoke != 'get') : + if (is_array(FeedWordPress::post('link_ids')) + and (FeedWordPress::post('action')==FWP_UPDATE_CHECKED)) : + $targets = $wpdb->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 (!is_null(FeedWordPress::post('update_uri'))) : + $targets = FeedWordPress::post('update_uri'); + if (!is_array($targets)) : + $targets = array($targets); + endif; + + $first = each($targets); + if (!is_numeric($first['key'])) : // URLs in keys + $targets = array_keys($targets); + endif; + $update_set = $targets; + endif; + endif; + return $update_set; + } + + function accept_multiadd () { + global $fwp_post; + + if (isset($fwp_post['cancel']) and $fwp_post['cancel']==__(FWP_CANCEL_BUTTON)) : + return true; // Continue .... + endif; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_feeds', /*capability=*/ 'manage_links'); + + $in = (isset($fwp_post['multilookup']) ? $fwp_post['multilookup'] : '') + .(isset($fwp_post['opml_lookup']) ? $fwp_post['opml_lookup'] : ''); + if (isset($fwp_post['confirm']) and $fwp_post['confirm']=='multiadd') : + $chex = $fwp_post['multilookup']; + $added = array(); $errors = array(); + foreach ($chex as $feed) : + if (isset($feed['add']) and $feed['add']=='yes') : + // Then, add in the URL. + $link_id = FeedWordPress::syndicate_link( + $feed['title'], + $feed['link'], + $feed['url'] + ); + if ($link_id and !is_wp_error($link_id)): + $added[] = $link_id; + else : + $errors[] = array($feed['url'], $link_id); + endif; + endif; + endforeach; + + print "<div class='updated'>\n"; + print "<p>Added ".count($added)." new syndicated sources.</p>"; + if (count($errors) > 0) : + print "<p>FeedWordPress encountered errors trying to add the following sources:</p> + <ul>\n"; + foreach ($errors as $err) : + $url = $err[0]; + $short = esc_html(feedwordpress_display_url($url)); + $url = esc_html($url); + $wp = $err[1]; + if (is_wp_error($err[1])) : + $error = $err[1]; + $mesg = " (<code>".$error->get_error_messages()."</code>)"; + else : + $mesg = ''; + endif; + print "<li><a href='$url'>$short</a>$mesg</li>\n"; + endforeach; + print "</ul>\n"; + endif; + print "</div>\n"; + + elseif (is_array($in) or strlen($in) > 0) : + add_meta_box( + /*id=*/ 'feedwordpress_multiadd_box', + /*title=*/ __('Add Feeds'), + /*callback=*/ array($this, 'multiadd_box'), + /*page=*/ $this->meta_box_context(), + /*context =*/ $this->meta_box_context() + ); + endif; + return true; // Continue... + } + + function display_multiadd_line ($line) { + $short_feed = esc_html(feedwordpress_display_url($line['feed'])); + $feed = esc_html($line['feed']); + $link = esc_html($line['link']); + $title = esc_html($line['title']); + $checked = $line['checked']; + $i = esc_html($line['i']); + + print "<li><label><input type='checkbox' name='multilookup[$i][add]' value='yes' $checked /> + $title</label> · <a href='$feed'>$short_feed</a>"; + + if (isset($line['extra'])) : + print " · ".esc_html($line['extra']); + endif; + + print "<input type='hidden' name='multilookup[$i][url]' value='$feed' /> + <input type='hidden' name='multilookup[$i][link]' value='$link' /> + <input type='hidden' name='multilookup[$i][title]' value='$title' /> + </li>\n"; + + flush(); + } + + function multiadd_box ($page, $box = NULL) { + global $fwp_post; + + $localData = NULL; + + if (isset($_FILES['opml_upload']['name']) and + (strlen($_FILES['opml_upload']['name']) > 0)) : + $in = 'tag:localhost'; + $localData = file_get_contents($_FILES['opml_upload']['tmp_name']); + $merge_all = true; + elseif (isset($fwp_post['multilookup'])) : + $in = $fwp_post['multilookup']; + $merge_all = false; + elseif (isset($fwp_post['opml_lookup'])) : + $in = $fwp_post['opml_lookup']; + $merge_all = true; + else : + $in = ''; + $merge_all = false; + endif; + + if (strlen($in) > 0) : + $lines = preg_split( + "/\s+/", + $in, + /*no limit soldier*/ -1, + PREG_SPLIT_NO_EMPTY + ); + + $i = 0; + ?> + <form id="multiadd-form" action="<?php print $this->form_action(); ?>" method="post"> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?> + <input type="hidden" name="multiadd" value="<?php print FWP_SYNDICATE_NEW; ?>" /> + <input type="hidden" name="confirm" value="multiadd" /> + + <input type="hidden" name="multiadd" value="<?php print FWP_SYNDICATE_NEW; ?>" /> + <input type="hidden" name="confirm" value="multiadd" /></div> + + <div id="multiadd-status"> + <p><img src="<?php print esc_url ( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" /> + Looking up feed information...</p> + </div> + + <div id="multiadd-buttons"> + <input type="submit" class="button" name="cancel" value="<?php _e(FWP_CANCEL_BUTTON); ?>" /> + <input type="submit" class="button-primary" value="<?php print _e('Subscribe to selected sources →'); ?>" /> + </div> + + <p><?php _e('Here are the feeds that FeedWordPress has discovered from the addresses that you provided. To opt out of a subscription, unmark the checkbox next to the feed.'); ?></p> + + <?php + print "<ul id=\"multiadd-list\">\n"; flush(); + foreach ($lines as $line) : + $url = trim($line); + if (strlen($url) > 0) : + // First, use FeedFinder to check the URL. + if (is_null($localData)) : + $finder = new FeedFinder($url, /*verify=*/ false, /*fallbacks=*/ 1); + else : + $finder = new FeedFinder('tag:localhost', /*verify=*/ false, /*fallbacks=*/ 1); + $finder->upload_data($localData); + endif; + + $feeds = array_values( + array_unique( + $finder->find() + ) + ); + + $found = false; + if (count($feeds) > 0) : + foreach ($feeds as $feed) : + $pie = FeedWordPress::fetch($feed); + if (!is_wp_error($pie)) : + $found = true; + + $short_feed = esc_html(feedwordpress_display_url($feed)); + $feed = esc_html($feed); + $title = esc_html($pie->get_title()); + $checked = ' checked="checked"'; + $link = esc_html($pie->get_link()); + + $this->display_multiadd_line(array( + 'feed' => $feed, + 'title' => $pie->get_title(), + 'link' => $pie->get_link(), + 'checked' => ' checked="checked"', + 'i' => $i, + )); + + $i++; // Increment field counter + + if (!$merge_all) : // Break out after first find + break; + endif; + endif; + endforeach; + endif; + + if (!$found) : + $this->display_multiadd_line(array( + 'feed' => $url, + 'title' => feedwordpress_display_url($url), + 'extra' => " [FeedWordPress couldn't detect any feeds for this URL.]", + 'link' => NULL, + 'checked' => '', + 'i' => $i, + )); + $i++; // Increment field counter + endif; + endif; + endforeach; + print "</ul>\n"; + ?> + </form> + + <script type="text/javascript"> + jQuery(document).ready( function () { + // Hide it now that we're done. + jQuery('#multiadd-status').fadeOut(500 /*ms*/); + } ); + </script> + <?php + endif; + + $this->_sources = NULL; // Force reload of sources list + return true; // Continue + } + + function display () { + global $wpdb; + global $fwp_post; + + if (FeedWordPress::needs_upgrade()) : + fwp_upgrade_page(); + return; + endif; + + ?> + <?php + $cont = true; + $dispatcher = array( + "feedfinder" => 'fwp_feedfinder_page', + FWP_SYNDICATE_NEW => 'fwp_feedfinder_page', + "switchfeed" => 'fwp_switchfeed_page', + FWP_UNSUB_CHECKED => 'multidelete_page', + FWP_DELETE_CHECKED => 'multidelete_page', + 'Unsubscribe' => 'multidelete_page', + FWP_RESUB_CHECKED => 'multiundelete_page', + ); + if (isset($_REQUEST['action']) and isset($dispatcher[$_REQUEST['action']])) : + $method = $dispatcher[$_REQUEST['action']]; + if (method_exists($this, $method)) : + $cont = $this->{$method}(); + else : + $cont = call_user_func($method); + endif; + elseif (isset($fwp_post['multiadd']) and $fwp_post['multiadd']==FWP_SYNDICATE_NEW) : + $cont = $this->accept_multiadd($fwp_post); + endif; + + if ($cont): + $links = $this->sources('Y'); + $potential_updates = (!$this->show_inactive() and (count($this->sources('Y')) > 0)); + + $this->open_sheet('Syndicated Sites'); + ?> + <div id="post-body"> + <?php + if ($potential_updates + or (count($this->updates_requested()) > 0)) : + add_meta_box( + /*id=*/ 'feedwordpress_update_box', + /*title=*/ __('Update feeds now'), + /*callback=*/ 'fwp_syndication_manage_page_update_box', + /*page=*/ $this->meta_box_context(), + /*context =*/ $this->meta_box_context() + ); + endif; + add_meta_box( + /*id=*/ 'feedwordpress_feeds_box', + /*title=*/ __('Syndicated sources'), + /*callback=*/ array(&$this, 'syndicated_sources_box'), + /*page=*/ $this->meta_box_context(), + /*context =*/ $this->meta_box_context() + ); + + do_action('feedwordpress_admin_page_syndication_meta_boxes', $this); + ?> + <div class="metabox-holder"> + <?php + fwp_do_meta_boxes($this->meta_box_context(), $this->meta_box_context(), $this); + ?> + </div> <!-- class="metabox-holder" --> + </div> <!-- id="post-body" --> + + <?php $this->close_sheet(/*dispatch=*/ NULL); ?> + + <div style="display: none"> + <div id="tags-input"></div> <!-- avoid JS error from WP 2.5 bug --> + </div> + <?php + endif; + } /* FeedWordPressSyndicationPage::display () */ + + function dashboard_box ($page, $box = NULL) { + global $fwp_path; + + $links = FeedWordPress::syndicated_links(array("hide_invisible" => false)); + $sources = $this->sources('*'); + + $visibility = 'Y'; + $hrefPrefix = $this->form_action(); + $activeHref = $hrefPrefix.'&visibility=Y'; + $inactiveHref = $hrefPrefix.'&visibility=N'; + + $lastUpdate = get_option('feedwordpress_last_update_all', NULL); + $automatic_updates = get_option('feedwordpress_automatic_updates', NULL); + + if ('init'==$automatic_updates) : + $update_setting = 'automatically before page loads'; + elseif ('shutdown'==$automatic_updates) : + $update_setting = 'automatically after page loads'; + else : + $update_setting = 'using a cron job or manual check-ins'; + endif; + + // Hey ho, let's go... + ?> + <p class="info" style="margin-bottom: 0px; border-bottom: 1px dotted black;">Managed by <a href="http://feedwordpress.radgeek.com/">FeedWordPress</a> + <?php print FEEDWORDPRESS_VERSION; ?>.</p> + <?php if (FEEDWORDPRESS_BLEG) : ?> + <div style="float: left; background: white; margin-top: 5px; margin-right: 5px;"><a href="<?php print $this->form_action(); ?>"><img src="<?php print esc_html(WP_PLUGIN_URL.'/'.$GLOBALS['fwp_path'].'/feedwordpress.png'); ?>" alt="" /></a></div> + <p class="info" style="margin-top: 0px; font-style: italic; font-size: 75%; color: #666;">If you find this tool useful for your daily work, you can + contribute to ongoing support and development with + <a href="http://feedwordpress.radgeek.com/donate/">a modest donation</a>.</p> + <br style="clear: left;" /> + <?php endif; ?> + + <div class="feedwordpress-actions"> + <h4>Updates</h4> + <ul class="options"> + <li><strong>Scheduled:</strong> <?php print $update_setting; ?> + (<a href="<?php print $this->form_action('feeds-page.php'); ?>">change setting</a>)</li> + + <li><?php if (!is_null($lastUpdate)) : ?> + <strong>Last checked:</strong> <?php print fwp_time_elapsed($lastUpdate); ?> + <?php else : ?> + <strong>Last checked:</strong> none yet + <?php endif; ?> </li> + + </ul> + </div> + + <div class="feedwordpress-stats"> + <h4>Subscriptions</h4> + <table> + <tbody> + <tr class="first"> + <td class="first b b-active"><a href="<?php print esc_html($activeHref); ?>"><?php print count($sources['Y']); ?></a></td> + <td class="t active"><a href="<?php print esc_html($activeHref); ?>">Active</a></td> + </tr> + + <tr> + <td class="b b-inactive"><a href="<?php print esc_html($inactiveHref); ?>"><?php print count($sources['N']); ?></a></td> + <td class="t inactive"><a href="<?php print esc_html($inactiveHref); ?>">Inactive</a></td> + </tr> + </table> + </div> + + <div id="add-single-uri"> + <?php if (count($sources['Y']) > 0) : ?> + <form id="check-for-updates" action="<?php print $this->form_action(); ?>" method="POST"> + <div class="container"><input type="submit" class="button-primary" name"update" value="<?php print FWP_CHECK_FOR_UPDATES; ?>" /> + <?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?> + <input type="hidden" name="update_uri" value="*" /></div> + </form> + <?php endif; ?> + + <form id="syndicated-links" action="<?php print $hrefPrefix; ?>&visibility=<?php print $visibility; ?>" method="post"> + <div class="container"><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?> + <label for="add-uri">Add: + <input type="text" name="lookup" id="add-uri" placeholder="Source URL" + value="Source URL" style="width: 55%;" /></label> + + <?php FeedWordPressSettingsUI::magic_input_tip_js('add-uri'); ?> + + <input style="vertical-align: middle;" type="image" src="<?php print WP_PLUGIN_URL.'/'.$fwp_path; ?>/plus.png" alt="<?php print FWP_SYNDICATE_NEW; ?>" name="action" value="<?php print FWP_SYNDICATE_NEW; ?>" /></div> + </form> + </div> <!-- id="add-single-uri" --> + + <br style="clear: both;" /> + + <?php + } /* FeedWordPressSyndicationPage::dashboard_box () */ + + function syndicated_sources_box ($page, $box = NULL) { + global $fwp_path; + + $links = FeedWordPress::syndicated_links(array("hide_invisible" => false)); + $sources = $this->sources('*'); + + $visibility = $this->visibility_toggle(); + $showInactive = $this->show_inactive(); + + $hrefPrefix = $this->form_action(); + ?> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?></div> + <div class="tablenav"> + + <div id="add-multiple-uri" class="hide-if-js"> + <form action="<?php print $hrefPrefix; ?>&visibility=<?php print $visibility; ?>" method="post"> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?></div> + <h4>Add Multiple Sources</h4> + <div>Enter one feed or website URL per line. If a URL links to a website which provides multiple feeds, FeedWordPress will use the first one listed.</div> + <div><textarea name="multilookup" rows="8" cols="60" + style="vertical-align: top"></textarea></div> + <div style="border-top: 1px dotted black; padding-top: 10px"> + <div class="alignright"><input type="submit" class="button-primary" name="multiadd" value="<?php print FWP_SYNDICATE_NEW; ?>" /></div> + <div class="alignleft"><input type="button" class="button-secondary" name="action" value="<?php print FWP_CANCEL_BUTTON; ?>" id="turn-off-multiple-sources" /></div> + </div> + </form> + </div> <!-- id="add-multiple-uri" --> + + <div id="upload-opml" style="float: right" class="hide-if-js"> + <h4>Import source list</h4> + <p>You can import a list of sources in OPML format, either by providing + a URL for the OPML document, or by uploading a copy from your + computer.</p> + + <form enctype="multipart/form-data" action="<?php print $hrefPrefix; ?>&visibility=<?php print $visibility; ?>" method="post"> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?><input type="hidden" name="MAX_FILE_SIZE" value="100000" /></div> + <div style="clear: both"><label for="opml-lookup" style="float: left; width: 8.0em; margin-top: 5px;">From URL:</label> <input type="text" id="opml-lookup" name="opml_lookup" value="OPML document" /></div> + <div style="clear: both"><label for="opml-upload" style="float: left; width: 8.0em; margin-top: 5px;">From file:</label> <input type="file" id="opml-upload" name="opml_upload" /></div> + + <div style="border-top: 1px dotted black; padding-top: 10px"> + <div class="alignright"><input type="submit" class="button-primary" name="action" value="<?php print FWP_SYNDICATE_NEW; ?>" /></div> + <div class="alignleft"><input type="button" class="button-secondary" name="action" value="<?php print FWP_CANCEL_BUTTON; ?>" id="turn-off-opml-upload" /></div> + </div> + </form> + </div> <!-- id="upload-opml" --> + + <div id="add-single-uri" class="alignright"> + <form id="syndicated-links" action="<?php print $hrefPrefix; ?>&visibility=<?php print $visibility; ?>" method="post"> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?></div> + <ul class="subsubsub"> + <li><label for="add-uri">New source:</label> + <input type="text" name="lookup" id="add-uri" value="Website or feed URI" /> + + <?php FeedWordPressSettingsUI::magic_input_tip_js('add-uri'); FeedWordPressSettingsUI::magic_input_tip_js('opml-lookup'); ?> + + <input type="hidden" name="action" value="feedfinder" /> + <input type="submit" class="button-secondary" name="action" value="<?php print FWP_SYNDICATE_NEW; ?>" /> + <div style="text-align: right; margin-right: 2.0em"><a id="turn-on-multiple-sources" href="#add-multiple-uri"><img style="vertical-align: middle" src="<?php print WP_PLUGIN_URL.'/'.$fwp_path; ?>/down.png" alt="" /> add multiple</a> + <span class="screen-reader-text"> or </span> + <a id="turn-on-opml-upload" href="#upload-opml"><img src="<?php print WP_PLUGIN_URL.'/'.$fwp_path; ?>/plus.png" alt="" style="vertical-align: middle" /> import source list</a></div> + </li> + </ul> + </form> + </div> <!-- class="alignright" --> + + <div class="alignleft"> + <?php + if (count($sources[$visibility]) > 0) : + fwp_syndication_manage_page_links_subsubsub($sources, $showInactive); + endif; + ?> + </div> <!-- class="alignleft" --> + + </div> <!-- class="tablenav" --> + + <form id="syndicated-links" action="<?php print $hrefPrefix; ?>&visibility=<?php print $visibility; ?>" method="post"> + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?></div> + + <?php if ($showInactive) : ?> + <div style="clear: right" class="alignright"> + <p style="font-size: smaller; font-style: italic">FeedWordPress used to syndicate + posts from these sources, but you have unsubscribed from them.</p> + </div> + <?php + endif; + ?> + + <?php + if (count($sources[$visibility]) > 0) : + $this->display_button_bar($showInactive); + else : + ?> + <?php fwp_syndication_manage_page_links_subsubsub($sources, $showInactive); ?> + <?php + endif; + + fwp_syndication_manage_page_links_table_rows($sources[$visibility], $this, $visibility); + $this->display_button_bar($showInactive); + ?> + </form> + <?php + } /* FeedWordPressSyndicationPage::syndicated_sources_box() */ + + function display_button_bar ($showInactive) { + ?> + <div style="clear: left" class="alignleft"> + <?php if ($showInactive) : ?> + <input class="button-secondary" type="submit" name="action" value="<?php print FWP_RESUB_CHECKED; ?>" /> + <input class="button-secondary" type="submit" name="action" value="<?php print FWP_DELETE_CHECKED; ?>" /> + <?php else : ?> + <input class="button-secondary" type="submit" name="action" value="<?php print FWP_UPDATE_CHECKED; ?>" /> + <input class="button-secondary delete" type="submit" name="action" value="<?php print FWP_UNSUB_CHECKED; ?>" /> + <?php endif ; ?> + </div> <!-- class="alignleft" --> + + <br class="clear" /> + <?php + } + + function bleg_thanks ($page, $box = NULL) { + ?> + <div class="donation-thanks"> + <h4>Thank you!</h4> + <p><strong>Thank you</strong> for your contribution to <a href="http://feedwordpress.radgeek.com/">FeedWordPress</a> development. + Your generous gifts make ongoing support and development for + FeedWordPress possible.</p> + <p>If you have any questions about FeedWordPress, or if there + is anything I can do to help make FeedWordPress more useful for + you, please <a href="http://feedwordpress.radgeek.com/contact">contact me</a> + and let me know what you're thinking about.</p> + <p class="signature">—<a href="http://radgeek.com/">Charles Johnson</a>, Developer, <a href="http://feedwordpress.radgeek.com/">FeedWordPress</a>.</p> + </div> + <?php + } /* FeedWordPressSyndicationPage::bleg_thanks () */ + + function bleg_box ($page, $box = NULL) { + ?> +<div class="donation-form"> +<h4>Keep FeedWordPress improving</h4> +<form action="https://www.paypal.com/cgi-bin/webscr" accept-charset="UTF-8" method="post"><div> +<p><a href="http://feedwordpress.radgeek.com/">FeedWordPress</a> makes syndication +simple and empowers you to stream content from all over the web into your +WordPress hub. That's got to be worth a few lattes. If you're finding FWP useful, +<a href="http://feedwordpress.radgeek.com/donate/">a modest gift</a> +is the best way to support steady progress on development, enhancements, +support, and documentation.</p> +<div class="donate"> +<input type="hidden" name="business" value="commerce@radgeek.com" /> +<input type="hidden" name="cmd" value="_xclick" /> +<input type="hidden" name="item_name" value="FeedWordPress donation" /> +<input type="hidden" name="no_shipping" value="1" /> +<input type="hidden" name="return" value="<?php print admin_url('admin.php'); ?>?page=<?php print $GLOBALS['fwp_path'] ?>/<?php print basename($this->filename); ?>&paid=yes" /> +<input type="hidden" name="currency_code" value="USD" /> +<input type="hidden" name="notify_url" value="http://feedwordpress.radgeek.com/ipn/donation" /> +<input type="hidden" name="custom" value="1" /> +<input type="image" name="submit" src="https://www.paypal.com/en_GB/i/btn/btn_donate_SM.gif" alt="Donate through PayPal" /> +</div> +</div></form> + +<p>You can make a gift online (or +<a href="http://feedwordpress.radgeek.com/donation">set up an automatic +regular donation</a>) using an existing PayPal account or any major credit card.</p> + +<div class="sod-off"> +<form style="text-align: center" action="<?php print $this->form_action(); ?>" method="POST"><div> +<input class="button" type="submit" name="maybe_later" value="Maybe Later" /> +<input class="button" type="submit" name="go_away" value="Dismiss" /> +</div></form> +</div> +</div> <!-- class="donation-form" --> + <?php + } /* FeedWordPressSyndicationPage::bleg_box () */ + + /** + * Override the default display of a save-settings button and replace + * it with nothing. + */ + function interstitial () { + /* NOOP */ + } /* FeedWordPressSyndicationPage::interstitial() */ + + function multidelete_page () { + global $wpdb; + global $fwp_post; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_feeds', /*capability=*/ 'manage_links'); + + if (isset($fwp_post['submit']) and $fwp_post['submit']==FWP_CANCEL_BUTTON) : + return true; // Continue without further ado. + endif; + + $link_ids = (isset($_REQUEST['link_ids']) ? $_REQUEST['link_ids'] : array()); + if (isset($_REQUEST['link_id'])) : array_push($link_ids, $_REQUEST['link_id']); endif; + + if (isset($GLOBALS['fwp_post']['confirm']) and $GLOBALS['fwp_post']['confirm']=='Delete'): + if (isset($GLOBALS['fwp_post']['link_action']) and is_array($GLOBALS['fwp_post']['link_action'])) : + $actions = $GLOBALS['fwp_post']['link_action']; + else : + $actions = array(); + endif; + + $do_it = array( + 'hide' => array(), + 'nuke' => array(), + 'delete' => array(), + ); + + foreach ($actions as $link_id => $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) : + if (FeedWordPressCompatibility::test_version(FWP_SCHEMA_29)) : + // Force scrubbing of deleted post + // rather than sending to Trashcan + wp_delete_post( + /*postid=*/ $post_id, + /*force_delete=*/ true + ); + else : + wp_delete_post($post_id); + endif; + 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 "<div class=\"updated\">\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 "</div>\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).") + "); + ?> + <form action="<?php print $this->form_action(); ?>" method="post"> + <div class="wrap"> + <?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?> + <input type="hidden" name="action" value="Unsubscribe" /> + <input type="hidden" name="confirm" value="Delete" /> + + <h2>Unsubscribe from Syndicated Links:</h2> + <?php foreach ($targets as $link) : + $subscribed = ('Y' == strtoupper($link->link_visible)); + $link_url = esc_html($link->link_url); + $link_name = esc_html($link->link_name); + $link_description = esc_html($link->link_description); + $link_rss = esc_html($link->link_rss); + ?> + <fieldset> + <legend><?php echo $link_name; ?></legend> + <table class="editform" width="100%" cellspacing="2" cellpadding="5"> + <tr><th scope="row" width="20%"><?php _e('Feed URI:') ?></th> + <td width="80%"><a href="<?php echo $link_rss; ?>"><?php echo $link_rss; ?></a></td></tr> + <tr><th scope="row" width="20%"><?php _e('Short description:') ?></th> + <td width="80%"><?php echo $link_description; ?></span></td></tr> + <tr><th width="20%" scope="row"><?php _e('Homepage:') ?></th> + <td width="80%"><a href="<?php echo $link_url; ?>"><?php echo $link_url; ?></a></td></tr> + <tr style="vertical-align:top"><th width="20%" scope="row">Subscription <?php _e('Options') ?>:</th> + <td width="80%"><ul style="margin:0; padding: 0; list-style: none"> + <?php if ($subscribed) : ?> + <li><input type="radio" id="hide-<?php echo $link->link_id; ?>" + name="link_action[<?php echo $link->link_id; ?>]" value="hide" checked="checked" /> + <label for="hide-<?php echo $link->link_id; ?>">Turn off the subscription for this + syndicated link<br/><span style="font-size:smaller">(Keep the feed information + and all the posts from this feed in the database, but don't syndicate any + new posts from the feed.)</span></label></li> + <?php endif; ?> + <li><input type="radio" id="nuke-<?php echo $link->link_id; ?>"<?php if (!$subscribed) : ?> checked="checked"<?php endif; ?> + name="link_action[<?php echo $link->link_id; ?>]" value="nuke" /> + <label for="nuke-<?php echo $link->link_id; ?>">Delete this syndicated link and all the + posts that were syndicated from it</label></li> + <li><input type="radio" id="delete-<?php echo $link->link_id; ?>" + name="link_action[<?php echo $link->link_id; ?>]" value="delete" /> + <label for="delete-<?php echo $link->link_id; ?>">Delete this syndicated link, but + <em>keep</em> posts that were syndicated from it (as if they were authored + locally).</label></li> + <li><input type="radio" id="nothing-<?php echo $link->link_id; ?>" + name="link_action[<?php echo $link->link_id; ?>]" value="nothing" /> + <label for="nothing-<?php echo $link->link_id; ?>">Keep this feed as it is. I changed + my mind.</label></li> + </ul> + </table> + </fieldset> + <?php endforeach; ?> + + <div class="submit"> + <input type="submit" name="submit" value="<?php _e(FWP_CANCEL_BUTTON); ?>" /> + <input class="delete" type="submit" name="submit" value="<?php _e(FWP_UNSUB_FULL) ?>" /> + </div> + </div> + <?php + return false; // Don't continue on to Syndicated Sites listing + endif; + } /* FeedWordPressSyndicationPage::multidelete_page() */ + + function multiundelete_page () { + global $wpdb; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_feeds', /*capability=*/ 'manage_links'); + + $link_ids = (isset($_REQUEST['link_ids']) ? $_REQUEST['link_ids'] : array()); + if (isset($_REQUEST['link_id'])) : array_push($link_ids, $_REQUEST['link_id']); endif; + + if (isset($GLOBALS['fwp_post']['confirm']) and $GLOBALS['fwp_post']['confirm']=='Undelete'): + if (isset($GLOBALS['fwp_post']['link_action']) and is_array($GLOBALS['fwp_post']['link_action'])) : + $actions = $GLOBALS['fwp_post']['link_action']; + else : + $actions = array(); + endif; + + $do_it = array( + 'unhide' => array(), + ); + + foreach ($actions as $link_id => $what) : + $do_it[$what][] = $link_id; + endforeach; + + $alter = array(); + if (count($do_it['unhide']) > 0) : + $unhiddem = "(".implode(', ', $do_it['unhide']).")"; + $alter[] = " + UPDATE $wpdb->links + SET link_visible = 'Y' + WHERE link_id IN {$unhiddem} + "; + 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 "<div class=\"updated\">\n"; + if (count($errs) > 0) : + echo "There were some problems processing your "; + echo "re-subscribe request. [SQL: ".implode('; ', $errs)."]"; + else : + echo "Your re-subscribe request(s) have been processed."; + endif; + echo "</div>\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).") + "); + ?> + <form action="<?php print $this->form_action(); ?>" method="post"> + <div class="wrap"> + <?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?> + <input type="hidden" name="action" value="<?php print FWP_RESUB_CHECKED; ?>" /> + <input type="hidden" name="confirm" value="Undelete" /> + + <h2>Re-subscribe to Syndicated Links:</h2> + <?php + foreach ($targets as $link) : + $subscribed = ('Y' == strtoupper($link->link_visible)); + $link_url = esc_html($link->link_url); + $link_name = esc_html($link->link_name); + $link_description = esc_html($link->link_description); + $link_rss = esc_html($link->link_rss); + + if (!$subscribed) : + ?> + <fieldset> + <legend><?php echo $link_name; ?></legend> + <table class="editform" width="100%" cellspacing="2" cellpadding="5"> + <tr><th scope="row" width="20%"><?php _e('Feed URI:') ?></th> + <td width="80%"><a href="<?php echo $link_rss; ?>"><?php echo $link_rss; ?></a></td></tr> + <tr><th scope="row" width="20%"><?php _e('Short description:') ?></th> + <td width="80%"><?php echo $link_description; ?></span></td></tr> + <tr><th width="20%" scope="row"><?php _e('Homepage:') ?></th> + <td width="80%"><a href="<?php echo $link_url; ?>"><?php echo $link_url; ?></a></td></tr> + <tr style="vertical-align:top"><th width="20%" scope="row">Subscription <?php _e('Options') ?>:</th> + <td width="80%"><ul style="margin:0; padding: 0; list-style: none"> + <li><input type="radio" id="unhide-<?php echo $link->link_id; ?>" + name="link_action[<?php echo $link->link_id; ?>]" value="unhide" checked="checked" /> + <label for="unhide-<?php echo $link->link_id; ?>">Turn back on the subscription + for this syndication source.</label></li> + <li><input type="radio" id="nothing-<?php echo $link->link_id; ?>" + name="link_action[<?php echo $link->link_id; ?>]" value="nothing" /> + <label for="nothing-<?php echo $link->link_id; ?>">Leave this feed as it is. + I changed my mind.</label></li> + </ul> + </table> + </fieldset> + <?php + endif; + endforeach; + ?> + + <div class="submit"> + <input class="button-primary delete" type="submit" name="submit" value="<?php _e('Re-subscribe to selected feeds »') ?>" /> + </div> + </div> + <?php + return false; // Don't continue on to Syndicated Sites listing + endif; + } /* FeedWordPressSyndicationPage::multiundelete_page() */ + + + +} /* class FeedWordPressSyndicationPage */ + +function fwp_dashboard_update_if_requested ($object) { + global $wpdb; + + $update_set = $object->updates_requested(); + + if (count($update_set) > 0) : + shuffle($update_set); // randomize order for load balancing purposes... + + $feedwordpress = new FeedWordPress; + add_action('feedwordpress_check_feed', 'update_feeds_mention'); + add_action('feedwordpress_check_feed_complete', 'update_feeds_finish', 10, 3); + + $crash_ts = $feedwordpress->crash_ts(); + + echo "<div class=\"update-results\">\n"; + echo "<ul>\n"; + $tdelta = NULL; + foreach ($update_set as $uri) : + if (!is_null($crash_ts) and (time() > $crash_ts)) : + echo "<li><p><strong>Further updates postponed:</strong> update time limit of ".$crash_dt." second".(($crash_dt==1)?"":"s")." exceeded.</p></li>"; + break; + endif; + + if ($uri == '*') : $uri = NULL; endif; + $delta = $feedwordpress->update($uri, $crash_ts); + if (!is_null($delta)) : + if (is_null($tdelta)) : + $tdelta = $delta; + else : + $tdelta['new'] += $delta['new']; + $tdelta['updated'] += $delta['updated']; + endif; + else : + echo "<li><p><strong>Error:</strong> There was a problem updating <a href=\"$uri\">$uri</a></p></li>\n"; + endif; + endforeach; + echo "</ul>\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 "<p>Update complete.".implode(' and', $mesg)."</p>"; + echo "\n"; flush(); + endif; + echo "</div> <!-- class=\"updated\" -->\n"; + endif; +} + +define('FEEDWORDPRESS_BLEG_MAYBE_LATER_OFFSET', (60 /*sec/min*/ * 60 /*min/hour*/ * 24 /*hour/day*/ * 31 /*days*/)); +define('FEEDWORDPRESS_BLEG_ALREADY_PAID_OFFSET', (60 /*sec/min*/ * 60 /*min/hour*/ * 24 /*hour/day*/ * 183 /*days*/)); +function fwp_syndication_manage_page_update_box ($object = NULL, $box = NULL) { + $bleg_box_hidden = null; + if (isset($_POST['maybe_later'])) : + $bleg_box_hidden = time() + FEEDWORDPRESS_BLEG_MAYBE_LATER_OFFSET; + elseif (isset($_REQUEST['paid']) and $_REQUEST['paid']) : + $bleg_box_hidden = time() + FEEDWORDPRESS_BLEG_ALREADY_PAID_OFFSET; + elseif (isset($_POST['go_away'])) : + $bleg_box_hidden = 'permanent'; + endif; + + if (!is_null($bleg_box_hidden)) : + update_option('feedwordpress_bleg_box_hidden', $bleg_box_hidden); + else : + $bleg_box_hidden = get_option('feedwordpress_bleg_box_hidden'); + endif; +?> + <?php + $bleg_box_ready = (FEEDWORDPRESS_BLEG and ( + !$bleg_box_hidden + or (is_numeric($bleg_box_hidden) and $bleg_box_hidden < time()) + )); + + if (isset($_REQUEST['paid']) and $_REQUEST['paid']) : + $object->bleg_thanks($subject, $box); + elseif ($bleg_box_ready) : + $object->bleg_box($object, $box); + endif; + ?> + + <form + action="<?php print $object->form_action(); ?>" + method="POST" + class="update-form<?php if ($bleg_box_ready) : ?> with-donation<?php endif; ?>" + > + <div><?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_feeds'); ?></div> + <p>Check currently scheduled feeds for new and updated posts.</p> + + <?php + fwp_dashboard_update_if_requested($object); + + if (!get_option('feedwordpress_automatic_updates')) : + ?> + <p class="heads-up"><strong>Note:</strong> Automatic updates are currently turned + <strong>off</strong>. New posts from your feeds will not be syndicated + until you manually check for them here. You can turn on automatic + updates under <a href="admin.php?page=<?php print $GLOBALS['fwp_path']; ?>/feeds-page.php">Feed & Update Settings<a></a>.</p> + <?php + endif; + ?> + + <div class="submit"><?php if ($object->show_inactive()) : ?> + <?php foreach ($object->updates_requested() as $req) : ?> + <input type="hidden" name="update_uri[]" value="<?php print esc_html($req); ?>" /> + <?php endforeach; ?> + <?php else : ?> + <input type="hidden" name="update_uri" value="*" /> + <?php endif; ?> + <input class="button-primary" type="submit" name="update" value="<?php _e(FWP_CHECK_FOR_UPDATES); ?>" /></div> + + <br style="clear: both" /> + </form> +<?php +} /* function fwp_syndication_manage_page_update_box () */ + +function fwp_feedfinder_page () { + global $post_source, $fwp_post, $syndicationPage; + + if (isset($fwp_post['opml_lookup']) or isset($_FILES['opml_upload'])) : + $syndicationPage->accept_multiadd(); + return true; + else : + $post_source = 'feedwordpress_feeds'; + + // With action=feedfinder, this goes directly to the feedfinder page + include_once(dirname(__FILE__) . '/feeds-page.php'); + return false; + endif; +} /* function fwp_feedfinder_page () */ + +function fwp_switchfeed_page () { + global $wpdb, $wp_db_version; + global $fwp_post; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_switchfeed', /*capability=*/ 'manage_links'); + + $changed = false; + if (!isset($fwp_post['Cancel'])): + if (isset($fwp_post['save_link_id']) and ($fwp_post['save_link_id']=='*')) : + $changed = true; + $link_id = FeedWordPress::syndicate_link($fwp_post['feed_title'], $fwp_post['feed_link'], $fwp_post['feed']); + if ($link_id): + $existingLink = new SyndicatedLink($link_id); + ?> +<div class="updated"><p><a href="<?php print $fwp_post['feed_link']; ?>"><?php print esc_html($fwp_post['feed_title']); ?></a> +has been added as a contributing site, using the feed at +<<a href="<?php print $fwp_post['feed']; ?>"><?php print esc_html($fwp_post['feed']); ?></a>>. +| <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&link_id=<?php print $link_id; ?>">Configure settings</a>.</p></div> +<?php else: ?> +<div class="updated"><p>There was a problem adding the feed. [SQL: <?php echo esc_html(mysql_error()); ?>]</p></div> +<?php endif; + elseif (isset($fwp_post['save_link_id'])): + $existingLink = new SyndicatedLink($fwp_post['save_link_id']); + $changed = $existingLink->set_uri($fwp_post['feed']); + + if ($changed): + $home = $existingLink->homepage(/*from feed=*/ false); + $name = $existingLink->name(/*from feed=*/ false); + ?> +<div class="updated"><p>Feed for <a href="<?php echo esc_html($home); ?>"><?php echo esc_html($name); ?></a> +updated to <<a href="<?php echo esc_html($fwp_post['feed']); ?>"><?php echo esc_html($fwp_post['feed']); ?></a>>.</p></div> + <?php + endif; + endif; + endif; + + if (isset($existingLink)) : + do_action('feedwordpress_admin_switchfeed', $fwp_post['feed'], $existingLink); + endif; + + if (!$changed) : + ?> +<div class="updated"><p>Nothing was changed.</p></div> + <?php + endif; + return true; // Continue +} + diff --git a/wp-content/plugins/feedwordpress/magpiefromsimplepie.class.php b/wp-content/plugins/feedwordpress/magpiefromsimplepie.class.php new file mode 100644 index 0000000000000000000000000000000000000000..f20041bda80aa63bc2743fdbd57739db3330bb7c --- /dev/null +++ b/wp-content/plugins/feedwordpress/magpiefromsimplepie.class.php @@ -0,0 +1,814 @@ +<?php +require_once(dirname(__FILE__).'/feedtime.class.php'); + +/** + * class MagpieFromSimplePie: compatibility layer to prevent existing filters + * from breaking. + * + * @since 2010.0203 + * @version 2010.0612 + */ + +class MagpieFromSimplePie { + var $pie; + var $originals; + + var $channel; + var $items; + var $textinput = array (); + var $image = array(); + + var $feed_type; + var $feed_version; + + var $_XMLNS_FAMILIAR = array ( + 'http://www.w3.org/2005/Atom' => 'atom' /* 1.0 */, + 'http://purl.org/atom/ns#' => 'atom' /* pre-1.0 */, + 'http://purl.org/rss/1.0/' => 'rss' /* 1.0 */, + 'http://backend.userland.com/RSS2' => 'rss' /* 2.0 */, + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf', + 'http://www.w3.org/1999/xhtml' => 'xhtml', + 'http://purl.org/dc/elements/1.1/' => 'dc', + 'http://purl.org/dc/terms/' => 'dcterms', + 'http://purl.org/rss/1.0/modules/content/' => 'content', + 'http://purl.org/rss/1.0/modules/syndication/' => 'sy', + 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo', + 'http://purl.org/rss/1.0/modules/dc/' => 'dc', + 'http://wellformedweb.org/CommentAPI/' => 'wfw', + 'http://webns.net/mvcb/' => 'admin', + 'http://purl.org/rss/1.0/modules/annotate/' => 'annotate', + 'http://xmlns.com/foaf/0.1/' => 'foaf', + 'http://madskills.com/public/xml/rss/module/trackback/' => 'trackback', + 'http://web.resource.org/cc/' => 'cc', + 'http://search.yahoo.com/mrss' => 'media', + 'http://search.yahoo.com/mrss/' => 'media', + 'http://video.search.yahoo.com/mrss' => 'media', + 'http://video.search.yahoo.com/mrss/' => 'media', + 'http://purl.org/syndication/thread/1.0' => 'thr', + 'http://purl.org/syndication/thread/1.0/' => 'thr', + 'http://www.w3.org/XML/1998/namespace' => 'xml', + 'http://www.itunes.com/dtds/podcast-1.0.dtd' => 'itunes', + 'http://a9.com/-/spec/opensearchrss/1.0/' => 'openSearch', + 'http://purl.org/rss/1.0/modules/slash/' => 'slash', + 'http://www.google.com/schemas/reader/atom/' => 'gr', + 'urn:atom-extension:indexing' => 'indexing', + ); + + /** + * MagpieFromSimplePie constructor + * + * @param SimplePie $pie The feed to convert to MagpieRSS format. + * @param mixed $item + * + * @uses SimplePie::get_items + * @uses MagpieFromSimplePie::processFeedData + * @uses MagpieFromSimplePie::processItemData + * @uses MagpieFromSimplePie::normalize + * @uses MagpieFromSimplePie::is_atom + */ + function MagpieFromSimplePie ($pie, $item = true) { + $this->pie = $pie; + + // item in {NULL, true} = process channel data + if (!is_a($item, 'SimplePie_Item')) : + $this->originals = $this->pie->get_items(); + + $this->channel = $this->processFeedData($this->pie->data); + else : + $this->originals = array($item); + $this->channel = NULL; + endif; + + // item in {true, SimplePie_Item} = process item data + if (!is_null($item)) : + foreach ($this->originals as $key => $item) : + $this->items[$key] = $this->processItemData($item->data); + endforeach; + else : + $this->items = NULL; + endif; + + $this->normalize(); + + // In case anyone goes poking around our private members (uh...) + $this->feed_type = ($this->is_atom() ? 'Atom' : 'RSS'); + $this->feed_version = $this->feed_version(); + $this->encoding = $pie->get_encoding(); + } /* MagpieFromSimplePie constructor */ + + /** + * MagpieFromSimplePie::get_items: returns an array of MagpieRSS format arrays + * equivalent to the SimplePie_Item objects in the SimplePie object from which this + * object was constructed. + * + * @return array An array of MagpieRSS-format arrays representing the items on this feed + */ + function get_items () { + return $this->items; + } /* MagpieFromSimplePie::get_items () */ + + /** + * MagpieFromSimplePie::get_item: returns a single MagpieRSS format array equivalent + * to a SimplePie_Item object from which this object was constructed. + * + * @return array A MagpieRSS-format array representing an item on this feed + */ + function get_item () { + if (is_array($this->items)) : + $ret = reset($this->items); + else : + $ret = NULL; + endif; + return $ret; + } /* MagpieFromSimplePie::get_item () */ + + /** + * MagpieFromSimplePie::processFeedData + * + * @param array $data + * @return array + * + * @uses MagpieFromSimplePie::processChannelData() + */ + function processFeedData ($data) { + $ret = array(); + if (isset($data['child'])) : foreach ($data['child'] as $ns => $elements) : + foreach ($elements as $name => $multi) : + foreach ($multi as $element) : + if ($name=='feed' or $name=='channel') : + // Don't need to process these + foreach (array( + '', + 'http://www.w3.org/2005/Atom', + 'http://purl.org/rss/1.0/', + 'http://backend.userland.com/RSS2' + ) as $ns) : + if (isset($element['child'][$ns]['entry'])) : unset($element['child'][$ns]['entry']); endif; + if (isset($element['child'][$ns]['item'])) : unset($element['child'][$ns]['item']); endif; + endforeach; + + $ret = $this->processChannelData($element) + $ret; + elseif (in_array(strtolower($name), array('rss', 'rdf'))) : + // Drop down to get to <channel> element + $ret = $this->processFeedData($element) + $ret; + endif; + endforeach; + endforeach; + endforeach; endif; + return $ret; + } /* MagpieFromSimplePie::processFeedData() */ + + /** + * MagpieFromSimplePie::processChannelData + * + * @param array $data + * @param array $path + * @return array + * + * @uses MagpieFromSimplePie::handleAttributes + * @uses MagpieFromSimplePie::handleChildren + */ + function processChannelData ($data, $path = array()) { + $ret = array(); + $tagPath = strtolower(implode('_', $path)); + + // Only process at the right level + if (strlen($tagPath) > 0 + and isset($data['data']) + and strlen($data['data']) > 0) : + $ret[$tagPath] = $data['data']; + endif; + + $ret = $this->handleAttributes($data, $path) + + $this->handleChildren($data, $path, 'processChannelData') + + $ret; + + return $ret; + } /* MagpieFromSimplePie::processChannelData() */ + + /** + * MagpieFromSimplePie::processItemData + * + * @param array $data + * @param array $path + * @return array + * + * @uses MagpieFromSimplePie::handleAttributes + * @uses MagpieFromSimplePie::handleChildren + */ + function processItemData ($data, $path = array()) { + $ret = array(); + $tagPath = strtolower(implode('_', $path)); + + if (strlen($tagPath) > 0 and isset($data['data']) and strlen($data['data']) > 0) : + $ret[$tagPath] = $data['data']; + endif; + + // Set up xml:base to be recorded in array + if (isset($data['xml_base_explicit']) and $data['xml_base_explicit']) : + $data['attribs']['']['xml:base'] = $data['xml_base']; + endif; + + $ret = $this->handleAttributes($data, $path) + + $this->handleChildren($data, $path, 'processItemData') + + $ret; + + return $ret; + } /* MagpieFromSimplePie::processItemData() */ + + /** + * MagpieFromSimplePie::handleAttributes + * + * @param array $data + * @param array $path + * @return array + */ + function handleAttributes ($data, $path) { + $tagPath = strtolower(implode('_', $path)); + $ret = array(); + if (isset($data['attribs'])) : foreach ($data['attribs'] as $ns => $pairs) : + if (isset($this->_XMLNS_FAMILIAR[$ns])) : + $ns = $this->_XMLNS_FAMILIAR[$ns]; + endif; + + foreach ($pairs as $attr => $value) : + $attr = strtolower($attr); + if ($ns=='rdf' and $attr=='about') : + $ret['about'] = $value; + elseif (strlen($tagPath) > 0) : + if (strlen($ns) > 0 and $this->is_namespaced($ns, /*attrib=*/ true)) : + $attr = $ns.':'.$attr; + endif; + + $ret[$tagPath.'@'.$attr] = $value; + if (isset($ret[$tagPath.'@']) and strlen($ret[$tagPath.'@'])>0) : + $ret[$tagPath.'@'] .= ','; + else : + $ret[$tagPath.'@'] = ''; + endif; + $ret[$tagPath.'@'] .= $attr; + endif; + endforeach; + endforeach; endif; + return $ret; + } /* MagpieFromSimplePie::handleAttributes() */ + + var $inImage = false; + var $inTextInput = false; + + /** + * MagpieFromSimplePie::handleChildren + * + * @param array $data + * @param array $path + * @return array + * + * @uses MagpieFromSimplePie::get_attrib + * @uses MagpieFromSimplePie::is_atom + * @uses MagpieFromSimplePie::increment_element + * @uses MagpieFromSimplePie::processItemData + */ + function handleChildren ($data, $path = array(), $method = 'processItemData') { + $tagPath = strtolower(implode('_', $path)); + $ret = array(); + if (isset($data['child'])) : foreach ($data['child'] as $ns => $elements) : + if (isset($this->_XMLNS_FAMILIAR[$ns])) : + $ns = $this->_XMLNS_FAMILIAR[$ns]; + endif; + if (''==$ns) : + $ns = ($this->is_atom() ? 'atom' : 'rss'); + endif; + + foreach ($elements as $tag => $multi) : foreach ($multi as $element) : + $copyOver = NULL; + + if ('image'==$tag and 'rss'==$ns) : + $this->inImage = true; + $childPath = array(); + $co = NULL; + elseif ('textinput'==strtolower($tag) and 'rss'==$ns) : + $this->inTextInput = true; + $childPath = array(); + $co = NULL; + else : + // Determine tag name; check #; increment # + $childTag = strtolower($tag); + if ('link'==$tag and 'atom'==$ns) : + $rel = $this->get_attrib( + /*ns=*/ array('', 'http://www.w3.org/2005/Atom'), + /*attr=*/ 'rel', + $element + ); + if ($rel != 'alternate') : + $childTag .= '_'.$rel; + endif; + $copyOver = $this->get_attrib( + /*ns=*/ array('', 'http://www.w3.org/2005/Atom'), + /*attr=*/ 'href', + $element + ); + elseif ('content'==$tag and 'atom'==$ns) : + $childTag = 'atom_'.$tag; + endif; + + $childTag = $this->increment_element($ret, $childTag, $ns, $path); + $childPath = $path; $childPath[] = strtolower($childTag); + + if (!is_null($copyOver)) : + $co = array(); + $co[implode('_', $childPath)] = $copyOver; + else : + $co = NULL; + endif; + endif; + + $arr = $this->{$method}($element, $childPath); + if ($co) : + $arr = $co + $arr; // Left-hand overwrites right-hand + endif; + + if ($this->inImage) : + $this->image = $arr + $this->image; + + // Close tag + if ('image'==$tag and 'rss'==$ns) : $this->inImage = false; endif; + elseif ($this->inTextInput) : + $this->textinput = $arr + $this->textinput; + + // Close tag + if ('textinput'==$tag and 'rss'==$ns) : $this->inTextInput = false; endif; + elseif ($this->is_namespaced($ns)) : + if (!isset($ret[$ns])) : $ret[$ns] = array(); endif; + $ret[$ns] = $arr + $ret[$ns]; + else : + $ret = $arr + $ret; + endif; + endforeach; endforeach; + + endforeach; endif; + return $ret; + } /* MagpieFromSimplePie::handleChildren() */ + + /** + * MagpieFromSimplePie::get_attrib + * + * @param array $namespaces + * @param string $attr + * @param array $element + * @param mixed $default + */ + function get_attrib ($namespaces, $attr, $element, $default = NULL) { + $ret = $default; + if (isset($element['attribs'])) : + foreach ($namespaces as $ns) : + if (isset($element['attribs'][$ns]) + and isset($element['attribs'][$ns][$attr])) : + $ret = $element['attribs'][$ns][$attr]; + break; + endif; + endforeach; + endif; + return $ret; + } /* MagpieFromSimplePie::get_attrib */ + + /** + * MagpieFromSimplePie::normalize + * + * @uses MagpieFromSimplePie::is_atom + * @uses MagpieFromSimplePie::is_rss + * @uses MagpieFromSimplePie::normalize_element + * @uses MagpieFromSimplePie::normalize_author_inheritance + * @uses MagpieFromSimplePie::normalize_atom_person + * @uses MagpieFromSimplePie::normalize_enclosure + * @uses MagpieFromSimplePie::normalize_category + * @uses MagpieFromSimplePie::normalize_dc_subject + * @uses FeedTime + * @uses FeedTime::timestamp + */ + function normalize () { + if (!is_null($this->channel)) : + // Normalize channel data + if ( $this->is_atom() ) : + // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!) + if ($this->feed_version() < 1.0) : + $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle'); + $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights'); + $this->normalize_element($this->channel, 'modified', $this->channel, 'updated'); + else : + $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline'); + $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright'); + $this->normalize_element($this->channel, 'updated', $this->channel, 'modified'); + endif; + $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person'); + $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person'); + + // Atom elements to RSS elements + $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description'); + + if ( isset($this->channel['logo']) ) : + $this->normalize_element($this->channel, 'logo', $this->image, 'url'); + $this->normalize_element($this->channel, 'link', $this->image, 'link'); + $this->normalize_element($this->channel, 'title', $this->image, 'title'); + endif; + + elseif ( $this->is_rss() ) : + // Normalize image element from where stupid MagpieRSS puts it + //$this->normalize_element($this->channel, 'image_title', $this->image, 'title'); + //$this->normalize_element($this->channel, 'image_link', $this->image, 'link'); + //$this->normalize_element($this->channel, 'image_url', $this->image, 'url'); + + // ... and, gag, textInput + //$this->normalize_element($this->channel, 'textinput_title', $this->textinput, 'title'); + //$this->normalize_element($this->channel, 'textinput_link', $this->textinput, 'link'); + //$this->normalize_element($this->channel, 'textinput_name', $this->textinput, 'name'); + //$this->normalize_element($this->channel, 'textinput_description', $this->textinput, 'description'); + + // RSS elements to Atom elements + $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3 + $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!) + $this->normalize_element($this->image, 'url', $this->channel, 'logo'); + endif; + endif; + + if (!is_null($this->items)) : + // Now loop through and normalize item data + for ( $i = 0; $i < count($this->items); $i++) : + $item = $this->items[$i]; + + // if atom populate rss fields and normalize 0.3 and 1.0 feeds + if ( $this->is_atom() ) : + // Atom 1.0 elements <=> Atom 0.3 elements + if ($this->feed_version() < 1.0) : + $this->normalize_element($item, 'modified', $item, 'updated'); + $this->normalize_element($item, 'issued', $item, 'published'); + else : + $this->normalize_element($item, 'updated', $item, 'modified'); + $this->normalize_element($item, 'published', $item, 'issued'); + endif; + + $this->normalize_author_inheritance($item, $this->originals[$i]); + + // Atom elements to RSS elements + $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person'); + $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person'); + $this->normalize_element($item, 'summary', $item, 'description'); + $this->normalize_element($item, 'atom_content', $item['content'], 'encoded'); + $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure'); + + // Categories + if ( isset($item['category#']) ) : // Atom 1.0 categories to dc:subject and RSS 2.0 categories + $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category'); + elseif ( isset($item['dc']['subject#']) ) : // dc:subject to Atom 1.0 and RSS 2.0 categories + $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject'); + endif; + + // Normalized item timestamp + $item_date = (isset($item['published']) ) ? $item['published'] : $item['updated']; + elseif ( $this->is_rss() ) : + // RSS elements to Atom elements + $this->normalize_element($item, 'description', $item, 'summary'); + $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure'); + + // Categories + if ( isset($item['category#']) ) : // RSS 2.0 categories to dc:subject and Atom 1.0 categories + $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category'); + elseif ( isset($item['dc']['subject#']) ) : // dc:subject to Atom 1.0 and RSS 2.0 categories + $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject'); + endif; + + // Normalized item timestamp + if (isset($item['pubdate'])) : + $item_date = $item['pubdate']; + elseif (isset($item['dc']['date'])) : + $item_date = $item['dc']['date']; + else : + $item_date = null; + endif; + endif; + + if ( $item_date ) : + $date_timestamp = new FeedTime($item_date); + + if (!$date_timestamp->failed()) : + $item['date_timestamp'] = $date_timestamp->timestamp(); + endif; + endif; + + $this->items[$i] = $item; + endfor; + endif; + } /* MagpieFromSimplePie::normalize() */ + + /** + * MagpieFromSimplePie::normalize_author_inheritance + * + * @param SimplePie_Item $original + * + * @uses SimplePie_Item::get_authors + * @uses SimplePie_Author::get_name + * @uses SimplePie_Author::get_link + * @uses SimplePie_Author::get_email + * @uses MagpieFromSimplePie::increment_element + */ + function normalize_author_inheritance (&$item, $original) { + // "If an atom:entry element does not contain + // atom:author elements, then the atom:author elements + // of the contained atom:source element are considered + // to apply. In an Atom Feed Document, the atom:author + // elements of the containing atom:feed element are + // considered to apply to the entry if there are no + // atom:author elements in the locations described + // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1> + if (!isset($item["author#"])) : + $authors = $original->get_authors(); + foreach ($authors as $author) : + $tag = $this->increment_element($item, 'author', 'atom', array()); + $item[$tag] = $item["{$tag}_name"] = $author->get_name(); + if ($author->get_link()) : $item["{$tag}_uri"] = $item["{$tag}_url"] = $author->get_link(); endif; + if ($author->get_email()) : $item["{$tag}_email"] = $author->get_email(); endif; + endforeach; + endif; + } /* MagpieFromSimplePie::normalize_author_inheritance() */ + + /** + * MagpieFromSimplePie::normalize_element + * Simplify the logic for normalize(). Makes sure that count of elements + * and each of multiple elements is normalized properly. If you need to + * mess with things like attributes or change formats or the like, pass + * it a callback to handle each element. + * + * @param array &$source + * @param string $from + * @param array &$dest + * @param string $to + * @param mixed $via + */ + function normalize_element (&$source, $from, &$dest, $to, $via = NULL) { + if (isset($source[$from]) or isset($source["{$from}#"])) : + if (isset($source["{$from}#"])) : + $n = $source["{$from}#"]; + $dest["{$to}#"] = $source["{$from}#"]; + else : + $n = 1; + endif; + + for ($i = 1; $i <= $n; $i++) : + if (isset($via) and is_callable(array($this, $via))) : // custom callback for ninja attacks + $this->{$via}($source, $from, $dest, $to, $i); + else : // just make it the same + $from_id = $this->element_id($from, $i); + $to_id = $this->element_id($to, $i); + + if (isset($source[$from_id])) : // Avoid PHP notice nastygrams + $dest[$to_id] = $source[$from_id]; + endif; + endif; + endfor; + endif; + } /* MagpieFromSimplePie::normalize_element */ + + /** + * MagpieFromSimplePie::normalize_enclosure + * + * @param array &$source + * @param string $from + * @param array &$dest + * @param string $to + * @param int $i + * + * @uses MagpieFromSimplePie::element_id + */ + function normalize_enclosure (&$source, $from, &$dest, $to, $i) { + $id_from = $this->element_id($from, $i); + $id_to = $this->element_id($to, $i); + if (isset($source["{$id_from}@"])) : + foreach (explode(',', $source["{$id_from}@"]) as $attr) : + if ($from=='link_enclosure' and $attr=='href') : // from Atom + $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"]; + $dest["{$id_to}"] = $source["{$id_from}@{$attr}"]; + elseif ($from=='enclosure' and $attr=='url') : // from RSS + $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"]; + $dest["{$id_to}"] = $source["{$id_from}@{$attr}"]; + else : + $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"]; + endif; + endforeach; + endif; + } /* MagpieFromSimplePie::normalize_enclosure */ + + /** + * MagpieFromSimplePie::normalize_atom_person + * + * @param array &$source + * @param string $person + * @param array &$dest + * @param string $to + * @param int $i + * + * + * @uses MagpieFromSimplePie::element_id + */ + function normalize_atom_person (&$source, $person, &$dest, $to, $i) { + $id = $this->element_id($person, $i); + $id_to = $this->element_id($to, $i); + + // Atom 0.3 <=> Atom 1.0 + if ($this->feed_version() >= 1.0) : + $used = 'uri'; $norm = 'url'; + else : + $used = 'url'; $norm = 'uri'; + endif; + + if (isset($source["{$id}_{$used}"])) : + $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"]; + endif; + + // Atom to RSS 2.0 and Dublin Core + // RSS 2.0 person strings should be valid e-mail addresses if possible. + if (isset($source["{$id}_email"])) : + $rss_author = $source["{$id}_email"]; + endif; + if (isset($source["{$id}_name"])) : + $rss_author = $source["{$id}_name"] + . (isset($rss_author) ? " <$rss_author>" : ''); + endif; + if (isset($rss_author)) : + $source[$id] = $rss_author; // goes to top-level author or contributor + $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor + endif; + } /* MagpieFromSimplePie::normalize_atom_person */ + + /** + * MagpieFromSimplePie::normalize_category: Normalize Atom 1.0 and + * RSS 2.0 categories to Dublin Core... + * + * @param array &$source + * @param string $from + * @param array &$dest + * @param string $to + * @param int $i + * + * @uses MagpieFromSimplePie::element_id + * @uses MagpieFromSimplePie::is_rss + */ + function normalize_category (&$source, $from, &$dest, $to, $i) { + $cat_id = $this->element_id($from, $i); + $dc_id = $this->element_id($to, $i); + + // first normalize category elements: Atom 1.0 <=> RSS 2.0 + if ( isset($source["{$cat_id}@term"]) ) : // category identifier + $source[$cat_id] = $source["{$cat_id}@term"]; + elseif ( $this->is_rss() ) : + $source["{$cat_id}@term"] = $source[$cat_id]; + endif; + + if ( isset($source["{$cat_id}@scheme"]) ) : // URI to taxonomy + $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"]; + elseif ( isset($source["{$cat_id}@domain"]) ) : + $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"]; + endif; + + // Now put the identifier into dc:subject + $dest[$dc_id] = $source[$cat_id]; + } /* MagpieFromSimplePie::normalize_category */ + + /** + * MagpieFromSimplePie::normalize_dc_subject: Normalize Dublin Core + * "subject" elements to Atom 1.0 and RSS 2.0 categories. + * + * @param array &$source + * @param string $from + * @param array &$dest + * @param string $to + * @param int $i + * + * @uses MagpieFromSimplePie::element_id + */ + function normalize_dc_subject (&$source, $from, &$dest, $to, $i) { + $dc_id = $this->element_id($from, $i); + $cat_id = $this->element_id($to, $i); + + $dest[$cat_id] = $source[$dc_id]; // RSS 2.0 + $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0 + } + + /** + * MagpieFromSimplePie::element_id + * Magic ID function for multiple elemenets. + * Can be called as static MagpieRSS::element_id() + * + * @param string $el + * @param int $counter + * @return string + */ + function element_id ($el, $counter) { + return $el . (($counter > 1) ? '#'.$counter : ''); + } /* MagpieFromSimplePie::element_id */ + + /** + * MagpieFromSimplePie::increment_element + * + * @param array &$data + * @param string $childTag + * @param string $ns + * @param array $path + */ + function increment_element (&$data, $childTag, $ns, $path) { + $counterIndex = strtolower(implode('_', array_merge($path, array($childTag.'#')))); + if ($this->is_namespaced($ns)) : + if (!isset($data[$ns])) : $data[$ns] = array(); endif; + if (!isset($data[$ns][$counterIndex])) : $data[$ns][$counterIndex] = 0; endif; + $data[$ns][$counterIndex] += 1; + $N = $data[$ns][$counterIndex]; + else : + if (!isset($data[$counterIndex])) : $data[$counterIndex] = 0; endif; + $data[$counterIndex] += 1; + $N = $data[$counterIndex]; + endif; + + if ($N > 1) : + $childTag .= '#'.$N; + endif; + return $childTag; + } /* MagpieFromSimplePie::increment_element */ + + /** + * MagpieFromSimplePie::is_namespaced + * + * @param string $ns + * @return bool + * + * @uses MagpieFromSimplePie::is_atom + * @uses MagpieFromSimplePie::is_rdf + */ + function is_namespaced ($ns, $attribute = false) { + // Atom vs. RSS + if ($this->is_atom()) : $root = array('', 'atom'); + else : $root = array('', 'rss'); + endif; + + // RDF formats; namespaced in attribs but not in elements + if (!$attribute and $this->is_rdf()) : + $root[] = 'rdf'; + endif; + + return !in_array(strtolower($ns), $root); + } /* MagpieFromSimplePie::is_namespaced */ + + /** + * MagpieFromSimplePie::is_atom + * + * @return bool + */ + function is_atom () { + return $this->pie->get_type() & SIMPLEPIE_TYPE_ATOM_ALL; + } /* MagpieFromSimplePie::increment_element */ + + /** + * MagpieFromSimplePie::is_rss + * + * @return bool + */ + function is_rss () { + return $this->pie->get_type() & SIMPLEPIE_TYPE_RSS_ALL; + } /* MagpieFromSimplePie::is_rss */ + + /** + * MagpieFromSimplePie::is_rdf + * + * @return bool + */ + function is_rdf () { + return $this->pie->get_type() & SIMPLEPIE_TYPE_RSS_RDF; + } /* MagpieFromSimplePie::is_rdf */ + + /** + * MagpieFromSimplePie::feed_version + * + * @return float + */ + function feed_version () { + $map = array ( + SIMPLEPIE_TYPE_ATOM_10 => 1.0, + SIMPLEPIE_TYPE_ATOM_03 => 0.3, + SIMPLEPIE_TYPE_RSS_090 => 0.90, + SIMPLEPIE_TYPE_RSS_091 => 0.91, + SIMPLEPIE_TYPE_RSS_092 => 0.92, + SIMPLEPIE_TYPE_RSS_093 => 0.93, + SIMPLEPIE_TYPE_RSS_094 => 0.94, + SIMPLEPIE_TYPE_RSS_10 => 1.0, + SIMPLEPIE_TYPE_RSS_20 => 2.0, + ); + + $ret = NULL; $type = $this->pie->get_type(); + foreach ($map as $flag => $version) : + if ($type & $flag) : + $ret = $version; + break; + endif; + endforeach; + return $ret; + } /* MagpieFromSimplePie::feed_version */ + +} /* class MagpieFromSimplePie */ + diff --git a/wp-content/plugins/feedwordpress/magpiemocklink.class.php b/wp-content/plugins/feedwordpress/magpiemocklink.class.php new file mode 100644 index 0000000000000000000000000000000000000000..49bcd453b0555d3c19fcb099a4e7faa3f0bb5f99 --- /dev/null +++ b/wp-content/plugins/feedwordpress/magpiemocklink.class.php @@ -0,0 +1,46 @@ +<?php +require_once(dirname(__FILE__) . '/syndicatedlink.class.php'); + +class MagpieMockLink extends SyndicatedLink { + var $url; + + function MagpieMockLink ($rss, $url) { + $this->link = $rss; + + if (is_array($rss) and isset($rss['simplepie']) and isset($rss['magpie'])) : + $this->simplepie = $rss['simplepie']; + $this->magpie = $rss['magpie']; + else : + $this->magpie = $rss; + endif; + + $this->url = $url; + $this->id = -1; + $this->settings = array( + 'unfamiliar category' => 'default', + + ); + } /* function MagpieMockLink::MagpieMockLink () */ + + function poll ($crash_ts = NULL) { + // Do nothing but update copy of feed + $this->simplepie = FeedWordPress::fetch($this->url); + $this->magpie = new MagpieFromSimplePie($this->simplepie); + + $this->link = $this->magpie; + } /* function MagpieMockLink::poll () */ + + function uri () { + return $this->url; + } /* function MagpieMockLink::uri() */ + + function homepage () { + return (!is_wp_error($this->simplepie) ? $this->simplepie->get_link() : null); + } /* function MagpieMockLink::homepage () */ + + function save_settings ($reload = false) { + // NOOP. + } +} /* class MagpieMockLink */ + + diff --git a/wp-content/plugins/feedwordpress/performance-page.php b/wp-content/plugins/feedwordpress/performance-page.php new file mode 100644 index 0000000000000000000000000000000000000000..0762318f3359af73a99a963a4960d2ef7e235f3b --- /dev/null +++ b/wp-content/plugins/feedwordpress/performance-page.php @@ -0,0 +1,119 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); + +class FeedWordPressPerformancePage extends FeedWordPressAdminPage { + function FeedWordPressPerformancePage () { + // Set meta-box context name + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpressperformancepage'); + $this->dispatch = 'feedwordpress_performance'; + $this->filename = __FILE__; + } + + function has_link () { return false; } + + function display () { + global $wpdb, $wp_db_version, $fwp_path; + global $fwp_post; + + if (FeedWordPress::needs_upgrade()) : + fwp_upgrade_page(); + return; + endif; + + // If this is a POST, validate source and user credentials + FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_performance', /*capability=*/ 'manage_options'); + + if (strtoupper($_SERVER['REQUEST_METHOD'])=='POST') : + $this->accept_POST($fwp_post); + do_action('feedwordpress_admin_page_performance_save', $GLOBALS['fwp_post'], $this); + endif; + + //////////////////////////////////////////////// + // Prepare settings page /////////////////////// + //////////////////////////////////////////////// + + $this->display_update_notice_if_updated('Performance'); + + $this->open_sheet('FeedWordPress Performance'); + ?> + <div id="post-body"> + <?php + $boxes_by_methods = array( + 'performance_box' => __('Performance'), + ); + + foreach ($boxes_by_methods as $method => $title) : + add_meta_box( + /*id=*/ 'feedwordpress_'.$method, + /*title=*/ $title, + /*callback=*/ array('FeedWordPressPerformancePage', $method), + /*page=*/ $this->meta_box_context(), + /*context=*/ $this->meta_box_context() + ); + endforeach; + do_action('feedwordpress_admin_page_performance_meta_boxes', $this); + ?> + <div class="metabox-holder"> + <?php + fwp_do_meta_boxes($this->meta_box_context(), $this->meta_box_context(), $this); + ?> + </div> <!-- class="metabox-holder" --> + </div> <!-- id="post-body" --> + + <?php + $this->close_sheet(); + } /* FeedWordPressPerformancePage::display () */ + + function accept_POST ($post) { + if (isset($post['create_index'])) : + FeedWordPress::create_guid_index(); + $this->updated = __('guid column index created on database table.'); + endif; + if (isset($post['remove_index'])) : + FeedWordPress::remove_guid_index(); + $this->updated = __('guid column index removed from database table.'); + endif; + + if (isset($post['clear_cache'])) : + $N = FeedWordPress::clear_cache(); + $feeds = (($N == 1) ? __("feed") : __("feeds")); + $this->updated = sprintf(__("Cleared %d cached %s from WordPress database."), $N, $feeds); + endif; + } /* FeedWordPressPerformancePage::accept_POST () */ + + /*static*/ function performance_box ($page, $box = NULL) { + // Hey ho, let's go... + ?> +<table class="editform" width="100%" cellspacing="2" cellpadding="5"> +<tr style="vertical-align: top"> +<th width="33%" scope="row">Feed cache:</th> +<td width="67%"><input class="button" type="submit" name="clear_cache" value="Clear all cached feeds from WordPress database" /> +<p>This will clear all cached copies of feed data from the WordPress database +and force FeedWordPress to make a fresh scan for updates on syndicated feeds.</p></td></tr> + +<tr style="vertical-align: top"> +<th width="33%" scope="row">Guid index:</th> +<td width="67%"><?php if (!FeedWordPress::has_guid_index()) : ?> +<input class="button" type="submit" name="create_index" value="Create index on guid column in posts database table" /> +<p>Creating this index may significantly improve performance on some large +FeedWordPress installations.</p> +<?php else : ?> + +<p>You have already created an index on the guid column in the WordPress posts +table. If you'd like to remove the index for any reason, you can do so here.</p> + +<input class="button" type="submit" name="remove_index" value="Remove index on guid column in posts database table" /> + +<?php endif; ?> + + +</td> +</tr> +</table> + <?php + } /* FeedWordPressPerformancePage::performance_box () */ +} /* class FeedWordPressPerformancePage */ + + $performancePage = new FeedWordPressPerformancePage; + $performancePage->display(); + diff --git a/wp-content/plugins/feedwordpress/plus.png b/wp-content/plugins/feedwordpress/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..f9865e6a44aeeb9e2fadffa9588dea791b1668cf Binary files /dev/null and b/wp-content/plugins/feedwordpress/plus.png differ diff --git a/wp-content/plugins/feedwordpress/posts-page.php b/wp-content/plugins/feedwordpress/posts-page.php new file mode 100644 index 0000000000000000000000000000000000000000..4d0bf9e7d93cab037819c6d9177a34cf00882691 --- /dev/null +++ b/wp-content/plugins/feedwordpress/posts-page.php @@ -0,0 +1,514 @@ +<?php +require_once(dirname(__FILE__) . '/admin-ui.php'); +require_once(dirname(__FILE__) . '/updatedpostscontrol.class.php'); + +class FeedWordPressPostsPage extends FeedWordPressAdminPage { + var $link = NULL; + var $updatedPosts = NULL; + + /** + * Construct the posts page object. + * + * @param mixed $link An object of class {@link SyndicatedLink} if created for one feed's settings, NULL if created for global default settings + */ + function FeedWordPressPostsPage ($link = -1) { + if (is_numeric($link) and -1 == $link) : + $link = FeedWordPressAdminPage::submitted_link(); + endif; + + FeedWordPressAdminPage::FeedWordPressAdminPage('feedwordpresspostspage', $link); + $this->dispatch = 'feedwordpress_admin_page_posts'; + $this->filename = __FILE__; + $this->updatedPosts = new UpdatedPostsControl($this); + + $this->pagenames = array( + 'default' => 'Posts', + 'settings-update' => 'Syndicated posts', + 'open-sheet' => 'Syndicated Posts & Links', + ); + } /* FeedWordPressPostsPage constructor */ + + function save_settings ($post) { + // custom post settings + $custom_settings = $this->custom_post_settings(); + + foreach ($post['notes'] as $mn) : + if (isset($mn['key0'])) : + $mn['key0'] = trim($mn['key0']); + if (strlen($mn['key0']) > 0) : + unset($custom_settings[$mn['key0']]); // out with the old + endif; + endif; + + if (isset($mn['key1'])) : + $mn['key1'] = trim($mn['key1']); + + if (($mn['action']=='update') and (strlen($mn['key1']) > 0)) : + $custom_settings[$mn['key1']] = $mn['value']; // in with the new + endif; + endif; + endforeach; + + $this->updatedPosts->accept_POST($post); + if ($this->for_feed_settings()) : + $alter = array (); + + $this->link->settings['postmeta'] = serialize($custom_settings); + + if (isset($post['resolve_relative'])) : + $this->link->settings['resolve relative'] = $post['resolve_relative']; + endif; + if (isset($post['munge_permalink'])) : + $this->link->settings['munge permalink'] = $post['munge_permalink']; + endif; + if (isset($post['munge_comments_feed_links'])) : + $this->link->settings['munge comments feed links'] = $post['munge_comments_feed_links']; + endif; + + // Post status, comment status, ping status + foreach (array('post', 'comment', 'ping') as $what) : + $sfield = "feed_{$what}_status"; + if (isset($post[$sfield])) : + if ($post[$sfield]=='site-default') : + unset($this->link->settings["{$what} status"]); + else : + $this->link->settings["{$what} status"] = $post[$sfield]; + endif; + endif; + endforeach; + + if (isset($post['syndicated_post_type'])) : + if ($post['syndicated_post_type']=='default') : + unset($this->link->settings['syndicated post type']); + else : + $this->link->settings['syndicated post type'] = $post['syndicated_post_type']; + endif; + endif; + + else : + // update_option ... + if (isset($post['feed_post_status'])) : + update_option('feedwordpress_syndicated_post_status', $post['feed_post_status']); + endif; + + update_option('feedwordpress_custom_settings', serialize($custom_settings)); + + update_option('feedwordpress_munge_permalink', $_REQUEST['munge_permalink']); + update_option('feedwordpress_use_aggregator_source_data', $_REQUEST['use_aggregator_source_data']); + update_option('feedwordpress_formatting_filters', $_REQUEST['formatting_filters']); + + if (isset($post['resolve_relative'])) : + update_option('feedwordpress_resolve_relative', $post['resolve_relative']); + endif; + if (isset($post['munge_comments_feed_links'])) : + update_option('feedwordpress_munge_comments_feed_links', $post['munge_comments_feed_links']); + endif; + if (isset($_REQUEST['feed_comment_status']) and ($_REQUEST['feed_comment_status'] == 'open')) : + update_option('feedwordpress_syndicated_comment_status', 'open'); + else : + update_option('feedwordpress_syndicated_comment_status', 'closed'); + endif; + + if (isset($_REQUEST['feed_ping_status']) and ($_REQUEST['feed_ping_status'] == 'open')) : + update_option('feedwordpress_syndicated_ping_status', 'open'); + else : + update_option('feedwordpress_syndicated_ping_status', 'closed'); + endif; + + if (isset($post['syndicated_post_type'])) : + update_option('feedwordpress_syndicated_post_type', $post['syndicated_post_type']); + endif; + endif; + parent::save_settings($post); + } + + /** + * Outputs "Publication" settings box. + * + * @since 2009.0713 + * @param object $page of class FeedWordPressPostsPage tells us whether this is + * a page for one feed's settings or for global defaults + * @param array $box + * + * @uses FeedWordPressPostsPage::these_posts_phrase() + * @uses FeedWordPress::syndicated_status() + * @uses SyndicatedLink::syndicated_status() + * @uses SyndicatedPost::use_api() + */ + /*static*/ function publication_box ($page, $box = NULL) { + $thesePosts = $page->these_posts_phrase(); + $postSelector = array( + 'publish' => "Publish %s immediately", + 'pending' => "Hold %s for review; mark as Pending", + 'draft' => "Save %s as drafts", + 'private' => "Save %s as private posts", + ); + $labels = array(); + foreach ($postSelector as $index => $value) : + $postSelector[$index] = sprintf(__($value), $thesePosts); + $labels[$index] = __(str_replace(' %s', '', strtolower(strtok($value, ';')))); + endforeach; + + $params = array( + 'input-name' => 'feed_post_status', + 'setting-default' => NULL, + 'global-setting-default' => 'publish', + 'labels' => $labels, + ); + + // Hey ho, let's go... + ?> + <table id="syndicated-publication-form" class="edit-form narrow"> + <tr><th scope="row"><?php _e('New posts:'); ?></th> + <td><?php + $this->setting_radio_control( + 'post status', 'syndicated_post_status', + $postSelector, $params + ); + ?> + </td></tr> + + <?php $page->updatedPosts->display(); ?> + </table> + + <?php + } /* FeedWordPressPostsPage::publication_box () */ + + /** + * Outputs "Formatting" settings box + * + * @since 2009.0713 + * @param object $page of class FeedWordPressPostsPage tells us whether this is + * a page for one feed's settings or for global defaults + * @param array $box + * + */ + function formatting_box ($page, $box = NULL) { + $thesePosts = $page->these_posts_phrase(); + + if ($page->for_feed_settings()) : + $formatting_filters = null; + $url = preg_replace('|/+$|', '', $page->link->homepage()); + else : + $formatting_filters = get_option('feedwordpress_formatting_filters', 'no'); + $url = 'http://example.com'; + endif; + ?> + <table class="edit-form narrow"> + <?php if (!is_null($formatting_filters)) : ?> + + <tr><th scope="row">Formatting filters:</th> + <td><select name="formatting_filters" size="1"> + <option value="no"<?php echo ($formatting_filters!='yes')?' selected="selected"':''; ?>>Protect syndicated posts from formatting filters</option> + <option value="yes"<?php echo ($formatting_filters=='yes')?' selected="selected"':''; ?>>Expose syndicated posts to formatting filters</option> + </select> + <p class="setting-description">If you have trouble getting plugins to work that are supposed to insert + elements after posts (like relevant links or a social networking + <q>Share This</q> button), try changing this option to see if it fixes your + problem.</p> + </td></tr> + + <?php endif; ?> + + <tr><th scope="row">Relative URIs:</th> + <td><p>If link or image in a syndicated post from <code><?php print $url; ?></code> + refers to a partial URI like <code>/about</code>, where should + the syndicated copy point to?</p> + + <?php + $options = array( + 'yes' => 'Resolve the URI so it points to <code>'.$url.'</code><br/><small style="margin-left: 2.0em;"><code>/contact</code> is rewritten as <code>'.$url.'/contact</code></small>', + 'no' => 'Leave relative URIs unchanged, so they point to this site<br/><small style="margin-left: 2.0em;"><code>/contact</code> is left as <code>/contact</code></small>', + ); + $params = array( + 'setting-default' => 'default', + 'global-setting-default' => 'yes', + 'labels' => array( + 'yes' => __('resolve relative URIs'), + 'no' => __('leave relative URIs unresolved'), + ), + 'default-input-value' => 'default', + ); + + $this->setting_radio_control( + 'resolve relative', 'resolve_relative', + $options, $params + ); + ?> + </td></tr> + + </table> + <?php + } /* FeedWordPressPostsPage::formatting_box() */ + + /** + * Output "Links" settings box + * + * @since 2009.0713 + * @param object $page of class FeedWordPressPostsPage tells us whether this is + * a page for one feed's settings or for global defaults + * @param array $box + * + */ + /*static*/ function links_box ($page, $box = NULL) { + $setting = array( + 'munge_permalink' => array( + 'yes' => __('The copy on the original website'), + 'no' => __('The local copy on this website'), + ), + ); + + if (!$page->for_feed_settings()) : + $use_aggregator_source_data = get_option('feedwordpress_use_aggregator_source_data'); + endif; + ?> + <table class="edit-form narrow"> + <tr><th scope="row">Permalinks point to:</th> + <td><?php + + $params = array( + 'setting-default' => 'default', + 'global-setting-default' => 'yes', + 'default-input-value' => 'default', + ); + $this->setting_radio_control( + 'munge permalink', 'munge_permalink', + $setting['munge_permalink'], $params + ); + ?> + + </td></tr> + + <?php if (!$page->for_feed_settings()) : ?> + <tr><th scope="row">Posts from aggregator feeds:</th> + <td><ul class="options"> + <li><label><input type="radio" name="use_aggregator_source_data" value="no"<?php echo ($use_aggregator_source_data!="yes")?' checked="checked"':''; ?>> Give the aggregator itself as the source of posts from an aggregator feed.</label></li> + <li><label><input type="radio" name="use_aggregator_source_data" value="yes"<?php echo ($use_aggregator_source_data=="yes")?' checked="checked"':''; ?>> Give the original source of the post as the source, not the aggregator.</label></li> + </ul> + <p class="setting-description">Some feeds (for example, those produced by FeedWordPress) aggregate content from several different sources, and include information about the original source of the post. + This setting controls what FeedWordPress will give as the source of posts from + such an aggregator feed.</p> + </td></tr> + <?php endif; ?> + </table> + + <?php + } /* FeedWordPressPostsPage::links_box() */ + + /** + * Output "Comments & Pings" settings box + * + * @since 2009.0713 + * @param object $page of class FeedWordPressPostsPage tells us whether this is + * a page for one feed's settings or for global defaults + * @param array $box + * + */ + /*static*/ function comments_and_pings_box ($page, $box = NULL) { + $whatsits = array( + 'comment' => array('label' => __('Comments'), 'accept' => 'Allow comments'), + 'ping' => array('label' => __('Pings'), 'accept' => 'Accept pings'), + ); + $onThesePosts = 'on '.$page->these_posts_phrase(); + + $mcflSettings = array( + "yes" => __('Point to comment feeds from the original website (when provided by the syndicated feed)'), + "no" => __('Point to local comment feeds on this website'), + ); + $mcflParams = array( + 'setting-default' => 'default', + 'global-setting-default' => 'yes', + 'labels' => array( + 'yes' => __('comment feeds from the original website'), + 'no' => __('local comment feeds on this website') + ), + 'default-input-value' => 'default', + ); + + $settings = array(); $params = array(); + foreach ($whatsits as $what => $how) : + $whatsits[$what]['default'] = FeedWordPress::syndicated_status($what, /*default=*/ 'closed'); + + // Set up array for selector + $settings[$what] = array( + 'open' => sprintf(__("{$how['accept']} %s"), __($onThesePosts)), + 'closed' => sprintf(__("Don't ".strtolower($how['accept'])." %s"), __($onThesePosts)), + ); + $params[$what] = array( + 'input-name' => "feed_${what}_status", + 'setting-default' => NULL, + 'global-setting-default' => FeedWordPress::syndicated_status($what, /*default=*/ 'closed'), + 'labels' => array( + 'open' => strtolower(__($how['accept'])), + 'closed' => strtolower(__("Don't ".$how['accept'])), + ), + ); + endforeach; + + // Hey ho, let's go... + ?> + <table class="edit-form narrow"> + <?php foreach ($whatsits as $what => $how) : ?> + + <tr><th scope="row"><?php print $how['label']; ?>:</th> + <td><?php + $this->setting_radio_control( + "$what status", "syndicated_${what}_status", + $settings[$what], $params[$what] + ); + ?></td></tr> + + <?php endforeach; ?> + + <tr><th scope="row"><?php _e('Comment feeds'); ?></th> + <td><p>When WordPress feeds and templates link to comments + feeds for <?php print $page->these_posts_phrase(); ?>, the + URLs for the feeds should...</p> + <?php + $this->setting_radio_control( + "munge comments feed links", "munge_comments_feed_links", + $mcflSettings, $mcflParams + ); + ?></td></tr> + </table> + + <?php + } /* FeedWordPressPostsPage::comments_and_pings_box() */ + + /*static*/ function custom_post_settings ($page = NULL) { + if (is_null($page)) : + $page = $this; + endif; + + if ($page->for_feed_settings()) : + $custom_settings = $page->link->setting("postmeta", NULL, array()); + else : + $custom_settings = get_option('feedwordpress_custom_settings'); + endif; + + if ($custom_settings and !is_array($custom_settings)) : + $custom_settings = unserialize($custom_settings); + endif; + + if (!is_array($custom_settings)) : + $custom_settings = array(); + endif; + + return $custom_settings; + } /* FeedWordPressPostsPage::custom_post_settings() */ + + /** + * Output "Custom Post Settings" settings box + * + * @since 2009.0713 + * @param object $page of class FeedWordPressPostsPage tells us whether this is + * a page for one feed's settings or for global defaults + * @param array $box + */ + /*static*/ function custom_post_settings_box ($page, $box = NULL) { + $custom_settings = FeedWordPressPostsPage::custom_post_settings($page); + ?> + <div id="postcustomstuff"> + <p>Custom fields can be used to add extra metadata to a post that you can <a href="http://codex.wordpress.org/Using_Custom_Fields">use in your theme</a>.</p> + <table id="meta-list" cellpadding="3"> + <tr> + <th>Key</th> + <th>Value</th> + <th>Action</th> + </tr> + + <?php + $i = 0; + foreach ($custom_settings as $key => $value) : + ?> + <tr style="vertical-align:top"> + <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo esc_html($key); ?>" /> + <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo esc_html($key); ?>" /></th> + <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo esc_html($value); ?></textarea></td> + <td width="10%"><select name="notes[<?php echo $i; ?>][action]"> + <option value="update">save changes</option> + <option value="delete">delete this setting</option> + </select></td> + </tr> + + <?php + $i++; + endforeach; + ?> + + <tr style="vertical-align: top"> + <th scope="row"><input type="text" size="10" name="notes[<?php echo $i; ?>][key1]" value="" /></th> + <td><textarea name="notes[<?php echo $i; ?>][value]" rows="2" cols="40"></textarea> + <p>Enter a text value, or a path to a data element from the syndicated item.<br/> + For data elements, you can use an XPath-like syntax wrapped in <code>$( ... )</code>.<br/> + <code>hello</code> = the text value <code><span style="background-color: #30FFA0;">hello</span></code><br/> + <code>$(author/email)</code> = the contents of <code><author><email><span style="background-color: #30FFA0">...</span></email></author></code><br/> + <code>$(media:content/@url)</code> = the contents of <code><media:content url="<span style="background-color: #30FFA0">...</span>">...</media:content></code></p> + </td> + <td><em>add new setting...</em><input type="hidden" name="notes[<?php echo $i; ?>][action]" value="update" /></td> + </tr> + </table> + </div> <!-- id="postcustomstuff" --> + + <?php + } /* FeedWordPressPostsPage::custom_post_settings_box() */ + + function custom_post_types_box ($page, $box = NULL) { + global $fwp_path; + + // local: syndicated post type // default NULL + // global: syndicated_post_type // default 'post' + // default-input-value => 'default' + + // Get all custom post types + $post_types = get_post_types(array( + '_builtin' => false, + ), 'objects'); + + $ul = array(); + $ul['post'] = __('Normal WordPress posts'); + foreach ($post_types as $post_type) : + $ul[$post_type->name] = __($post_type->labels->name); + endforeach; + + $params = array( + 'global-setting-default' => 'post', + 'default-input-value' => 'default', + ); + + // Hey, ho, let's go... + ?> + <table class="edit-form narrow"> + <tbody> + <tr><th><?php _e('Custom Post Types:'); ?></th> + <td><p>Incoming syndicated posts should be stored in the + posts database as...</p> + <?php + $this->setting_radio_control( + 'syndicated post type', 'syndicated_post_type', + $ul, $params + ); + ?> + </td></tr> + </tbody> + </table> + <?php + } /* FeedWordPressPostsPage::custom_post_types_box() */ + + function display () { + $this->boxes_by_methods = array( + 'publication_box' => __('Syndicated Posts'), + 'links_box' => __('Links'), + 'formatting_box' => __('Formatting'), + 'comments_and_pings_box' => __('Comments & Pings'), + 'custom_post_settings_box' => __('Custom Post Settings (to apply to each syndicated post)'), + 'custom_post_types_box' => ('Custom Post Types (advanced database settings)'), + ); + + parent::display(); + } /* FeedWordPressPostsPage::display () */ +} + + $postsPage = new FeedWordPressPostsPage; + $postsPage->display(); + diff --git a/wp-content/plugins/feedwordpress/readme.txt b/wp-content/plugins/feedwordpress/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..ec5a052a0539a1d9a2bb7b5905e3608beeb01be5 --- /dev/null +++ b/wp-content/plugins/feedwordpress/readme.txt @@ -0,0 +1,1586 @@ +=== FeedWordPress === +Contributors: Charles Johnson +Donate link: http://feedwordpress.radgeek.com/ +Tags: syndication, aggregation, feed, atom, rss +Requires at least: 3.0 +Tested up to: 3.0.1 +Stable tag: 2010.0905 + +FeedWordPress syndicates content from feeds you choose into your WordPress weblog. + +== Description == + +* Author: [Charles Johnson](http://radgeek.com/contact) +* Project URI: <http://projects.radgeek.com/feedwordpress> +* License: GPL 2. See License below for copyright jots and tittles. + +FeedWordPress is an Atom/RSS aggregator for WordPress. It syndicates content +from feeds that you choose into your WordPress weblog; if you syndicate several +feeds then you can use WordPress's posts database and templating engine as the +back-end of an aggregation ("planet") website. It was developed, originally, +because I needed a more flexible replacement for [Planet][] +to use at [Feminist Blogs][]. + +[Planet]: http://www.planetplanet.org/ +[Feminist Blogs]: http://feministblogs.org/ + +FeedWordPress is designed with flexibility, ease of use, and ease of +configuration in mind. You'll need a working installation of WordPress or +WordPress MU (version [3.0] or later), and also FTP or SFTP access to your web +host. The ability to create cron jobs on your web host is helpful but not +required. You *don't* need to tweak any plain-text configuration files and you +*don't* need shell access to your web host to make it work. (Although, I should +point out, web hosts that *don't* offer shell access are *bad web hosts*.) + + [WordPress]: http://wordpress.org/ + [WordPress MU]: http://mu.wordpress.org/ + [3.0]: http://codex.wordpress.org/Version_3.0 + +== Installation == + +To use FeedWordPress, you will need: + +* an installed and configured copy of [WordPress][] or [WordPress MU][] + (version 3.0 or later). + +* FTP, SFTP or shell access to your web host + += New Installations = + +1. Download the FeedWordPress installation package and extract the files on + your computer. + +2. Create a new directory named `feedwordpress` in the `wp-content/plugins` + directory of your WordPress installation. Use an FTP or SFTP client to + upload the contents of your FeedWordPress archive to the new directory + that you just created on your web host. + +3. Log in to the WordPress Dashboard and activate the FeedWordPress plugin. + +4. Once the plugin is activated, a new **Syndication** section should + appear in your WordPress admin menu. Click here to add new syndicated + feeds, set up configuration options, and determine how FeedWordPress + will check for updates. For help, see the [FeedWordPress Quick Start][] + page. + +[FeedWordPress Quick Start]: http://feedwordpress.radgeek.com/wiki/quick-start + += Upgrades = + +To *upgrade* an existing installation of FeedWordPress to the most recent +release: + +1. Download the FeedWordPress installation package and extract the files on + your computer. + +2. Upload the new PHP files to `wp-content/plugins/feedwordpress`, + overwriting any existing FeedWordPress files that are there. + +3. Log in to your WordPress administrative interface immediately in order + to see whether there are any further tasks that you need to perform + to complete the upgrade. + +4. Enjoy your newer and hotter installation of FeedWordPress + +== Using and Customizing FeedWordPress == + +FeedWordPress has many options which can be accessed through the WordPress +Dashboard, and a lot of functionality accessible programmatically through +WordPress templates or plugins. For further documentation of the ins and +outs, see the documentation at the [FeedWordPress project homepage][]. + + [FeedWordPress project homepage]: http://feedwordpress.radgeek.com/ + +== Changelog == + += 2010.0905 = + +* BUGFIX: CATEGORIES AND TAGS CORRECTLY ASSIGNED IN AUTOMATIC UPDATES. + Version 2010.0903 switched over to a new way of assigning categories and + tags as part of its support for handling custom taxonomies. + Unfortunately, the method that it uses is subjected to some checks of + the current user's capabilities, which creates problems for posts that + are being inserted into the WordPress database when there *is* no + current user logged in (as, for example, when an update is being carried + out from a cron job or automatic update). The result was that posts + from cron jobs and automatic updates ended up with no Categories and no + Tags being assigned. This bug has now been fixed: in 2010.0905, Tags and + Categories should be correctly assigned to all posts, regardless of + whether they were added from manual updates, cron jobs, or automatic + updates. + += 2010.0903 = + +* WORDPRESS 3 REQUIRED: Please note that this release of FeedWordPress + *requires* WordPress 3.0 or later. If you are currently using a 2.x + branch of WordPress, you will need to upgrade to WordPress 3 before you + can successfully upgrade FeedWordPress. + +* BUGFIX: NO MORE DISAPPEARING "SYNDICATED SOURCES" PANEL; INTERNET + EXPLORER UI GLITCH APPARENTLY FIXED: Several users independently + reported a problem with FWP 2010.0623 and various versions of IE. A + problem with the HTML markup caused IE (but not Firefox or + Chrome) to completely hide the Syndicated Sources administration panel + (the main list of currently-syndicated sources, and the main location + for adding new sources, under the Syndication menu item) when a user + added their first syndicated feed. Maddeningly, the glitch seemed to + affect some IE users and not others: I was never able to reproduce the + problem for myself on my own machines. However, the markup of Syndicated + Sources has undergone significant changes and corrections since + 2010.0623, and two independent sources who had been having this problem + confirm that they no longer encounter it with the updated version. For + the time being, I am going to declare this bug squashed. + +* BUGFIX: MORE PROTECTION AGAINST FATAL ERRORS FROM PLUGGABLE VERSIONS OF + SimplePie: FeedWordPress now takes some precautions that should help to + better avoid conflicts for users who have installed pluggable versions + of SimplePie for another plugin or theme. (You may not know that you have + done this; but if you've been encountering fatal errors indicating that + you cannot redeclare class SimplePie, or something along those lines, + there is now a better chance that those fatal errors will be eliminated. + +* PERFORMANCE: SIGNIFICANTLY REDUCED MEMORY CONSUMPTION FOR LARGE UPDATES: + FeedWordPress is still a memory-hungry little module, especially when + you are dealing with very large feeds. However, users should notice a + significant reduction in memory overloads, especially if they update a + large number of feeds at once. + +* USER INTERFACE IMPROVEMENTS: Nothing is radically different, but there's + been a fair amount of extra spit and polish added, including a convenient + new Dashboard widget that may save you a trip to the Syndication menu, + a lot of effort to make the relationship between global and feed-by-feed + settings more obvious to the user and more easily controllable, to make + navigation between settings pages easier, to sand off a few rough edges, + and to make other improvements on the margins. I hope you'll like how + it looks. + +* ADDING MULTIPLE FEEDS: FeedWordPress now provides a convenient mode for + adding multiple feeds at once, using either a copy-and-pasted list, or + else an OPML file. Go to Syndication --> Syndicated Sources and check + out the two new buttons underneath the New Source input box. When you + have to add a number of feeds at once, this can save you considerable + time and trouble. + +* IMPROVED HANDLING OF AUTHORS WITH DUPLICATE E-MAIL ADDRESS AND AUTHORS + WITH NAMES WRITTEN IN FOREIGN SCRIPTS: WordPress 3 is increasingly picky + about what it will accept for new author accounts, and some of the + conditions it imposes can cause error conditions that prevent posts from + being properly syndicated, or properly attributed, if authors happen to + have identical e-mail addresses, or if users are given usernames that are + written in non-Western scripts. FeedWordPress now handles these much + better, and systematically works to avoid clashes between syndicated + authors' account names or in their e-mail addresses, which should result + in significantly better results in mapping author names to WordPress + user accounts. + +* MAPPING CATEGORIES ON SYNDICATED POSTS TO TAGS NOW BETTER SUPPORTED: + In previous versions, the only way for the Categories provided by a + syndicated feed to be mapped into Post Tags was to instruct FWP to + create new tags, rather than new categories, for unfamiliar categories + from the feed. This works fine if you want tags to be the default; but + if you want only a *specific* set of tags, there was no way to get them + without getting most or all other categories imported as tags. You can + now do this by creating a tag (under Posts ==> Post Tags) before + importing the post; when the syndicated category matches a pre-existing + tag, the incoming post will be tagged with that tag, without creating + a local Post Category. + +* REL-TAG MICROFORMAT SUPPORT FOR INLINE TAGS: Syndicated posts that + contain inline tags, marked up using the Rel-Tag microformat + <http://microformats.org/wiki/rel-tag>, are now tagged with the tags + provided by Rel-Tag format links. + +* MUCH GREATER CONTROL OVER CATEGORY AND TAG MAPPING: This is partly the + result of building in support for a potentially endless set of custom + taxonomies (see below), but in general there has been a great deal of + effort towards giving you more control over how categories and tags + provided by the feed are mapped into terms on the local blog. In + particular, you can now force FeedWordPress to create only categories + from categories and tags provided by the feed; or to create only tags; + or to search both categories and tags for a match; or you can simply + force it to drop all of the categories provided by the feed and use + only categories or tags that you explicitly provide. In addition, you + can now also choose whether to override global categories settings with + a local, feed-specific setting; or whether to *add together* *both* the + global categories and the local feed-specific categories -- depending + on whatever your use-case may demand. + +* CUSTOM POST TYPES AND TAXONOMY SUPPORTS: This is mainly for the + super-geeky, but if you use other plugins or themes that make + significant use of WordPress's support for custom post types and custom + taxonomies, you may be pleased to find that FeedWordPress now allows you + to feed incoming posts into any custom feed type that you wish, and to + map categories and tags from the feed to custom taxonomies as well as + to the standard Category and Tag taxonomies. + +* STORING NAMESPACED CUSTOM FEED ELEMENTS IN POST CUSTOM FIELDS: If you + would like to use FeedWordPress's support for storing custom meta-data + from feed elements in the custom fields for a post (for example, to + store geolocation data or iTunes media meta-data), you'll find that it's + now much easier for you to access these namespaced elements. You always + could access them, but in previous versions you might have to write + something ugly like $(/{http://www.w3.org/2003/01/geo/wgs84_pos#}lat) + just to get at the value of a `<geo:lat>` tag. Now, as long as you use + the same mnemonic codes that the feed producer used, you should always + be able to write a nice, simple expression like $(/geo:lat) to get the + value of a <geo:lat> tag. + +* CUSTOM DIRECTORY STRUCTURE SUPPORT: if you poke at it enough, WordPress + is relatively flexible about where it should store admin interface code, + uploaded content, plugins, and a number of other things that occupy an + important place in the WordPress directory structure. Previous versions + of FeedWordPress encountered serious errors or broke entirely when used + with directory structures other than the default. This should now be + fixed: FWP now supports custom directory structures wherever WordPress + allows them to be customized, rather than depending on the default + locations. Enjoy your freedom! + +* MANY NEW FILTERS AND API UTILITY FUNCTIONS FOR ADD-ON PROGRAMMERS: There + have been too many improvements to list them all in this ChangeLog, but + it means that much more power and ease for folks who are customizing + FeedWordPress through PHP filters or add-on modules. Fuller + documentation will be put up at the Wiki at feedwordpress.radgeek.org + soon. + += 2010.0623 = + +* WORDPRESS 3.0 COMPATIBILITY / AUTHOR MAPPING INTERFACE ISSUES: I + resolved a couple of outstanding issues with the author mapping + interface (Syndication --> Authors), which were preventing new users + from being created correctly and author mapping rules from being set up + correctly. These partly had to do with new restrictions on user account + creation introduced in WordPress 3.0; anyway, they should now be fixed. + +* MORE EFFICIENT SYNDICATED URL LOOKUPS: Several users noticed that the + bug fix introduced in 2010.0528 for compatibility with post-listing + plugins caused a lot more queries to the database in order to look up + numerical post IDs from the URL provided to the filter. This shouldn't + cause any major problems, but it is not as efficient as it could be; the + code now takes advantage of a more efficient way of doing things, + which usually will not require any additional database queries. + +* SIMPLEPIE FEED UPDATE ISSUES: If you have been having significant + problems with getting feeds to update correctly, this may be the result + of some bugs in the implementation of SimplePie caching that ships with + WordPress (as of version 3.0). (You would most commonly experience this + error if you continually saw errors such as "No feed found at <...>" in + your updates.) Fortunately, SimplePie allows for a great deal of + extensibility and this allows me to work around the problem; these + error conditions should now be mostly eliminated when the underlying + feed is valid. + +* UI: SHOW INACTIVE SOURCES: When you use the default unsubscribe option + -- which turns off the subscription to a feed while preserving the posts + from it and the syndication-related meta-data for the feed -- the + unsubscribed feed can now easily be viewed in a special "Inactive" + section of the Syndicated Sources page. (As a side benefit, if you've + accidentally, or only temporarily, turned off the subscription to a + feed, it is now much easier to restore the feed to being active, or to + delete it permanently, if you prefer. + +* UI: FEED FINDER / SWITCH FEED INTERFACE IMPROVEMENTS: changes to styling + and options for the feed finder / switch feed, which should now make it + easier, in some cases, to find alternative feeds, and make interface + options more clearly visible. + +* FILTERS: `syndicated_item_published` and `syndicated_item_updated` NOW + PROPERLY AFFECT THE DATING OF POSTS. These filters used to affect some + date-related settings, but not others -- and, most importantly, not the + final date that is set for a post's publication or last-modified date + in the WordPress database. Now, they do affect that, as they should. + (Filters should receive, and return, a long integer, representing a Unix + epoch relative timestamp.) + +* MAGIC URL TO CLEAR THE CACHE: Suppose that you need to clear the feed + cache, for whatever reason; suppose, even, that you need to clear it on + a regular basis. One way you might do this is by logging into the + FeedWordPress administrative interface and going to Syndication --> + Performance. Another way you might do it, now, is to simply send an + HTTP request to a magic URL provided by FeedWordPress: if your blog is + at example.com, the URL would be <http://example.com/?clear_cache=1> + += 2010.0602 = + +* CATEGORY BOX INTERFACE ELEMENT FIXED FOR WP 3.0: Stylesheet changes + between WordPress 2.9.x and the WordPress 3.0 RC caused the Categories + box under **Syndication --> Categories & Tags** to malfunction. This + has been fixed. + +* LINK CATEGORY SELECTION BOX IN SYNDICATION ==> FEEDS FIXED FOR WP 2.8 + AND 2.9: A WP 3.0 compatibility change introduced in 2010.0531 + inadvertently broke the Syndicated Link Category selector under + Syndication --> Feeds & Updates in WP 2.8 and WP 2.9, causing the post + categories to be displayed in the selector rather than the link + categories. This should now be fixed so that the selector will work + correctly under both the current versions of WordPress and the 3.0 RC. + +* MORE PERMISSIVE HANDLING OF FEEDS WITH BAD CONTENT-TYPE HEADERS: One of + the small advantages that MagpieRSS had over SimplePie is that it was + more tolerant about parsing well-formed feeds that the remote web server + happened to deliver with weird or incorrect HTTP Content-type headers. + In feeds affected by this problem, the new SimplePie parser would simply + fail to find a feed, due to its being led astray by the contents of the + Content-type header. This version includes an extension to SimplePie's + content-type sniffer that offers more permissive handling of the HTTP + headers. + +* MORE FULL-TEXT "EXCERPTS" NOW PROPERLY SHORTENED. Version 2010.0528 + introduced code to control for cases in which elements intended for + item summaries are (ill-advisedly) used to carry the full text of posts; + past versions of FeedWordPress would simply include the full text of the + post in the excerpt field, but newer versions now attempt to detect + this condition when it arises and to head it off, by blanking out the + excerpt field and filling it with an automatically generated short, + plain text excerpt from the full content. This release broadens the + test conditions that indicate when an excerpt field is treated as + identical to the full text of the post, and should therefore improve + the handling of some feeds (such as Google Reader feeds) where the full + text of each post was still appearing in the excerpt field. + +* FILTERS: `syndicated_item_published` AND `syndicated_item_updated` + FILTERS NOW ALLOW FILTER AUTHORS TO CHANGE POST TIMESTAMPS. You can now + use the `syndicated_item_published` and `syndicated_item_updated` filter + hooks to write filters or add-ons which directly change the post date + and most-recently-updated timestamps on incoming syndicated posts. Props + to niska for pointing out where the filters needed to be applied in + order to change WordPress's internal timestamps for incoming posts. + += 2010.0531 = + +* PERMALINK / CUSTOM FIELDS PROBLEM RESOLVED: An issue in 2010.0528 caused + some posts to be imported without the proper syndication-related + meta-data being attached (thus causing permalinks to point back to the + aggregator website rather than to the source website, among other + problems). This problem has been resolved (and a fix has been applied + which will resolve the problem for any posts affected by this problem, + if the original post is recent enough to still be available on the feed). + +* UI: The "Back End" section has been split into two separate sections -- + "Performance" (dealing with caching, database index, and other + performance tweaks), and "Diagnostics" (dealing with debug mode, + update logging, and a number of new diagnostic tests which I will be + rolling out over the next few releases). + +* Several minor interface bug fixes and PHP warning notices eliminated. + += 2010.0528 = + +#### Compatibility #### + +* SIMPLEPIE IS NOW USED TO PARSE FEEDS; NO MORE MAGPIERSS UPGRADES NEEDED: + One of the biggest changes in this release is that FeedWordPress no + longer depends on MagpieRSS to parse feeds, and has switched to the much + more up-to-date and flexible SimplePie feed parser, which is included as + a standard part of WordPress versions 2.8 and later. Using SimplePie will + hopefully allow for better handling of feeds going further, and will + allow me greater flexibility in determining how exactly the feed parser + will operate. It also means that FeedWordPress no longer requires + special upgrades to the WordPress core MagpieRSS files, and should + eliminate quite a bit of complexity. + +* MAGPIERSS COMPATIBILITY LAYER FOR EXISTING FILTERS AND ADD-ONS: However, + I have also implemented a compatibility layer to ensure that existing + filters and add-ons for FeedWordPress which depended on the MagpieRSS + data format *should not be broken* by the switch to SimplePie. Going + forward, I recommend that new filters and add-ons be written to take + advantage of the SimplePie object representations of items, feeds, etc., + rather than the MagpieRSS arrays, but the MagpieRSS arrays will still + be available and older filters should continue to work as they have in + the past. + +* COMPATIBILITY WITH WORDPRESS 2.9.x and 3.0: This release has been tested + for the existing WordPress 2.9.x branch and with the upcoming release of + WordPress 3.0. Changes in the user interface JavaScript between WordPress + 2.8.x and WordPress 2.9 caused the tag box interface element to break in + the Syndication --> Categories & Tags settings page; changes in the API + functions for adding new authors caused fatal errors under certain + conditions in WordPress 3.0. These breakages have been fixed. + +* DROPPED LEGACY SUPPORT FOR WORDPRESS PRIOR TO 2.8: Because SimplePie is + not included with versions of WordPress prior to 2.8, I have chosen to + drop legacy support for WordPress versions 1.5 through 2.7. If you are + using FeedWordPress with a version of WordPress before 2.8, you will + have to upgrade your installation of WordPress in order to take + advantage of this release. + +* PHP 5.3 COMPATIBILITY: A couple of compatibility issues, which were + causing fatal errors amd ugly warnings for users of PHP 5.3, + have been eliminated. + +#### Features and Processing #### + +* INTERFACE REORGANIZATION: The interface restructuring, began with + Version 2009.0612, has been completed. Catch-all settings pages have + been eliminated entirely for pages that cover each aspect of handling + a feed: Feeds & Updates, Posts & Links, Authors, Categories & Tags, + and Back End handling of the database and diagnostic information. + Extensive new interface hooks allow add-on modules to significantly + change or extend the FeedWordPress admin interface and workflow. + +* STORING INFORMATION FROM THE FEED IN CUSTOM FIELDS: Many users + have written to request the ability to store information from elements + in the feed in a custom field on each post. (So that, for example, if + post includes a `itunes:duration` element, you could store the contents + in a Custom Field called `duration` on the post (for a Theme to access + later). The Custom Post Settings under Syndication --> Posts & Links now + allow you to access any item or feed tag, using a syntax similar to + a much-simplified version of XPath. See Posts & Links settings for + details. + +* UPDATE-FREEZING ON MANUALLY EDITED POSTS: FeedWordPress now allows you + to mark posts that have been manually edited, so that the changes you + make will not be overwritten by later updates from the feed. If you make + manual edits to a particular post, just check the "Manual editing" + checkbox in order to protect your changes from being overwritten. If you + want to block *all* posts from being updated after they are imported + for the first time, a new "Updated Posts" setting in Posts & Links + allows you to freeze all posts from a particular feed, or all syndicated + posts. + +* SETTING: FEED-BY-FEED SETTINGS FOR WHERE PERMALINKS POINT TO: You've + always been able to tell FeedWordPress whether permalinks for posts + should point to the original source of the story or the local copy. Now + you can choose different policies for different feeds, instead of one + global policy for all feeds. (Of course, you can still use a global + default if you prefer.) + +* SETTING: USER CONTROL OVER TIMING BASIS. You can now determine the + schedule on which feeds are considered ready to poll for updates -- + by default feeds become ready for polling after about 1 hour. You can + now increase or decrease the time window under Syndication --> Feeds & + Updates. (However, please pay *CAREFUL ATTENTION* to the recommendations + and DO NOT set the scheduling lower than 60 minutes unless you are + ABSOLUTELY SURE that you have specific permission from webmaster who + provides that specific feed to poll more frequently than that. If you + set this too low (and about 60 minutes is the polite minimum if you + haven't been given a different figure), most webmasters will consider + the frequent hits on their server as rude, or even downright abusive. + +* OTHER SETTINGS: New settings also include the ability to stop FWP from + resolving relative URLs within syndicated content, and the ability to + choose whether FeedWordPress should indicate the comment feed from the + original source, or the local comment feed, when providing the comment + feed URL for a syndicated post. + +#### Parsing #### + +* BETTER DATE HANDLING -- FEWER FLASHBACKS TO 1969 and 1970: FeedWordPress + has made some bugfixes and some improvements in the logic for parsing + dates. This should allow FeedWordPress to correctly parse more dates in + more feeds; and, in the last resort, when FeedWordPress fails to + correctly parse a date, to fall back to a more intelligent default. This + should hopefully avoid most or all error conditions that have resulted + in articles being erroneously dated to the dawn of the Unix epoch + (31 December 1969 or 1 January 1970). + +* FULL-TEXT "EXCERPTS" NOW PROPERLY SHORTENED. Based on a straightforward + reading of the existing RSS specs, it's reasonable for the + rss:description element to be read as a plaintext summary or excerpt for + the item containing the description -- with the full text of the item, + if available, in another, better-suited element, such as the de facto + standard content:encoded extension element. The problem is that uses of + RSS rarely have much to do with anything like a straightforward reading + of the specs. As a result, many actual RSS producers in the wild put the + full text of the article in a description element. But since + FeedWordPress has treated this text as a summary, this produces + aggregated posts with lengthy "excerpts" containing the full text of the + article. This release of FeedWordPress fixes the problem by doing a + little digging before treating rss:description as a summary: if the + description element is used properly as a plain text summary, then + FeedWordPress will take the summary provided by the feed, rather than + recreating its own excerpt from the full text; but if an RSS item has no + full-text element other than description, FeedWordPress will treat the + description element as the full text of the article, and generate a + shortened excerpt automatically from that text. + +#### API #### + +* TEMPLATE API: new template tags `get_local_permalink()` and + `the_local_permalink()` allow you to access the permalink for a post on + your aggregator site, even when FeedWordPress is rewriting permalinks to + point to the original source site. + +* NEW HOOKS FOR ADD-ONS AND FILTERS: I have added a number of new hooks + which allow add-on modules to filter more precisely, gather information + at more points, and to enhance the FeedWordPress admin interface. For + a list of new hooks and documentation, see the FeedWordPress + documentation wiki at + <http://feedwordpress.radgeek.com/wiki/add-ons-and-filters> + +* FILTER API: A number of new utility methods have been added to the + SyndicatedPost class to make it easier for filters and add-ons to + +* FILTER API: Globals $fwp_channel and $fwp_feedmeta DEPRECATED. These + global variables, originally introduced to allow filters access to + information about the source feed in `syndicated_item` filters (which + were passed in through global variables rather than as parameters + because of a bug in WP 1.5 which was then fixed in 1.5.1) have been + DEPRECATED. If you have any filters or add-ons which still depend on + these global variables, you should see about fixing them to access data + about the source feed using the SyndicatedPost::link element instead. + For documentation, see the FeedWordPress documentation wiki at + <http://feedwordpress.radgeek.com/wiki/syndicatedpost> and + <http://feedwordpress.radgeek.com/wiki/syndicatedlink>. + +* DIAGNOSTICS: I've included a number of new diagnostic options and + messages, which should allow an experienced user to better investigate + any problems that may crop up. + +#### Bug Fixes #### + +* BUGFIX: & IN PERMALINKS NO LONGER CAUSING ATOM OR HTML VALIDATION + EFFORTS: Many users reported an issue in which syndicating a feed with + special XML characters in the URLs (& was the most common, since it is + used to separate HTTP GET parameters) would cause the aggregator's + feeds to produce invalid (malformed) XML. This update addresses the + issue in Atom feeds. Unfortunately, it has not been technically possible + to address the problem in RSS 2.0 feeds, due to limitations on + WordPress's internal templates for RSS feeds. + +* BUGFIX: BROKEN URLS IN "POPULAR POSTS" AND SIMILAR PLUGINS SHOULD NO + LONGER BE BROKEN. A number of users noticed an issue where plugins and + templates that listed posts in locations outside of the post loop + (for example, "Popular Posts"-style plugins that listed posts in the + sidebar), often produced the wrong URL for post links. (Typically, all + the posts listed would get the same wrong URL.) This should now be + fixed. Thanks to Björn for sending in a quick fix! + +* MINOR BUGFIXES: This release includes a number of fixes to minor bugs + and compatibility issues, including: silent failures of the "Syndicate" + button, "Illegal Offset Type" error messages from MagpieRSS, and others. + += 2009.0707 = + +* BUGFIX: WORDPRESS 2.8 AJAX COMPATIBILITY ISSUES RESOLVED (blank or + truncated "Syndicated Sites" administration page): Due to changes in the + AJAX interface elements between WordPress 2.7 and WordPress 2.8, several + FeedWordPress users encountered an issue where the front "Syndication" + page in the FeedWordPress administrative interface would come up blank, + without the normal "Syndicated Sites" list and "Update" control, or + sometimes wth the boxes visible but one or both of them truncated, with + only the title bar. This issue should now be resolved: with the new + version of FeedWordPress, the compatibility issue that caused the + disappearance should be eliminated, and if boxes are shown with only + their handle visible, you should once again be able to drop down the + rest of the box by clicking once on its title bar. + +* BUGFIX: TAG SETTING WIDGET FIXED. Due to changes in interface elements + between WordPress 2.7 and WordPress 2.8, people using FeedWordPress with + WordPress 2.8 found that the widget for setting tags to be applied to + all syndicated posts, or all syndicated posts from a particular feed, + no longer displayed "Add" and "Remove" buttons for individual tags. This + issue has now been fixed, and the tagging widget should once again work + more or less exactly like the tagging widget for individual posts in the + normal WordPress admin interface. + += 2009.0618 = + +* BUGFIX: MYSTERY ERRORS WITH WITH WP_Http_Fsockopen HTTP TRANSPORT + ELIMINATED: Thanks to a combination of a subtle bug in FeedWordPress, + and changes to the HTTP transport code in WordPress, a number of users + encountered an error in which any time they attempted to add a new feed + through the FeedFinder interface, FeedWordPress would fail and display + an HTTP request failure diagnostic message. The subtle bug has been + fixed, and with it, most of these errors should now be eliminated. + + Be sure to upgrade your MagpieRSS to the most recent MagpieRSS version + after you have insalled FeedWordPress 2009.0618, or this bug fix will + not take effect. + += 2009.0613 = + +* INTERFACE/BUGFIX: WORDPRESS 2.8 CATEGORY BOX FIX. Thanks to a subtle + change in class names between the WordPress 2.7 and 2.8 stylesheets, + category boxes in the FeedWordPress settings interface tended to overflow + and have a lot of messy-looking overlapping text under WordPress 2.8. + This has now been fixed. + +* FeedFinder FAILURE DIAGNOSTICS: When FWP's FeedFinder fails to find any + feeds at a given URL (for example, when you are trying to add a + subscription through the administrative interface and you run into an + error message), FeedWordPress now provides more diagnostic information + for the reasons behind the failure. If that helps you, great; if not, + it should help me respond more intelligently to your support request.. + += 2009.0612 = + +* WORDPRESS 2.8 COMPATIBILITY: FeedWordPress 2009.0612 has been tested for + compatibility with the recent version 2.8 release of WordPress. + +* INTERFACE RESTRUCTURING: In order to avoid settings posts from becoming + too crowded, and to modularize and better organize the user interface, + new "Posts" and "Categories & Tags" subpages have been created under the + "Syndication" menu. "Posts" controls settings for individal syndicated + posts (such as publication status, comment and ping status, whether or + not to use the original location of the post as the permalink, whether + or not to expose posts to formatting filters, and so on). "Categories & + Tags" controls settings for assigning new syndicated posts to categories + and tags, such as categories or tags to apply to all syndicated posts, + and how to handle categories that do not yet exist in the WordPress + database. These subpages, like the Authors subpage, handle settings for + the global default level and for individual syndicated feeds. + + Corresponding to these new subpages, the old Syndication Settings and + Feed Settings subpages have been cleaned up and simplified, and now only + link to the appropriate subpages for options that can be set in the + Posts, Authors, or Categories & Tags subpages. + +* FEATURE: ADD CUSTOM SETTINGS TO EACH SYNDICATED POST: FeedWordPress has + long had an interface for creating custom settings for each syndicated + *feed* which could be retrieved in templates using the `get_feed_meta()` + template function. But it had no feature for adding custom fields to + each individual syndicated *post*. In response to requests from users, I + have added the ability to apply custom fields to each individual + syndicated post, using the new Syndication --> Posts subpage. You can + set up custom fields to be applied to every syndicated post, or custom + fields to be applied to syndicated posts from a particular feed. + +* FEATURE: MAGPIERSS VERSION CHECK AND UPGRADE: FeedWordPress will attempt + to determine whether or not you are using the upgraded version of + MagpieRSS that comes packaged with FeedWordPress. If not, it will throw + an error on admin pages, and, if you are a site administrator, it will + give you the option to ignore the error message, or to attempt an + automatic upgrade (using a native file copy). If the file copy fails, + FeedWordPress will offer some guidance on how to perform the upgrade + manually. + +* BLANK POSTS PROBLEM NO LONGER OCCURS WITH OLD & BUSTED MAGPIERSS: Due + to the fact that I relied on a content normalization that occurs in my + upgraded version of MagpieRSS, but not in the old & busted version of + MagpieRSS that ships with WordPress, until this version, if you tried to + syndicate an Atom feed without having performed the (*strongly + recommended*) MagpieRSS upgrade, all of the posts would come up with + completely blank contents. That's not because MagpieRSS couldn't read + the data, but rather because the new Magpie version puts that data in a + location where the old version doesn't, and I was only looking in that + newer location. Now it checks for both, meaning that posts will continue + to display their contents even if you don't upgrade MagpieRSS. (But you + **really should** upgrade it, anyway.) + +* BUGFIX: RELATIVE URI RESOLUTION FOR POST CONTENT RESTORED. Some time + back, I added support for resolving relative URIs against xml:base on + feeds that support it to the MagpieRSS upgrade in FeedWordPress. Then I + took out code that did the same thing from the main FeedWordPress code. + Of course, the problem is that some people, even though it is clearly + stupid or evil to do so, still include relative URIs for images or links + in posts on feed formats that do *not* adequately support xml:base + (notably, RSS 2.0 feeds). In response to a user request, I have added + this functionality back in, so that MagpieRSS will resolve any relative + URIs that it knows how to resolve using xml:base, and then FeedWordPress + will attempt to resolve any relative URIs that are left over afterwards. + +* BUGFIX: INTERFACE OPTION FOR SETTING SYNDICATED POST PUBLICATION STATUS + ON A FEED-BY-FEED BASIS HAS BEEN RESTORED: Due to a version-checking + bug, users of WordPress 2.7.x lost an option from the "Edit a syndicated + feed" interface which allowed them to determine whether newly syndicated + posts should be published immediately, held as "Pending Review," saved + as drafts, or saved as private posts. (The option to change this + setting globally remained in place, but users could no longer set it on + a feed-by-feed basis.) The version-checking bug has been fixed, and the + option has been restored. + +* BUGFIX: "ARE YOU SURE?" FATAL ERROR ELIMINATED AND SECURITY IMPROVED: + Under certain circumstances (for example, when users have configured + their browser or proxy not to send HTTP Referer headers, for privacy or + other reasons), many features in the FeedWordPress administrative + interface (such as adding new feeds or changing settings) would hit a + fatal error, displaying only a cryptic message reading "Are you sure?" + and a blank page following it. This problem has been eliminated by + taking advantage of WordPress's nonce functions, which allow the + security check which ran into this error to work properly even without + receiving an HTTP Referer header. (N.B.: WordPress's nonce functions + were first introduced in WordPress 2.0.3. If you're using FeedWordPress + with an older version of WordPress, there's no fix for this problem: + you'll just need to turn Referer headers back on. Sorry.) + +* BUGFIX: MANUALLY-ALTERED POST STATUS, COMMENT STATUS, AND PING STATUS NO + LONGER REVERTED BY POST UPDATES: If you manually altered the post status, + comment status, or ping status of a syndicated post from what it was set + to when first syndicated -- for example, if you had a feed that was set + to bring in new posts as "Pending Review," and you then marked some of + the pending posts as "Published" and others as "Unpublished" -- then + in previous versions of FeedWordPress, these manual changes to the + status would be lost -- so that, for example, your Published or Unpublished + articles would revert to Pending Review -- if the source feed made any + upates to the item. This could make the Pending Review feature both + unreliable and also extremely frustrating to work with. The good news is + that this bug has since been fixed: if you manually update the status + of a post, it will no longer be reverted if or when the post is updated. + +* BUGFIX: OCCASIONAL FATAL ERROR ON UPDATE ELIMINATED: Under certain + limited conditions (specifically, when both the title and the content of + a post to be updated are empty), an attempt to update the post would + result in a fatal error. This has been fixed. + +* INTERFACE: "CONFIGURE SETTINGS" CONVENIENCE LINK ADDED TO CONFIRMATION + MESSAGE WHEN A NEW FEED IS ADDED: When you add a new subscription to + FeedWordPress, the message box that appears to confirm it now includes a + handy link to the feed's settings subpage, so that you can quickly set + up any special settings you may want to set up for the new feed, without + having to hunt through the list of all your other subscriptions to pick + out the new one. + +* INTERFACE: SIMPLIFYING AND CLARIFYING AUTOMATIC UPDATES SETTINGS. I have + removed an interval setting for the cronless automatic updates which has + confused many FeedWordPress users. In past versions of FWP, when you + turned on automatic updates, you would be presented with a time interval + setting which controlled how often FeedWordPress would check for feeds + ready to be polled for updates. (That is, it DID NOT control how often + feeds *would be polled*; it controlled how often FeedWordPress would + *check* for feeds that *had become ready to poll*. The schedule on which + feeds became ready for polling was still controlled either by requests + encoded in elements within the feed itself, or else according to an + internal calculation within FeedWordPress, averaging out to about 1 hour, + if the feed did not include any scheduling request elements.) Since many + users very often (and understandably) confused the purpose of this + setting, and since the setting is for a feature that's actually very + unlikely to require any manual control by the user, I have removed the + setting; FeedWordPress now simply uses the default value of checking for + feeds to poll every 10 minutes. + +* FEEDFINDER PERFORMANCE IMPROVEMENT: FeedWordPress's FeedFinder class + now uses `array_unique()` to make sure that it doesn't waste time + repeatedly iterating over and polling the same URI. Props to Camilo + (<http://projects.radgeek.com/2008/12/14/feedwordpress-20081214/#comment-20090122160414>). + += 2008.1214 = + +* WORDPRESS 2.7 COMPATIBILITY: FeedWordPress has been tested for + compatibility with the newly released WordPress 2.7. WordPress 2.7 has + deprecated the Snoopy library for HTTP requests, which caused a fatal + error for users who had not installed the MagpieRSS upgrade (or whose + installation of the MagpieRSS upgrade was overwritten by a recent update + of WordPress). FeedWordPress now handles things gracefully when Snoopy + is not immediately available. + +* INTERFACE SPIFFED UP: Interface elements have been updated so that + FeedWordPress's management interface fits in more naturally with the + WordPress 2.7 interface (including a new logo and a number of small + interface tweaks). + +* BUG WITH TAGS FOR SYNDICATED ARTICLES FIXED: Several users encountered a + bug with the option to add tags to all syndicated posts under + Syndication --> Settings -- if you told FeedWordPress to add more than + one tag to all syndicated posts, instead of doing so correctly, it would + add a *single* tag instead, whose name was composed of the names of all + the tags you asked it to add. This bug was the result of nothing more + dignified than a typographical error on my part. It has now been fixed. + +* MORE INFORMATION AVAILABLE WHEN FEEDWORDPRESS CAN'T FIND A FEED: When + you enter a URL for a new syndication source, FeedWordPress uses a + simple feed-finding algorithm (originally based on Mark Pilgrim's + Universal Feed Finder) to try to determine whether the URL is the URL + for a feed, or, if the URL points to an ordinary website rather than to + a feed, whether there is a feed for that website. All well and good, but + if FeedWordPress failed to find a feed, for whatever reason, it would + typically return nothing more than a nasty little note to the effect of + "no feed found," without any explanation of what went wrong. + FeedWordPress now keeps track of error conditions from the HTTP + requests that it uses in the course of looking for the feed, and so may + be able to give you a bit more information about the nature of the + problem if something goes wrong. + + += 2008.1105 = + +* INTERFACE RESTRUCTURING AND SYNDICATION --> AUTHORS PAGE: As a first + step towards modularizing and better organizing the user interface, a + new "Authors" subpage has been created under the Syndication menu, which + controls settings for syndicated authors, both at the global default + level and at level of individual syndicated feeds. + +* BUG RELATED TO THE ATTRIBUTION OF POSTS TO THE WRONG AUTHOR FIXED: Some + users encountered an issue in which posts by different authors on + different blogs -- especially blogs generated by Blogger -- were + mistakenly attributed to a single author. The problem was caused by the + way in which FeedWordPress matches syndicated authors to user accounts + in the WordPress database: normally, if two feeds each list an author + with the same e-mail address, they are counted as being the same person. + Normally this works well, but it creates an issue in cases where + blogging software assigns a single anonymous e-mail address to users who + do not want their real e-mail address published. This is, for example, + what Blogger does (by giving all users a default e-mail address of + <noreply@blogger.com> if they don't want their own e-mail address + listed). FeedWordPress now allows the user to correct for this problem + with a couple of new settings under **Syndication --> Authors**, which + allow users to turn off e-mail based author matching for particular + addresses, or, if desired, to turn it off entirely. By default, e-mail + based author matching is still turned on, but disabled for a list of + known generic e-mail addresses. Right now, the "list" consists entirely + of <noreply@blogger.com>; if you know other addresses that should be + added, please [contact me](http://radgeek.com/contact) to let me know. + + Please note that if you have already encountered this issue on your + blog, upgrading FeedWordPress will prevent it from re-occurring in the + future, but you still need to do two other things to fix the existing + problem on your blog. + + First, for each feed where posts have been mis-attributed, you need to + change the existing author mapping rules to re-map a a syndicated + author's name to the proper target account. Go to **Syndication --> + Authors**, select the feed you want to change from the drop-down list, + and then change the settings under the "Syndicated Authors" section. + (You will probably need to select "will be assigned to a new user..." to + create a new user account with the appropriate name.) + + Second, for each feed where posts have been mis-attributed, you need to + re-assign already-syndicated posts that were mis-attributed to the + correct author. You can do that from **Syndication --> Authors** by + using the author re-assignment feature, described below. + +* AUTHOR RE-ASSIGNMENT FOR A PARTICULAR FEED: The author settings page + for each syndicated feed, under **Syndication --> Authors**, now + includes an section titled "Fixing mis-matched authors," which provides + an interface for re-assigning or deleting all posts attributed to a + particular author on a particular feed. + +* SUPPORT FOR `<atom:source>` ELEMENT IN SYNDICATED FEEDS: Some feeds + (for example, those produced by FeedWordPress) aggregate content from + several different sources, and include information about the original + source of the post in an `<atom:source>` element. A new setting under + **Syndication --> Options** allows you to control what FeedWordPress + will report as the source of posts syndicated from aggregator feeds in + your templates and feeds: you can have FeedWordPress report that the + source of a post is the aggregator feed itself, or you can have it + report that the source of a post is the original source that the + aggregator originally syndicated the post from. + + By default, FeedWordPress will report the aggregator, not the original + source, as the source of a syndicated item. + +* LOAD BALANCING AND TIME LIMITING FEATURES FOR UPDATES: Some users have + encountered issues due to running up against PHP execution time limits + during the process of updating large syndicated feeds, or a very large + set of syndicated feeds. FeedWordPress now has a feature that allows you + to limit the total amount of time spent updating a feed, through the + "Time limit on updates" setting under **Syndication --> Options**. By + turning on this setting and adjusting the time limit to a low enough + figure to avoid your PHP installation's time-out setting. (PHP execution + time limits are usually in the vicinity of 30 seconds, so an update + time limit of 25 seconds or so should provide plenty of time for updates + while allowing a cushion of time for other, non-update-related functions + to do their work.) + + If feed updates are interrupted by the time limit, FeedWordPress uses + some simple load balancing features to make sure that updates to other + feeds will not be blocked by the time-hogging feed, and will also make + sure that when the interrupted update is resumed, FeedWordPress will + skip ahead to resume processing items at the point at which it was + interrupted last time, so that posts further down in the feed will + eventually get processed, and not get blocked by the amount of time it + takes to process the items higher up in the feed. + +* `guid` INDEX CREATION BUTTON: FeedWordPress frequently issues queries on + the `guid` column of the WordPress posts database (since it uses post + guid URIs to keep track of which posts it has syndicated). In very large + FeedWordPress installations, you can often significantly improve + performance by creating a database index on the `guid` column, but + normally you would need to poke around with MySQL or a tool like + phpMyAdmin to do this. FeedWordPress can now save you the trouble: to + create an index on the `guid` column, just go to + **Syndication --> Options**, and mash the button at the bottom of the + "Back End" section. + += 2008.1101 = + +* INTERFACE BUG THAT PREVENTED ADDING NEW SITES FIXED: The UI reforms in + FWP 2008.1030 unintentionally introduced a bug that prevents clean + installations of FeedWordPress from providing an input box for adding + new feeds to the list of syndicated feeds. This bug has been fixed. + += 2008.1030 = + +* WORDPRESS 2.6 COMPATIBILITY: FeedWordPress should now be compatible with + WordPress 2.6, and should work more or less seamlessly with the new post + revision system. A bug which caused multiple new revisions to be created + for posts on certain feeds, regardless of whether or not the item had + been updated, has been fixed. + +* INTERFACE IMPROVEMENTS: The user interface has been substantially + restyled to fit in better with the visual style of WordPress 2.5 and + 2.6. + +* YOUTUBE BUG FIXED: POSTS SYNDICATED THROUGH AN AUTOMATIC UPDATE ARE NO + LONGER STRIPPED OF `<OBJECT>` TAGS AND CERTAIN OTHER HTML ELEMENTS: Due + to the way that some versions of WordPress process posts that are + inserted into the database when no user is logged in, many users + experienced an issue where YouTube videos and other content using the + HTML `<object>` tag would be stripped out of posts that were syndicated + during an automatic update. (Posts that were syndicated through manual + updates from within the WordPress Dashboard were not affected, because + the issue does not arise when an update is executed under a logged-in + administrator's credentials.) This bug has now been fixed; YouTube + videos and other content using `<object>` tags should now appear + properly in syndicated posts, regardless of the way in which the post + was syndicated. + +* AJAX BUGS FIXED: Bugs which blocked the normal operation of WordPress + 2.5's AJAX interface elements when FeedWordPress was activated have been + fixed. + +* TAG SUPPORT: A couple of features have been introduced to take advantage + of the tagging features in WordPress 2.3.x, 2.5.x, and 2.6.x. Now, when + unfamiliar categories are encountered for posts on a feed, you can + choose for FeedWordPress (1) to drop the category; (2) to drop the + category and to filter out any post that does not match at least one + familiar category; (3) to create a new category with that name, or, + now, you can also have FeedWordPress (4) create a new *tag* with that + name. This option can be set site-wide under Syndication --> Options, + or it can be set on a feed-by-feed basis in a feed's Edit screen. + + In addition, you can now set particular tags to apply to all incoming + syndicated posts, under Syndication --> Options, or you can set tags + to apply to all incoming syndicated posts from a particular feed in that + feed's Edit screen. + +* FORMATTING FILTERS: There is a new option available under Syndication -> + Options which allows users to choose whether or not to expose syndicated + posts to being altered by formatting filters. By default, FeedWordPress + has always protected syndicated posts (which are already in display-ready + HTML when they are syndicated) from being reformatted by formatting + filters. However, this approach means that certain plugins which depend + on formatting filters (for example, to add "Share This" bars or relevant + links to the end of a post) are blocked from working on any syndicated + posts. If you want to use one of these plugins together with + FeedWordPress, you can now do so by changing the "Formatting Filters" + setting from "Protect" to "Expose." + +* `<atom:source>` ELEMENTS NOW INCLUDED IN ATOM FEED: Atom 1.0 provides + a standard method for aggregators to indicate information about the original source of + a syndicated post, using the `<atom:source>` element. FeedWordPress now + introduces standard `<atom:source>` elements including the title, homepage, and + feed URI of the source from which a syndicated post was syndicated. Cf. + <http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.source> + +* MODULARIZATION OF CODE: The code for different elements of FeedWordPress + has been broken out into several modules for easier inspection, + documentation, and maintenance of the code. + +* VERSIONING SCHEME CHANGED: FeedWordPress's feature set has proven stable + enough that it can now be removed from beta status; a good thing, since + I was very quickly running out of version numbers to use. New releases + of FeedWordPress will have version numbers based on the date of their + release. + += 0.993 = + +* WORDPRESS 2.5.1 COMPATIBILITY: FeedWordPress should now be compatible + with WordPress 2.5.1. + +* WORDPRESS 2.5 INTERFACE IMPROVEMENTS: FeedWordPress's Dashboard + interface has undergone several cosmetic changes that should help it + integrate better with the WordPress Dashboard interface in WordPress + version 2.5.x. + +* SYNDICATED POSTS CAN BE MARKED AS "PENDING REVIEW": WordPress 2.3 users + can now take advantage of WordPress's new "Pending Review" features for + incoming syndicated posts. Posts marked as "Pending Review" are not + published immediately, but are marked as ready to be reviewed by an + Administrator or Editor, who can then choose to publish the post or + hold it back. If you want to review syndicated posts from a particular + feed, or from all feeds, before they are posted, then use + Syndication --> Syndicated Sites --> Edit or Syndication --> Options to + change the settings for handling new posts. + +* AWARE OF NEW URI FOR del.icio.us FEEDS: Previous releases of + FeedWordPress already automatically split del.icio.us tags up + appropriately appropriately when generating categories. (del.icio.us + feeds smoosh all the tags into a single `<dc:subject>` element, + separated by spaces; FeedWordPress un-smooshes them into multiple + categories by separating them at whitespace.) Unfortunately, del.icio.us + recently broke the existing behavior by changing host names for their + feeds from del.icio.us to feeds.delicious.com. Version 0.993 accounts + for the new host name and un-breaks the tag splitting. + += 0.992 = + +* AUTHOR RE-MAPPING: FeedWordPress now offers considerable control over + how author names on a feed are translated into usernames within the + WordPress database. When a post by an unrecognized author comes in, + Administrators can now specify any username as the default username to + assign the post to by setting the option in Syndication --> Options + (formerly FeedWordPress only allowed you to assign such posts to user + #1, the site administrator). Administrators can also create re-mapping + rules for particular feeds (under Syndication --> Syndicated Sites --> + Edit), so that (for example) any posts attributed to "Administrator" + on the feed <http://praxeology.net/blog/feed/> will be assigned to + a user named "Roderick T. Long," rather than a user named + "Administrator." These settings also allow administrators to filter out + posts by particular users, and to control what will happen when + FeedWordPress encounters a post by an unrecognized user on that + particular feed. + +* BUG RELATED TO URIS CONTAINING AMPERSAND CHARACTERS FIXED: A bug in + WordPress 2.x's handling of URIs in Blogroll links created problems for + updating any feeds whose URIs included an ampersand character, such as + Google News RSS feeds and other feeds that have multiple parameters + passed through HTTP GET. If you experienced this bug, the most likely + effect was that FeedWordPress simply would not import new posts from a + feed when instructred to do so, returning a "0 new posts" response. In + other cases, it might lead to unpredictable results from feed updates, + such as importing posts which were not contained in the feed being + syndicated, but which did appear elsewhere on the same website. This bug + has, hopefully, been resolved, by correcting for the bug in WordPress. + += 0.991 = + +* WORDPRESS MU COMPATIBILITY: FeedWordPress should now be compatible with + recent releases of WordPress MU. Once FeedWordPress is made available + as a plugin, each individual blog can choose to activate FeedWordPress + and syndicate content from its own set of contributors. + +* DISPLAY OF MAGPIE WARNINGS: A number of MagpieRSS warnings or error + messages that were displayed when performing an automatic update are + no longer displayed, unless debugging parameters have been explicitly + enabled. + +* BUG RELATED TO INTERNATIONAL CHARACTERS IN AUTHOR NAMES FIXED: Due to a + subtle incompatability between the way that FeedWordPress generated new + user information, and the way that WordPress 2.0 and later added new + authors to the database, FeedWordPress might end up creating duplicate + authors, or throwing a critical error message, when it encountered + authors whose names included international characters. This + incompatability has now been fixed; hopefully, authors with + international characters in their names should now be handled properly. + +* `<media:content>` BUG IN MAGPIERSS FIXED: A bug in MagpieRSS's handling + of namespaced elements has been fixed. Among other things, this bug + caused items containing a Yahoo MediaRSS `<media:content>` element (such + as many of the feeds produced by wordpress.com) to be represented + incorrectly, with only a capital "A" where the content of the post + should have been. Feeds containing `<media:content>` elements should now + be syndicated correctly. + +* update_feedwordpress PARAMETER: You can now use an HTTP GET parameter + (`update_feedwordpress=1`) to request that FeedWordPress poll its feeds + for updates. When used together with a crontab or other means of + scheduling tasks, this means that you can keep your blog automatically + updated on a regular schedule, even if you do not choose to use the + cron-less automatic updates option. + +* Some minor interface-related bugs were also fixed. + + += 0.99 = + +Version 0.99 adds several significant new features, fixes some bugs, and +provides compatability with WordPress 2.2.x and 2.3.x. + +* WORDPRESS 2.2 AND 2.3 COMPATIBILITY: FeedWordPress should now be + compatible with WordPress version 2.2 and the upcoming WordPress + version 2.3. In particular, it has been tested extensively against + WordPress 2.2.3 and WordPress 2.3 Release Candidate 1. + +* AUTOMATIC UPDATES WITHOUT CRON: FeedWordPress now allows you to + automatically schedule checks for new posts without using external task + scheduling tools such as cron. In order to enable automatic updates, go + to **Syndication --> Options** and set "Check for new posts" to + "automatically." For details, see "Automatic Feed Updates" in + README.text. + + An important side-effect of the changes to the update system is that if + you were previously using the cron job and the `update-feeds.php` script + to schedule updates, you need to change your cron set-up. The old + `update-feeds.php` script no longer exists. Instead, if you wish to use + a cron job to guarantee updates on a particular schedule, you should + have the cron job fetch the front page of your blog (for example, by + using `curl http://www.zyx.com/blog/ > /dev/null`) instead of activating + the `update-feeds.php` script. If automatic updates have been enabled, + fetching the front page will automatically trigger the update process. + +* INTERFACE REORGANIZATION: All FeedWordPress functions are now located + under a top-level "Syndication" menu in the WordPress Dashboard. To + manage the list of syndicated sites, manually check for new posts on + one or more feeds, or syndicate a new site, you should use the main page + under **Syndication**. To change global settings for FeedWordPress, + you should use **Syndication --> Options**. + +* FILE STRUCTURE REORGANIZATION: Due to a combination of changing styles + for FeedWordPress plugins and lingering bugs in the FeedWordPress admin + menu code, the code for FeedWordPress is now contained in two different + PHP files, which should be installed together in a subdirectory of your + plugins directory named `feedwordpress`. (See README.text for + installation and upgrade instructions relating to the change.) + +* MULTIPLE CATEGORIES SETTING: Some feeds use non-standard methods to + indicate multiple categories within a single category element. (The most + popular site to do this is del.icio.us, which separates tags with a + space.) FeedWordPress now allows you to set an optional setting, for any + feed which does this, indicating the character or characters used to + divide multiple categories, using a Perl-compatible regular expression. + (In the case of del.icio.us feeds, FeedWordPress will automatically use + \s for the pattern without your having to do any further configuration.) + To turn this setting on, simply use the "Edit" link for the feed that + you want to turn it on for. + +* REGULAR EXPRESSION BUG FIXED: Eliminated a minor bug in the regular + expressions for e-mail addresses (used in parsing RSS `author` + elements), which could produce unsightly error messages for some users + parsing RSS 2.0 feeds. + +* DATE / UPDATE BUG FIXED: A bug in date handling was eliminated that may + have caused problems if any of (1) WordPress, or (2) PHP, or (3) your + web server, or (4) your MySQL server, has been set to use a different + time zone from the one that any of the others is set to use. If + FeedWordPress has not been properly updating updated posts, or has been + updating posts when there shouldn't be any changes for the update, this + release may solve that problem. + +* GOOGLE READER BUGS FIXED: A couple of bugs that made it difficult for + FeedWordPress to interact with Google Reader public feeds have been + fixed. Firstly, if you encountered an error message reading "There was a + problem adding the newsfeed. [SQL: ]" when you tried to add the feed, + the cause of this error has been fixed. Secondly, if you succeeded in + getting FeedWordPress to check a Google Reader feed, only to find that + the title of posts had junk squashed on to the end of them, that bug + has been fixed too. To fix this bug, you must install the newest version + of the optional MagpieRSS upgrade. + +* FILTER PARAMETERS: Due to an old, old bug in WordPress 1.5.0 (which was + what was available back when I first wrote the filter interface), + FeedWordPress has traditionally only passed one parameter to + syndicated_item and syndicated_post filters functions -- an array + containing either the Magpie representation of a syndicated item from + the feed, or the database representation of a post about to be inserted + into the WordPress database. If you needed information about the feed + that the item came from, this was accessible only through a pair of + global variables, $fwp_channel and $fwp_feedmeta. + + Since it's been a pretty long time since WordPress 1.5.0 was in + widespread usage, I have gone ahead and added an optional second + parameter to the invocation of the syndicated_item and syndicated_post + filters. If you have written a filter for FeedWordPress that uses either + of these hooks, you can now register that filter to accept 2 parameters. + If you do so, the second parameter will be a SyndicatedPost object, + which, among other things, allows you to access information about the + feed from which an item is syndicated using the $post->feed and the + $post->feedmeta elements (where $post is the name of the second + parameter). + + NOTE THAT THE OLD GLOBAL VARIABLES ARE STILL AVAILABLE, for the time + being at least, so existing filters will not break with the upgrade. + They should be considered deprecated, however, and may be eliminated in + the future. + +* FILTER CHANGE / BUGFIX: the array that is passed as the first argument + syndicated_post filters no longer is no longer backslash-escaped for + MySQL when filters are called. This was originally a bug, or an + oversight; the contents of the array should only be escaped for the + database *after* they have gone through all filters. IF YOU HAVE WRITTEN + ANY syndicated_post FILTERS THAT PRESUME THE OLD BEHAVIOR OF PASSING IN + STRINGS THAT ARE ALREADY BACKSLASH-ESCAPED, UPDATE YOUR FILTERS + ACCORDINGLY. + +* OTHER MINOR BUGFIXES AND INTERNAL CHANGES: The internal architecture of + FeedWordPress has been significantly changed to make the code more + modular and clean; hopefully this should help reduce the number of + compatibility updates that are needed, and make them easier and quicker + when they are needed. + += 0.981 = + +Version 0.981 is a narrowly targeted bugfix and compatibility release, whose +main purpose is to resolve a major outstanding problem: the incompatibility +between version 0.98 of WordPress and the recently released WordPress 2.1. + +* WORDPRESS 2.1 COMPATIBILITY: FeedWordPress is now compatible with + WordPress 2.1, as well as retaining its existing support for WordPress + 2.0 and 1.5. Incompatibilities that resulted in database warnings, fatal + errors, and which prevented FeedWordPress from syndicating new posts, + have been eliminated. + +* RSS-FUNCTIONS.PHP RENAMED TO RSS.PHP: if you use the upgraded MagpieRSS + replacement that's included with FeedWordPress, be sure to note that + there are now *two* files to upload from the `OPTIONAL/wp-includes` + subdirectory in order to carry out the upgrade: rss-functions.php and + rss.php. **It is necessary to upload both files**, due to a change in + the file naming scheme in WordPress 2.1, and it is necessary to do so + whether you are using WordPress 2.1 or not. If you only upload the + `rss-functions.php` file as in previous installations you will not have + a working copy of MagpieRSS; the rss.php file contains the actual code. + +* DATE BUG AFFECTING SOME PHP INSTALLATIONS RESOLVED: due to a subtle bug + in parse_w3cdtf(), some installations of PHP encountered problems with + FeedWordPress's attempt to date posts, which would cause some new posts + on Atom feeds to be dated as if they had apppeared in 1969 or 1970 + (thus, effectively, never appearing on front page at all). This bug in + the date handling should now be fixed. + +* PHP <?=...?> SHORT FORM ELIMINATED: some installations of PHP do not + allow the <?=...?> short form for printing PHP values, which was used + extensively in the FeedWordPress interface code. Since this could cause + fatal errors for users with the wrong installation of PHP, the short + form has been replaced with full PHP echo statements, and is no longer + used in FeedWordPress. + +* BETTER USER INTERFACE INTEGRATION WITH WORDPRESS 2.x: Some minor changes + have been made to help the FeedWordPress interface pages blend in better + with the user interface when running under WordPress 2.x. + +* GLOBAL CATEGORIES BUG RESOLVED: a bug that prevented some users from + setting one or more categories to apply to syndicated posts from all + feeds (using the checkbox interface under Options --> Syndication) has + been resolved. + += 0.98 = + +* WORDPRESS 2.0 COMPATIBILITY: This is a narrowly-targeted release to + solve a major outstanding problem. FeedWordPress is now compatible with + both WordPress 1.5 and WordPress 2.0. Incompatibilities that caused + fatal SQL errors, and a more subtle bug with off-kilter counts of posts + under a given category, have been resolved. FeedWordPress tests for + database schema using the global $wp_db_version variable (if null, then + we presume that we're dealing with WordPress 1.5). + + NOTE: I have **not** fully tested FeedWordPress with WordPress 2.0. + Further testing may reveal more bugs. However, you should now be able + to get at least basic FeedWordPress functionality up and running. + +* AUTHOR MATCHING: FeedWordPress tests several fields to see if it can + identify the author of the post as a user already in the WordPress user + database. In previous versions, it tested the user login, the nickname, + and tested for "aliases" listed in the Profile (see documentation). FWP + now also matches authors on the basis of e-mail address (*if* an e-mail + address is present). This is particularly helpful for formats such as + RSS 2.0, in which authors are primarily identified by e-mail addresses. + += 0.97 = + +* INSTALLATION PROCEDURE: Some of the changes between 0.96 and 0.97 + require upgrades to the meta-data stored by FeedWordPress to work + properly. Thus, if you are upgrading from 0.96 or earlier to 0.97, most + FeedWordPress operations (including updates and template functions) + WILL BE DISABLED until you run the upgrade procedure. Fortunately, + running the upgrade procedure is easy: just go to either Options --> + Syndication or Links --> Syndicated in the WordPress Dashboard and press + the button. + +* FEED FORMAT SUPPORT: Support has been added for the Atom 1.0 IETF + standard. Several other elements are also newly supported + (dcterms:created, dcterms:issued, dcterms:modified, dc:identifier, + proper support for the RSS 2.0 guid element, the RSS 2.0 author element, + the use of Atom author or Dublin Core dc:creator constructs at the feed + level to identify the author of individual items, etc.) + + N.B.: full support of several Atom 1.0 features, such as categories + and enclosures, requires you to install the optional rss-functions.php + upgrade in your wp-includes directory. + +* BUG FIX: Running `update-feeds.php` from command line or crontab + returned "I don't syndicate..." errors. It turns out that WordPress + sometimes tramples on the internal PHP superglobals that I depended on + to determine whether or not the script was being invoked from the + command line. This has been fixed (the variables are now checked + *before* WordPress can trample them). Note that `update-feeds.php` has + been thoroughly overhauled anyway; see below for details. + +* BUG FIX: Duplicate categories or author names. Fixed two bugs that could + create duplicate author and/or category names when the name contained + either (a) certain international characters (causing a mismatch between + MySQL and PHP's handling of lowercasing text), or (b) characters that + have a special meaning in regular expressions (causing MySQL errors when + looking for the author or category due to regexp syntax errors). These + should now be fixed thanks to careful escaping of names that go into + regular expressions and careful matching of lowercasing functions + (comparing results from PHP only to other results from PHP, and results + from MySQL only to other results from MySQL). + +* BUG FIX: Items dated December 31, 1969 should appear less often. The + function for parsing W3C date-time format dates that ships with + MagpieRSS can only correctly parse fully-specified dates with a + fully-specified time, but valid W3C date-time format dates may omit the + time, the day of the month, or even the month. Some feeds in the wild + date their items with coarse-grained dates, so the optional + `rss-functions.php` upgrade now includes a more flexible parse_w3cdtf() + function that will work with both coarse-grained and fully-specified + dates. (If parts of the date or the time are omitted, they are filled in + with values based on the current time, so '2005-09-10' will be dated to + the current time on that day; '2004' will be dated to this day and time + one year ago. + + N.B.: This fix is only available in the optional `rss-functions.php` + upgrade. + +* BUG FIX: Evil use of HTTP GET has been undone. The WordPress interface + is riddled with inappropriate (non-idempotent) uses of HTTP GET queries + (ordinary links that make the server do something with significant + side-effects, such as deleting a post or a link from the database). + FeedWordPress did some of this too, especially in places where it aped + the WordPress interface (e.g. the "Delete" links in Links --> + Syndicated). That's bad business, though. I've changed the interface so + that all the examples of improper side-effects that I can find now + require an HTTP POST to take effect. I think I got pretty much + everything; if there's anything that I missed, let me know. + + Further reading: [Sam Ruby 2005-05-06: This Stuff Matters](http://www.intertwingly.net/blog/2005/05/06/This-Stuff-Matters) + +* BUG FIX: Categories applied by `cats` setting should no longer prevent + category-based filtering from working. In FeedWordPress, you can (1) + apply certain categories to all syndicated posts, or all posts from + a particular feed; and (2) filter out all posts that don't match one + of the categories that are already in the WordPress database (allowing + for simple category-based filtering; just load up WordPress with the + categories you want to accept, and then tell FeedWordPress not to create + new ones). However, the way that (1) and (2) were implemented meant that + you couldn't effectively use them together; once you applied a known + category to all syndicated posts from a particular feed, it meant that + they'd have at least one familiar category (the category or categories + you were applying), and that would get all posts past the filter no + matter what categories they were originally from. + + Well, no longer. You can still apply categories to all syndicated posts + (using either Syndication --> Options, or the feed-level settings under + Links --> Syndicated). But these categories are not applied to the post + until *after* it has already passed by the "familiar categories" filter. + So now, if you want, you can do category filtering and *then* apply as + many categories as you please to all and only posts that pass the filter. + +* BUG FIX: Other minor typos and HTML gaffes were fixed along the way. + +* PERFORMANCE: get_feed_meta() no longer hits the database for information + on every call; it now caches link data in memory, so FeedWordPress only + goes to the database once for each syndicated link. This may + substantially improve performance if your database server resources + are tight and your templates make a lot of use of custom settings from + get_feed_meta(). + +* API CHANGE: Link ID numbers, rather than RSS URIs, are now used to + identify the feed from which a post is syndicated when you use template + functions such as get_feed_meta(). The practical upshot of this is you + can switch feeds, or change the feed address for a particular syndicated + site, without breaking your templates for all the posts that were + syndicated from the earlier URI. + +* API CHANGE: if you have plugins or templates that make use of the + get_feed_meta() function or the $fwp_feedmeta global, note that the + data formerly located under the `uri` and `name` fields is now located + under the `link/uri` field and the `link/name` field, respectively. Note + also that you can access the link ID number for any given feed under the + global $fwp_feedmeta['link/id'] (in plugins) or + get_feed_meta('link/id') (in a template in post contexts). + +* FEATURE: the settings for individual feeds can now be edited using a + humane interface (where formerly you had to tweak key-value pairs in the + Link Notes section). To edit settings for a feed, pick the feed that you + want under Links --> Syndicated and click the Edit link. + +* FEATURE: The "Unsubscribe" button (formerly "Delete") in Links --> + Syndicated now offers three options for unsubscribing from a feed: (1) + turning off the subscription without deleting the feed data or affecting + posts that were syndicated from the feed (this works by setting the Link + for the feed as "invisible"); (2) deleting the feed data and all of the + posts that were syndicated from the feed; or (3) deleting the feed data + and *keeping* the posts that were syndicated from the feed + setting the Link to "Invisible" (meaning that it will not be displayed + in lists of the site links on the front page, and it won't be checked + for updates; (2) deleting the Link and all of the posts that were + syndicated from its feed; or (3) deleting the feed data but keeping the + posts that were syndicated (which will henceforward be treated as if + they were local rather than syndicated posts). (Note that (1) is usually + the best option for aggregator sites, unless you want to clean up the + results of an error or a test.) + +* FEATURE / BUG FIX: If you have been receiving mysterious "I don't + syndicate...", or "(local) HTTP status code was not 200", or "(local) + transport error - could not open socket", or "parse error - not well + formed" errors, then this update may solve your problems, and if it does + *not* solve them, it will at least make the reasons for the problems + easier to understand. That's because I've overhauled the way that + FeedWordPress goes about updating feeds. + + If you use the command-line PHP scripting method to run scheduled + updates, then not much should change for you, except for fewer + mysterious errors. If you have done updates by sending periodic HTTP + requests to <http://your-blog.com/path/wp-content/update-feeds.php>, + then the details have changed somewhat; mostly in such a way as to make + things easier on you. See the README file or online documentation on + Staying Current for the details. + +* FEATURE: FeedWordPress now features a more sophisticated system for + timed updates. Instead of polling *every* subscribed feed for updates + *each* time `update-feeds.php` is run, FeedWordPress now keeps track of + the last time it polled each feed, and only polls them again after a + certain period of time has passed. The amount of time is normally set + randomly for each feed, in a period between 30 minutes and 2 hours (so + as to stagger updates over time rather than polling all of the feeds at once. However, the length of time between updates can also be set + directly by the feed, which brings us to ... + +* FEATURE: FeedWordPress now respects the settings in the `ttl` and + Syndication Module RSS elements. Feeds with these elements set will not + be polled any more frequently than they indicate with these feeds unless + the user manually forces FeedWordPress to poll the feed (see Links --> + Syndicated --> Edit settings). + += 0.96 = + +* FEATURE: support has been added for enclosures in RSS 2.0 and Atom + 0.6+ newsfeeds. WordPress already supports adding enclosures to an + item; FeedWordPress merely gets the information on the enclosure + from the feed it is syndicating and plugs that information directly + into the WordPress database so that (among other things) that post + will have its enclosure listed in your blog's RSS 2 newsfeed. + + Note that enclosure support requires using the optional MagpieRSS + upgrade (i.e., replacing your `wp-includes/rss-functions.php` with + `OPTIONAL/wp-includes/rss-functions.php` from the FWP archive) + +* FEATURE: for completeness's sake, there is now a feed setting, + `hardcode url`, that allows you to set the URI for the front page + of a contributor's website manually (that is, prevent it from being + automatically updated from the feed channel link on each update). To + set the URI manually, put a line like this in the Link Notes section + of a feed: + + hardcode url: yes + + You can also instruct FeedWordPress to use hardcoded URIs by default + on all feeds using Options --> Syndication + +* FEATURE: by default, when FeedWordPress finds new syndicated posts, + it (1) publishes them immediately, (2) turns comments off, and (3) + turns trackback / pingback pings off. You can now alter all three + default behaviors (e.g., to allow pings on syndicated posts, or to + send newly-syndicated posts to the draft pile for moderation) using + Options --> Syndication + + += From 0.91 to 0.95 = + +* BUG FIX: Fixed an obscure bug in the handling of categories: + categories with trailing whitespace could cause categories with + duplicate names to be created. This no longer happens. While I was + at it I tightened up the operation of + FeedWordPress::lookup_categories() a bit in general. + +* FEATURE DEPRECATED: the feed setting `hardcode categories` is now + deprecated in favor of `unknown categories` (see below), which + allows you to strip off any syndication categories not already in + your database using `unknown categories: default` or `unknown + categories: filter`. If you have `hardcode categories: yes` set on a + feed, this will be treated as `unknown categories: default` (i.e., + no new categories will be added, but if a post doesn't match any of + the categories it will be added in the default category--usually + "Uncategorized" or "General"). + +* FEATURE: You can now set global defaults as to whether or not + FeedWordPress will update the Link Name and Link Description + settings for feeds automatically from the feed title and feed + tagline. (By default, it does, as it has in past versions.) Whether + this behavior is turned on or off, you can still override the + default behavior using feed settings of `hardcode name: yes`, + `hardcode name: no`, `hardcode description: yes`, or `hardcode + description: no`. + +* FEATURE: Users can now provide one or several "aliases" for an + author, just as they can for a category. For example, to make + FeedWordPress treat posts by "Joseph Cardinal Ratzinger" and "Pope + Benedict XVI" as by the same author, edit the user profile for Pope + Benedict XVI and add a line like this to the "User profile" field: + + a.k.a.: Joseph Cardinal Ratzinger + + You can add several aliases, each on a line by itself. You can also + add any other text you like to the Profile without interfering with + the aliases. + +* FEATURE: Users can now choose how to handle syndicated posts that + are in unfamiliar categories or by unfamiliar authors (i.e., + categories or authors whose names are not yet in the WordPress + database). By default, FeedWordPress will (as before) create a new + category (or new author) and use it for the current post and any + future posts. This behavior can be changed, either for all feeds or + for one or another particular feed. + + There are now three different options for an unfamiliar author: (1) + FeedWordPress can create a new author account and attribute the + syndicated post to the new account; (2) FeedWordPress can attribute + the post to an author if the author's name is familiar, and to a + default author (currently, this means the Site Administrator + account) if it is not; (3) FeedWordPress can drop posts by + unfamiliar authors and syndicate only posts by authors who are + already in the database. + + There are, similarly, two different options for an unfamiliar + category: (1) FeedWordPress can create new categories and place the + syndicated post in them; (2) FeedWordPress can drop the unfamiliar + categories and place syndicated posts only in categories that it is + already familiar with. In addition, FeedWordPress 0.95 lets you + choose whether posts that are in *no* familiar categories should be + syndicated (and placed in the default category for the blog) or + simply dropped. + + You can set the default behavior for both authors and categories + using the settings in Options --> Syndication. You can also set + different behavior for specific feeds by adding the `unfamiliar + author` and / or `unfamiliar categories` settings to the Link Notes + section of a feed: + + unfamiliar author: (create|default|filter) + unfamiliar categories: (create|default|filter) + + A setting of `unfamiliar author: create` will make FeedWordPress + create new authors to match unfamiliar author names *for this feed + alone*. A setting of `unfamiliar author: default` will make it + assign posts from unfamiliar authors to the default user account. A + setting of `unfamiliar author: filter` will cause all posts (from + this feed alone) to be dropped unless they are by an author already + listed in the database. Similiarly, `unfamiliar categories: create` + will make FeedWordPress create new categories to match unfamiliar + category names *for this feed alone*; `unfamiliar categories: + default` will cause it to drop any unfamiliar category names; and + `unfamiliar categories: filter` will cause it to *both* drop any + unfamiliar category names *and* to only syndicate posts that are + placed in one or more familiar categories. + + These two new features allow users to do some coarse-grained + filtering without having to write a PHP filter. Specifically, they + offer an easy way for you to filter feeds by category or by author. + Suppose, for example, that you only wanted to syndicate posts that + your contributors place in the "Llamas" category. You could do so by + setting up your installation of WordPress so that the only category + in the database is "Llamas," and then use Options --> Syndication to + set "Unfamiliar categories" to "don't create new categories and + don't syndicate posts unless they match at least one familiar + category". Now, when you update, only posts in the "Llamas" category + will be syndicated by FeedWordPress. + + Similarly, if you wanted to filter one particular feed so that only + posts by (for example) the author "Earl J. Llama" were syndicated to + your site, you could do so by creating a user account for Earl J. + Llama, then adding the following line to the settings for the feed + in Link Notes: + + unfamiliar author: filter + + This will cause any posts from this feed that are not authored by + Earl J. Llama to be discarded, and only the posts by Earl J. Llama + will be syndicated. (If the setting is used on one specific feed, it + will not affect how posts from other feeds are syndicated.) + +== License == + +The FeedWordPress plugin is copyright © 2005-2010 by Charles Johnson. It uses +code derived or translated from: + +- [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](kellan@protest.net) +- [MagpieRSS][] by [Kellan Elliot-McCrea](kellan@protest.net) +- [Ultra-Liberal Feed Finder][] by [Mark Pilgrim](mark@diveintomark.org) +- [WordPress Blog Tool and Publishing Platform](http://wordpress.org/) + +according to the terms of the [GNU General Public License][]. + +This program is free software; you can redistribute it and/or modify it under +the terms of the [GNU General Public License][] as published by the Free +Software Foundation; either version 2 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + + [wp-rss-aggregate.php]: http://laughingmeme.org/archives/002203.html + [MagpieRSS]: http://magpierss.sourceforge.net/ + [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/ + [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/ + [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html + diff --git a/wp-content/plugins/feedwordpress/syndicatedlink.class.php b/wp-content/plugins/feedwordpress/syndicatedlink.class.php new file mode 100644 index 0000000000000000000000000000000000000000..784071308e949f36666da864ef590cc86d4742b8 --- /dev/null +++ b/wp-content/plugins/feedwordpress/syndicatedlink.class.php @@ -0,0 +1,743 @@ +<?php +# 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. + +require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php'); + +class SyndicatedLink { + var $id = null; + var $link = null; + var $settings = array (); + var $simplepie = null; + 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): + $pair = explode(": ", $note, 2); + $key = (isset($pair[0]) ? $pair[0] : null); + $value = (isset($pair[1]) ? $pair[1] : null); + if (!is_null($key) and !is_null($value)) : + // 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` and `unfamiliar categories` are deprecated in favor of `unfamiliar category` + if ( + isset($this->settings['unfamiliar categories']) + and !isset($this->settings['unfamiliar category']) + ) : + $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories']; + endif; + if ( + FeedWordPress::affirmative($this->settings, 'hardcode categories') + and !isset($this->settings['unfamiliar category']) + ) : + $this->settings['unfamiliar category'] = 'default'; + endif; + + // Set this up automagically for del.icio.us + $bits = parse_url($this->link->link_rss); + $tagspacers = array('del.icio.us', 'feeds.delicious.com'); + if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) : + $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds + endif; + + // Simple lists + foreach ($this->imploded_settings() as $what) : + if (isset($this->settings[$what])): + $this->settings[$what] = explode( + FEEDWORDPRESS_CAT_SEPARATOR, + $this->settings[$what] + ); + endif; + endforeach; + + if (isset($this->settings['terms'])) : + $this->settings['terms'] = explode(FEEDWORDPRESS_CAT_SEPARATOR.FEEDWORDPRESS_CAT_SEPARATOR, $this->settings['terms']); + $terms = array(); + foreach ($this->settings['terms'] as $line) : + $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line); + $tax = array_shift($line); + $terms[$tax] = $line; + endforeach; + $this->settings['terms'] = $terms; + endif; + + if (isset($this->settings['map authors'])) : + $author_rules = explode("\n\n", $this->settings['map authors']); + $ma = array(); + foreach ($author_rules as $rule) : + list($rule_type, $author_name, $author_action) = explode("\n", $rule); + + // Normalize for case and whitespace + $rule_type = strtolower(trim($rule_type)); + $author_name = strtolower(trim($author_name)); + $author_action = strtolower(trim($author_action)); + + $ma[$rule_type][$author_name] = $author_action; + endforeach; + $this->settings['map authors'] = $ma; + endif; + endif; + } /* SyndicatedLink::SyndicatedLink () */ + + function found () { + return is_object($this->link) and !is_wp_error($this->link); + } /* SyndicatedLink::found () */ + + 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; + } /* SyndicatedLink::stale () */ + + function poll ($crash_ts = NULL) { + global $wpdb; + + FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$this->link->link_rss.']'); + + $this->simplepie = apply_filters( + 'syndicated_feed', + FeedWordPress::fetch($this->link->link_rss), + $this + ); + + // Filter compatibility mode + if (is_wp_error($this->simplepie)) : + $this->magpie = $this->simplepie; + else : + $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL); + endif; + + $new_count = NULL; + + $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished'); + if ($resume) : + // pick up where we left off + $processed = array_map('trim', explode("\n", $this->settings['update/processed'])); + else : + // begin at the beginning + $processed = array(); + endif; + + if (is_wp_error($this->simplepie)) : + $new_count = $this->simplepie; + // Error; establish an error setting. + $theError = array(); + $theError['ts'] = time(); + $theError['since'] = time(); + $theError['object'] = $this->simplepie; + + $oldError = $this->setting('update/error', NULL, NULL); + if (is_string($oldError)) : + $oldError = unserialize($oldError); + endif; + + if (!is_null($oldError)) : + // Copy over the in-error-since timestamp + $theError['since'] = $oldError['since']; + + // If this is a repeat error, then we should take + // a step back before we try to fetch it again. + $this->settings['update/last'] = time(); + $this->settings['update/ttl'] = $this->automatic_ttl(); + $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this); + $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl_from_error', $this->settings['update/ttl'], $this); + + $this->settings['update/timed'] = 'automatically'; + endif; + + do_action('syndicated_feed_error', $theError, $oldError, $this); + + $this->settings['update/error'] = serialize($theError); + $this->save_settings(/*reload=*/ true); + + elseif (is_object($this->simplepie)) : + // Success; clear out error setting, if any. + if (isset($this->settings['update/error'])) : + unset($this->settings['update/error']); + endif; + + $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'] = $this->automatic_ttl(); + $this->settings['update/timed'] = 'automatically'; + endif; + $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this); + + if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') : + $this->settings['update/hold'] = 'scheduled'; + endif; + + $this->settings['update/unfinished'] = 'yes'; + + $update[] = "link_notes = '".$wpdb->escape($this->settings_to_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' + "); + do_action('update_syndicated_feed', $this->id, $this); + + # -- Add new posts from feed and update any updated posts + $crashed = false; + + $posts = apply_filters( + 'syndicated_feed_items', + $this->simplepie->get_items(), + &$this + ); + + $this->magpie->originals = $posts; + + if (is_array($posts)) : + foreach ($posts as $key => $item) : + $post = new SyndicatedPost($item, $this); + + if (!$resume or !in_array(trim($post->guid()), $processed)) : + $processed[] = $post->guid(); + if (!$post->filtered()) : + $new = $post->store(); + if ( $new !== false ) $new_count[$new]++; + endif; + + if (!is_null($crash_ts) and (time() > $crash_ts)) : + $crashed = true; + break; + endif; + endif; + unset($post); + endforeach; + endif; + $suffix = ($crashed ? 'crashed' : 'completed'); + do_action('update_syndicated_feed_items', $this->id, $this); + do_action("update_syndicated_feed_items_${suffix}", $this->id, $this); + + // Copy back any changes to feed settings made in the course of updating (e.g. new author rules) + $to_notes = $this->settings; + + $this->settings['update/processed'] = $processed; + if (!$crashed) : + $this->settings['update/unfinished'] = 'no'; + endif; + + $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'"; + + // Update the properties of the link from the feed information + $result = $wpdb->query(" + UPDATE $wpdb->links + SET $update_set + WHERE link_id='$this->id' + "); + + do_action("update_syndicated_feed_completed", $this->id, $this); + endif; + + // All done; let's clean up. + $this->magpie = NULL; + + // Avoid circular-reference memory leak in PHP < 5.3. + // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks> + if (method_exists($this->simplepie, '__destruct')) : + $this->simplepie->__destruct(); + endif; + $this->simplepie = NULL; + + return $new_count; + } /* SyndicatedLink::poll() */ + + /** + * Updates the URL for the feed syndicated by this link. + * + * @param string $url The new feed URL to use for this source. + * @return bool TRUE on success, FALSE on failure. + */ + function set_uri ($url) { + global $wpdb; + + if ($this->found()) : + // Update link_rss + $result = $wpdb->query(" + UPDATE $wpdb->links + SET + link_rss = '".$wpdb->escape($url)."' + WHERE link_id = '".$wpdb->escape($this->id)."' + "); + + $ret = ($result ? true : false); + else : + $ret = false; + endif; + return $ret; + } /* SyndicatedLink::set_uri () */ + + function deactivate () { + global $wpdb; + + $wpdb->query($wpdb->prepare(" + UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d + ", (int) $this->id)); + } /* SyndicatedLink::deactivate () */ + + function delete () { + global $wpdb; + + $wpdb->query($wpdb->prepare(" + DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id' + AND meta_value = '%s' + ", $this->id)); + + $wpdb->query($wpdb->prepare(" + DELETE FROM $wpdb->links WHERE link_id = %d + ", (int) $this->id)); + + $this->id = NULL; + } /* SyndicatedLink::delete () */ + + function nuke () { + global $wpdb; + + // Make a list of the items syndicated from this feed... + $post_ids = $wpdb->get_col($wpdb->prepare(" + SELECT post_id FROM $wpdb->postmeta + WHERE meta_key = 'syndication_feed_id' + AND meta_value = '%s' + ", $this->id)); + + // ... and kill them all + if (count($post_ids) > 0) : + foreach ($post_ids as $post_id) : + // Force scrubbing of deleted post + // rather than sending to Trashcan + wp_delete_post( + /*postid=*/ $post_id, + /*force_delete=*/ true + ); + endforeach; + endif; + + $this->delete(); + } /* SyndicatedLink::nuke () */ + + function map_name_to_new_user ($name, $newuser_name) { + global $wpdb; + + if (strlen($newuser_name) > 0) : + $newuser_id = fwp_insert_new_user($newuser_name); + if (is_numeric($newuser_id)) : + if (is_null($name)) : // Unfamiliar author + $this->settings['unfamiliar author'] = $newuser_id; + else : + $this->settings['map authors']['name'][$name] = $newuser_id; + endif; + else : + // TODO: Add some error detection and reporting + endif; + else : + // TODO: Add some error reporting + endif; + } /* SyndicatedLink::map_name_to_new_user () */ + + function imploded_settings () { + return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter'); + } + function settings_to_notes () { + $to_notes = $this->settings; + + unset($to_notes['link/id']); // Magic setting; don't save + unset($to_notes['link/uri']); // Magic setting; don't save + unset($to_notes['link/name']); // Magic setting; don't save + unset($to_notes['hardcode categories']); // Deprecated + unset($to_notes['unfamiliar categories']); // Deprecated + + // Collapse array settings + if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) : + $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']); + endif; + + foreach ($this->imploded_settings() as $what) : + if (isset($to_notes[$what]) and is_array($to_notes[$what])) : + $to_notes[$what] = implode( + FEEDWORDPRESS_CAT_SEPARATOR, + $to_notes[$what] + ); + endif; + endforeach; + + if (isset($to_notes['terms']) and is_array($to_notes['terms'])) : + $tt = array(); + foreach ($to_notes['terms'] as $tax => $terms) : + $tt[] = $tax.FEEDWORDPRESS_CAT_SEPARATOR.implode(FEEDWORDPRESS_CAT_SEPARATOR, $terms); + endforeach; + $to_notes['terms'] = implode(FEEDWORDPRESS_CAT_SEPARATOR.FEEDWORDPRESS_CAT_SEPARATOR, $tt); + endif; + + // Collapse the author mapping rule structure back into a flat string + if (isset($to_notes['map authors'])) : + $ma = array(); + foreach ($to_notes['map authors'] as $rule_type => $author_rules) : + foreach ($author_rules as $author_name => $author_action) : + $ma[] = $rule_type."\n".$author_name."\n".$author_action; + endforeach; + endforeach; + $to_notes['map authors'] = implode("\n\n", $ma); + endif; + + $notes = ''; + foreach ($to_notes as $key => $value) : + $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n"; + endforeach; + return $notes; + } /* SyndicatedLink::settings_to_notes () */ + + function save_settings ($reload = false) { + global $wpdb; + + // Save channel-level meta-data + foreach (array('link_name', 'link_description', 'link_url') as $what) : + $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'"; + endforeach; + + // Save settings to the notes field + $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'"; + + // Update the properties of the link from settings changes, etc. + $update_set = implode(", ", $alter); + + $result = $wpdb->query(" + UPDATE $wpdb->links + SET $update_set + WHERE link_id='$this->id' + "); + + if ($reload) : + // force reload of link information from DB + if (function_exists('clean_bookmark_cache')) : + clean_bookmark_cache($this->id); + endif; + endif; + } /* SyndicatedLink::save_settings () */ + + /** + * Retrieves the value of a setting, allowing for a global setting to be + * used as a fallback, or a constant value, or both. + * + * @param string $name The link setting key + * @param mixed $fallback_global If the link setting is nonexistent or marked as a use-default value, fall back to the value of this global setting. + * @param mixed $fallback_value If the link setting and the global setting are nonexistent or marked as a use-default value, fall back to this constant value. + * @return bool TRUE on success, FALSE on failure. + */ + function setting ($name, $fallback_global = NULL, $fallback_value = NULL) { + $ret = NULL; + if (isset($this->settings[$name])) : + $ret = $this->settings[$name]; + endif; + + $no_value = ( + is_null($ret) + or (is_string($ret) and strtolower($ret)=='default') + ); + + if ($no_value and !is_null($fallback_global)) : + $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL); + endif; + + $no_value = ( + is_null($ret) + or (is_string($ret) and strtolower($ret)=='default') + ); + + if ($no_value and !is_null($fallback_value)) : + $ret = $fallback_value; + endif; + return $ret; + } /* SyndicatedLink::setting () */ + + function update_setting ($name, $value, $default = 'default') { + if (!is_null($value) and $value != $default) : + $this->settings[$name] = $value; + else : // Zap it. + unset($this->settings[$name]); + endif; + } /* SyndicatedLink::update_setting () */ + + function uri () { + return (is_object($this->link) ? $this->link->link_rss : NULL); + } /* SyndicatedLink::uri () */ + + function property_cascade ($fromFeed, $link_field, $setting, $simplepie_method) { + $value = NULL; + if ($fromFeed) : + if (isset($this->settings[$setting])) : + $value = $this->settings[$setting]; + elseif (is_object($this->simplepie) + and method_exists($this->simplepie, $simplepie_method)) : + $value = $this->simplepie->{$simplepie_method}(); + endif; + else : + $value = $this->link->{$link_field}; + endif; + return $value; + } /* SyndicatedLink::property_cascade () */ + + function homepage ($fromFeed = true) { + return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link'); + } /* SyndicatedLink::homepage () */ + + function name ($fromFeed = true) { + return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title'); + } /* SyndicatedLink::name () */ + + function guid () { + $ret = $this->setting('feed/id', NULL, $this->uri()); + + // If we can get it live from the feed, do so. + if (is_object($this->simplepie)) : + $search = array( + array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'), + array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'), + array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'), + array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'), + array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'), + ); + + foreach ($search as $pair) : + if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) : + $ret = $id_tags[0]['data']; + break; + elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) : + $ret = $id_tags[0]['data']; + break; + endif; + endforeach; + endif; + return $ret; + } + + 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() */ + + function automatic_ttl () { + // spread out over a time interval for staggered updates + $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD); + if (!is_numeric($updateWindow) or ($updateWindow < 1)) : + $updateWindow = DEFAULT_UPDATE_PERIOD; + endif; + + $fudgedInterval = $updateWindow+rand(0, 2*($updateWindow/3)); + return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this); + } /* SyndicatedLink::automatic_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; + } /* 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; + } /* SyndicatedLink::hardcode () */ + + function syndicated_status ($what, $default, $fallback = true) { + global $wpdb; + + // Use local setting if we have it + if ( isset($this->settings["$what status"]) ) : + $ret = $this->settings["$what status"]; + + // Or fall back to global default if we can + elseif ($fallback) : + $ret = FeedWordPress::syndicated_status($what, $default); + + // Or use default value if we can't. + else : + $ret = $default; + + endif; + + return $wpdb->escape(trim(strtolower($ret))); + } /* SyndicatedLink:syndicated_status () */ + + function taxonomies () { + $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post'); + return get_object_taxonomies(array('object_type' => $post_type), 'names'); + } /* SyndicatedLink::taxonomies () */ + +} // class SyndicatedLink + diff --git a/wp-content/plugins/feedwordpress/syndicatedpost.class.php b/wp-content/plugins/feedwordpress/syndicatedpost.class.php new file mode 100644 index 0000000000000000000000000000000000000000..4be045296ae9ad28c3585de7a8d95ac5bb6e4331 --- /dev/null +++ b/wp-content/plugins/feedwordpress/syndicatedpost.class.php @@ -0,0 +1,1971 @@ +<?php +require_once(dirname(__FILE__).'/feedtime.class.php'); + +/** + * class SyndicatedPost: FeedWordPress uses to manage the conversion of + * incoming items from the feed parser into posts for the WordPress + * database. It contains several internal management methods primarily + * of interest to someone working on the FeedWordPress source, as well + * as some utility methods for extracting useful data from many + * different feed formats, which may be useful to FeedWordPress users + * who make use of feed data in PHP add-ons and filters. + * + * @version 2010.0905 + */ +class SyndicatedPost { + var $item = null; // MagpieRSS representation + var $entry = null; // SimplePie_Item representation + + var $link = null; + var $feed = null; + var $feedmeta = null; + + var $xmlns = array (); + + var $post = array (); + + var $_freshness = null; + var $_wp_id = null; + + /** + * SyndicatedPost constructor: Given a feed item and the source from + * which it was taken, prepare a post that can be inserted into the + * WordPress database on request, or updated in place if it has already + * been syndicated. + * + * @param array $item The item syndicated from the feed. + * @param SyndicatedLink $source The feed it was syndicated from. + */ + function SyndicatedPost ($item, &$source) { + global $wpdb; + + if (is_array($item) + and isset($item['simplepie']) + and isset($item['magpie'])) : + $this->entry = $item['simplepie']; + $this->item = $item['magpie']; + $item = $item['magpie']; + elseif (is_a($item, 'SimplePie_Item')) : + $this->entry = $item; + + // convert to Magpie for compat purposes + $mp = new MagpieFromSimplePie($source->simplepie, $this->entry); + $this->item = $mp->get_item(); + + // done with conversion object + $mp = NULL; unset($mp); + else : + $this->item = $item; + endif; + + $this->link =& $source; + $this->feed = $source->magpie; + $this->feedmeta = $source->settings; + + FeedWordPress::diagnostic('feed_items', 'Considering item ['.$this->guid().'] "'.$this->entry->get_title().'"'); + + # Dealing with namespaces can get so fucking fucked. + $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR; + $this->xmlns['reverse'] = array(); + foreach ($this->xmlns['forward'] as $url => $ns) : + if (!isset($this->xmlns['reverse'][$ns])) : + $this->xmlns['reverse'][$ns] = array(); + endif; + $this->xmlns['reverse'][$ns][] = $url; + endforeach; + + // Fucking SimplePie. + $this->xmlns['reverse']['rss'][] = ''; + + # These globals were originally an ugly kludge around a bug in + # apply_filters from WordPress 1.5. The bug was fixed in 1.5.1, + # and I sure hope at this point that nobody writing filters for + # FeedWordPress is still relying on them. + # + # Anyway, I hereby declare them DEPRECATED as of 8 February + # 2010. I'll probably remove the globals within 1-2 releases in + # the interests of code hygiene and memory usage. If you + # currently use them in your filters, I advise you switch off to + # accessing the public members SyndicatedPost::feed and + # SyndicatedPost::feedmeta. + + global $fwp_channel, $fwp_feedmeta; + $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta; + + // Trigger global syndicated_item filter. + $changed = apply_filters('syndicated_item', $this->item, $this); + $this->item = $changed; + + // Allow for feed-specific syndicated_item filters. + $changed = apply_filters( + "syndicated_item_".$source->uri(), + $this->item, + $this + ); + $this->item = $changed; + + # 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'] = apply_filters( + 'syndicated_item_title', + $this->entry->get_title(), $this + ); + + $this->post['named']['author'] = apply_filters( + 'syndicated_item_author', + $this->author(), $this + ); + // This just gives us an alphanumeric name for the author. + // We look up (or create) the numeric ID for the author + // in SyndicatedPost::add(). + + $this->post['post_content'] = apply_filters( + 'syndicated_item_content', + $this->content(), $this + ); + + $excerpt = apply_filters('syndicated_item_excerpt', $this->excerpt(), $this); + if (!empty($excerpt)): + $this->post['post_excerpt'] = $excerpt; + endif; + + $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this); + $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this); + $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this); + + // 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', apply_filters('syndicated_item_published', $this->published(/*fallback=*/ true, /*default=*/ -1), $this) + $offset); + $this->post['post_modified'] = gmdate('Y-m-d H:i:s', apply_filters('syndicated_item_updated', $this->updated(/*fallback=*/ true, /*default=*/ -1), $this) + $offset); + $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', apply_filters('syndicated_item_published', $this->published(/*fallback=*/ true, /*default=*/ -1), $this)); + $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', apply_filters('syndicated_item_updated', $this->updated(/*fallback=*/ true, /*default=*/ -1), $this)); + + // 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'] = apply_filters('syndicated_item_guid', $this->guid(), $this); + + // User-supplied custom settings to apply to each post. Do first so that FWP-generated custom settings will overwrite if necessary; thus preventing any munging + $default_custom_settings = get_option('feedwordpress_custom_settings'); + if ($default_custom_settings and !is_array($default_custom_settings)) : + $default_custom_settings = unserialize($default_custom_settings); + endif; + if (!is_array($default_custom_settings)) : + $default_custom_settings = array(); + endif; + + $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null); + if ($custom_settings and !is_array($custom_settings)) : + $custom_settings = unserialize($custom_settings); + endif; + if (!is_array($custom_settings)) : + $custom_settings = array(); + endif; + + $postMetaIn = array_merge($default_custom_settings, $custom_settings); + $postMetaOut = array(); + + // Big ugly fuckin loop to do any element substitutions + // that we may need. + foreach ($postMetaIn as $key => $values) : + if (is_string($values)) : $values = array($values); endif; + + $postMetaOut[$key] = array(); + foreach ($values as $value) : + if (preg_match('/\$\( ([^)]+) \)/x', $value, $ref)) : + $elements = $this->query($ref[1]); + foreach ($elements as $element) : + $postMetaOut[$key][] = str_replace( + $ref[0], + $element, + $value + ); + endforeach; + else : + $postMetaOut[$key][] = $value; + endif; + endforeach; + endforeach; + + foreach ($postMetaOut as $key => $values) : + $this->post['meta'][$key] = array(); + foreach ($values as $value) : + $this->post['meta'][$key][] = apply_filters("syndicated_post_meta_{$key}", $value, $this); + endforeach; + endforeach; + + // RSS 2.0 / Atom 1.0 enclosure support + $enclosures = $this->entry->get_enclosures(); + if (is_array($enclosures)) : foreach ($enclosures as $enclosure) : + $this->post['meta']['enclosure'][] = + apply_filters('syndicated_item_enclosure_url', $enclosure->get_link(), $this)."\n". + apply_filters('syndicated_item_enclosure_length', $enclosure->get_length(), $this)."\n". + apply_filters('syndicated_item_enclosure_type', $enclosure->get_type(), $this); + endforeach; endif; + + // In case you want to point back to the blog this was + // syndicated from. + + $sourcemeta['syndication_source'] = apply_filters( + 'syndicated_item_source_title', + $this->link->name(), + $this + ); + $sourcemeta['syndication_source_uri'] = apply_filters( + 'syndicated_item_source_link', + $this->link->homepage(), + $this + ); + $sourcemeta['syndication_source_id'] = apply_filters( + 'syndicated_item_source_id', + $this->link->guid(), + $this + ); + + // Make use of atom:source data, if present in an aggregated feed + $entry_source = $this->source(); + if (!is_null($entry_source)) : + foreach ($entry_source as $what => $value) : + if (!is_null($value)) : + if ($what=='title') : $key = 'syndication_source'; + elseif ($what=='feed') : $key = 'syndication_feed'; + else : $key = "syndication_source_${what}"; + endif; + + $sourcemeta["${key}_original"] = apply_filters( + 'syndicated_item_original_source_'.$what, + $value, + $this + ); + endif; + endforeach; + endif; + + foreach ($sourcemeta as $meta_key => $value) : + if (!is_null($value)) : + $this->post['meta'][$meta_key] = $value; + endif; + endforeach; + + // Store information on human-readable and machine-readable comment URIs + + // Human-readable comment URI + $commentLink = apply_filters('syndicated_item_comments', $this->comment_link(), $this); + if (!is_null($commentLink)) : $this->post['meta']['rss:comments'] = $commentLink; endif; + + // Machine-readable content feed URI + $commentFeed = apply_filters('syndicated_item_commentrss', $this->comment_feed(), $this); + if (!is_null($commentFeed)) : $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif; + // Yeah, yeah, now I know that it's supposed to be + // wfw:commentRss. Oh well. Path dependence, sucka. + + // Store information to identify the feed that this came from + if (isset($this->feedmeta['link/uri'])) : + $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri']; + endif; + if (isset($this->feedmeta['link/id'])) : + $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id']; + endif; + + if (isset($this->item['source_link_self'])) : + $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self']; + endif; + + // In case you want to know the external permalink... + $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink()); + + // Store a hash of the post content for checking whether something needs to be updated + $this->post['meta']['syndication_item_hash'] = $this->update_hash(); + + // Categories: start with default categories, if any. + $cats = array(); + if ('no' != $this->link->setting('add/category', NULL, 'yes')) : + $fc = get_option("feedwordpress_syndication_cats"); + if ($fc) : + $cats = array_merge($cats, explode("\n", $fc)); + endif; + endif; + + $fc = $this->link->setting('cats', NULL, array()); + if (is_array($fc)) : + $cats = array_merge($cats, $fc); + endif; + $this->post['pretax']['category'] = $cats; + + // Now add categories from the post, if we have 'em + $cats = array(); + $post_cats = $this->entry->get_categories(); + if (is_array($post_cats)) : foreach ($post_cats as $cat) : + $cat_name = $cat->get_term(); + if (!$cat_name) : $cat_name = $cat->get_label(); endif; + + if ($this->link->setting('cat_split', NULL, NULL)) : + $pcre = "\007".$this->feedmeta['cat_split']."\007"; + $cats = array_merge( + $cats, + preg_split( + $pcre, + $cat_name, + -1 /*=no limit*/, + PREG_SPLIT_NO_EMPTY + ) + ); + else : + $cats[] = $cat_name; + endif; + endforeach; endif; + + $this->post['taxed']['category'] = apply_filters('syndicated_item_categories', $cats, $this); + + // Tags: start with default tags, if any + $tags = array(); + if ('no' != $this->link->setting('add/post_tag', NULL, 'yes')) : + $ft = get_option("feedwordpress_syndication_tags", NULL); + $tags = (is_null($ft) ? array() : explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft)); + endif; + + $ft = $this->link->setting('tags', NULL, array()); + if (is_array($ft)) : + $tags = array_merge($tags, $ft); + endif; + $this->post['pretax']['post_tag'] = $tags; + + // Scan post for /a[@rel='tag'] and use as tags if present + $tags = $this->inline_tags(); + $this->post['taxed']['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this); + + $taxonomies = $this->link->taxonomies(); + $feedTerms = $this->link->setting('terms', NULL, array()); + $globalTerms = get_option('feedwordpress_syndication_terms', array()); + + $specials = array('category' => 'cats', 'post_tag' => 'tags'); + foreach ($taxonomies as $tax) : + if (!isset($specials[$tax])) : + $terms = array(); + + // See if we should get the globals + if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) : + if (isset($globalTerms[$tax])) : + $terms = $globalTerms[$tax]; + endif; + endif; + + // Now merge in the locals + if (isset($feedTerms[$tax])) : + $terms = array_merge($terms, $feedTerms[$tax]); + endif; + + // That's all, folks. + $this->post['pretax'][$tax] = $terms; + endif; + endforeach; + + $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this); + endif; + } /* SyndicatedPost::SyndicatedPost() */ + + ##################################### + #### EXTRACT DATA FROM FEED ITEM #### + ##################################### + + /** + * SyndicatedPost::query uses an XPath-like syntax to query arbitrary + * elements within the syndicated item. + * + * @param string $path + * @returns array of string values representing contents of matching + * elements or attributes + */ + function query ($path) { + $urlHash = array(); + + // Allow {url} notation for namespaces. URLs will contain : and /, so... + preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER); + foreach ($match as $ref) : + $urlHash[md5($ref[1])] = $ref[1]; + endforeach; + + foreach ($urlHash as $hash => $url) : + $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path); + endforeach; + + $path = explode('/', $path); + foreach ($path as $index => $node) : + if (preg_match('/{#([^}]+)}/', $node, $ref)) : + if (isset($urlHash[$ref[1]])) : + $path[$index] = str_replace( + '{#'.$ref[1].'}', + '{'.$urlHash[$ref[1]].'}', + $node + ); + endif; + endif; + endforeach; + + // Start out with a get_item_tags query. + $node = ''; + while (strlen($node)==0 and !is_null($node)) : + $node = array_shift($path); + endwhile; + + switch ($node) : + case 'feed' : + case 'channel' : + $node = array_shift($path); + $data = $this->get_feed_root_element(); + $data = array_merge($data, $this->get_feed_channel_elements()); + break; + case 'item' : + $node = array_shift($path); + default : + $data = array($this->entry->data); + $method = NULL; + endswitch; + + while (!is_null($node)) : + if (strlen($node) > 0) : + $matches = array(); + + list($axis, $element) = $this->xpath_name_and_axis($node); + + foreach ($data as $datum) : + if (!is_string($datum) and isset($datum[$axis])) : + foreach ($datum[$axis] as $ns => $elements) : + if (isset($elements[$element])) : + // Potential match. + // Check namespace. + if (is_string($elements[$element])) : // Attribute + $addenda = array($elements[$element]); + $contexts = array($datum); + else : // Element + $addenda = $elements[$element]; + $contexts = $elements[$element]; + endif; + + foreach ($addenda as $index => $addendum) : + $context = $contexts[$index]; + + $namespaces = $this->xpath_possible_namespaces($node, $context); + if (in_array($ns, $namespaces)) : + $matches[] = $addendum; + endif; + endforeach; + endif; + endforeach; + endif; + endforeach; + + $data = $matches; + endif; + $node = array_shift($path); + endwhile; + + $matches = array(); + foreach ($data as $datum) : + if (is_string($datum)) : + $matches[] = $datum; + elseif (isset($datum['data'])) : + $matches[] = $datum['data']; + endif; + endforeach; + return $matches; + } /* SyndicatedPost::query() */ + + function get_feed_root_element () { + $matches = array(); + foreach ($this->link->simplepie->data['child'] as $ns => $root) : + foreach ($root as $element => $data) : + $matches = array_merge($matches, $data); + endforeach; + endforeach; + return $matches; + } /* SyndicatedPost::get_feed_root_element() */ + + function get_feed_channel_elements () { + $rss = array( + SIMPLEPIE_NAMESPACE_RSS_090, + SIMPLEPIE_NAMESPACE_RSS_10, + 'http://backend.userland.com/RSS2', + SIMPLEPIE_NAMESPACE_RSS_20, + ); + + $matches = array(); + foreach ($rss as $ns) : + $data = $this->link->simplepie->get_feed_tags($ns, 'channel'); + if (!is_null($data)) : + $matches = array_merge($matches, $data); + endif; + endforeach; + return $matches; + } /* SyndicatedPost::get_feed_channel_elements() */ + + function xpath_default_namespace () { + // Get the default namespace. + $type = $this->link->simplepie->get_type(); + if ($type & SIMPLEPIE_TYPE_ATOM_10) : + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10; + elseif ($type & SIMPLEPIE_TYPE_ATOM_03) : + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03; + elseif ($type & SIMPLEPIE_TYPE_RSS_090) : + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090; + elseif ($type & SIMPLEPIE_TYPE_RSS_10) : + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10; + elseif ($type & SIMPLEPIE_TYPE_RSS_20) : + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20; + else : + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20; + endif; + return $defaultNS; + } /* SyndicatedPost::xpath_default_namespace() */ + + function xpath_name_and_axis ($node) { + $ns = NULL; $element = NULL; + + if (substr($node, 0, 1)=='@') : + $axis = 'attribs'; $node = substr($node, 1); + else : + $axis = 'child'; + endif; + + if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) : + $element = $ref[2]; + elseif (strpos($node, ':') !== FALSE) : + list($xmlns, $element) = explode(':', $node, 2); + else : + $element = $node; + endif; + return array($axis, $element); + } /* SyndicatedPost::xpath_local_name () */ + + function xpath_possible_namespaces ($node, $datum = array()) { + $ns = NULL; $element = NULL; + + if (substr($node, 0, 1)=='@') : + $attr = '@'; $node = substr($node, 1); + else : + $attr = ''; + endif; + + if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) : + $ns = array($ref[1]); + elseif (strpos($node, ':') !== FALSE) : + list($xmlns, $element) = explode(':', $node, 2); + + if (isset($this->xmlns['reverse'][$xmlns])) : + $ns = $this->xmlns['reverse'][$xmlns]; + else : + $ns = array($xmlns); + endif; + + // Fucking SimplePie. For attributes in default xmlns. + $defaultNS = $this->xpath_default_namespace(); + if (isset($this->xmlns['forward'][$defaultNS]) + and ($xmlns==$this->xmlns['forward'][$defaultNS])) : + $ns[] = ''; + endif; + + if (isset($datum['xmlns'])) : + if (isset($datum['xmlns'][$xmlns])) : + $ns[] = $datum['xmlns'][$xmlns]; + endif; + endif; + else : + // Often in SimplePie, the default namespace gets stored + // as an empty string rather than a URL. + $ns = array($this->xpath_default_namespace(), ''); + endif; + return array_unique($ns); + } /* SyndicatedPost::xpath_possible_namespaces() */ + + function content () { + $content = NULL; + if (isset($this->item['atom_content'])) : + $content = $this->item['atom_content']; + elseif (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']; + elseif (isset($this->item['description'])) : + $content = $this->item['description']; + endif; + return $content; + } /* SyndicatedPost::content() */ + + function excerpt () { + # Identify and sanitize excerpt: atom:summary, or rss:description + $excerpt = $this->entry->get_description(); + + # Many RSS feeds use rss:description, inadvisably, to + # carry the entire post (typically with escaped HTML). + # If that's what happened, we don't want the full + # content for the excerpt. + $content = $this->content(); + + // Ignore whitespace, case, and tag cruft. + $theExcerpt = preg_replace('/\s+/', '', strtolower(strip_tags($excerpt))); + $theContent = preg_replace('/\s+/', '', strtolower(strip_Tags($content))); + + if ( empty($excerpt) or $theExcerpt == $theContent ) : + # If content is available, generate an excerpt. + if ( strlen(trim($content)) > 0 ) : + $excerpt = strip_tags($content); + if (strlen($excerpt) > 255) : + $excerpt = substr($excerpt,0,252).'...'; + endif; + endif; + endif; + return $excerpt; + } /* SyndicatedPost::excerpt() */ + + function permalink () { + // Handles explicit <link> elements and also RSS 2.0 cases with + // <guid isPermaLink="true">, etc. Hooray! + $permalink = $this->entry->get_link(); + return $permalink; + } + + function created () { + $date = ''; + if (isset($this->item['dc']['created'])) : + $date = $this->item['dc']['created']; + elseif (isset($this->item['dcterms']['created'])) : + $date = $this->item['dcterms']['created']; + elseif (isset($this->item['created'])): // Atom 0.3 + $date = $this->item['created']; + endif; + + $epoch = new FeedTime($date); + return $epoch->timestamp(); + } /* SyndicatedPost::created() */ + + function published ($fallback = true, $default = NULL) { + $date = ''; + $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 + $date = $this->item['dc']['date']; + elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions + $date = $this->item['dcterms']['issued']; + elseif (isset($this->item['published'])) : // Atom 1.0 + $date = $this->item['published']; + elseif (isset($this->item['issued'])): // Atom 0.3 + $date = $this->item['issued']; + elseif (isset($this->item['pubdate'])): // RSS 2.0 + $date = $this->item['pubdate']; + endif; + + if (strlen($date) > 0) : + $time = new FeedTime($date); + $epoch = $time->timestamp(); + elseif ($fallback) : // Fall back to <updated> / <modified> if present + $epoch = $this->updated(/*fallback=*/ false, /*default=*/ $default); + endif; + + # If everything failed, then default to the current time. + if (is_null($epoch)) : + if (-1 == $default) : + $epoch = time(); + else : + $epoch = $default; + endif; + endif; + + return $epoch; + } /* SyndicatedPost::published() */ + + function updated ($fallback = true, $default = -1) { + $date = ''; + $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 + $date = $this->item['dc']['modified']; + elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions + $date = $this->item['dcterms']['modified']; + elseif (isset($this->item['modified'])): // Atom 0.3 + $date = $this->item['modified']; + elseif (isset($this->item['updated'])): // Atom 1.0 + $date = $this->item['updated']; + endif; + + if (strlen($date) > 0) : + $time = new FeedTime($date); + $epoch = $time->timestamp(); + elseif ($fallback) : // Fall back to issued / dc:date + $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default); + endif; + + # If everything failed, then default to the current time. + if (is_null($epoch)) : + if (-1 == $default) : + $epoch = time(); + else : + $epoch = $default; + endif; + endif; + + return $epoch; + } /* SyndicatedPost::updated() */ + + function update_hash () { + return md5(serialize($this->item)); + } /* SyndicatedPost::update_hash() */ + + 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->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 : + $link = $this->permalink(); + if (is_null($link)) : $link = $this->link->uri(); endif; + $guid .= '://'.md5($link.'/'.$this->item['title']); + endif; + endif; + return $guid; + } /* SyndicatedPost::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; + elseif ($this->link->name()) : + $author['name'] = $this->link->name(); + else : + $url = parse_url($this->link->uri()); + $author['name'] = $url['host']; + 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']; + elseif (isset($this->feed->channel['link'])) : + $author['uri'] = $this->feed->channel['link']; + endif; + + return $author; + } /* SyndicatedPost::author() */ + + /** + * SyndicatedPost::inline_tags: Return a list of all the tags embedded + * in post content using the a[@rel="tag"] microformat. + * + * @since 2010.0630 + * @return array of string values containing the name of each tag + */ + function inline_tags () { + $tags = array(); + $content = $this->content(); + $pattern = FeedWordPressHTML::tagWithAttributeRegex('a', 'rel', 'tag'); + preg_match_all($pattern, $content, $refs, PREG_SET_ORDER); + if (count($refs) > 0) : + foreach ($refs as $ref) : + $tag = FeedWordPressHTML::tagWithAttributeMatch($ref); + $tags[] = $tag['content']; + endforeach; + endif; + return $tags; + } + + /** + * SyndicatedPost::isTaggedAs: Test whether a feed item is + * tagged / categorized with a given string. Case and leading and + * trailing whitespace are ignored. + * + * @param string $tag Tag to check for + * + * @return bool Whether or not at least one of the categories / tags on + * $this->item is set to $tag (modulo case and leading and trailing + * whitespace) + */ + function isTaggedAs ($tag) { + $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace + + // Check to see if this is tagged with $tag + $currentCategory = 'category'; + $currentCategoryNumber = 1; + + // If we have the new MagpieRSS, the number of category elements + // on this item is stored under index "category#". + if (isset($this->item['category#'])) : + $numberOfCategories = (int) $this->item['category#']; + + // We REALLY shouldn't have the old and busted MagpieRSS, but in + // case we do, it doesn't support multiple categories, but there + // might still be a single value under the "category" index. + elseif (isset($this->item['category'])) : + $numberOfCategories = 1; + + // No standard category or tag elements on this feed item. + else : + $numberOfCategories = 0; + + endif; + + $isSoTagged = false; // Innocent until proven guilty + + // Loop through category elements; if there are multiple + // elements, they are indexed as category, category#2, + // category#3, ... category#N + while ($currentCategoryNumber <= $numberOfCategories) : + if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) : + $isSoTagged = true; // Got it! + break; + endif; + + $currentCategoryNumber += 1; + $currentCategory = 'category#'.$currentCategoryNumber; + endwhile; + + return $isSoTagged; + } /* SyndicatedPost::isTaggedAs() */ + + /** + * SyndicatedPost::enclosures: returns an array with any enclosures + * that may be attached to this syndicated item. + * + * @param string $type If you only want enclosures that match a certain + * MIME type or group of MIME types, you can limit the enclosures + * that will be returned to only those with a MIME type which + * matches this regular expression. + * @return array + */ + function enclosures ($type = '/.*/') { + $enclosures = array(); + + if (isset($this->item['enclosure#'])) : + // Loop through enclosure, enclosure#2, enclosure#3, .... + for ($i = 1; $i <= $this->item['enclosure#']; $i++) : + $eid = (($i > 1) ? "#{$id}" : ""); + + // Does it match the type we want? + if (preg_match($type, $this->item["enclosure{$eid}@type"])) : + $enclosures[] = array( + "url" => $this->item["enclosure{$eid}@url"], + "type" => $this->item["enclosure{$eid}@type"], + "length" => $this->item["enclosure{$eid}@length"], + ); + endif; + endfor; + endif; + return $enclosures; + } /* SyndicatedPost::enclosures() */ + + function source ($what = NULL) { + $ret = NULL; + $source = $this->entry->get_source(); + if ($source) : + $ret = array(); + $ret['title'] = $source->get_title(); + $ret['uri'] = $source->get_link(); + $ret['feed'] = $source->get_link(0, 'self'); + + if ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) : + $ret['id'] = $id_tags[0]['data']; + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) : + $ret['id'] = $id_tags[0]['data']; + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) : + $ret['id'] = $id_tags[0]['data']; + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'guid')) : + $ret['id'] = $id_tags[0]['data']; + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'guid')) : + $ret['id'] = $id_tags[0]['data']; + endif; + endif; + + if (!is_null($what) and is_scalar($what)) : + $ret = $ret[$what]; + endif; + return $ret; + } + + function comment_link () { + $url = null; + + // RSS 2.0 has a standard <comments> element: + // "<comments> is an optional sub-element of <item>. If present, + // it is the url of the comments page for the item." + // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt> + if (isset($this->item['comments'])) : + $url = $this->item['comments']; + endif; + + // The convention in Atom feeds is to use a standard <link> + // element with @rel="replies" and @type="text/html". + // Unfortunately, SimplePie_Item::get_links() allows us to filter + // by the value of @rel, but not by the value of @type. *sigh* + + // Try Atom 1.0 first + $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'); + + // Fall back and try Atom 0.3 + if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif; + + // Now loop through the elements, screening by @rel and @type + if (is_array($linkElements)) : foreach ($linkElements as $link) : + $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate'); + $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL); + $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL); + + if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) : + $url = $href; + endif; + endforeach; endif; + + return $url; + } + + function comment_feed () { + $feed = null; + + // Well Formed Web comment feeds extension for RSS 2.0 + // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments> + // + // N.B.: Correct capitalization is wfw:commentRss, but + // wfw:commentRSS is common in the wild (partly due to a typo in + // the original spec). In any case, our item array is normalized + // to all lowercase anyways. + if (isset($this->item['wfw']['commentrss'])) : + $feed = $this->item['wfw']['commentrss']; + endif; + + // In Atom 1.0, the convention is to use a standard link element + // with @rel="replies". Sometimes this is also used to pass a + // link to the human-readable comments page, so we also need to + // check link/@type for a feed MIME type. + // + // Which is why I'm not using the SimplePie_Item::get_links() + // method here, incidentally: it doesn't allow you to filter by + // @type. *sigh* + if (isset($this->item['link_replies'])) : + // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type + $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1; + for ($i = 1; $i <= $N; $i++) : + $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : ''); + if (isset($this->item[$currentElement.'@type']) + and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) : + $feed = $this->item[$currentElement]; + endif; + endfor; + endif; + return $feed; + } /* SyndicatedPost::comment_feed() */ + + ################################## + #### BUILT-IN CONTENT FILTERS #### + ################################## + + var $uri_attrs = array ( + array('a', 'href'), + array('applet', 'codebase'), + array('area', 'href'), + array('blockquote', 'cite'), + array('body', 'background'), + array('del', 'cite'), + array('form', 'action'), + array('frame', 'longdesc'), + array('frame', 'src'), + array('iframe', 'longdesc'), + array('iframe', 'src'), + array('head', 'profile'), + array('img', 'longdesc'), + array('img', 'src'), + array('img', 'usemap'), + array('input', 'src'), + array('input', 'usemap'), + array('ins', 'cite'), + array('link', 'href'), + array('object', 'classid'), + array('object', 'codebase'), + array('object', 'data'), + array('object', 'usemap'), + array('q', 'cite'), + array('script', 'src') + ); /* var SyndicatedPost::$uri_attrs */ + + var $_base = null; + + function resolve_single_relative_uri ($refs) { + $tag = FeedWordPressHTML::attributeMatch($refs); + $url = SimplePie_Misc::absolutize_url($tag['value'], $this->_base); + return $tag['prefix'] . $url . $tag['suffix']; + } /* function SyndicatedPost::resolve_single_relative_uri() */ + + function resolve_relative_uris ($content, $obj) { + $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes'); + if ($set and $set != 'no') : + // Fallback: if we don't have anything better, use the + // item link from the feed + $obj->_base = $obj->permalink(); // Reset the base for resolving relative URIs + + // What we should do here, properly, is to use + // SimplePie_Item::get_base() -- but that method is + // currently broken. Or getting down and dirty in the + // SimplePie representation of the content tags and + // grabbing the xml_base member for the content element. + // Maybe someday... + + foreach ($obj->uri_attrs as $pair) : + list($tag, $attr) = $pair; + $pattern = FeedWordPressHTML::attributeRegex($tag, $attr); + $content = preg_replace_callback ( + $pattern, + array(&$obj, 'resolve_single_relative_uri'), + $content + ); + endforeach; + endif; + + return $content; + } /* function SyndicatedPost::resolve_relative_uris () */ + + var $strip_attrs = array ( + array('[a-z]+', 'target'), +// array('[a-z]+', 'style'), +// array('[a-z]+', 'on[a-z]+'), + ); + + function strip_attribute_from_tag ($refs) { + $tag = FeedWordPressHTML::attributeMatch($refs); + return $tag['before_attribute'].$tag['after_attribute']; + } + + function sanitize_content ($content, $obj) { + # This kind of sucks. I intend to replace it with + # lib_filter sometime soon. + foreach ($obj->strip_attrs as $pair): + list($tag,$attr) = $pair; + $pattern = FeedWordPressHTML::attributeRegex($tag, $attr); + + $content = preg_replace_callback ( + $pattern, + array(&$obj, 'strip_attribute_from_tag'), + $content + ); + endforeach; + return $content; + } /* SyndicatedPost::sanitize() */ + + ##################### + #### POST STATUS #### + ##################### + + /** + * SyndicatedPost::filtered: check whether or not this post has been + * screened out by a registered filter. + * + * @return bool TRUE iff post has been filtered out by a previous filter + */ + function filtered () { + return is_null($this->post); + } /* SyndicatedPost::filtered() */ + + /** + * SyndicatedPost::freshness: check whether post is a new post to be + * inserted, a previously syndicated post that needs to be updated to + * match the latest revision, or a previously syndicated post that is + * still up-to-date. + * + * @return int A status code representing the freshness of the post + * 0 = post already syndicated; no update needed + * 1 = post already syndicated, but needs to be updated to latest + * 2 = post has not yet been syndicated; needs to be created + */ + 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' + "); + + if (!$result) : + $this->_freshness = 2; // New content + else: + $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id); + if (count($stored_update_hashes) > 0) : + $stored_update_hash = $stored_update_hashes[0]; + $update_hash_changed = ($stored_update_hash != $this->update_hash()); + else : + $update_hash_changed = true; // Can't find syndication meta-data + endif; + + preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref); + + $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]); + $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL); + + $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id); + $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]); + $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL)); + + // Check timestamps... + $updated = ( + !is_null($updated_ts) + and ($updated_ts > $last_rev_ts) + ); + + + // Or the hash... + $updated = ($updated or $update_hash_changed); + + // But only if the post is not frozen. + $updated = ( + $updated + and !$frozen_post + and !$frozen_feed + ); + + if ($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; + endif; + return $this->_freshness; + } + + ################################################# + #### INTERNAL STORAGE AND MANAGEMENT METHODS #### + ################################################# + + 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 ( + $this->link->setting('unfamiliar author', 'unfamiliar_author', 'create') + ); + + if (is_null($this->post['post_author'])) : + $this->post = NULL; + endif; + endif; + + if (!$this->filtered() and $freshness > 0) : + $consider = array( + 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')), + 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')), + ); + + $termSet = array(); $valid = null; + foreach ($consider as $what => $taxes) : + if (!is_null($this->post)) : // Not filtered out yet + # -- Look up, or create, numeric ID for categories + $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']); + + // Eliminate dummy variables + $taxonomies = array_filter($taxonomies, 'remove_dummy_zero'); + + $terms = $this->category_ids ( + $this->post['taxed'][$what], + $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what), + /*taxonomies=*/ $taxonomies, + array( + 'singleton' => false, // I don't like surprises + 'filters' => true, + ) + ); + + if (is_null($terms) or is_null($termSet)) : + // filtered out -- no matches + else : + $valid = true; + + // filter mode off, or at least one match + foreach ($terms as $tax => $term_ids) : + if (!isset($termSet[$tax])) : + $termSet[$tax] = array(); + endif; + $termSet[$tax] = array_merge($termSet[$tax], $term_ids); + endforeach; + endif; + endif; + endforeach; + + if (is_null($valid)) : // Plonked + $this->post = NULL; + else : // We can proceed + $this->post['tax_input'] = array(); + foreach ($termSet as $tax => $term_ids) : + if (!isset($this->post['tax_input'][$tax])) : + $this->post['tax_input'][$tax] = array(); + endif; + $this->post['tax_input'][$tax] = array_merge( + $this->post['tax_input'][$tax], + $term_ids + ); + endforeach; + + // Now let's add on the feed and global presets + foreach ($this->post['pretax'] as $tax => $term_ids) : + if (!isset($this->post['tax_input'][$tax])) : + $this->post['tax_input'][$tax] = array(); + endif; + + $this->post['tax_input'][$tax] = array_merge ( + $this->post['tax_input'][$tax], + $this->category_ids ( + /*terms=*/ $term_ids, + /*unfamiliar=*/ 'create:'.$tax, // These are presets; for those added in a tagbox editor, the tag may not yet exist + /*taxonomies=*/ array($tax), + array( + 'singleton' => true, + )) + ); + endforeach; + endif; + endif; + + if (!$this->filtered() and $freshness > 0) : + unset($this->post['named']); + $this->post = apply_filters('syndicated_post', $this->post, $this); + + // Allow for feed-specific syndicated_post filters. + $this->post = apply_filters( + "syndicated_post_".$this->link->uri(), + $this->post, + $this + ); + endif; + + // Hook in early to make sure these get inserted if at all possible + add_action( + /*hook=*/ 'transition_post_status', + /*callback=*/ array(&$this, 'add_rss_meta'), + /*priority=*/ -10000, /* very early */ + /*arguments=*/ 3 + ); + + if (!$this->filtered() and $freshness == 2) : + // The item has not yet been added. So let's add it. + FeedWordPress::diagnostic('syndicated_posts', 'Inserting new post "'.$this->post['post_title'].'"'); + + $this->insert_new(); + do_action('post_syndicated_item', $this->wp_id(), $this); + + $ret = 'new'; + elseif (!$this->filtered() and $freshness == 1) : + FeedWordPress::diagnostic('syndicated_posts', 'Updating existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"'); + + $this->post['ID'] = $this->wp_id(); + $this->update_existing(); + do_action('update_syndicated_item', $this->wp_id(), $this); + + $ret = 'updated'; + else : + $ret = false; + endif; + + // Remove add_rss_meta hook + remove_action( + /*hook=*/ 'transition_post_status', + /*callback=*/ array(&$this, 'add_rss_meta'), + /*priority=*/ -10000, /* very early */ + /*arguments=*/ 3 + ); + + return $ret; + } /* function SyndicatedPost::store () */ + + function insert_post ($update = false) { + global $wpdb; + + $dbpost = $this->normalize_post(/*new=*/ true); + if (!is_null($dbpost)) : + $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks + + // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data + add_action('_wp_put_post_revision', array($this, 'fix_revision_meta')); + + // Kludge to prevent kses filters from stripping the + // content of posts when updating without a logged in + // user who has `unfiltered_html` capability. + add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11); + + if ($update and function_exists('get_post_field')) : + // Don't munge status fields that the user may + // have reset manually + $doNotMunge = array('post_status', 'comment_status', 'ping_status'); + + foreach ($doNotMunge as $field) : + $dbpost[$field] = get_post_field($field, $this->wp_id()); + endforeach; + endif; + + // WP3's wp_insert_post scans current_user_can() for the + // tax_input, with no apparent way to override. Ugh. + add_action( + /*hook=*/ 'transition_post_status', + /*callback=*/ array(&$this, 'add_terms'), + /*priority=*/ -10001, /* very early */ + /*arguments=*/ 3 + ); + + $this->_wp_id = wp_insert_post($dbpost); + + remove_action( + /*hook=*/ 'transition_post_status', + /*callback=*/ array(&$this, 'add_terms'), + /*priority=*/ -10001, /* very early */ + /*arguments=*/ 3 + ); + + // Turn off ridiculous fucking kludges #1 and #2 + remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta')); + remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11); + + $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__)); + endif; + } /* function SyndicatedPost::insert_post () */ + + function insert_new () { + $this->insert_post(/*update=*/ false); + } /* SyndicatedPost::insert_new() */ + + function update_existing () { + $this->insert_post(/*update=*/ true); + } /* SyndicatedPost::update_existing() */ + + /** + * SyndicatedPost::normalize_post() + * + * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post. + * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions + */ + function normalize_post ($new = true) { + global $wpdb; + + $out = array(); + + // Why the fuck doesn't wp_insert_post already do this? + foreach ($this->post as $key => $value) : + if (is_string($value)) : + $out[$key] = $wpdb->escape($value); + else : + $out[$key] = $value; + endif; + endforeach; + + $fullPost = $out['post_title'].$out['post_content']; + $fullPost .= (isset($out['post_excerpt']) ? $out['post_excerpt'] : ''); + if (strlen($fullPost) < 1) : + // FIXME: Option for filtering out empty posts + endif; + if (strlen($out['post_title'])==0) : + $offset = (int) get_option('gmt_offset') * 60 * 60; + if (isset($this->post['meta']['syndication_source'])) : + $source_title = $this->post['meta']['syndication_source']; + else : + $feed_url = parse_url($this->post['meta']['syndication_feed']); + $source_title = $feed_url['host']; + endif; + + $out['post_title'] = $source_title + .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset); + // FIXME: Option for what to fill a blank title with... + endif; + + return $out; + } + + /** + * SyndicatedPost::validate_post_id() + * + * @param array $dbpost An array representing the post we attempted to insert or update + * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called. + */ + function validate_post_id ($dbpost, $ns) { + if (is_array($ns)) : $ns = implode('::', $ns); + else : $ns = (string) $ns; endif; + + // This should never happen. + if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) : + FeedWordPress::critical_bug( + /*name=*/ $ns.'::_wp_id', + /*var =*/ array( + "\$this->_wp_id" => $this->_wp_id, + "\$dbpost" => $dbpost, + "\$this" => $this + ), + /*line # =*/ __LINE__ + ); + endif; + } /* SyndicatedPost::validate_post_id() */ + + /** + * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up + * meta-data (authorship, etc.) when storing revisions of an updated + * syndicated post. + * + * In their infinite wisdom, the WordPress coders have made it completely + * impossible for a plugin that uses wp_insert_post() to set certain + * meta-data (such as the author) when you store an old revision of an + * updated post. Instead, it uses the WordPress defaults (= currently + * active user ID if the process is running with a user logged in, or + * = #0 if there is no user logged in). This results in bogus authorship + * data for revisions that are syndicated from off the feed, unless we + * use a ridiculous kludge like this to end-run the munging of meta-data + * by _wp_put_post_revision. + * + * @param int $revision_id The revision ID to fix up meta-data + */ + function fix_revision_meta ($revision_id) { + global $wpdb; + + $post_author = (int) $this->post['post_author']; + + $revision_id = (int) $revision_id; + $wpdb->query(" + UPDATE $wpdb->posts + SET post_author={$this->post['post_author']} + WHERE post_type = 'revision' AND ID='$revision_id' + "); + } /* SyndicatedPost::fix_revision_meta () */ + + /** + * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing + * an automatic update, that generally means that wp_insert_post() is + * being called under the user credentials of whoever is viewing the + * blog at the time -- usually meaning no user at all. But if WordPress + * gets a wp_insert_post() when current_user_can('unfiltered_html') is + * false, it will run the content of the post through a kses function + * that strips out lots of HTML tags -- notably <object> and some others. + * This causes problems for syndicating (for example) feeds that contain + * YouTube videos. It also produces an unexpected asymmetry between + * automatically-initiated updates and updates initiated manually from + * the WordPress Dashboard (which are usually initiated under the + * credentials of a logged-in admin, and so don't get run through the + * kses function). So, to avoid the whole mess, what we do here is + * just forcibly disable the kses munging for a single syndicated post, + * by restoring the contents of the `post_content` field. + * + * @param string $content The content of the post, after other filters have gotten to it + * @return string The original content of the post, before other filters had a chance to munge it. + */ + function avoid_kses_munge ($content) { + global $wpdb; + return $wpdb->escape($this->post['post_content']); + } + + /** + * SyndicatedPost::add_terms() -- if FeedWordPress is processing an + * automatic update, that generally means that wp_insert_post() is being + * called under the user credentials of whoever is viewing the blog at + * the time -- which usually means no user at all. But wp_insert_post() + * checks current_user_can() before assigning any of the terms in a + * post's tax_input structure -- which is unfortunate, since + * current_user_can() always returns FALSE when there is no current user + * logged in. Meaning that automatic updates get no terms assigned. + * + * So, wp_insert_post() is not going to do the term assignments for us. + * If you want something done right.... + * + * @param string $new_status Unused action parameter. + * @param string $old_status Unused action parameter. + * @param object $post The database record for the post just inserted. + */ + function add_terms ($new_status, $old_status, $post) { + if ( is_array($this->post) and isset($this->post['tax_input']) and is_array($this->post['tax_input']) ) : + foreach ($this->post['tax_input'] as $taxonomy => $terms) : + if (is_array($terms)) : + $terms = array_filter($terms); // strip out empties + endif; + + wp_set_post_terms($post->ID, $terms, $taxonomy); + endforeach; + endif; + } /* SyndicatedPost::add_terms () */ + + /** + * 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. + * + * @param string $new_status Unused action parameter. + * @param string $old_status Unused action parameter. + * @param object $post The database record for the post just inserted. + */ + function add_rss_meta ($new_status, $old_status, $post) { + FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}'); + + global $wpdb; + if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) : + $postId = $post->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 ) : + $eKey = $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='$eKey' + "); + + // Allow for either a single value or an array + if (!is_array($values)) $values = array($values); + foreach ( $values as $value ) : + FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($value, /*no newlines=*/ true)); + add_post_meta($postId, $key, $value, /*unique=*/ false); + 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. + * + * @param string $unfamiliar_author + * + * @return NULL|int The numeric ID of the author to attribute the post to + * NULL if the post should be filtered out. + */ + function author_id ($unfamiliar_author = 'create') { + global $wpdb; + + $a = $this->post['named']['author']; + + $source = $this->source(); + $forbidden = apply_filters('feedwordpress_forbidden_author_names', + array('admin', 'administrator', 'www', 'root')); + + $candidates = array(); + $candidates[] = $a['name']; + if (!is_null($source)) : $candidates[] = $source['title']; endif; + $candidates[] = $this->link->name(/*fromFeed=*/ true); + $candidates[] = $this->link->name(/*fromFeed=*/ false); + if (strlen($this->link->homepage()) > 0) : $candidates[] = feedwordpress_display_url($this->link->homepage()); endif; + $candidates[] = feedwordpress_display_url($this->link->uri()); + $candidates[] = 'unknown author'; + + $author = NULL; + while (is_null($author) and ($candidate = each($candidates))) : + if (!is_null($candidate['value']) + and (strlen(trim($candidate['value'])) > 0) + and !in_array(strtolower(trim($candidate['value'])), $forbidden)) : + $author = $candidate['value']; + endif; + endwhile; + + $email = (isset($a['email']) ? $a['email'] : NULL); + $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL); + + + $hostUrl = $this->link->homepage(); + if (is_null($hostUrl) or (strlen($hostUrl) < 0)) : + $hostUrl = $this->link->uri(); + endif; + + $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email")); + if ($match_author_by_email and !FeedWordPress::is_null_email($email)) : + $test_email = $email; + else : + $test_email = NULL; + endif; + + // Never can be too careful... + $login = sanitize_user($author, /*strict=*/ true); + + // Possible for, e.g., foreign script author names + if (strlen($login) < 1) : + // No usable characters in author name for a login. + // (Sometimes results from, e.g., foreign scripts.) + // + // We just need *something* in Western alphanumerics, + // so let's try the domain name. + // + // Uniqueness will be guaranteed below if necessary. + + $url = parse_url($hostUrl); + + $login = sanitize_user($url['host'], /*strict=*/ true); + if (strlen($login) < 1) : + // This isn't working. Frak it. + $login = 'syndicated'; + endif; + endif; + + $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); + $test_email = $wpdb->escape($test_email); + $authorUrl = $wpdb->escape($authorUrl); + + // Check for an existing author rule.... + if (isset($this->link->settings['map authors']['name']['*'])) : + $author_rule = $this->link->settings['map authors']['name']['*']; + elseif (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) : + $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))]; + else : + $author_rule = NULL; + endif; + + // User name is mapped to a particular author. If that author ID exists, use it. + if (is_numeric($author_rule) and get_userdata((int) $author_rule)) : + $id = (int) $author_rule; + + // User name is filtered out + elseif ('filter' == $author_rule) : + $id = NULL; + + else : + // Check the database for an existing author record that might fit + + // First try the user core data table. + $id = $wpdb->get_var( + "SELECT ID FROM $wpdb->users + WHERE TRIM(LCASE(display_name)) = TRIM(LCASE('$author')) + OR ( + LENGTH(TRIM(LCASE(user_email))) > 0 + AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email')) + )"); + + // If that fails, look for aliases in the user meta data table + if (is_null($id)) : + $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; + + // ... if you don't find one, then do what you need to do + if (is_null($id)) : + if ($unfamiliar_author === 'create') : + $userdata = array(); + + // WordPress 3 is going to pitch a fit if we attempt to register + // more than one user account with an empty e-mail address, so we + // need *something* here. Ugh. + if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) : + $url = parse_url($hostUrl); + $email = $nice_author.'@'.$url['host']; + endif; + + #-- 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'] = $authorUrl; + $userdata['display_name'] = $author; + $userdata['role'] = 'contributor'; + + do { // Keep trying until you get it right. Or until PHP crashes, I guess. + $id = wp_insert_user($userdata); + if (is_wp_error($id)) : + $codes = $id->get_error_code(); + switch ($codes) : + case 'empty_user_login' : + case 'existing_user_login' : + // Add a random disambiguator + $userdata['user_login'] .= substr(md5(uniqid(microtime())), 0, 6); + break; + case 'existing_user_email' : + // No disassemble! + $parts = explode('@', $userdata['user_email'], 2); + + // Add a random disambiguator as a gmail-style username extension + $parts[0] .= '+'.substr(md5(uniqid(microtime())), 0, 6); + + // Reassemble + $userdata['user_email'] = $parts[0].'@'.$parts[1]; + break; + endswitch; + endif; + } while (is_wp_error($id)); + elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) : + $id = (int) $unfamiliar_author; + elseif ($unfamiliar_author === 'default') : + $id = 1; + endif; + endif; + endif; + + if ($id) : + $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id; + + // Multisite: Check whether the author has been recorded + // on *this* blog before. If not, put her down as a + // Contributor for *this* blog. + $user = new WP_User((int) $id); + if (empty($user->roles)) : + $user->add_role('contributor'); + endif; + endif; + return $id; + } // function SyndicatedPost::author_id () + + /** + * category_ids: look up (and create) category ids from a list of categories + * + * @param array $cats + * @param string $unfamiliar_category + * @param array|null $taxonomies + * @return array + */ + function category_ids ($cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) { + $singleton = (isset($params['singleton']) ? $params['singleton'] : true); + $allowFilters = (isset($params['filters']) ? $params['filters'] : false); + + $catTax = 'category'; + + if (is_null($taxonomies)) : + $taxonomies = array('category'); + endif; + + // 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); + + $terms = array(); + foreach ($taxonomies as $tax) : + $terms[$tax] = array(); + endforeach; + + foreach ($cats as $cat_name) : + if (preg_match('/^{([^#}]*)#([0-9]+)}$/', $cat_name, $backref)) : + $cat_id = (int) $backref[2]; + $tax = $backref[1]; + if (strlen($tax) < 1) : + $tax = $catTax; + endif; + + $term = term_exists($cat_id, $tax); + if (!is_wp_error($term) and !!$term) : + if (!isset($terms[$tax])) : + $terms[$tax] = array(); + endif; + $terms[$tax][] = $cat_id; + endif; + elseif (strlen($cat_name) > 0) : + $familiar = false; + foreach ($taxonomies as $tax) : + if ($tax!='category' or strtolower($cat_name)!='uncategorized') : + $term = term_exists($cat_name, $tax); + if (!is_wp_error($term) and !!$term) : + $familiar = true; + + if (is_array($term)) : + $term_id = (int) $term['term_id']; + else : + $term_id = (int) $term; + endif; + + if (!isset($terms[$tax])) : + $terms[$tax] = array(); + endif; + $terms[$tax][] = $term_id; + break; // We're done here. + endif; + endif; + endforeach; + + if (!$familiar) : + if ('tag'==$unfamiliar_category) : + $unfamiliar_category = 'create:post_tag'; + endif; + + if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) : + $tax = $catTax; // Default + if (isset($ref[2]) and strlen($ref[2]) > 2) : + $tax = $ref[2]; + endif; + $term = wp_insert_term($cat_name, $tax); + if (is_wp_error($term)) : + FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__); + else : + if (!isset($terms[$tax])) : + $terms[$tax] = array(); + endif; + $terms[$tax][] = (int) $term['term_id']; + endif; + endif; + endif; + endif; + endforeach; + + $filtersOn = $allowFilters; + if ($allowFilters) : + $filters = array_filter( + $this->link->setting('match/filter', 'match_filter', array()), + 'remove_dummy_zero' + ); + $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0)); + endif; + + // Check for filter conditions + foreach ($terms as $tax => $term_ids) : + if ($filtersOn + and (count($term_ids)==0) + and in_array($tax, $filters)) : + $terms = NULL; // Drop the post + break; + else : + $terms[$tax] = array_unique($term_ids); + endif; + endforeach; + + if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs + $terms = end($terms); + endif; + return $terms; + } // function SyndicatedPost::category_ids () + + function use_api ($tag) { + global $wp_db_version; + switch ($tag) : + case 'wp_insert_post': + // 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); + break; + case 'post_status_pending': + $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23); + break; + endswitch; + return $ret; + } // function SyndicatedPost::use_api () + +} /* class SyndicatedPost */ + diff --git a/wp-content/plugins/feedwordpress/syndication.php b/wp-content/plugins/feedwordpress/syndication.php new file mode 100644 index 0000000000000000000000000000000000000000..bebb069f79e8ee495fca1aee041b12185cacf5b6 --- /dev/null +++ b/wp-content/plugins/feedwordpress/syndication.php @@ -0,0 +1,6 @@ +<?php + require_once(dirname(__FILE__).'/feedwordpresssyndicationpage.class.php'); + + $syndicationPage = new FeedWordPressSyndicationPage(__FILE__); + $syndicationPage->display(); + diff --git a/wp-content/plugins/feedwordpress/updatedpostscontrol.class.php b/wp-content/plugins/feedwordpress/updatedpostscontrol.class.php new file mode 100644 index 0000000000000000000000000000000000000000..d505268fea0a489cd8f500cc5556c4db52914850 --- /dev/null +++ b/wp-content/plugins/feedwordpress/updatedpostscontrol.class.php @@ -0,0 +1,62 @@ +<?php +class UpdatedPostsControl { + var $page; + function UpdatedPostsControl (&$page) { + $this->page =& $page; + } /* UpdatedPostsControl constructor */ + + function display () { + $settings = array( + // This is all bass-ackwards because the actual yes/no + // setting is whether to *freeze* posts out of being + // updated, not whether to *expose* it to being updated, + // but in the UI we ask whether the user wants to + // *expose* it. I have my reasons. Stop judging me! + 'no' => __('Yes, update the syndicated copy to match'), + 'yes' => __('No, leave the syndicated copy unmodified'), + ); + $params = array( + 'setting-default' => 'default', + 'global-setting-default' => 'no', + 'labels' => array('yes' => 'leave unmodified', 'no' => 'update to match'), + 'default-input-value' => 'default', + ); + + if ($this->page->for_feed_settings()) : + $aFeed = 'this feed'; + else : + $aFeed = 'a syndicated feed'; + endif; + ?> + <tr> + <th scope="row"><?php _e('Updated posts:') ?></th> + <td><p>When <?php print $aFeed; ?> includes updated content for + a post that was already syndicated, should the syndicated copy + of the post be updated to match the revised version?</p> + + <?php + $this->page->setting_radio_control( + 'freeze updates', 'freeze_updates', + $settings, $params + ); + ?> + + </td></tr> + <?php + } /* UpdatedPostsControl::display() */ + + function accept_POST ($post) { + if ($this->page->for_feed_settings()) : + if (isset($post['freeze_updates'])) : + $this->page->link->settings['freeze updates'] = $post['freeze_updates']; + endif; + else : + // Updated posts + if (isset($post['freeze_updates'])) : + update_option('feedwordpress_freeze_updates', $post['freeze_updates']); + endif; + endif; + } /* UpdatedPostsControl::accept_POST() */ +} /* class UpdatedPostsControl */ + +