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