Advertisement
Guest User

Feed Wordpress Plugin Duplicate Post Fix

a guest
Nov 6th, 2013
304
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 75.12 KB | None | 0 0
  1. <?php
  2. require_once(dirname(__FILE__).'/feedtime.class.php');
  3.  
  4. /**
  5.  * class SyndicatedPost: FeedWordPress uses to manage the conversion of
  6.  * incoming items from the feed parser into posts for the WordPress
  7.  * database. It contains several internal management methods primarily
  8.  * of interest to someone working on the FeedWordPress source, as well
  9.  * as some utility methods for extracting useful data from many
  10.  * different feed formats, which may be useful to FeedWordPress users
  11.  * who make use of feed data in PHP add-ons and filters.
  12.  *
  13.  * @version 2011.0831
  14.  */
  15. class SyndicatedPost {
  16.     var $item = null;   // MagpieRSS representation
  17.     var $entry = null;  // SimplePie_Item representation
  18.  
  19.     var $link = null;
  20.     var $feed = null;
  21.     var $feedmeta = null;
  22.    
  23.     var $xmlns = array ();
  24.  
  25.     var $post = array ();
  26.  
  27.     var $named = array ();
  28.     var $preset_terms = array ();
  29.     var $feed_terms = array ();
  30.    
  31.     var $_freshness = null;
  32.     var $_wp_id = null;
  33.     var $_wp_post = null;
  34.  
  35.     /**
  36.      * SyndicatedPost constructor: Given a feed item and the source from
  37.      * which it was taken, prepare a post that can be inserted into the
  38.      * WordPress database on request, or updated in place if it has already
  39.      * been syndicated.
  40.      *
  41.      * @param array $item The item syndicated from the feed.
  42.      * @param SyndicatedLink $source The feed it was syndicated from.
  43.      */
  44.     function SyndicatedPost ($item, &$source) {
  45.         global $wpdb;
  46.  
  47.         if (is_array($item)
  48.         and isset($item['simplepie'])
  49.         and isset($item['magpie'])) :
  50.             $this->entry = $item['simplepie'];
  51.             $this->item = $item['magpie'];
  52.             $item = $item['magpie'];
  53.         elseif (is_a($item, 'SimplePie_Item')) :
  54.             $this->entry = $item;
  55.            
  56.             // convert to Magpie for compat purposes
  57.             $mp = new MagpieFromSimplePie($source->simplepie, $this->entry);
  58.             $this->item = $mp->get_item();
  59.            
  60.             // done with conversion object
  61.             $mp = NULL; unset($mp);
  62.         else :
  63.             $this->item = $item;
  64.         endif;
  65.  
  66.         $this->link =& $source;
  67.         $this->feed = $source->magpie;
  68.         $this->feedmeta = $source->settings;
  69.  
  70.         FeedWordPress::diagnostic('feed_items', 'Considering item ['.$this->guid().'] "'.$this->entry->get_title().'"');
  71.  
  72.         # Dealing with namespaces can get so fucking fucked.
  73.         $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR;
  74.         $this->xmlns['reverse'] = array();
  75.         foreach ($this->xmlns['forward'] as $url => $ns) :
  76.             if (!isset($this->xmlns['reverse'][$ns])) :
  77.                 $this->xmlns['reverse'][$ns] = array();
  78.             endif;
  79.             $this->xmlns['reverse'][$ns][] = $url;
  80.         endforeach;
  81.        
  82.         // Fucking SimplePie.
  83.         $this->xmlns['reverse']['rss'][] = '';
  84.  
  85.         # These globals were originally an ugly kludge around a bug in
  86.         # apply_filters from WordPress 1.5. The bug was fixed in 1.5.1,
  87.         # and I sure hope at this point that nobody writing filters for
  88.         # FeedWordPress is still relying on them.
  89.         #
  90.         # Anyway, I hereby declare them DEPRECATED as of 8 February
  91.         # 2010. I'll probably remove the globals within 1-2 releases in
  92.         # the interests of code hygiene and memory usage. If you
  93.         # currently use them in your filters, I advise you switch off to
  94.         # accessing the public members SyndicatedPost::feed and
  95.         # SyndicatedPost::feedmeta.
  96.  
  97.         global $fwp_channel, $fwp_feedmeta;
  98.         $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta;
  99.  
  100.         // Trigger global syndicated_item filter.
  101.         $changed = apply_filters('syndicated_item', $this->item, $this);
  102.         $this->item = $changed;
  103.        
  104.         // Allow for feed-specific syndicated_item filters.
  105.         $changed = apply_filters(
  106.             "syndicated_item_".$source->uri(),
  107.             $this->item,
  108.             $this
  109.         );
  110.         $this->item = $changed;
  111.        
  112.         # Filters can halt further processing by returning NULL
  113.         if (is_null($this->item)) :
  114.             $this->post = NULL;
  115.         else :
  116.             # Note that nothing is run through $wpdb->escape() here.
  117.             # That's deliberate. The escaping is done at the point
  118.             # of insertion, not here, to avoid double-escaping and
  119.             # to avoid screwing with syndicated_post filters
  120.  
  121.             $this->post['post_title'] = apply_filters(
  122.                 'syndicated_item_title',
  123.                 $this->entry->get_title(), $this
  124.             );
  125.  
  126.             $this->named['author'] = apply_filters(
  127.                 'syndicated_item_author',
  128.                 $this->author(), $this
  129.             );
  130.             // This just gives us an alphanumeric name for the author.
  131.             // We look up (or create) the numeric ID for the author
  132.             // in SyndicatedPost::add().
  133.  
  134.             $this->post['post_content'] = apply_filters(
  135.                 'syndicated_item_content',
  136.                 $this->content(), $this
  137.             );
  138.            
  139.             $excerpt = apply_filters('syndicated_item_excerpt', $this->excerpt(), $this);
  140.             if (!empty($excerpt)):
  141.                 $this->post['post_excerpt'] = $excerpt;
  142.             endif;
  143.  
  144.             // Dealing with timestamps in WordPress is so fucking fucked.
  145.             $offset = (int) get_option('gmt_offset') * 60 * 60;
  146.             $post_date_gmt = $this->published(array('default' => -1));
  147.             $post_modified_gmt = $this->updated(array('default' => -1));
  148.  
  149.             $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $post_date_gmt);
  150.             $this->post['post_date'] = gmdate('Y-m-d H:i:s', $post_date_gmt + $offset);
  151.             $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $post_modified_gmt);
  152.             $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $post_modified_gmt + $offset);
  153.  
  154.             // Use feed-level preferences or the global default.
  155.             $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
  156.             $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
  157.             $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
  158.  
  159.             // Unique ID (hopefully a unique tag: URI); failing that, the permalink
  160.             $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
  161.  
  162.             // User-supplied custom settings to apply to each post.
  163.             // Do first so that FWP-generated custom settings will
  164.             // overwrite if necessary; thus preventing any munging.
  165.             $postMetaIn = $this->link->postmeta(array("parsed" => true));
  166.             $postMetaOut = array();
  167.            
  168.             foreach ($postMetaIn as $key => $meta) :
  169.                 $postMetaOut[$key] = $meta->do_substitutions($this);
  170.             endforeach;
  171.            
  172.             foreach ($postMetaOut as $key => $values) :
  173.                 $this->post['meta'][$key] = array();
  174.                 foreach ($values as $value) :
  175.                     $this->post['meta'][$key][] = apply_filters("syndicated_post_meta_{$key}", $value, $this);
  176.                 endforeach;
  177.             endforeach;
  178.            
  179.             // RSS 2.0 / Atom 1.0 enclosure support
  180.             $enclosures = $this->entry->get_enclosures();
  181.             if (is_array($enclosures)) : foreach ($enclosures as $enclosure) :
  182.                 $this->post['meta']['enclosure'][] =
  183.                     apply_filters('syndicated_item_enclosure_url', $enclosure->get_link(), $this)."\n".
  184.                     apply_filters('syndicated_item_enclosure_length', $enclosure->get_length(), $this)."\n".
  185.                     apply_filters('syndicated_item_enclosure_type', $enclosure->get_type(), $this);
  186.             endforeach; endif;
  187.  
  188.             // In case you want to point back to the blog this was
  189.             // syndicated from.
  190.            
  191.             $sourcemeta['syndication_source'] = apply_filters(
  192.                 'syndicated_item_source_title',
  193.                 $this->link->name(),
  194.                 $this
  195.             );
  196.             $sourcemeta['syndication_source_uri'] = apply_filters(
  197.                 'syndicated_item_source_link',
  198.                 $this->link->homepage(),
  199.                 $this
  200.             );
  201.             $sourcemeta['syndication_source_id'] = apply_filters(
  202.                 'syndicated_item_source_id',
  203.                 $this->link->guid(),
  204.                 $this
  205.             );
  206.            
  207.             // Make use of atom:source data, if present in an aggregated feed
  208.             $entry_source = $this->source();
  209.             if (!is_null($entry_source)) :
  210.                 foreach ($entry_source as $what => $value) :
  211.                     if (!is_null($value)) :
  212.                         if ($what=='title') : $key = 'syndication_source';
  213.                         elseif ($what=='feed') : $key = 'syndication_feed';
  214.                         else : $key = "syndication_source_${what}";
  215.                         endif;
  216.                        
  217.                         $sourcemeta["${key}_original"] = apply_filters(
  218.                             'syndicated_item_original_source_'.$what,
  219.                             $value,
  220.                             $this
  221.                         );
  222.                     endif;
  223.                 endforeach;
  224.             endif;
  225.            
  226.             foreach ($sourcemeta as $meta_key => $value) :
  227.                 if (!is_null($value)) :
  228.                     $this->post['meta'][$meta_key] = $value;
  229.                 endif;
  230.             endforeach;
  231.  
  232.             // Store information on human-readable and machine-readable comment URIs
  233.            
  234.             // Human-readable comment URI
  235.             $commentLink = apply_filters('syndicated_item_comments', $this->comment_link(), $this);
  236.             if (!is_null($commentLink)) : $this->post['meta']['rss:comments'] = $commentLink; endif;
  237.  
  238.             // Machine-readable content feed URI
  239.             $commentFeed = apply_filters('syndicated_item_commentrss', $this->comment_feed(), $this);
  240.             if (!is_null($commentFeed)) :   $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif;
  241.             // Yeah, yeah, now I know that it's supposed to be
  242.             // wfw:commentRss. Oh well. Path dependence, sucka.
  243.  
  244.             // Store information to identify the feed that this came from
  245.             if (isset($this->feedmeta['link/uri'])) :
  246.                 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
  247.             endif;
  248.             if (isset($this->feedmeta['link/id'])) :
  249.                 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
  250.             endif;
  251.  
  252.             if (isset($this->item['source_link_self'])) :
  253.                 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
  254.             endif;
  255.  
  256.             // In case you want to know the external permalink...
  257.             $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink());
  258.  
  259.             // Store a hash of the post content for checking whether something needs to be updated
  260.             $this->post['meta']['syndication_item_hash'] = $this->update_hash();
  261.  
  262.             // Categories: start with default categories, if any.
  263.             $cats = array();
  264.             if ('no' != $this->link->setting('add/category', NULL, 'yes')) :
  265.                 $fc = get_option("feedwordpress_syndication_cats");
  266.                 if ($fc) :
  267.                     $cats = array_merge($cats, explode("\n", $fc));
  268.                 endif;
  269.             endif;
  270.  
  271.             $fc = $this->link->setting('cats', NULL, array());
  272.             if (is_array($fc)) :
  273.                 $cats = array_merge($cats, $fc);
  274.             endif;
  275.             $this->preset_terms['category'] = $cats;
  276.            
  277.             // Now add categories from the post, if we have 'em
  278.             $cats = array();
  279.             $post_cats = $this->entry->get_categories();
  280.             if (is_array($post_cats)) : foreach ($post_cats as $cat) :
  281.                 $cat_name = $cat->get_term();
  282.                 if (!$cat_name) : $cat_name = $cat->get_label(); endif;
  283.                
  284.                 if ($this->link->setting('cat_split', NULL, NULL)) :
  285.                     $pcre = "\007".$this->feedmeta['cat_split']."\007";
  286.                     $cats = array_merge(
  287.                         $cats,
  288.                         preg_split(
  289.                             $pcre,
  290.                             $cat_name,
  291.                             -1 /*=no limit*/,
  292.                             PREG_SPLIT_NO_EMPTY
  293.                         )
  294.                     );
  295.                 else :
  296.                     $cats[] = $cat_name;
  297.                 endif;
  298.             endforeach; endif;
  299.  
  300.             $this->feed_terms['category'] = apply_filters('syndicated_item_categories', $cats, $this);
  301.            
  302.             // Tags: start with default tags, if any
  303.             $tags = array();
  304.             if ('no' != $this->link->setting('add/post_tag', NULL, 'yes')) :
  305.                 $ft = get_option("feedwordpress_syndication_tags", NULL);
  306.                 $tags = (is_null($ft) ? array() : explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft));
  307.             endif;
  308.            
  309.             $ft = $this->link->setting('tags', NULL, array());
  310.             if (is_array($ft)) :
  311.                 $tags = array_merge($tags, $ft);
  312.             endif;
  313.             $this->preset_terms['post_tag'] = $tags;
  314.            
  315.             // Scan post for /a[@rel='tag'] and use as tags if present
  316.             $tags = $this->inline_tags();
  317.             $this->feed_terms['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this);
  318.            
  319.             $taxonomies = $this->link->taxonomies();
  320.             $feedTerms = $this->link->setting('terms', NULL, array());
  321.             $globalTerms = get_option('feedwordpress_syndication_terms', array());
  322.  
  323.             $specials = array('category' => 'cats', 'post_tag' => 'tags');
  324.             foreach ($taxonomies as $tax) :
  325.                 if (!isset($specials[$tax])) :
  326.                     $terms = array();
  327.                
  328.                     // See if we should get the globals
  329.                     if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) :
  330.                         if (isset($globalTerms[$tax])) :
  331.                             $terms = $globalTerms[$tax];
  332.                         endif;
  333.                     endif;
  334.                    
  335.                     // Now merge in the locals
  336.                     if (isset($feedTerms[$tax])) :
  337.                         $terms = array_merge($terms, $feedTerms[$tax]);
  338.                     endif;
  339.  
  340.                     // That's all, folks.
  341.                     $this->preset_terms[$tax] = $terms;
  342.                 endif;
  343.             endforeach;
  344.  
  345.             $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this);
  346.         endif;
  347.     } /* SyndicatedPost::SyndicatedPost() */
  348.  
  349.     #####################################
  350.     #### EXTRACT DATA FROM FEED ITEM ####
  351.     #####################################
  352.  
  353.     function substitution_function ($name) {
  354.         $ret = NULL;
  355.        
  356.         switch ($name) :
  357.         // Allowed PHP string functions
  358.         case 'trim':
  359.         case 'ltrim':
  360.         case 'rtrim':
  361.         case 'strtoupper':
  362.         case 'strtolower':
  363.         case 'urlencode':
  364.         case 'urldecode':
  365.             $ret = $name;
  366.         endswitch;
  367.         return $ret;
  368.     }
  369.    
  370.     /**
  371.      * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
  372.      * elements within the syndicated item.
  373.      *
  374.      * @param string $path
  375.      * @returns array of string values representing contents of matching
  376.      * elements or attributes
  377.      */
  378.      function query ($path) {
  379.         $urlHash = array();
  380.  
  381.         // Allow {url} notation for namespaces. URLs will contain : and /, so...
  382.         preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
  383.         foreach ($match as $ref) :
  384.             $urlHash[md5($ref[1])] = $ref[1];
  385.         endforeach;
  386.    
  387.         foreach ($urlHash as $hash => $url) :
  388.             $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
  389.         endforeach;
  390.  
  391.         $path = explode('/', $path);
  392.         foreach ($path as $index => $node) :
  393.             if (preg_match('/{#([^}]+)}/', $node, $ref)) :
  394.                 if (isset($urlHash[$ref[1]])) :
  395.                     $path[$index] = str_replace(
  396.                         '{#'.$ref[1].'}',
  397.                         '{'.$urlHash[$ref[1]].'}',
  398.                         $node
  399.                     );
  400.                 endif;
  401.             endif;
  402.         endforeach;
  403.  
  404.         // Start out with a get_item_tags query.
  405.         $node = '';
  406.         while (strlen($node)==0 and !is_null($node)) :
  407.             $node = array_shift($path);
  408.         endwhile;
  409.        
  410.         switch ($node) :
  411.         case 'feed' :
  412.         case 'channel' :
  413.             $node = array_shift($path);
  414.             $data = $this->get_feed_root_element();
  415.             $data = array_merge($data, $this->get_feed_channel_elements());
  416.             break;
  417.         case 'item' :
  418.             $node = array_shift($path);
  419.         default :
  420.             $data = array($this->entry->data);
  421.             $method = NULL;
  422.         endswitch;
  423.  
  424.         while (!is_null($node)) :
  425.             if (strlen($node) > 0) :
  426.                 $matches = array();
  427.  
  428.                 list($axis, $element) = $this->xpath_name_and_axis($node);
  429.  
  430.                 foreach ($data as $datum) :
  431.                     if (!is_string($datum) and isset($datum[$axis])) :
  432.                         foreach ($datum[$axis] as $ns => $elements) :
  433.                             if (isset($elements[$element])) :
  434.                                 // Potential match.
  435.                                 // Check namespace.
  436.                                 if (is_string($elements[$element])) : // Attribute
  437.                                     $addenda = array($elements[$element]);
  438.                                     $contexts = array($datum);
  439.                                 else : // Element
  440.                                     $addenda = $elements[$element];
  441.                                     $contexts = $elements[$element];
  442.                                 endif;
  443.                                
  444.                                 foreach ($addenda as $index => $addendum) :
  445.                                     $context = $contexts[$index];
  446.  
  447.                                     $namespaces = $this->xpath_possible_namespaces($node, $context);
  448.                                     if (in_array($ns, $namespaces)) :
  449.                                         $matches[] = $addendum;
  450.                                     endif;
  451.                                 endforeach;
  452.                             endif;
  453.                         endforeach;                    
  454.                     endif;
  455.                 endforeach;
  456.        
  457.                 $data = $matches;
  458.             endif;
  459.             $node = array_shift($path);
  460.         endwhile;
  461.    
  462.         $matches = array();
  463.         foreach ($data as $datum) :
  464.             if (is_string($datum)) :
  465.                 $matches[] = $datum;
  466.             elseif (isset($datum['data'])) :
  467.                 $matches[] = $datum['data'];
  468.             endif;
  469.         endforeach;
  470.         return $matches;
  471.     } /* SyndicatedPost::query() */
  472.  
  473.     function get_feed_root_element () {
  474.         $matches = array();
  475.         foreach ($this->link->simplepie->data['child'] as $ns => $root) :
  476.             foreach ($root as $element => $data) :
  477.                 $matches = array_merge($matches, $data);
  478.             endforeach;
  479.         endforeach;
  480.         return $matches;
  481.     } /* SyndicatedPost::get_feed_root_element() */
  482.  
  483.     function get_feed_channel_elements () {
  484.         $rss = array(
  485.                 SIMPLEPIE_NAMESPACE_RSS_090,
  486.                 SIMPLEPIE_NAMESPACE_RSS_10,
  487.                 'http://backend.userland.com/RSS2',
  488.                 SIMPLEPIE_NAMESPACE_RSS_20,
  489.         );
  490.        
  491.         $matches = array();
  492.         foreach ($rss as $ns) :
  493.             $data = $this->link->simplepie->get_feed_tags($ns, 'channel');
  494.             if (!is_null($data)) :
  495.                 $matches = array_merge($matches, $data);
  496.             endif;
  497.         endforeach;
  498.         return $matches;
  499.     } /* SyndicatedPost::get_feed_channel_elements() */
  500.  
  501.     function xpath_default_namespace () {
  502.         // Get the default namespace.
  503.         $type = $this->link->simplepie->get_type();
  504.         if ($type & SIMPLEPIE_TYPE_ATOM_10) :
  505.             $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
  506.         elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
  507.             $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
  508.         elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
  509.             $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
  510.         elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
  511.             $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
  512.         elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
  513.             $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
  514.         else :
  515.             $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
  516.         endif;
  517.         return $defaultNS; 
  518.     } /* SyndicatedPost::xpath_default_namespace() */
  519.  
  520.     function xpath_name_and_axis ($node) {
  521.         $ns = NULL; $element = NULL;
  522.  
  523.         if (substr($node, 0, 1)=='@') :
  524.             $axis = 'attribs'; $node = substr($node, 1);
  525.         else :
  526.             $axis = 'child';
  527.         endif;
  528.  
  529.         if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
  530.             $element = $ref[2];
  531.         elseif (strpos($node, ':') !== FALSE) :
  532.             list($xmlns, $element) = explode(':', $node, 2);
  533.         else :
  534.             $element = $node;
  535.         endif;
  536.         return array($axis, $element);
  537.     } /* SyndicatedPost::xpath_local_name () */
  538.  
  539.     function xpath_possible_namespaces ($node, $datum = array()) {
  540.         $ns = NULL; $element = NULL;
  541.  
  542.         if (substr($node, 0, 1)=='@') :
  543.             $attr = '@'; $node = substr($node, 1);
  544.         else :
  545.             $attr = '';
  546.         endif;
  547.  
  548.         if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
  549.             $ns = array($ref[1]);
  550.         elseif (strpos($node, ':') !== FALSE) :
  551.             list($xmlns, $element) = explode(':', $node, 2);
  552.            
  553.             if (isset($this->xmlns['reverse'][$xmlns])) :
  554.                 $ns = $this->xmlns['reverse'][$xmlns];
  555.             else :
  556.                 $ns = array($xmlns);
  557.             endif;
  558.            
  559.             // Fucking SimplePie. For attributes in default xmlns.
  560.             $defaultNS = $this->xpath_default_namespace();
  561.             if (isset($this->xmlns['forward'][$defaultNS])
  562.             and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
  563.                 $ns[] = '';
  564.             endif;
  565.            
  566.             if (isset($datum['xmlns'])) :
  567.                 if (isset($datum['xmlns'][$xmlns])) :
  568.                     $ns[] = $datum['xmlns'][$xmlns];
  569.                 endif;
  570.             endif;
  571.         else :
  572.             // Often in SimplePie, the default namespace gets stored
  573.             // as an empty string rather than a URL.
  574.             $ns = array($this->xpath_default_namespace(), '');
  575.         endif;
  576.         return array_unique($ns);
  577.     } /* SyndicatedPost::xpath_possible_namespaces() */
  578.  
  579.     function content () {
  580.         $content = NULL;
  581.         if (isset($this->item['atom_content'])) :
  582.             $content = $this->item['atom_content'];
  583.         elseif (isset($this->item['xhtml']['body'])) :
  584.             $content = $this->item['xhtml']['body'];
  585.         elseif (isset($this->item['xhtml']['div'])) :
  586.             $content = $this->item['xhtml']['div'];
  587.         elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
  588.             $content = $this->item['content']['encoded'];
  589.         elseif (isset($this->item['description'])) :
  590.             $content = $this->item['description'];
  591.         endif;
  592.         return $content;
  593.     } /* SyndicatedPost::content() */
  594.  
  595.     function excerpt () {
  596.         # Identify and sanitize excerpt: atom:summary, or rss:description
  597.         $excerpt = $this->entry->get_description();
  598.            
  599.         # Many RSS feeds use rss:description, inadvisably, to
  600.         # carry the entire post (typically with escaped HTML).
  601.         # If that's what happened, we don't want the full
  602.         # content for the excerpt.
  603.         $content = $this->content();
  604.        
  605.         // Ignore whitespace, case, and tag cruft.
  606.         $theExcerpt = preg_replace('/\s+/', '', strtolower(strip_tags($excerpt)));
  607.         $theContent = preg_replace('/\s+/', '', strtolower(strip_tags($content)));
  608.  
  609.         if ( empty($excerpt) or $theExcerpt == $theContent ) :
  610.             # If content is available, generate an excerpt.
  611.             if ( strlen(trim($content)) > 0 ) :
  612.                 $excerpt = strip_tags($content);
  613.                 if (strlen($excerpt) > 255) :
  614.                     $excerpt = substr($excerpt,0,252).'...';
  615.                 endif;
  616.             endif;
  617.         endif;
  618.         return $excerpt;
  619.     } /* SyndicatedPost::excerpt() */
  620.  
  621.     function permalink () {
  622.         // Handles explicit <link> elements and also RSS 2.0 cases with
  623.         // <guid isPermaLink="true">, etc. Hooray!
  624.         $permalink = $this->entry->get_link();
  625.         return $permalink;
  626.     }
  627.  
  628.     function created ($params = array()) {
  629.         $unfiltered = false; $default = NULL;
  630.         extract($params);
  631.        
  632.         $date = '';
  633.         if (isset($this->item['dc']['created'])) :
  634.             $date = $this->item['dc']['created'];
  635.         elseif (isset($this->item['dcterms']['created'])) :
  636.             $date = $this->item['dcterms']['created'];
  637.         elseif (isset($this->item['created'])): // Atom 0.3
  638.             $date = $this->item['created'];
  639.         endif;
  640.  
  641.         $time = new FeedTime($date);
  642.         $ts = $time->timestamp();
  643.         if (!$unfiltered) :
  644.             apply_filters('syndicated_item_created', $ts, $this);
  645.         endif;
  646.         return $ts;
  647.     } /* SyndicatedPost::created() */
  648.  
  649.     function published ($params = array(), $default = NULL) {
  650.         $fallback = true; $unfiltered = false;
  651.         if (!is_array($params)) : // Old style
  652.             $fallback = $params;
  653.         else : // New style
  654.             extract($params);
  655.         endif;
  656.        
  657.         $date = '';
  658.         $ts = null;
  659.  
  660.         # RSS is a fucking mess. Figure out whether we have a date in
  661.         # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
  662.         # epoch format for reformatting. If we can't find anything,
  663.         # we'll use the last-updated time.
  664.         if (isset($this->item['dc']['date'])):              // Dublin Core
  665.             $date = $this->item['dc']['date'];
  666.         elseif (isset($this->item['dcterms']['issued'])) :      // Dublin Core extensions
  667.             $date = $this->item['dcterms']['issued'];
  668.         elseif (isset($this->item['published'])) :          // Atom 1.0
  669.             $date = $this->item['published'];
  670.         elseif (isset($this->item['issued'])):              // Atom 0.3
  671.             $date = $this->item['issued'];
  672.         elseif (isset($this->item['pubdate'])):             // RSS 2.0
  673.             $date = $this->item['pubdate'];
  674.         endif;
  675.        
  676.         if (strlen($date) > 0) :
  677.             $time = new FeedTime($date);
  678.             $ts = $time->timestamp();
  679.         elseif ($fallback) :                        // Fall back to <updated> / <modified> if present
  680.             $ts = $this->updated(/*fallback=*/ false, /*default=*/ $default);
  681.         endif;
  682.        
  683.         # If everything failed, then default to the current time.
  684.         if (is_null($ts)) :
  685.             if (-1 == $default) :
  686.                 $ts = time();
  687.             else :
  688.                 $ts = $default;
  689.             endif;
  690.         endif;
  691.        
  692.         if (!$unfiltered) :
  693.             $ts = apply_filters('syndicated_item_published', $ts, $this);
  694.         endif;
  695.         return $ts;
  696.     } /* SyndicatedPost::published() */
  697.  
  698.     function updated ($params = array(), $default = -1) {
  699.         $fallback = true; $unfiltered = false;
  700.         if (!is_array($params)) : // Old style
  701.             $fallback = $params;
  702.         else : // New style
  703.             extract($params);
  704.         endif;
  705.  
  706.         $date = '';
  707.         $ts = null;
  708.  
  709.         # As far as I know, only dcterms and Atom have reliable ways to
  710.         # specify when something was *modified* last. If neither is
  711.         # available, then we'll try to get the time of publication.
  712.         if (isset($this->item['dc']['modified'])) :             // Not really correct
  713.             $date = $this->item['dc']['modified'];
  714.         elseif (isset($this->item['dcterms']['modified'])) :        // Dublin Core extensions
  715.             $date = $this->item['dcterms']['modified'];
  716.         elseif (isset($this->item['modified'])):            // Atom 0.3
  717.             $date = $this->item['modified'];
  718.         elseif (isset($this->item['updated'])):         // Atom 1.0
  719.             $date = $this->item['updated'];
  720.         endif;
  721.        
  722.         if (strlen($date) > 0) :
  723.             $time = new FeedTime($date);
  724.             $ts = $time->timestamp();
  725.         elseif ($fallback) :                        // Fall back to issued / dc:date
  726.             $ts = $this->published(/*fallback=*/ false, /*default=*/ $default);
  727.         endif;
  728.        
  729.         # If everything failed, then default to the current time.
  730.         if (is_null($ts)) :
  731.             if (-1 == $default) :
  732.                 $ts = time();
  733.             else :
  734.                 $ts = $default;
  735.             endif;
  736.         endif;
  737.  
  738.         if (!$unfiltered) :
  739.             apply_filters('syndicated_item_updated', $ts, $this);
  740.         endif;
  741.         return $ts;
  742.     } /* SyndicatedPost::updated() */
  743.  
  744.     var $_hashes = array();
  745.     function stored_hashes ($id = NULL) {
  746.         if (is_null($id)) :
  747.             $id = $this->wp_id();
  748.         endif;
  749.  
  750.         if (!isset($this->_hashes[$id])) :
  751.             $this->_hashes[$id] = get_post_custom_values(
  752.                 'syndication_item_hash', $id
  753.             );
  754.             if (is_null($this->_hashes[$id])) :
  755.                 $this->_hashes[$id] = array();
  756.             endif;
  757.         endif;
  758.         return $this->_hashes[$id];
  759.     }
  760.  
  761.     function update_hash ($hashed = true) {
  762.         // Basis for tracking possible changes to item.
  763.         $hash = array(
  764.             "title" => $this->entry->get_title(),
  765.             "link" => $this->permalink(),
  766.             "content" => $this->content(),
  767.             "excerpt" => $this->excerpt(),
  768.         );
  769.        
  770.         if ($hashed) :
  771.             $hash = md5(serialize($hash));
  772.         endif;
  773.        
  774.         return $hash;
  775.     } /* SyndicatedPost::update_hash() */
  776.  
  777.     /*static*/ function normalize_guid_prefix () {
  778.         return trailingslashit(get_bloginfo('url')).'?guid=';
  779.     }
  780.    
  781.     /*static*/ function normalize_guid ($guid) {
  782.         $guid = trim($guid);
  783.         if (preg_match('/^[0-9a-z]{32}$/i', $guid)) : // MD5
  784.             $guid = SyndicatedPost::normalize_guid_prefix().strtolower($guid);
  785.         elseif ((strlen(esc_url($guid)) == 0) or (esc_url($guid) != $guid)) :
  786.             $guid = SyndicatedPost::normalize_guid_prefix().md5($guid);
  787.         endif;
  788.         $guid = trim($guid);
  789.         return $guid;
  790.     } /* SyndicatedPost::normalize_guid() */
  791.    
  792.     function guid () {
  793.         $guid = null;
  794.         if (isset($this->item['id'])):                      // Atom 0.3 / 1.0
  795.             $guid = $this->item['id'];
  796.         elseif (isset($this->item['atom']['id'])) :     // Namespaced Atom
  797.             $guid = $this->item['atom']['id'];
  798.         elseif (isset($this->item['guid'])) :               // RSS 2.0
  799.             $guid = $this->item['guid'];
  800.         elseif (isset($this->item['dc']['identifier'])) :   // yeah, right
  801.             $guid = $this->item['dc']['identifier'];
  802.         endif;
  803.        
  804.         // Un-set or too long to use as-is. Generate a tag: URI.
  805.         if (is_null($guid) or strlen($guid) > 250) :
  806.             // In case we need to check this again
  807.             $original_guid = $guid;
  808.            
  809.             // The feed does not seem to have provided us with a
  810.             // usable unique identifier, so we'll have to cobble
  811.             // together a tag: URI that might work for us. The base
  812.             // of the URI will be the host name of the feed source ...
  813.             $bits = parse_url($this->link->uri());
  814.             $guid = 'tag:'.$bits['host'];
  815.  
  816.             // Some ill-mannered feeds (for example, certain feeds
  817.             // coming from Google Calendar) have extraordinarily long
  818.             // guids -- so long that they exceed the 255 character
  819.             // width of the WordPress guid field. But if the string
  820.             // gets clipped by MySQL, uniqueness tests will fail
  821.             // forever after and the post will be endlessly
  822.             // reduplicated. So, instead, Guids Of A Certain Length
  823.             // are hashed down into a nice, manageable tag: URI.
  824.             if (!is_null($original_guid)) :
  825.                 $guid .= ',2010-12-03:id.'.md5($original_guid);
  826.            
  827.             // If we have a date of creation, then we can use that
  828.             // to uniquely identify the item. (On the other hand, if
  829.             // the feed producer was consicentious enough to
  830.             // generate dates of creation, she probably also was
  831.             // conscientious enough to generate unique identifiers.)
  832.             elseif (!is_null($this->created())) :
  833.                 $guid .= '://post.'.date('YmdHis', $this->created());
  834.            
  835.             // Otherwise, use both the URI of the item, *and* the
  836.             // item's title. We have to use both because titles are
  837.             // often not unique, and sometimes links aren't unique
  838.             // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
  839.             // some podcasts). But it's rare to have *both* the same
  840.             // title *and* the same link for two different items. So
  841.             // this is about the best we can do.
  842.             else :
  843.                 $link = $this->permalink();
  844.                 if (is_null($link)) : $link = $this->link->uri(); endif;
  845.                 $guid .= '://'.md5($link.'/'.$this->item['title']);
  846.             endif;
  847.         endif;
  848.         return $guid;
  849.     } /* SyndicatedPost::guid() */
  850.    
  851.     function author () {
  852.         $author = array ();
  853.        
  854.         $aa = $this->entry->get_authors();
  855.         if (count($aa) > 0) :
  856.             $a = reset($aa);
  857.  
  858.             $author = array(
  859.             'name' => $a->get_name(),
  860.             'email' => $a->get_email(),
  861.             'uri' => $a->get_link(),
  862.             );
  863.         endif;
  864.  
  865.         if (FEEDWORDPRESS_COMPATIBILITY) :
  866.             // Search through the MagpieRSS elements: Atom, Dublin Core, RSS
  867.             if (isset($this->item['author_name'])):
  868.                 $author['name'] = $this->item['author_name'];
  869.             elseif (isset($this->item['dc']['creator'])):
  870.                 $author['name'] = $this->item['dc']['creator'];
  871.             elseif (isset($this->item['dc']['contributor'])):
  872.                 $author['name'] = $this->item['dc']['contributor'];
  873.             elseif (isset($this->feed->channel['dc']['creator'])) :
  874.                 $author['name'] = $this->feed->channel['dc']['creator'];
  875.             elseif (isset($this->feed->channel['dc']['contributor'])) :
  876.                 $author['name'] = $this->feed->channel['dc']['contributor'];
  877.             elseif (isset($this->feed->channel['author_name'])) :
  878.                 $author['name'] = $this->feed->channel['author_name'];
  879.             elseif ($this->feed->is_rss() and isset($this->item['author'])) :
  880.                 // The author element in RSS is allegedly an
  881.                 // e-mail address, but lots of people don't use
  882.                 // it that way. So let's make of it what we can.
  883.                 $author = parse_email_with_realname($this->item['author']);
  884.                
  885.                 if (!isset($author['name'])) :
  886.                     if (isset($author['email'])) :
  887.                         $author['name'] = $author['email'];
  888.                     else :
  889.                         $author['name'] = $this->feed->channel['title'];
  890.                     endif;
  891.                 endif;
  892.             endif;
  893.         endif;
  894.        
  895.         if (!isset($author['name']) or is_null($author['name'])) :
  896.             // Nothing found. Try some crappy defaults.
  897.             if ($this->link->name()) :
  898.                 $author['name'] = $this->link->name();
  899.             else :
  900.                 $url = parse_url($this->link->uri());
  901.                 $author['name'] = $url['host'];
  902.             endif;
  903.         endif;
  904.        
  905.         if (FEEDWORDPRESS_COMPATIBILITY) :
  906.             if (isset($this->item['author_email'])):
  907.                 $author['email'] = $this->item['author_email'];
  908.             elseif (isset($this->feed->channel['author_email'])) :
  909.                 $author['email'] = $this->feed->channel['author_email'];
  910.             endif;
  911.            
  912.             if (isset($this->item['author_uri'])):
  913.                 $author['uri'] = $this->item['author_uri'];
  914.             elseif (isset($this->item['author_url'])):
  915.                 $author['uri'] = $this->item['author_url'];
  916.             elseif (isset($this->feed->channel['author_uri'])) :
  917.                 $author['uri'] = $this->item['author_uri'];
  918.             elseif (isset($this->feed->channel['author_url'])) :
  919.                 $author['uri'] = $this->item['author_url'];
  920.             elseif (isset($this->feed->channel['link'])) :
  921.                 $author['uri'] = $this->feed->channel['link'];
  922.             endif;
  923.         endif;
  924.        
  925.         return $author;
  926.     } /* SyndicatedPost::author() */
  927.  
  928.     /**
  929.      * SyndicatedPost::inline_tags: Return a list of all the tags embedded
  930.      * in post content using the a[@rel="tag"] microformat.
  931.      *
  932.      * @since 2010.0630
  933.      * @return array of string values containing the name of each tag
  934.      */
  935.     function inline_tags () {
  936.         $tags = array();
  937.         $content = $this->content();
  938.         $pattern = FeedWordPressHTML::tagWithAttributeRegex('a', 'rel', 'tag');
  939.         preg_match_all($pattern, $content, $refs, PREG_SET_ORDER);
  940.         if (count($refs) > 0) :
  941.             foreach ($refs as $ref) :
  942.                 $tag = FeedWordPressHTML::tagWithAttributeMatch($ref);
  943.                 $tags[] = $tag['content'];
  944.             endforeach;
  945.         endif;
  946.         return $tags;
  947.     }
  948.    
  949.     /**
  950.      * SyndicatedPost::isTaggedAs: Test whether a feed item is
  951.      * tagged / categorized with a given string. Case and leading and
  952.      * trailing whitespace are ignored.
  953.      *
  954.      * @param string $tag Tag to check for
  955.      *
  956.      * @return bool Whether or not at least one of the categories / tags on
  957.      *  $this->item is set to $tag (modulo case and leading and trailing
  958.      *  whitespace)
  959.      */
  960.     function isTaggedAs ($tag) {
  961.         $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
  962.  
  963.         // Check to see if this is tagged with $tag
  964.         $currentCategory = 'category';
  965.         $currentCategoryNumber = 1;
  966.  
  967.         // If we have the new MagpieRSS, the number of category elements
  968.         // on this item is stored under index "category#".
  969.         if (isset($this->item['category#'])) :
  970.             $numberOfCategories = (int) $this->item['category#'];
  971.        
  972.         // We REALLY shouldn't have the old and busted MagpieRSS, but in
  973.         // case we do, it doesn't support multiple categories, but there
  974.         // might still be a single value under the "category" index.
  975.         elseif (isset($this->item['category'])) :
  976.             $numberOfCategories = 1;
  977.  
  978.         // No standard category or tag elements on this feed item.
  979.         else :
  980.             $numberOfCategories = 0;
  981.  
  982.         endif;
  983.  
  984.         $isSoTagged = false; // Innocent until proven guilty
  985.  
  986.         // Loop through category elements; if there are multiple
  987.         // elements, they are indexed as category, category#2,
  988.         // category#3, ... category#N
  989.         while ($currentCategoryNumber <= $numberOfCategories) :
  990.             if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
  991.                 $isSoTagged = true; // Got it!
  992.                 break;
  993.             endif;
  994.  
  995.             $currentCategoryNumber += 1;
  996.             $currentCategory = 'category#'.$currentCategoryNumber;
  997.         endwhile;
  998.  
  999.         return $isSoTagged;
  1000.     } /* SyndicatedPost::isTaggedAs() */
  1001.  
  1002.     /**
  1003.      * SyndicatedPost::enclosures: returns an array with any enclosures
  1004.      * that may be attached to this syndicated item.
  1005.      *
  1006.      * @param string $type If you only want enclosures that match a certain
  1007.      *  MIME type or group of MIME types, you can limit the enclosures
  1008.      *  that will be returned to only those with a MIME type which
  1009.      *  matches this regular expression.
  1010.      * @return array
  1011.      */
  1012.     function enclosures ($type = '/.*/') {
  1013.         $enclosures = array();
  1014.  
  1015.         if (isset($this->item['enclosure#'])) :
  1016.             // Loop through enclosure, enclosure#2, enclosure#3, ....
  1017.             for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
  1018.                 $eid = (($i > 1) ? "#{$id}" : "");
  1019.  
  1020.                 // Does it match the type we want?
  1021.                 if (preg_match($type, $this->item["enclosure{$eid}@type"])) :
  1022.                     $enclosures[] = array(
  1023.                         "url" => $this->item["enclosure{$eid}@url"],
  1024.                         "type" => $this->item["enclosure{$eid}@type"],
  1025.                         "length" => $this->item["enclosure{$eid}@length"],
  1026.                     );
  1027.                 endif;
  1028.             endfor;
  1029.         endif;
  1030.         return $enclosures;    
  1031.     } /* SyndicatedPost::enclosures() */
  1032.  
  1033.     function source ($what = NULL) {
  1034.         $ret = NULL;
  1035.         $source = $this->entry->get_source();
  1036.         if ($source) :
  1037.             $ret = array();
  1038.             $ret['title'] = $source->get_title();
  1039.             $ret['uri'] = $source->get_link();
  1040.             $ret['feed'] = $source->get_link(0, 'self');
  1041.            
  1042.             if ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) :
  1043.                 $ret['id'] = $id_tags[0]['data'];
  1044.             elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) :
  1045.                 $ret['id'] = $id_tags[0]['data'];
  1046.             elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) :
  1047.                 $ret['id'] = $id_tags[0]['data'];
  1048.             elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'guid')) :
  1049.                 $ret['id'] = $id_tags[0]['data'];
  1050.             elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'guid')) :
  1051.                 $ret['id'] = $id_tags[0]['data'];
  1052.             endif;
  1053.         endif;
  1054.        
  1055.         if (!is_null($what) and is_scalar($what)) :
  1056.             $ret = $ret[$what];
  1057.         endif;
  1058.         return $ret;
  1059.     }
  1060.    
  1061.     function comment_link () {
  1062.         $url = null;
  1063.        
  1064.         // RSS 2.0 has a standard <comments> element:
  1065.         // "<comments> is an optional sub-element of <item>. If present,
  1066.         // it is the url of the comments page for the item."
  1067.         // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
  1068.         if (isset($this->item['comments'])) :
  1069.             $url = $this->item['comments'];
  1070.         endif;
  1071.  
  1072.         // The convention in Atom feeds is to use a standard <link>
  1073.         // element with @rel="replies" and @type="text/html".
  1074.         // Unfortunately, SimplePie_Item::get_links() allows us to filter
  1075.         // by the value of @rel, but not by the value of @type. *sigh*
  1076.        
  1077.         // Try Atom 1.0 first
  1078.         $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
  1079.        
  1080.         // Fall back and try Atom 0.3
  1081.         if (is_null($linkElements)) : $linkElements =  $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
  1082.        
  1083.         // Now loop through the elements, screening by @rel and @type
  1084.         if (is_array($linkElements)) : foreach ($linkElements as $link) :
  1085.             $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
  1086.             $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
  1087.             $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL);
  1088.  
  1089.             if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) :
  1090.                 $url = $href;
  1091.             endif;
  1092.         endforeach; endif;
  1093.  
  1094.         return $url;
  1095.     }
  1096.  
  1097.     function comment_feed () {
  1098.         $feed = null;
  1099.  
  1100.         // Well Formed Web comment feeds extension for RSS 2.0
  1101.         // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments>
  1102.         //
  1103.         // N.B.: Correct capitalization is wfw:commentRss, but
  1104.         // wfw:commentRSS is common in the wild (partly due to a typo in
  1105.         // the original spec). In any case, our item array is normalized
  1106.         // to all lowercase anyways.
  1107.         if (isset($this->item['wfw']['commentrss'])) :
  1108.             $feed = $this->item['wfw']['commentrss'];
  1109.         endif;
  1110.  
  1111.         // In Atom 1.0, the convention is to use a standard link element
  1112.         // with @rel="replies". Sometimes this is also used to pass a
  1113.         // link to the human-readable comments page, so we also need to
  1114.         // check link/@type for a feed MIME type.
  1115.         //
  1116.         // Which is why I'm not using the SimplePie_Item::get_links()
  1117.         // method here, incidentally: it doesn't allow you to filter by
  1118.         // @type. *sigh*
  1119.         if (isset($this->item['link_replies'])) :
  1120.             // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
  1121.             $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
  1122.             for ($i = 1; $i <= $N; $i++) :
  1123.                 $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
  1124.                 if (isset($this->item[$currentElement.'@type'])
  1125.                 and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
  1126.                     $feed = $this->item[$currentElement];
  1127.                 endif;
  1128.             endfor;
  1129.         endif;
  1130.         return $feed;
  1131.     } /* SyndicatedPost::comment_feed() */
  1132.  
  1133.     ##################################
  1134.     #### BUILT-IN CONTENT FILTERS ####
  1135.     ##################################
  1136.  
  1137.     var $uri_attrs = array (
  1138.         array('a', 'href'),
  1139.         array('applet', 'codebase'),
  1140.         array('area', 'href'),
  1141.         array('blockquote', 'cite'),
  1142.         array('body', 'background'),
  1143.         array('del', 'cite'),
  1144.         array('form', 'action'),
  1145.         array('frame', 'longdesc'),
  1146.         array('frame', 'src'),
  1147.         array('iframe', 'longdesc'),
  1148.         array('iframe', 'src'),
  1149.         array('head', 'profile'),
  1150.         array('img', 'longdesc'),
  1151.         array('img', 'src'),
  1152.         array('img', 'usemap'),
  1153.         array('input', 'src'),
  1154.         array('input', 'usemap'),
  1155.         array('ins', 'cite'),
  1156.         array('link', 'href'),
  1157.         array('object', 'classid'),
  1158.         array('object', 'codebase'),
  1159.         array('object', 'data'),
  1160.         array('object', 'usemap'),
  1161.         array('q', 'cite'),
  1162.         array('script', 'src')
  1163.     ); /* var SyndicatedPost::$uri_attrs */
  1164.  
  1165.     var $_base = null;
  1166.  
  1167.     function resolve_single_relative_uri ($refs) {
  1168.         $tag = FeedWordPressHTML::attributeMatch($refs);
  1169.         $url = SimplePie_Misc::absolutize_url($tag['value'], $this->_base);
  1170.         return $tag['prefix'] . $url . $tag['suffix'];
  1171.     } /* function SyndicatedPost::resolve_single_relative_uri() */
  1172.  
  1173.     function resolve_relative_uris ($content, $obj) {
  1174.         $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
  1175.         if ($set and $set != 'no') :
  1176.             // Fallback: if we don't have anything better, use the
  1177.             // item link from the feed
  1178.             $obj->_base = $obj->permalink(); // Reset the base for resolving relative URIs
  1179.  
  1180.             // What we should do here, properly, is to use
  1181.             // SimplePie_Item::get_base() -- but that method is
  1182.             // currently broken. Or getting down and dirty in the
  1183.             // SimplePie representation of the content tags and
  1184.             // grabbing the xml_base member for the content element.
  1185.             // Maybe someday...
  1186.  
  1187.             foreach ($obj->uri_attrs as $pair) :
  1188.                 list($tag, $attr) = $pair;
  1189.                 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
  1190.                 $content = preg_replace_callback (
  1191.                     $pattern,
  1192.                     array($obj, 'resolve_single_relative_uri'),
  1193.                     $content
  1194.                 );
  1195.             endforeach;
  1196.         endif;
  1197.        
  1198.         return $content;
  1199.     } /* function SyndicatedPost::resolve_relative_uris () */
  1200.  
  1201.     var $strip_attrs = array (
  1202.         array('[a-z]+', 'target'),
  1203. //      array('[a-z]+', 'style'),
  1204. //      array('[a-z]+', 'on[a-z]+'),
  1205.     );
  1206.  
  1207.     function strip_attribute_from_tag ($refs) {
  1208.         $tag = FeedWordPressHTML::attributeMatch($refs);
  1209.         return $tag['before_attribute'].$tag['after_attribute'];
  1210.     }
  1211.  
  1212.     function sanitize_content ($content, $obj) {
  1213.         # This kind of sucks. I intend to replace it with
  1214.         # lib_filter sometime soon.
  1215.         foreach ($obj->strip_attrs as $pair):
  1216.             list($tag,$attr) = $pair;
  1217.             $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
  1218.  
  1219.             $content = preg_replace_callback (
  1220.                 $pattern,
  1221.                 array($obj, 'strip_attribute_from_tag'),
  1222.                 $content
  1223.             );
  1224.         endforeach;
  1225.         return $content;
  1226.     } /* SyndicatedPost::sanitize() */
  1227.  
  1228.     #####################
  1229.     #### POST STATUS ####
  1230.     #####################
  1231.  
  1232.     /**
  1233.      * SyndicatedPost::filtered: check whether or not this post has been
  1234.      * screened out by a registered filter.
  1235.      *
  1236.      * @return bool TRUE iff post has been filtered out by a previous filter
  1237.      */
  1238.     function filtered () {
  1239.         return is_null($this->post);
  1240.     } /* SyndicatedPost::filtered() */
  1241.  
  1242.     /**
  1243.      * SyndicatedPost::freshness: check whether post is a new post to be
  1244.      * inserted, a previously syndicated post that needs to be updated to
  1245.      * match the latest revision, or a previously syndicated post that is
  1246.      * still up-to-date.
  1247.      *
  1248.      * @return int A status code representing the freshness of the post
  1249.      *  0 = post already syndicated; no update needed
  1250.      *  1 = post already syndicated, but needs to be updated to latest
  1251.      *  2 = post has not yet been syndicated; needs to be created
  1252.      */
  1253.     function freshness () {
  1254.         global $wpdb;
  1255.         global $post;
  1256.         $tempPost = $post;
  1257.  
  1258.         if ($this->filtered()) : // This should never happen.
  1259.             FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
  1260.         endif;
  1261.        
  1262.         if (is_null($this->_freshness)) : // Not yet checked and cached.
  1263.             $guid = $this->post['guid'];
  1264.             $eguid = $wpdb->escape($this->post['guid']);
  1265.  
  1266.             $the_query = get_posts(array(
  1267.                 'fields' => '_synfresh', // id, guid, post_modified_gmt
  1268.                 'ignore_sticky_posts' => true,
  1269.                 'guid' => $guid
  1270.             ));
  1271.  
  1272.             $old_post = NULL;
  1273.  
  1274.             foreach( $the_query as $post ) :
  1275.             setup_postdata($post);
  1276.             $old_post = $post;
  1277.         endforeach;
  1278.  
  1279.         $post = $tempPost;
  1280.         setup_postdata($post);         
  1281.             if (is_null($old_post)) : // No post with this guid
  1282.                 FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a NEW POST.');
  1283.                 $this->_wp_id = NULL;
  1284.                 $this->_freshness = 2; // New content
  1285.             else :
  1286.                 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $old_post->post_modified_gmt, $backref);
  1287.  
  1288.                 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
  1289.                 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
  1290.                
  1291.                 // Check timestamps...
  1292.                 $updated = (
  1293.                     !is_null($updated_ts)
  1294.                     and ($updated_ts > $last_rev_ts)
  1295.                 );
  1296.  
  1297.                 $updatedReason = NULL;
  1298.                 if ($updated) :
  1299.                     $updatedReason = preg_replace(
  1300.                         "/\s+/", " ",
  1301.                         'has been marked with a new timestamp ('
  1302.                         .date('Y-m-d H:i:s', $updated_ts)
  1303.                         ." > "
  1304.                         .date('Y-m-d H:i:s', $last_rev_ts)
  1305.                         .')'
  1306.                     );
  1307.                 else :
  1308.                     // Or the hash...
  1309.                     $hash = $this->update_hash();
  1310.                     $seen = $this->stored_hashes($old_post->ID);
  1311.                     if (count($seen) > 0) :
  1312.                         $updated = !in_array($hash, $seen); // Not seen yet?
  1313.                     else :
  1314.                         $updated = true; // Can't find syndication meta-data
  1315.                     endif;
  1316.                    
  1317.                     if ($updated and FeedWordPress::diagnostic_on('feed_items:freshness:reasons')) :
  1318.                         $updatedReason = ' has a not-yet-seen update hash: '
  1319.                         .FeedWordPress::val($hash)
  1320.                         .' not in {'
  1321.                         .implode(", ", array_map(array('FeedWordPress', 'val'), $seen))
  1322.                         .'}. Basis: '
  1323.                         .FeedWordPress::val(array_keys($this->update_hash(false)));
  1324.                     endif;
  1325.                 endif;
  1326.                
  1327.                 $frozen = false;
  1328.                 if ($updated) : // Ignore if the post is frozen
  1329.                     $frozen = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
  1330.                     if (!$frozen) :
  1331.                         $frozen_values = get_post_custom_values('_syndication_freeze_updates', $old_post->ID);
  1332.                         $frozen = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
  1333.                        
  1334.                         if ($frozen) :
  1335.                             $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A UPDATE LOCK ON THIS POST, EVEN THOUGH IT '.$updatedReason;
  1336.                         endif;
  1337.                     else :
  1338.                         $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A FEEDWORDPRESS UPDATE LOCK, EVEN THOUGH IT '.$updatedReason;
  1339.                     endif;
  1340.                 endif;
  1341.                 $updated = ($updated and !$frozen);
  1342.  
  1343.                 if ($updated) :
  1344.                     FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is an update of an existing post.');
  1345.                     if (!is_null($updatedReason)) :
  1346.                         $updatedReason = preg_replace('/\s+/', ' ', $updatedReason);
  1347.                         FeedWordPress::diagnostic('feed_items:freshness:reasons', 'Item ['.$guid.'] "'.$this->entry->get_title().'" '.$updatedReason);
  1348.                     endif;
  1349.                     $this->_freshness = 1; // Updated content
  1350.                     $this->_wp_id = $old_post->ID;
  1351.                     $this->_wp_post = $old_post;
  1352.  
  1353.                     // We want this to keep a running list of all the
  1354.                     // processed update hashes.
  1355.                     $this->post['meta']['syndication_item_hash'] = array_merge(
  1356.                         $this->stored_hashes(),
  1357.                         array($this->update_hash())
  1358.                     );
  1359.                 else :
  1360.                     FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a duplicate of an existing post.');
  1361.                     $this->_freshness = 0; // Same old, same old
  1362.                     $this->_wp_id = $old_post->ID;
  1363.                 endif;
  1364.             endif;
  1365.         endif;
  1366.         return $this->_freshness;
  1367.     }
  1368.  
  1369.     #################################################
  1370.     #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
  1371.     #################################################
  1372.  
  1373.     function wp_id () {
  1374.         if ($this->filtered()) : // This should never happen.
  1375.             FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
  1376.         endif;
  1377.        
  1378.         if (is_null($this->_wp_id) and is_null($this->_freshness)) :
  1379.             $fresh = $this->freshness(); // sets WP DB id in the process
  1380.         endif;
  1381.         return $this->_wp_id;
  1382.     }
  1383.  
  1384.     function store () {
  1385.         global $wpdb;
  1386.  
  1387.         if ($this->filtered()) : // This should never happen.
  1388.             FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
  1389.         endif;
  1390.        
  1391.         $freshness = $this->freshness();
  1392.         if ($freshness > 0) :
  1393.             # -- Look up, or create, numeric ID for author
  1394.             $this->post['post_author'] = $this->author_id (
  1395.                 $this->link->setting('unfamiliar author', 'unfamiliar_author', 'create')
  1396.             );
  1397.  
  1398.             if (is_null($this->post['post_author'])) :
  1399.                 FeedWordPress::diagnostic('feed_items:rejected', 'Filtered out item ['.$this->guid().'] without syndication: no author available');
  1400.                 $this->post = NULL;
  1401.             endif;
  1402.         endif;
  1403.        
  1404.         if (!$this->filtered() and $freshness > 0) :
  1405.             $consider = array(
  1406.                 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')),
  1407.                 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')),
  1408.             );
  1409.  
  1410.             $termSet = array(); $valid = null;
  1411.             foreach ($consider as $what => $taxes) :
  1412.                 if (!is_null($this->post)) : // Not filtered out yet
  1413.                     # -- Look up, or create, numeric ID for categories
  1414.                     $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
  1415.    
  1416.                     // Eliminate dummy variables
  1417.                     $taxonomies = array_filter($taxonomies, 'remove_dummy_zero');
  1418.    
  1419.                     $terms = $this->category_ids (
  1420.                         $this->feed_terms[$what],
  1421.                         $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what),
  1422.                         /*taxonomies=*/ $taxonomies,
  1423.                         array(
  1424.                           'singleton' => false, // I don't like surprises
  1425.                           'filters' => true,
  1426.                         )
  1427.                     );
  1428.                    
  1429.                     if (is_null($terms) or is_null($termSet)) :
  1430.                         // filtered out -- no matches
  1431.                     else :
  1432.                         $valid = true;
  1433.  
  1434.                         // filter mode off, or at least one match
  1435.                         foreach ($terms as $tax => $term_ids) :
  1436.                             if (!isset($termSet[$tax])) :
  1437.                                 $termSet[$tax] = array();
  1438.                             endif;
  1439.                             $termSet[$tax] = array_merge($termSet[$tax], $term_ids);
  1440.                         endforeach;
  1441.                     endif;
  1442.                 endif;
  1443.             endforeach;
  1444.  
  1445.             if (is_null($valid)) : // Plonked
  1446.                 $this->post = NULL;
  1447.             else : // We can proceed
  1448.                 $this->post['tax_input'] = array();
  1449.                 foreach ($termSet as $tax => $term_ids) :
  1450.                     if (!isset($this->post['tax_input'][$tax])) :
  1451.                         $this->post['tax_input'][$tax] = array();
  1452.                     endif;
  1453.                     $this->post['tax_input'][$tax] = array_merge(
  1454.                         $this->post['tax_input'][$tax],
  1455.                         $term_ids
  1456.                     );
  1457.                 endforeach;
  1458.  
  1459.                 // Now let's add on the feed and global presets
  1460.                 foreach ($this->preset_terms as $tax => $term_ids) :
  1461.                     if (!isset($this->post['tax_input'][$tax])) :
  1462.                         $this->post['tax_input'][$tax] = array();
  1463.                     endif;
  1464.                    
  1465.                     $this->post['tax_input'][$tax] = array_merge (
  1466.                         $this->post['tax_input'][$tax],
  1467.                         $this->category_ids (
  1468.                         /*terms=*/ $term_ids,
  1469.                         /*unfamiliar=*/ 'create:'.$tax, // These are presets; for those added in a tagbox editor, the tag may not yet exist
  1470.                         /*taxonomies=*/ array($tax),
  1471.                         array(
  1472.                           'singleton' => true,
  1473.                         ))
  1474.                     );
  1475.                 endforeach;
  1476.             endif;
  1477.         endif;
  1478.        
  1479.         if (!$this->filtered() and $freshness > 0) :
  1480.             // Filter some individual fields
  1481.            
  1482.             // If there already is a post slug (from syndication or by manual
  1483.             // editing) don't cause WP to overwrite it by sending in a NULL
  1484.             // post_name. Props Chris Fritz 2012-11-28.
  1485.             $post_name = (is_null($this->_wp_post) ? NULL : $this->_wp_post->post_name);           
  1486.  
  1487.             // Allow filters to set post slug. Props niska.
  1488.             $post_name = apply_filters('syndicated_post_slug', $post_name, $this);
  1489.             if (!empty($post_name)) :
  1490.                 $this->post['post_name'] = $post_name;
  1491.             endif;
  1492.            
  1493.             $this->post = apply_filters('syndicated_post', $this->post, $this);
  1494.            
  1495.             // Allow for feed-specific syndicated_post filters.
  1496.             $this->post = apply_filters(
  1497.                 "syndicated_post_".$this->link->uri(),
  1498.                 $this->post,
  1499.                 $this
  1500.             );
  1501.         endif;
  1502.        
  1503.         // Hook in early to make sure these get inserted if at all possible
  1504.         add_action(
  1505.             /*hook=*/ 'transition_post_status',
  1506.             /*callback=*/ array($this, 'add_rss_meta'),
  1507.             /*priority=*/ -10000, /* very early */
  1508.             /*arguments=*/ 3
  1509.         );
  1510.  
  1511.         $retval = array(1 => 'updated', 2 => 'new');
  1512.        
  1513.         $ret = false;
  1514.         if (!$this->filtered() and isset($retval[$freshness])) :           
  1515.             $diag = array(
  1516.                 1 => 'Updating existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"',
  1517.                 2 => 'Inserting new post "'.$this->post['post_title'].'"',
  1518.             );
  1519.             FeedWordPress::diagnostic('syndicated_posts', $diag[$freshness]);
  1520.  
  1521.             $this->insert_post(/*update=*/ ($freshness == 1));
  1522.            
  1523.             $hook = array(  1 => 'update_syndicated_item', 2 => 'post_syndicated_item' );
  1524.             do_action($hook[$freshness], $this->wp_id(), $this);
  1525.  
  1526.             $ret = $retval[$freshness];
  1527.         endif;
  1528.  
  1529.         // If this is a legit, non-filtered post, tag it as found on the feed
  1530.         // regardless of fresh or stale status
  1531.         if (!$this->filtered()) :
  1532.             $key = '_feedwordpress_retire_me_' . $this->link->id;
  1533.             delete_post_meta($this->wp_id(), $key);
  1534.            
  1535.             $status = get_post_field('post_status', $this->wp_id());
  1536.             if ('fwpretired'==$status and $this->link->is_incremental()) :
  1537.                 FeedWordPress::diagnostic('syndicated_posts', "Un-retiring previously retired post # ".$this->wp_id()." due to re-appearance on non-incremental feed.");
  1538.                 set_post_field('post_status', $this->post['post_status'], $this->wp_id());
  1539.                 wp_transition_post_status($this->post['post_status'], $status, $old_status, $this->post);
  1540.             endif;
  1541.         endif;
  1542.        
  1543.         // Remove add_rss_meta hook
  1544.         remove_action(
  1545.             /*hook=*/ 'transition_post_status',
  1546.             /*callback=*/ array($this, 'add_rss_meta'),
  1547.             /*priority=*/ -10000, /* very early */
  1548.             /*arguments=*/ 3
  1549.         );
  1550.  
  1551.         return $ret;
  1552.     } /* function SyndicatedPost::store () */
  1553.    
  1554.     function insert_post ($update = false) {
  1555.         global $wpdb;
  1556.  
  1557.         $dbpost = $this->normalize_post(/*new=*/ true);
  1558.         if (!is_null($dbpost)) :
  1559.             $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
  1560.    
  1561.             // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
  1562.             add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
  1563.                
  1564.             // Kludge to prevent kses filters from stripping the
  1565.             // content of posts when updating without a logged in
  1566.             // user who has `unfiltered_html` capability.
  1567.             $mungers = array('wp_filter_kses', 'wp_filter_post_kses');
  1568.             $removed = array();
  1569.             foreach ($mungers as $munger) :
  1570.                 if (has_filter('content_save_pre', $munger)) :
  1571.                     remove_filter('content_save_pre', $munger);
  1572.                     $removed[] = $munger;
  1573.                 endif;
  1574.             endforeach;
  1575.            
  1576.             if ($update and function_exists('get_post_field')) :
  1577.                 // Don't munge status fields that the user may
  1578.                 // have reset manually
  1579.                 $doNotMunge = array('post_status', 'comment_status', 'ping_status');
  1580.                
  1581.                 foreach ($doNotMunge as $field) :
  1582.                     $dbpost[$field] = get_post_field($field, $this->wp_id());
  1583.                 endforeach;
  1584.             endif;
  1585.            
  1586.             // WP3's wp_insert_post scans current_user_can() for the
  1587.             // tax_input, with no apparent way to override. Ugh.
  1588.             add_action(
  1589.             /*hook=*/ 'transition_post_status',
  1590.             /*callback=*/ array($this, 'add_terms'),
  1591.             /*priority=*/ -10001, /* very early */
  1592.             /*arguments=*/ 3
  1593.             );
  1594.            
  1595.             // WP3 appears to override whatever you give it for
  1596.             // post_modified. Ugh.
  1597.             add_action(
  1598.             /*hook=*/ 'transition_post_status',
  1599.             /*callback=*/ array($this, 'fix_post_modified_ts'),
  1600.             /*priority=*/ -10000, /* very early */
  1601.             /*arguments=*/ 3
  1602.             );
  1603.  
  1604.             if ($update) :
  1605.                 $this->post['ID'] = $this->wp_id();
  1606.                 $dbpost['ID'] = $this->post['ID'];
  1607.             endif;
  1608.            
  1609.             $this->_wp_id = wp_insert_post($dbpost, /*return wp_error=*/ true);
  1610.  
  1611.             remove_action(
  1612.             /*hook=*/ 'transition_post_status',
  1613.             /*callback=*/ array($this, 'add_terms'),
  1614.             /*priority=*/ -10001, /* very early */
  1615.             /*arguments=*/ 3
  1616.             );
  1617.  
  1618.             remove_action(
  1619.             /*hook=*/ 'transition_post_status',
  1620.             /*callback=*/ array($this, 'fix_post_modified_ts'),
  1621.             /*priority=*/ -10000, /* very early */
  1622.             /*arguments=*/ 3
  1623.             );
  1624.  
  1625.             // Turn off ridiculous fucking kludges #1 and #2
  1626.             remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
  1627.             foreach ($removed as $filter) :
  1628.                 add_filter('content_save_pre', $filter);
  1629.             endforeach;
  1630.            
  1631.             $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__));
  1632.         endif;
  1633.     } /* function SyndicatedPost::insert_post () */
  1634.    
  1635.     function insert_new () {
  1636.         $this->insert_post(/*update=*/ false);
  1637.     } /* SyndicatedPost::insert_new() */
  1638.  
  1639.     function update_existing () {
  1640.         $this->insert_post(/*update=*/ true);
  1641.     } /* SyndicatedPost::update_existing() */
  1642.  
  1643.     /**
  1644.      * SyndicatedPost::normalize_post()
  1645.      *
  1646.      * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
  1647.      * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
  1648.      */
  1649.     function normalize_post ($new = true) {
  1650.         global $wpdb;
  1651.  
  1652.         $out = $this->post;
  1653.  
  1654.         $fullPost = $out['post_title'].$out['post_content'];
  1655.         $fullPost .= (isset($out['post_excerpt']) ? $out['post_excerpt'] : '');
  1656.         if (strlen($fullPost) < 1) :
  1657.             // FIXME: Option for filtering out empty posts
  1658.         endif;
  1659.         if (strlen($out['post_title'])==0) :
  1660.             $offset = (int) get_option('gmt_offset') * 60 * 60;
  1661.             if (isset($this->post['meta']['syndication_source'])) :
  1662.                 $source_title = $this->post['meta']['syndication_source'];
  1663.             else :
  1664.                 $feed_url = parse_url($this->post['meta']['syndication_feed']);
  1665.                 $source_title = $feed_url['host'];
  1666.             endif;
  1667.            
  1668.             $out['post_title'] = $source_title
  1669.                 .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
  1670.             // FIXME: Option for what to fill a blank title with...
  1671.         endif;
  1672.  
  1673.         // Normalize the guid if necessary.
  1674.         $out['guid'] = SyndicatedPost::normalize_guid($out['guid']);
  1675.  
  1676.         // Why the fuck doesn't wp_insert_post already do this?
  1677.         foreach ($out as $key => $value) :
  1678.             if (is_string($value)) :
  1679.                 $out[$key] = $wpdb->escape($value);
  1680.             else :
  1681.                 $out[$key] = $value;
  1682.             endif;
  1683.         endforeach;
  1684.  
  1685.         return $out;
  1686.     }
  1687.  
  1688.     /**
  1689.      * SyndicatedPost::validate_post_id()
  1690.      *
  1691.      * @param array $dbpost An array representing the post we attempted to insert or update
  1692.      * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
  1693.      */
  1694.     function validate_post_id ($dbpost, $is_update, $ns) {
  1695.         if (is_array($ns)) : $ns = implode('::', $ns);
  1696.         else : $ns = (string) $ns; endif;
  1697.  
  1698.         // This should never happen.
  1699.         if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
  1700.             $verb = ($is_update ? 'update existing' : 'insert new');
  1701.             $guid = $this->guid();
  1702.             $url = $this->permalink();
  1703.             $feed = $this->link->uri(array('add_params' => true));
  1704.  
  1705.             // wp_insert_post failed. Diagnostics, or barf up a critical bug
  1706.             // notice if we are in debug mode.
  1707.             $mesg = "Failed to $verb item [$guid]. WordPress API returned no valid post ID.\n"
  1708.                 ."\t\tID = ".serialize($this->_wp_id)."\n"
  1709.                 ."\t\tURL = ".FeedWordPress::val($url)
  1710.                 ."\t\tFeed = ".FeedWordPress::val($feed);
  1711.             FeedWordPress::diagnostic('updated_feeds:errors', "WordPress API error: $mesg");
  1712.             FeedWordPress::diagnostic('feed_items:rejected', $mesg);
  1713.  
  1714.             $mesg = <<<EOM
  1715. The WordPress API returned an invalid post ID
  1716.                when FeedWordPress tried to $verb item $guid
  1717.                [URL: $url]
  1718.                from the feed at $feed
  1719.  
  1720. $ns::_wp_id
  1721. EOM;
  1722.             FeedWordPress::noncritical_bug(
  1723.                 /*message=*/ $mesg,
  1724.                 /*var =*/ array(
  1725.                     "\$this->_wp_id" => $this->_wp_id,
  1726.                     "\$dbpost" => $dbpost,
  1727.                 ),
  1728.                 /*line # =*/ __LINE__, /*filename=*/ __FILE__
  1729.             );
  1730.         endif;
  1731.     } /* SyndicatedPost::validate_post_id() */
  1732.    
  1733.     /**
  1734.      * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
  1735.      * meta-data (authorship, etc.) when storing revisions of an updated
  1736.      * syndicated post.
  1737.      *
  1738.      * In their infinite wisdom, the WordPress coders have made it completely
  1739.      * impossible for a plugin that uses wp_insert_post() to set certain
  1740.      * meta-data (such as the author) when you store an old revision of an
  1741.      * updated post. Instead, it uses the WordPress defaults (= currently
  1742.      * active user ID if the process is running with a user logged in, or
  1743.      * = #0 if there is no user logged in). This results in bogus authorship
  1744.      * data for revisions that are syndicated from off the feed, unless we
  1745.      * use a ridiculous kludge like this to end-run the munging of meta-data
  1746.      * by _wp_put_post_revision.
  1747.      *
  1748.      * @param int $revision_id The revision ID to fix up meta-data
  1749.      */
  1750.     function fix_revision_meta ($revision_id) {
  1751.         global $wpdb;
  1752.        
  1753.         $post_author = (int) $this->post['post_author'];
  1754.        
  1755.         $revision_id = (int) $revision_id;
  1756.         $wpdb->query("
  1757.         UPDATE $wpdb->posts
  1758.         SET post_author={$this->post['post_author']}
  1759.         WHERE post_type = 'revision' AND ID='$revision_id'
  1760.         ");
  1761.     } /* SyndicatedPost::fix_revision_meta () */
  1762.      
  1763.     /**
  1764.      * SyndicatedPost::add_terms() -- if FeedWordPress is processing an
  1765.      * automatic update, that generally means that wp_insert_post() is being
  1766.      * called under the user credentials of whoever is viewing the blog at
  1767.      * the time -- which usually means no user at all. But wp_insert_post()
  1768.      * checks current_user_can() before assigning any of the terms in a
  1769.      * post's tax_input structure -- which is unfortunate, since
  1770.      * current_user_can() always returns FALSE when there is no current user
  1771.      * logged in. Meaning that automatic updates get no terms assigned.
  1772.      *
  1773.      * So, wp_insert_post() is not going to do the term assignments for us.
  1774.      * If you want something done right....
  1775.      *
  1776.      * @param string $new_status Unused action parameter.
  1777.      * @param string $old_status Unused action parameter.
  1778.      * @param object $post The database record for the post just inserted.
  1779.      */
  1780.     function add_terms ($new_status, $old_status, $post) {
  1781.         if ($new_status!='inherit') : // Bail if we are creating a revision.
  1782.             if ( is_array($this->post) and isset($this->post['tax_input']) and is_array($this->post['tax_input']) ) :
  1783.                 foreach ($this->post['tax_input'] as $taxonomy => $terms) :
  1784.                     if (is_array($terms)) :
  1785.                         $terms = array_filter($terms); // strip out empties
  1786.                     endif;
  1787.                     wp_set_post_terms($post->ID, $terms, $taxonomy);
  1788.                 endforeach;
  1789.             endif;
  1790.         endif;
  1791.     } /* SyndicatedPost::add_terms () */
  1792.    
  1793.     /**
  1794.      * SyndicatedPost::fix_post_modified_ts() -- We would like to set
  1795.      * post_modified and post_modified_gmt to reflect the value of
  1796.      * <atom:updated> or equivalent elements on the feed. Unfortunately,
  1797.      * wp_insert_post() refuses to acknowledge explicitly-set post_modified
  1798.      * fields and overwrites them, either with the post_date (if new) or the
  1799.      * current timestamp (if updated).
  1800.      *
  1801.      * So, wp_insert_post() is not going to do the last-modified assignments
  1802.      * for us. If you want something done right....
  1803.      *
  1804.      * @param string $new_status Unused action parameter.
  1805.      * @param string $old_status Unused action parameter.
  1806.      * @param object $post The database record for the post just inserted.
  1807.      */
  1808.     function fix_post_modified_ts ($new_status, $old_status, $post) {
  1809.         global $wpdb;
  1810.         if ($new_status!='inherit') : // Bail if we are creating a revision.
  1811.             $wpdb->update( $wpdb->posts, /*data=*/ array(
  1812.             'post_modified' => $this->post['post_modified'],
  1813.             'post_modified_gmt' => $this->post['post_modified_gmt'],
  1814.             ), /*where=*/ array('ID' => $post->ID) );
  1815.         endif;
  1816.     } /* SyndicatedPost::fix_post_modified_ts () */
  1817.    
  1818.     /**
  1819.      * SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
  1820.      * using the space for custom keys. The set of keys and values to add is
  1821.      * specified by the keys and values of $post['meta']. This is used to
  1822.      * store anything that the WordPress user might want to access from a
  1823.      * template concerning the post's original source that isn't provided
  1824.      * for by standard WP meta-data (i.e., any interesting data about the
  1825.      * syndicated post other than author, title, timestamp, categories, and
  1826.      * guid). It's also used to hook into WordPress's support for
  1827.      * enclosures.
  1828.      *
  1829.      * @param string $new_status Unused action parameter.
  1830.      * @param string $old_status Unused action parameter.
  1831.      * @param object $post The database record for the post just inserted.
  1832.      */
  1833.     function add_rss_meta ($new_status, $old_status, $post) {
  1834.         global $wpdb;
  1835.         if ($new_status!='inherit') : // Bail if we are creating a revision.
  1836.             FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}');
  1837.  
  1838.             if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
  1839.                 $postId = $post->ID;
  1840.                
  1841.                 // Aggregated posts should NOT send out pingbacks.
  1842.                 // WordPress 2.1-2.2 claim you can tell them not to
  1843.                 // using $post_pingback, but they don't listen, so we
  1844.                 // make sure here.
  1845.                 $result = $wpdb->query("
  1846.                 DELETE FROM $wpdb->postmeta
  1847.                 WHERE post_id='$postId' AND meta_key='_pingme'
  1848.                 ");
  1849.    
  1850.                 foreach ( $this->post['meta'] as $key => $values ) :
  1851.                     $eKey = $wpdb->escape($key);
  1852.    
  1853.                     // If this is an update, clear out the old
  1854.                     // values to avoid duplication.
  1855.                     $result = $wpdb->query("
  1856.                     DELETE FROM $wpdb->postmeta
  1857.                     WHERE post_id='$postId' AND meta_key='$eKey'
  1858.                     ");
  1859.                    
  1860.                     // Allow for either a single value or an array
  1861.                     if (!is_array($values)) $values = array($values);
  1862.                     foreach ( $values as $value ) :
  1863.                     FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($value, /*no newlines=*/ true));
  1864.                         add_post_meta($postId, $key, $value, /*unique=*/ false);
  1865.                     endforeach;
  1866.                 endforeach;
  1867.             endif;
  1868.         endif;
  1869.     } /* SyndicatedPost::add_rss_meta () */
  1870.  
  1871.     /**
  1872.      * SyndicatedPost::author_id (): get the ID for an author name from
  1873.      * the feed. Create the author if necessary.
  1874.      *
  1875.      * @param string $unfamiliar_author
  1876.      *
  1877.      * @return NULL|int The numeric ID of the author to attribute the post to
  1878.      *  NULL if the post should be filtered out.
  1879.      */
  1880.     function author_id ($unfamiliar_author = 'create') {
  1881.         global $wpdb;
  1882.  
  1883.         $a = $this->named['author'];
  1884.  
  1885.         $source = $this->source();
  1886.         $forbidden = apply_filters('feedwordpress_forbidden_author_names',
  1887.             array('admin', 'administrator', 'www', 'root'));
  1888.        
  1889.         // Prepare the list of candidates to try for author name: name from
  1890.         // feed, original source title (if any), immediate source title live
  1891.         // from feed, subscription title, prettied version of feed homepage URL,
  1892.         // prettied version of feed URL, or, failing all, use "unknown author"
  1893.         // as last resort
  1894.  
  1895.         $candidates = array();
  1896.         $candidates[] = $a['name'];
  1897.         if (!is_null($source)) : $candidates[] = $source['title']; endif;
  1898.         $candidates[] = $this->link->name(/*fromFeed=*/ true);
  1899.         $candidates[] = $this->link->name(/*fromFeed=*/ false);
  1900.         if (strlen($this->link->homepage()) > 0) : $candidates[] = feedwordpress_display_url($this->link->homepage()); endif;
  1901.         $candidates[] = feedwordpress_display_url($this->link->uri());
  1902.         $candidates[] = 'unknown author';
  1903.        
  1904.         // Pick the first one that works from the list, screening against empty
  1905.         // or forbidden names.
  1906.        
  1907.         $author = NULL;
  1908.         while (is_null($author) and ($candidate = each($candidates))) :
  1909.             if (!is_null($candidate['value'])
  1910.             and (strlen(trim($candidate['value'])) > 0)
  1911.             and !in_array(strtolower(trim($candidate['value'])), $forbidden)) :
  1912.                 $author = $candidate['value'];
  1913.             endif;
  1914.         endwhile;
  1915.        
  1916.         $email = (isset($a['email']) ? $a['email'] : NULL);
  1917.         $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL);
  1918.  
  1919.        
  1920.         $hostUrl = $this->link->homepage();
  1921.         if (is_null($hostUrl) or (strlen($hostUrl) < 0)) :
  1922.             $hostUrl = $this->link->uri();
  1923.         endif;
  1924.  
  1925.         $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
  1926.         if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
  1927.             $test_email = $email;
  1928.         else :
  1929.             $test_email = NULL;
  1930.         endif;
  1931.  
  1932.         // Never can be too careful...
  1933.         $login = sanitize_user($author, /*strict=*/ true);
  1934.  
  1935.         // Possible for, e.g., foreign script author names
  1936.         if (strlen($login) < 1) :
  1937.             // No usable characters in author name for a login.
  1938.             // (Sometimes results from, e.g., foreign scripts.)
  1939.             //
  1940.             // We just need *something* in Western alphanumerics,
  1941.             // so let's try the domain name.
  1942.             //
  1943.             // Uniqueness will be guaranteed below if necessary.
  1944.  
  1945.             $url = parse_url($hostUrl);
  1946.            
  1947.             $login = sanitize_user($url['host'], /*strict=*/ true);
  1948.             if (strlen($login) < 1) :
  1949.                 // This isn't working. Frak it.
  1950.                 $login = 'syndicated';
  1951.             endif;
  1952.         endif;
  1953.  
  1954.         $login = apply_filters('pre_user_login', $login);
  1955.  
  1956.         $nice_author = sanitize_title($author);
  1957.         $nice_author = apply_filters('pre_user_nicename', $nice_author);
  1958.  
  1959.         $reg_author = $wpdb->escape(preg_quote($author));
  1960.         $author = $wpdb->escape($author);
  1961.         $email = $wpdb->escape($email);
  1962.         $test_email = $wpdb->escape($test_email);
  1963.         $authorUrl = $wpdb->escape($authorUrl);
  1964.  
  1965.         // Check for an existing author rule....
  1966.         if (isset($this->link->settings['map authors']['name']['*'])) :
  1967.             $author_rule = $this->link->settings['map authors']['name']['*'];
  1968.         elseif (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
  1969.             $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
  1970.         else :
  1971.             $author_rule = NULL;
  1972.         endif;
  1973.  
  1974.         // User name is mapped to a particular author. If that author ID exists, use it.
  1975.         if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
  1976.             $id = (int) $author_rule;
  1977.  
  1978.         // User name is filtered out
  1979.         elseif ('filter' == $author_rule) :
  1980.             $id = NULL;
  1981.        
  1982.         else :
  1983.             // Check the database for an existing author record that might fit
  1984.  
  1985.             // First try the user core data table.
  1986.             $id = $wpdb->get_var(
  1987.             "SELECT ID FROM $wpdb->users
  1988.             WHERE TRIM(LCASE(display_name)) = TRIM(LCASE('$author'))
  1989.             OR TRIM(LCASE(user_login)) = TRIM(LCASE('$author'))
  1990.             OR (
  1991.                 LENGTH(TRIM(LCASE(user_email))) > 0
  1992.                 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
  1993.             )");
  1994.    
  1995.             // If that fails, look for aliases in the user meta data table
  1996.             if (is_null($id)) :
  1997.                 $id = $wpdb->get_var(
  1998.                 "SELECT user_id FROM $wpdb->usermeta
  1999.                 WHERE
  2000.                     (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
  2001.                     OR (
  2002.                         meta_key = 'description'
  2003.                         AND TRIM(LCASE(meta_value))
  2004.                         RLIKE CONCAT(
  2005.                             '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
  2006.                             TRIM(LCASE('$reg_author')),
  2007.                             '( |\\t|\\r)*(\\n|\$)'
  2008.                         )
  2009.                     )
  2010.                 ");
  2011.             endif;
  2012.  
  2013.             // ... if you don't find one, then do what you need to do
  2014.             if (is_null($id)) :
  2015.                 if ($unfamiliar_author === 'create') :
  2016.                     $userdata = array();
  2017.  
  2018.                     // WordPress 3 is going to pitch a fit if we attempt to register
  2019.                     // more than one user account with an empty e-mail address, so we
  2020.                     // need *something* here. Ugh.
  2021.                     if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
  2022.                         $url = parse_url($hostUrl);
  2023.                         $email = $nice_author.'@'.$url['host'];
  2024.                     endif;
  2025.  
  2026.                     #-- user table data
  2027.                     $userdata['ID'] = NULL; // new user
  2028.                     $userdata['user_login'] = $login;
  2029.                     $userdata['user_nicename'] = $nice_author;
  2030.                     $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
  2031.                     $userdata['user_email'] = $email;
  2032.                     $userdata['user_url'] = $authorUrl;
  2033.                     $userdata['nickname'] = $author;
  2034.                     $parts = preg_split('/\s+/', trim($author), 2);
  2035.                     if (isset($parts[0])) : $userdata['first_name'] = $parts[0]; endif;
  2036.                     if (isset($parts[1])) : $userdata['last_name'] = $parts[1]; endif;
  2037.                     $userdata['display_name'] = $author;
  2038.                     $userdata['role'] = 'contributor';
  2039.                    
  2040.                     do { // Keep trying until you get it right. Or until PHP crashes, I guess.
  2041.                         $id = wp_insert_user($userdata);
  2042.                         if (is_wp_error($id)) :
  2043.                             $codes = $id->get_error_code();
  2044.                             switch ($codes) :
  2045.                             case 'empty_user_login' :
  2046.                             case 'existing_user_login' :
  2047.                                 // Add a random disambiguator
  2048.                                 $userdata['user_login'] .= substr(md5(uniqid(microtime())), 0, 6);
  2049.                                 break;
  2050.                             case 'existing_user_email' :
  2051.                                 // No disassemble!
  2052.                                 $parts = explode('@', $userdata['user_email'], 2);
  2053.                                
  2054.                                 // Add a random disambiguator as a gmail-style username extension
  2055.                                 $parts[0] .= '+'.substr(md5(uniqid(microtime())), 0, 6);
  2056.                                
  2057.                                 // Reassemble
  2058.                                 $userdata['user_email'] = $parts[0].'@'.$parts[1];
  2059.                                 break;
  2060.                             endswitch;
  2061.                         endif;
  2062.                     } while (is_wp_error($id));
  2063.                 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
  2064.                     $id = (int) $unfamiliar_author;
  2065.                 elseif ($unfamiliar_author === 'default') :
  2066.                     $id = 1;
  2067.                 endif;
  2068.             endif;
  2069.         endif;
  2070.  
  2071.         if ($id) :
  2072.             $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
  2073.            
  2074.             // Multisite: Check whether the author has been recorded
  2075.             // on *this* blog before. If not, put her down as a
  2076.             // Contributor for *this* blog.
  2077.             $user = new WP_User((int) $id);
  2078.             if (empty($user->roles)) :
  2079.                 $user->add_role('contributor');
  2080.             endif;
  2081.         endif;
  2082.         return $id;
  2083.     } /* function SyndicatedPost::author_id () */
  2084.  
  2085.     /**
  2086.      * category_ids: look up (and create) category ids from a list of categories
  2087.      *
  2088.      * @param array $cats
  2089.      * @param string $unfamiliar_category
  2090.      * @param array|null $taxonomies
  2091.      * @return array
  2092.      */
  2093.     function category_ids ($cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
  2094.         $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
  2095.         $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
  2096.        
  2097.         $catTax = 'category';
  2098.  
  2099.         if (is_null($taxonomies)) :
  2100.             $taxonomies = array('category');
  2101.         endif;
  2102.  
  2103.         // We need to normalize whitespace because (1) trailing
  2104.         // whitespace can cause PHP and MySQL not to see eye to eye on
  2105.         // VARCHAR comparisons for some versions of MySQL (cf.
  2106.         // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
  2107.         // because I doubt most people want to make a semantic
  2108.         // distinction between 'Computers' and 'Computers  '
  2109.         $cats = array_map('trim', $cats);
  2110.  
  2111.         $terms = array();
  2112.         foreach ($taxonomies as $tax) :
  2113.             $terms[$tax] = array();
  2114.         endforeach;
  2115.        
  2116.         foreach ($cats as $cat_name) :
  2117.             if (preg_match('/^{([^#}]*)#([0-9]+)}$/', $cat_name, $backref)) :
  2118.                 $cat_id = (int) $backref[2];
  2119.                 $tax = $backref[1];
  2120.                 if (strlen($tax) < 1) :
  2121.                     $tax = $catTax;
  2122.                 endif;
  2123.  
  2124.                 $term = term_exists($cat_id, $tax);
  2125.                 if (!is_wp_error($term) and !!$term) :
  2126.                     if (!isset($terms[$tax])) :
  2127.                         $terms[$tax] = array();
  2128.                     endif;
  2129.                     $terms[$tax][] = $cat_id;
  2130.                 endif;
  2131.             elseif (strlen($cat_name) > 0) :
  2132.                 $familiar = false;
  2133.                 foreach ($taxonomies as $tax) :
  2134.                     if ($tax!='category' or strtolower($cat_name)!='uncategorized') :
  2135.                         $term = term_exists($cat_name, $tax);
  2136.                         if (!is_wp_error($term) and !!$term) :
  2137.                             $familiar = true;
  2138.    
  2139.                             if (is_array($term)) :
  2140.                                 $term_id = (int) $term['term_id'];
  2141.                             else :
  2142.                                 $term_id = (int) $term;
  2143.                             endif;
  2144.                            
  2145.                             if (!isset($terms[$tax])) :
  2146.                                 $terms[$tax] = array();
  2147.                             endif;
  2148.                             $terms[$tax][] = $term_id;
  2149.                             break; // We're done here.
  2150.                         endif;
  2151.                     endif;
  2152.                 endforeach;
  2153.                
  2154.                 if (!$familiar) :
  2155.                     if ('tag'==$unfamiliar_category) :
  2156.                         $unfamiliar_category = 'create:post_tag';
  2157.                     endif;
  2158.                    
  2159.                     if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
  2160.                         $tax = $catTax; // Default
  2161.                         if (isset($ref[2]) and strlen($ref[2]) > 2) :
  2162.                             $tax = $ref[2];
  2163.                         endif;
  2164.                         $term = wp_insert_term($cat_name, $tax);
  2165.                         if (is_wp_error($term)) :
  2166.                             FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__, __FILE__);
  2167.                         else :
  2168.                             if (!isset($terms[$tax])) :
  2169.                                 $terms[$tax] = array();
  2170.                             endif;
  2171.                             $terms[$tax][] = (int) $term['term_id'];
  2172.                         endif;
  2173.                     endif;
  2174.                 endif;
  2175.             endif;
  2176.         endforeach;
  2177.  
  2178.         $filtersOn = $allowFilters;
  2179.         if ($allowFilters) :
  2180.             $filters = array_filter(
  2181.                 $this->link->setting('match/filter', 'match_filter', array()),
  2182.                 'remove_dummy_zero'
  2183.             );
  2184.             $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
  2185.         endif;
  2186.        
  2187.         // Check for filter conditions
  2188.         foreach ($terms as $tax => $term_ids) :
  2189.             if ($filtersOn
  2190.             and (count($term_ids)==0)
  2191.             and in_array($tax, $filters)) :
  2192.                 $terms = NULL; // Drop the post
  2193.                 break;
  2194.             else :
  2195.                 $terms[$tax] = array_unique($term_ids);
  2196.             endif;
  2197.         endforeach;
  2198.  
  2199.         if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
  2200.             $terms = end($terms);
  2201.         endif;
  2202.         return $terms;
  2203.     } // function SyndicatedPost::category_ids ()
  2204.  
  2205. } /* class SyndicatedPost */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement