diff --git a/wp-content/plugins/feedwordpress/diagnostics-page.php b/wp-content/plugins/feedwordpress/diagnostics-page.php index 46c397502585c3bc6cb250cd9aa52462485d100e..66f0dc2979032db1bc4dbdb2e09789be582130f2 100644 --- a/wp-content/plugins/feedwordpress/diagnostics-page.php +++ b/wp-content/plugins/feedwordpress/diagnostics-page.php @@ -356,23 +356,36 @@ function clone_http_test_args_keyvalue_prototype () { <td><a href="#http-test-args" onclick="return clone_http_test_args_keyvalue_prototype();">+ Add</a></td> </tr> </table> + </td> + </tr> + <tr> + <th>XPath:</th> + <td><div><input type="text" name="http_test_xpath" value="" placeholder="xpath-like query" /></div> + <div><p>Leave blank to test HTTP, fill in to test a query.</p></div> + </td> + </tr> + <?php if (isset($page->test_html['http_test'])) : ?> + <tr> + <th scope="row">RESULTS:</th> + <td> + <div>URL: <code><?php print esc_html($page->test_html['url']); ?></code></div> <div style="position: relative"> <div style="width: 100%; overflow: scroll; background-color: #eed"> <pre style="white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -o-pre-wrap;"><?php print $page->test_html['http_test']; ?></pre> </div> </div> - <?php endif; ?> </td> </tr> + <?php endif; ?> </table> <?php } /* FeedWordPressDiagnosticsPage::tests_box () */ - var $test_html; - static function do_http_test ($post) { + private $test_html; + public function do_http_test ($post) { if (isset($post['http_test_url']) and isset($post['http_test_method'])) : $url = $post['http_test_url']; @@ -410,6 +423,7 @@ function clone_http_test_args_keyvalue_prototype () { break; endswitch; + $this->test_html['url'] = $url; $this->test_html['http_test'] = esc_html(MyPHP::val($out)); endif; } /* FeedWordPressDiagnosticsPage::do_http_test () */ diff --git a/wp-content/plugins/feedwordpress/feedwordpress-elements.js b/wp-content/plugins/feedwordpress/feedwordpress-elements.js index c3d799a767db6a9a631cb5613dbf27bab84015e0..b2665dbba4b6148acf3d52758ca748230f95ce39 100644 --- a/wp-content/plugins/feedwordpress/feedwordpress-elements.js +++ b/wp-content/plugins/feedwordpress/feedwordpress-elements.js @@ -629,7 +629,7 @@ function fwp_xpathtest_ok (response, result_id, destination) { if (response.results instanceof Array) { for (var i = 0; i < response.results.length; i++) { - resultsHtml += '<li>result['+i.toString()+'] = <code>'+response.results[i]+'</code></li>'; + resultsHtml += '<li>result['+(i+1).toString()+'] = <code>'+response.results[i]+'</code></li>'; } } else { resultsHtml += '<li>result = <code>' + response.results + '</code></li>'; diff --git a/wp-content/plugins/feedwordpress/feedwordpress.php b/wp-content/plugins/feedwordpress/feedwordpress.php index a92f2f916a0cbca6d348a81d5fe75af4617b2f67..753ca5eee9893a6307d380e195166f01ac46473d 100644 --- a/wp-content/plugins/feedwordpress/feedwordpress.php +++ b/wp-content/plugins/feedwordpress/feedwordpress.php @@ -3,7 +3,7 @@ Plugin Name: FeedWordPress Plugin URI: http://feedwordpress.radgeek.com/ Description: simple and flexible Atom/RSS syndication for WordPress -Version: 2015.0514 +Version: 2016.0420 Author: Charles Johnson Author URI: http://radgeek.com/ License: GPL @@ -11,7 +11,7 @@ License: GPL /** * @package FeedWordPress - * @version 2015.0514 + * @version 2016.0420 */ # This uses code derived from: @@ -32,7 +32,7 @@ License: GPL # -- Don't change these unless you know what you're doing... -define ('FEEDWORDPRESS_VERSION', '2015.0514'); +define ('FEEDWORDPRESS_VERSION', '2016.0420'); define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact'); if (!defined('FEEDWORDPRESS_BLEG')) : diff --git a/wp-content/plugins/feedwordpress/readme.txt b/wp-content/plugins/feedwordpress/readme.txt index 2ea28d8ccff5e659bd25ed4ca1766b2b9513431a..fe6342b57ee5e2ec2286982da70c7b5fe0d10226 100644 --- a/wp-content/plugins/feedwordpress/readme.txt +++ b/wp-content/plugins/feedwordpress/readme.txt @@ -3,8 +3,8 @@ Contributors: Charles Johnson Donate link: http://feedwordpress.radgeek.com/ Tags: syndication, aggregation, feed, atom, rss Requires at least: 3.0 -Tested up to: 4.2.2 -Stable tag: 2015.0514 +Tested up to: 4.5.2 +Stable tag: 2016.0420 FeedWordPress syndicates content from feeds you choose into your WordPress weblog. @@ -20,7 +20,7 @@ appears as a series of special posts in your WordPress posts database. 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][] to use at Feminist Blogs, an aggregator site that I used to administer. [Planet]: http://www.planetplanet.org/ [Feminist Blogs]: http://feministblogs.org/ @@ -94,6 +94,33 @@ outs, see the documentation at the [FeedWordPress project homepage][]. == Changelog == += 2016.0420 = + +* WORDPRESS COMPATIBILITY: Tested with new versions of WordPress up to 4.5. + +* FILTERS AND ADD-ONS: Allow filters and add-ons to filter terms and taxonomy + (categories, tags, custom taxonomies, etc.) more thoroughly and more + fine-grainedly using syndicated_post_terms_match, syndicated_post_terms_match_{taxonomy}, + syndicated_post_terms_unfamiliar, syndicated_post_terms_mapping, + syndicated_item_feed_terms, and syndicated_item_preset_terms filters. + +* FILTERS AND ADD-ONS: Globals $fwp_channel and $fwp_feedmeta REMOVED. + These global variables, originally introduced to allow filters access to + information about the source feed in `syndicated_item` filters were + deprecated 6+ years ago. If you have any filters or add-ons which still + depend on these global variables, you've been using obsolete techniques + and 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>. + +* BUGFIX: Syndication > Diagnostics HTTP diagnostic test widget was broken due to + a dumb error on my part. Now fixed. + +* SMALL CODING CHANGES: Lots of small changes to code organization, incorporation + of some PHP 5.x coding conventions, etc. + = 2015.0514 = * IMPORTANT SECURITY UPDATE: This version includes two important fixes for diff --git a/wp-content/plugins/feedwordpress/syndicatedpost.class.php b/wp-content/plugins/feedwordpress/syndicatedpost.class.php index f8fba139c4749aa07db94bd3cb380c712aca2c6f..eb3bdbed0e6dfbe1a45a304940a0ed13ad696ce2 100644 --- a/wp-content/plugins/feedwordpress/syndicatedpost.class.php +++ b/wp-content/plugins/feedwordpress/syndicatedpost.class.php @@ -1,6 +1,7 @@ <?php require_once(dirname(__FILE__).'/feedtime.class.php'); require_once(dirname(__FILE__).'/syndicatedpostterm.class.php'); +require_once(dirname(__FILE__).'/syndicatedpostxpathquery.class.php'); /** * class SyndicatedPost: FeedWordPress uses to manage the conversion of @@ -42,7 +43,7 @@ class SyndicatedPost { * @param array $item The item syndicated from the feed. * @param SyndicatedLink $source The feed it was syndicated from. */ - function SyndicatedPost ($item, &$source) { + function __construct ($item, &$source) { global $wpdb; if ( empty($item) && empty($source) ) @@ -86,21 +87,6 @@ class SyndicatedPost { // 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; @@ -265,93 +251,15 @@ class SyndicatedPost { // 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->preset_terms['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->feed_terms['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->preset_terms['post_tag'] = $tags; - - // Scan post for /a[@rel='tag'] and use as tags if present - $tags = $this->inline_tags(); - $this->feed_terms['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->preset_terms[$tax] = $terms; - endif; - endforeach; + // Categories, Tags, and other Terms: from settings assignments (global settings, subscription settings), + // and from feed assignments (item metadata, post content) + $this->preset_terms = apply_filters('syndicated_item_preset_terms', $this->get_terms_from_settings(), $this); + $this->feed_terms = apply_filters('syndicated_item_feed_terms', $this->get_terms_from_feeds(), $this); $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this); endif; - } /* SyndicatedPost::SyndicatedPost() */ + } /* SyndicatedPost::__construct() */ ##################################### #### EXTRACT DATA FROM FEED ITEM #### @@ -383,97 +291,26 @@ class SyndicatedPost { * 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; + $xq = new SyndicatedPostXPathQuery(array("path" => $path)); - 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; + $feedChannel = array_merge( + $this->get_feed_root_element(), + $this->get_feed_channel_elements() + ); - $data = $matches; - endif; - $node = array_shift($path); - endwhile; + $matches = $xq->match(array( + "type" => $this->link->simplepie->get_type(), + "xmlns" => $this->xmlns, + "map" => array( + "/" => array($this->entry->data), + "item" => array($this->entry->data), + "feed" => $feedChannel, + "channel" => $feedChannel + ), + "context" => $this->entry->data, + "parent" => $feedChannel, + )); - $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() */ @@ -505,84 +342,6 @@ class SyndicatedPost { 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 get_categories ($params = array()) { return $this->entry->get_categories(); } @@ -976,6 +735,119 @@ class SyndicatedPost { return $author; } /* SyndicatedPost::author() */ + /** + * SyndicatedPost::get_terms_from_settings(): Return an array of terms to associate with the incoming + * post based on the Categories, Tags, and other terms associated with each new post by the user's + * settings (global and feed-specific). + * + * @since 2016.0331 + * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.), + * and a list of term codes (either alphanumeric names, or ID numbers encoded in a format that + * SyndicatedLink::category_ids() can understand) within that taxonomy + * + */ + public function get_terms_from_settings () { + // 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; + $preset_terms['category'] = $cats; + + // 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; + $preset_terms['post_tag'] = $tags; + + $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) : + // category and tag settings have already previously been handled + // but if this is from another taxonomy, then... + 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. + $preset_terms[$tax] = $terms; + endif; + endforeach; + + return $preset_terms; + } /* SyndicatedPost::get_terms_from_settings () */ + + /** + * SyndicatedPost::get_terms_from_feeds(): Return an array of terms to associate with the incoming + * post based on the contents of the subscribed feed (atom:category and rss:category elements, dc:subject + * elements, tags embedded using microformats in the post content, etc.) + * + * @since 2016.0331 + * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.), + * and a list of alphanumeric term names + */ + public function get_terms_from_feeds () { + // 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; + + $feed_terms['category'] = apply_filters('syndicated_item_categories', $cats, $this); + + // Scan post for /a[@rel='tag'] and use as tags if present + $tags = $this->inline_tags(); + $feed_terms['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this); + + return $feed_terms; + } /* SyndicatedPost::get_terms_from_feeds () */ + /** * SyndicatedPost::inline_tags: Return a list of all the tags embedded * in post content using the a[@rel="tag"] microformat. @@ -1568,13 +1440,17 @@ class SyndicatedPost { // We have to check again in case post has been filtered during // the author_id lookup if ($this->has_fresh_content()) : - $consider = array( - 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')), - 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')), - ); + $mapping = apply_filters('syndicated_post_terms_mapping', array( + 'category' => array('abbr' => 'cats', 'unfamiliar' => 'category', 'domain' => array('category', 'post_tag')), + 'post_tag' => array('abbr' => 'tags', 'unfamiliar' => 'post_tag', 'domain' => array('post_tag')), + ), $this); $termSet = array(); $valid = null; - foreach ($consider as $what => $taxes) : + foreach ($this->feed_terms as $what => $anTerms) : + // Default to using the inclusive procedures (for cats) rather than exclusive (for inline tags) + $taxes = (isset($mapping[$what]) ? $mapping[$what] : $mapping['category']); + $unfamiliar = $taxes['unfamiliar']; + 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']); @@ -1582,9 +1458,24 @@ class SyndicatedPost { // Eliminate dummy variables $taxonomies = array_filter($taxonomies, 'remove_dummy_zero'); + // Allow FWP add-on filters to control the taxonomies we use to search for a term + $taxonomies = apply_filters("syndicated_post_terms_match", $taxonomies, $what, $this); + $taxonomies = apply_filters("syndicated_post_terms_match_${what}", $taxonomies, $this); + + // Allow FWP add-on filters to control with greater precision what happens on unmatched + $unmatched = apply_filters("syndicated_post_terms_unfamiliar", + $this->link->setting( + "unfamiliar {$unfamiliar}", + "unfamiliar_{$unfamiliar}", + 'create:'.$unfamiliar + ), + $what, + $this + ); + $terms = $this->category_ids ( - $this->feed_terms[$what], - $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what), + $anTerms, + $unmatched, /*taxonomies=*/ $taxonomies, array( 'singleton' => false, // I don't like surprises @@ -1727,6 +1618,8 @@ class SyndicatedPost { $dbpost = $this->normalize_post(/*new=*/ true); + $ret = null; + if (!is_null($dbpost)) : $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks @@ -1791,28 +1684,34 @@ class SyndicatedPost { $dbpost['ID'] = $this->_wp_id; endif; - // Now that we've made sure the original exists, insert - // this version here as a revision. - $revision_id = _wp_put_post_revision($dbpost, /*autosave=*/ false); - - if (!$this->this_revision_needs_original_post()) : + // Sanity check: if the attempt to insert post + // returned an error, then feeding that error + // object in to _wp_put_post_revision() would + // cause a fatal error. Better to break out. + if (!is_wp_error($this->_wp_id)) : + // Now that we've made sure the original exists, insert + // this version here as a revision. + $revision_id = _wp_put_post_revision($dbpost, /*autosave=*/ false); + + if (!$this->this_revision_needs_original_post()) : - if ($this->this_revision_is_current()) : + if ($this->this_revision_is_current()) : - wp_restore_post_revision($revision_id); + wp_restore_post_revision($revision_id); - else : + else : - // If we do not activate this revision, then the - // add_rss_meta will not be called, which is - // more or less as it should be, but that means - // we have to actively record this revision's - // update hash from here. - $postId = $this->post['ID']; - $key = 'syndication_item_hash'; - $hash = $this->update_hash(); - FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($hash, /*no newlines=*/ true)); - add_post_meta( $postId, $key, $hash, /*unique=*/ false ); + // If we do not activate this revision, then the + // add_rss_meta will not be called, which is + // more or less as it should be, but that means + // we have to actively record this revision's + // update hash from here. + $postId = $this->post['ID']; + $key = 'syndication_item_hash'; + $hash = $this->update_hash(); + FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($hash, /*no newlines=*/ true)); + add_post_meta( $postId, $key, $hash, /*unique=*/ false ); + endif; endif; endif; @@ -1837,7 +1736,10 @@ class SyndicatedPost { endforeach; $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__)); + + $ret = $this->_wp_id; endif; + return $ret; } /* function SyndicatedPost::insert_post () */ function insert_new () { @@ -1884,11 +1786,46 @@ class SyndicatedPost { return $out; } + public function db_sanitize_post_check_encoding ($out) { + // Check encoding recursively: every string field needs to be checked + // for character encoding issues. This is a bit problematic because we + // *should* be using DB_CHARSET, but DB_CHARSET sometimes has values + // that work for MySQL but not for PHP mb_check_encoding. So instead + // we must rely on WordPress setting blog_charset and hope that the user + // has got their database encoding set up to roughly match + $charset = get_option('blog_charset', 'utf8'); + + foreach ($out as $key => $value) : + if (is_string($value)) : + + if (!function_exists('mb_check_encoding') or mb_check_encoding($value, $charset)) : + $out[$key] = $value; + else : + $fromCharset = mb_detect_encoding($value, mb_detect_order(), /*strict=*/ true); + $out[$key] = mb_convert_encoding($value, $charset, $fromCharset); + endif; + + elseif (is_array($value)) : + $out[$key] = $this->db_sanitize_post_check_encoding($value); + + else : + $out[$key] = $value; + endif; + + endforeach; + + return $out; + } /* SyndicatedPost::db_sanitize_post_check_encoding () */ + function db_sanitize_post ($out) { - // < 3.6. Core API, including wp_insert_post(), will expect - // properly slashed data. If wp_slash() exists, then this is - // after the big change-over, and wp_insert_post() etc. will - // expect *un*-slashed data. + global $wp_db_version; + + $out = $this->db_sanitize_post_check_encoding($out); + + // < 3.6. Core API, including `wp_insert_post()`, expects + // properly slashed data. If `wp_slash()` exists, then + // this is after the big change-over in how data slashing + // was handled. if (!function_exists('wp_slash')) : foreach ($out as $key => $value) : @@ -1898,7 +1835,21 @@ class SyndicatedPost { $out[$key] = $value; endif; endforeach; - + + // For revisions [@23416,@23554), core API expects + // unslashed data. Cf. <https://core.trac.wordpress.org/browser/trunk/wp-includes/post.php?rev=23416> + // NOOP for those revisions. + + // In revisions @23554 to present, `wp_insert_post()` + // expects slashed data once again. + // Cf. <https://core.trac.wordpress.org/changeset/23554/trunk/wp-includes/post.php?contextall=1> + // But at least now we can use the wp_slash API function to do that. + // Hooray. + + elseif ($wp_db_version >= 23524) : + + $out = wp_slash($out); + endif; return $out; @@ -2338,4 +2289,3 @@ EOM; } /* SyndicatedPost::category_ids () */ } /* class SyndicatedPost */ - diff --git a/wp-content/plugins/feedwordpress/syndicatedpostterm.class.php b/wp-content/plugins/feedwordpress/syndicatedpostterm.class.php index 01f61a95709a65f9dd7abc97f5b26039d805de7b..ff70a375967535a75231b026fe19855ba4c801e1 100644 --- a/wp-content/plugins/feedwordpress/syndicatedpostterm.class.php +++ b/wp-content/plugins/feedwordpress/syndicatedpostterm.class.php @@ -59,6 +59,9 @@ class SyndicatedPostTerm { protected function search () { + // Initialize + $found = null; + // Either this is a numbered term code, which supplies the ID // and the taxonomy explicitly (e.g.: {category#2}; in which // case we have set $this->tax to a unit array containing only @@ -90,7 +93,7 @@ class SyndicatedPostTerm { 'CHECKED familiarity of term ' .json_encode($this->term) .' across '.json_encode($this->tax) - . ' with result: '.json_encode($record) + . ' with result: '.json_encode($found) ); return $this->exists; diff --git a/wp-content/plugins/feedwordpress/syndicatedpostxpathquery.class.php b/wp-content/plugins/feedwordpress/syndicatedpostxpathquery.class.php new file mode 100644 index 0000000000000000000000000000000000000000..7219c61631751b70f56ff96e94a0755c4aa6e1fa --- /dev/null +++ b/wp-content/plugins/feedwordpress/syndicatedpostxpathquery.class.php @@ -0,0 +1,462 @@ +<?php +/** + * class SyndicatedPostXPathQuery: implements an XPath-like syntax used to query + * arbitrary elements within the syndicated item. + * + */ +class SyndicatedPostXPathQuery { + private $path; + private $parsedPath; + private $feed_type; + private $xmlns; + private $urlHash = array(); + + /** + * SyndicatedPostXPathQuery::__construct + * + * @param array $args + * @uses wp_parse_args + * + */ + public function __construct ($args = array()) { + if (is_string($args)) : + $args = array("path" => $args); + endif; + + $args = wp_parse_args($args, array( + "path" => "", + )); + + $this->setPath($args['path']); + } /* SyndicatedPostXPathQuery::__construct() */ + + /** + * SyndicatedPostXPathQuery::getPath + * + * @param array $args + * @return mixed + */ + public function getPath ($args = array()) { + $args = wp_parse_args($args, array( + "parsed" => false, + )); + + return ($args['parsed'] ? $this->parsedPath : $this->path); + } /* SyndicatedPostXPathQuery::getPath () */ + + /** + * SyndicatedPostXPathQuery::setPath + * + * @param string $path + */ + public function setPath ($path) { + $this->urlHash = array(); + + $this->path = $path; + + // Allow {url} notation for namespaces. URLs will contain : and /, so... + preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER); + foreach ($match as $ref) : + $this->urlHash[md5($ref[1])] = $ref[1]; + endforeach; + + foreach ($this->urlHash as $hash => $url) : + $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path); + endforeach; + + $path = $this->parsePath(/*cur=*/ $path, /*orig=*/ $path); + + $this->parsedPath = $path; + + } /* SyndicatedPostXPathQuery::setPath() */ + + /** + * SyndicatedPostXPathQuery::snipSlug + * + * @return string + */ + protected function snipSlug ($path, $start, $n) { + $slug = substr($path, $start, ($n-$start)); + if (strlen($slug) > 0) : + if (preg_match('/{#([^}]+)}/', $slug, $ref)) : + if (isset($this->urlHash[$ref[1]])) : + $slug = str_replace( + '{#'.$ref[1].'}', + '{'.$this->urlHash[$ref[1]].'}', + $slug + ); + endif; + endif; + endif; + return $slug; + } /* SyndicatedPostXPathQuery::snipSlug () */ + + /** + * SyndicatedPostXPathQuery::parsePath () + * + * @param mixed $path + * @param mixed $rootPath + * @return array|object + */ + public function parsePath ($path, $rootPath) { + if (is_array($path)) : + // This looks like it's already been parsed. + $pp = $path; + else : + $pp = array(); + + // Okay let's parse this thing. + $n = 0; $start = 0; $state = 'slug'; + while ($state != '$') : + switch ($state) : + case 'slash' : + $slug = $this->snipSlug($path, $start, $n); + if (strlen($slug) > 0) : + $pp[] = $slug; + endif; + + $n++; + // don't include the slash in our next slug + $start = $n; + + $state = (($n < strlen($path)) ? 'slug' : '$'); + + break; + case 'brackets' : + + // first, snip off what we've consumed so far + $slug = $this->snipSlug($path, $start, $n); + if (strlen($slug) > 0) : + $pp[] = $slug; + endif; + + // now, chase the ] + $depth = 1; + $n++; $start = $n; + + // find the end of the [square-bracketed] expression + while ($depth > 0 and $n != '') : + $tok = ((strlen($path) > $n) ? $path[$n] : ''); + switch ($tok) : + case '' : + // ERROR STATE: syntax error + $depth = -1; + $state = 'syntax-error'; + break; + case '[' : + $depth++; + break; + case ']' : + $depth--; + break; + default : + // NOOP + endswitch; + $n++; + endwhile; + + if ($state != 'syntax-error') : + $bracketed = substr($path, $start, ($n-$start)-1); + + // recursive parsing + $oFilter = new stdClass; + $oFilter->verb = 'has'; + $oFilter->query = $this->parsePath($bracketed, $rootPath); + $pp[] = $oFilter; + + $start = $n; + + $state = 'slash-expected'; + endif; + break; + + case 'slash-expected' : + $tok = ((strlen($path) > $n) ? $path[$n] : ''); + if ($tok == '/' or $tok == '') : + $state = 'slash'; + else : + $state = 'syntax-error'; + endif; + break; + case 'syntax-error' : + $pp = new WP_Error('xpath', __("Syntax error", "feedwordpress")); + $state = '$'; + break; + case 'slug' : + default : + $tok = ((strlen($path) > $n) ? $path[$n] : ''); + switch ($tok) : + case '' : + case '/' : + $state = 'slash'; + break; + case '[' : + $state = 'brackets'; + break; + default : + $n++; + endswitch; + endswitch; + endwhile; + endif; + return $pp; + } /* SyndicatedPostXPathQuery::parsePath() */ + + /** + * SyndicatedPostXPathQuery::match + * + * @param string $path + * @return array + */ + public function match ($r = array()) { + $path = $this->parsedPath; + + $r = wp_parse_args($r, array( + "type" => SIMPLEPIE_TYPE_ATOM_10, + "xmlns" => array(), + "map" => array(), + "context" => array(), + "parent" => array(), + "format" => "string", + )); + + $this->feed_type = $r['type']; + $this->xmlns = $r['xmlns']; + + // Start out with a get_item_tags query. + $node = ''; + while (strlen($node)==0 and !is_null($node)) : + $node = array_shift($path); + endwhile; + + if (is_string($node) and isset($r['map'][$node])) : + $data = $r['map'][$node]; + $node = array_shift($path); + else : + $data = $r['map']['/']; + endif; + + $matches = $data; + while (!is_null($node)) : + if (is_object($node) OR strlen($node) > 0) : + list($axis, $element) = $this->xpath_name_and_axis($node); + if ('self'==$axis) : + if (is_object($element) and property_exists($element, 'verb')) : + + $subq = new self(array("path" => $element->query)); + $result = $subq->match(array( + "type" => $r['type'], + "xmlns" => $r['xmlns'], + "map" => array( + "/" => $matches, + ), + "context" => $matches, + "parent" => $r['parent'], + "format" => "object", + )); + + // when format = 'object' we should get back + // a sparse array of arrays, with indices = indices + // from the input array, each element = an array of + // one or more matching elements + + if ($element->verb = 'has' and is_array($result)) : + + $results = array(); + foreach (array_keys($result) as $a) : + $results[$a] = $matches[$a]; + endforeach; + + $matches = $results; + $data = $matches; + endif; + + elseif (is_numeric($node)) : + + // according to W3C, sequence starts at position 1, not 0 + // so subtract 1 to line up with PHP array starting at 0 + $idx = intval($element) - 1; + if (isset($matches[$idx])) : + $data = array($idx => $matches[$idx]); + else : + $data = array(); + endif; + + $matches = array($idx => $data); + endif; + + else : + $matches = array(); + + foreach ($data as $idx => $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); + + // Element + else : + $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; + endif; + $node = array_shift($path); + endwhile; + + $matches = array(); + foreach ($data as $idx => $datum) : + if ($r['format'] == 'string') : + if (is_string($datum)) : + $matches[] = $datum; + elseif (isset($datum['data'])) : + $matches[] = $datum['data']; + endif; + else : + $matches[$idx] = $datum; + endif; + endforeach; + + return $matches; + } /* SyndicatedPostXPathQuery::match() */ + + public function xpath_default_namespace () { + // Get the default namespace. + $type = $this->feed_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; + } /* SyndicatedPostXPathQuery::xpath_default_namespace() */ + + public function xpath_name_and_axis ($node) { + $ns = NULL; $element = NULL; + + $axis = 'child'; // "In effect, `child` is the default axis." + if (is_object($node) and property_exists($node, 'verb')): + if ('has'==$node->verb) : + $axis = 'self'; + endif; + elseif (strpos($node, '::') !== false) : + list($axis, $node) = explode("::", $node, 2); + if ($axis=='attribute') : + $axis = 'attribs'; // map from W3C to SimplePie's idiosyncratic notation + endif; + elseif (substr($node, 0, 1)=='@') : + $axis = 'attribs'; $node = substr($node, 1); + elseif (is_numeric($node)) : + $axis = 'self'; + elseif (substr($node, 0, 1)=='/') : + // FIXME: properly, we should check for // and if we have it, + // treat that as short for /descendent-or-self::node()/ + $axis = 'child'; $node = substr($node, 1); + else : + // NOOP + endif; + + if (is_string($node) and preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) : + $element = $ref[2]; + elseif (is_string($node) and strpos($node, ':') !== FALSE) : + list($xmlns, $element) = explode(':', $node, 2); + else : + $element = $node; + endif; + return array($axis, $element); + } /* SyndicatedPostXPathQuery::xpath_name_and_axis () */ + + public 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); + } /* SyndicatedPostXPathQuery::xpath_possible_namespaces() */ + +} /* class SyndicatedPostXPathQuery */ + +// When called directly, run through and perform some tests. +if (basename($_SERVER['SCRIPT_FILENAME'])==basename(__FILE__)) : + # some day when I am a grown-up developer I might include + # some test cases in this here section + # we need to implement wp_parse_args(), __(), and class WP_Error ... + #function wp_parse_args ($r, $defaults) { + # return array_merge($defaults, $r); + #} + #function __($text, $domain) { + # return $text; + #} + #class WP_Error { + # public function __construct ( $slug, $message ) { + # /*DBG*/ echo $slug; + # /*DBG*/ echo ": "; + # /*DBG*/ echo $message; + # } + #} + # + #header("Content-type: text/plain"); + # + #$spxq = new SyndicatedPostXPathQuery(array("path" => $_REQUEST['p'])); + # + #var_dump($spxq); +endif;