PluginProbe
FeedWordPress / 2010.0528
FeedWordPress v2010.0528
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 +901 -485 2009.11122010.0528 View file →
@@ -1,39 +1,96 @@
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.0528
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
11 27 var $_freshness = null;
12 28 var $_wp_id = null;
13 29
14 - function SyndicatedPost ($item, $link) {
30 + /**
31 + * SyndicatedPost constructor: Given a feed item and the source from
32 + * which it was taken, prepare a post that can be inserted into the
33 + * WordPress database on request, or updated in place if it has already
34 + * been syndicated.
35 + *
36 + * @param array $item The item syndicated from the feed.
37 + * @param SyndicatedLink $source The feed it was syndicated from.
38 + */
39 + function SyndicatedPost ($item, $source) {
15 40 global $wpdb;
16 41
17 - $this->link = $link;
18 - $feedmeta = $link->settings;
19 - $feed = $link->magpie;
42 + if (is_array($item)
43 + and isset($item['simplepie'])
44 + and isset($item['magpie'])) :
45 + $this->entry = $item['simplepie'];
46 + $this->item = $item['magpie'];
47 + $item = $item['magpie'];
48 + else :
49 + $this->item = $item;
50 + endif;
20 51
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.
52 + $this->link = $source;
53 + $this->feed = $source->magpie;
54 + $this->feedmeta = $source->settings;
55 +
56 + # Dealing with namespaces can get so fucking fucked.
57 + $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR;
58 + $this->xmlns['reverse'] = array();
59 + foreach ($this->xmlns['forward'] as $url => $ns) :
60 + if (!isset($this->xmlns['reverse'][$ns])) :
61 + $this->xmlns['reverse'][$ns] = array();
62 + endif;
63 + $this->xmlns['reverse'][$ns][] = $url;
64 + endforeach;
65 +
66 + // Fucking SimplePie.
67 + $this->xmlns['reverse']['rss'][] = '';
68 +
69 + # These globals were originally an ugly kludge around a bug in
70 + # apply_filters from WordPress 1.5. The bug was fixed in 1.5.1,
71 + # and I sure hope at this point that nobody writing filters for
72 + # FeedWordPress is still relying on them.
26 73 #
27 - # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
74 + # Anyway, I hereby declare them DEPRECATED as of 8 February
75 + # 2010. I'll probably remove the globals within 1-2 releases in
76 + # the interests of code hygiene and memory usage. If you
77 + # currently use them in your filters, I advise you switch off to
78 + # accessing the public members SyndicatedPost::feed and
79 + # SyndicatedPost::feedmeta.
80 +
28 81 global $fwp_channel, $fwp_feedmeta;
29 - $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
82 + $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta;
30 83
31 - $this->feed = $feed;
32 - $this->feedmeta = $feedmeta;
33 -
34 - $this->item = $item;
84 + // Trigger global syndicated_item filter.
35 85 $this->item = apply_filters('syndicated_item', $this->item, $this);
86 +
87 + // Allow for feed-specific syndicated_item filters.
88 + $this->item = apply_filters(
89 + "syndicated_item_".$source->uri(),
90 + $this->item,
91 + $this
92 + );
36 93
37 94 # Filters can halt further processing by returning NULL
38 95 if (is_null($this->item)) :
39 96 $this->post = NULL;
@@ -42,51 +99,31 @@
42 99 # That's deliberate. The escaping is done at the point
43 100 # of insertion, not here, to avoid double-escaping and
44 101 # to avoid screwing with syndicated_post filters
45 102
46 - $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
103 + $this->post['post_title'] = apply_filters(
104 + 'syndicated_item_title',
105 + $this->entry->get_title(), $this
106 + );
47 107
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);
108 + $this->post['named']['author'] = apply_filters(
109 + 'syndicated_item_author',
110 + $this->author(), $this
111 + );
112 + // This just gives us an alphanumeric name for the author.
113 + // We look up (or create) the numeric ID for the author
114 + // in SyndicatedPost::add().
52 115
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 -
116 + $this->post['post_content'] = apply_filters(
117 + 'syndicated_item_content',
118 + $this->content(), $this
119 + );
120 +
121 + $excerpt = apply_filters('syndicated_item_excerpt', $this->excerpt(), $this);
80 122 if (!is_null($excerpt)):
81 123 $this->post['post_excerpt'] = $excerpt;
82 124 endif;
83 125
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 -
89 126 $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
90 127 $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
91 128 $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
92 129
@@ -91,12 +128,12 @@
91 128 $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
92 129
93 130 // Dealing with timestamps in WordPress is so fucking fucked.
94 131 $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());
132 + $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published(/*fallback=*/ true, /*default=*/ -1) + $offset);
133 + $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated(/*fallback=*/ true, /*default=*/ -1) + $offset);
134 + $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published(/*fallback=*/ true, /*default=*/ -1));
135 + $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated(/*fallback=*/ true, /*default=*/ -1));
99 136
100 137 // Use feed-level preferences or the global default.
101 138 $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
102 139 $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
@@ -120,20 +157,49 @@
120 157 endif;
121 158 if (!is_array($custom_settings)) :
122 159 $custom_settings = array();
123 160 endif;
124 - $this->post['meta'] = array_merge($default_custom_settings, $custom_settings);
161 +
162 + $postMetaIn = array_merge($default_custom_settings, $custom_settings);
163 + $postMetaOut = array();
125 164
165 + // Big ugly fuckin loop to do any element substitutions
166 + // that we may need.
167 + foreach ($postMetaIn as $key => $values) :
168 + if (is_string($values)) : $values = array($values); endif;
169 +
170 + $postMetaOut[$key] = array();
171 + foreach ($values as $value) :
172 + if (preg_match('/\$\( ([^)]+) \)/x', $value, $ref)) :
173 + $elements = $this->query($ref[1]);
174 + foreach ($elements as $element) :
175 + $postMetaOut[$key][] = str_replace(
176 + $ref[0],
177 + $element,
178 + $value
179 + );
180 + endforeach;
181 + else :
182 + $postMetaOut[$key][] = $value;
183 + endif;
184 + endforeach;
185 + endforeach;
186 +
187 + foreach ($postMetaOut as $key => $values) :
188 + $this->post['meta'][$key] = array();
189 + foreach ($values as $value) :
190 + $this->post['meta'][$key][] = apply_filters("syndicated_post_meta_{$key}", $value, $this);
191 + endforeach;
192 + endforeach;
193 +
126 194 // 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;
135 - endif;
195 + $enclosures = $this->entry->get_enclosures();
196 + if (is_array($enclosures)) : foreach ($enclosures as $enclosure) :
197 + $this->post['meta']['enclosure'][] =
198 + apply_filters('syndicated_item_enclosure_url', $enclosure->get_link(), $this)."\n".
199 + apply_filters('syndicated_item_enclosure_length', $enclosure->get_length(), $this)."\n".
200 + apply_filters('syndicated_item_enclosure_type', $enclosure->get_type(), $this);
201 + endforeach; endif;
136 202
137 203 // In case you want to point back to the blog this was syndicated from
138 204 if (isset($this->feed->channel['title'])) :
139 205 $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
@@ -156,33 +222,26 @@
156 222 $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
157 223 endif;
158 224
159 225 // 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']);
162 - endif;
163 226
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']);
167 - endif;
227 + // Human-readable comment URI
228 + $commentLink = apply_filters('syndicated_item_comments', $this->comment_link(), $this);
229 + if (!is_null($commentLink)) : $this->post['meta']['rss:comments'] = $commentLink; endif;
168 230
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]);
178 - endif;
179 - endfor;
180 - endif;
231 + // Machine-readable content feed URI
232 + $commentFeed = apply_filters('syndicated_item_commentrss', $this->comment_feed(), $this);
233 + if (!is_null($commentFeed)) : $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif;
234 + // Yeah, yeah, now I know that it's supposed to be
235 + // wfw:commentRss. Oh well. Path dependence, sucka.
181 236
182 237 // 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'];
238 + if (isset($this->feedmeta['link/uri'])) :
239 + $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
240 + endif;
241 + if (isset($this->feedmeta['link/id'])) :
242 + $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
243 + endif;
185 244
186 245 if (isset($this->item['source_link_self'])) :
187 246 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
188 247 endif;
@@ -187,20 +246,10 @@
187 246 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
188 247 endif;
189 248
190 249 // In case you want to know the external permalink...
191 - if (isset($this->item['link'])) :
192 - $permalink = $this->item['link'];
250 + $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink());
193 251
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'];
198 - endif;
199 - endif;
200 -
201 - $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $permalink);
202 -
203 252 // Store a hash of the post content for checking whether something needs to be updated
204 253 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
205 254
206 255 // Feed-by-feed options for author and category creation
@@ -248,14 +297,680 @@
248 297 $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
249 298 endif;
250 299 $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
251 300 endif;
252 - } // SyndicatedPost::SyndicatedPost()
301 + } /* SyndicatedPost::SyndicatedPost() */
253 302
303 + #####################################
304 + #### EXTRACT DATA FROM FEED ITEM ####
305 + #####################################
306 +
307 + /**
308 + * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
309 + * elements within the syndicated item.
310 + *
311 + * @param string $path
312 + * @returns array of string values representing contents of matching
313 + * elements or attributes
314 + */
315 + function query ($path) {
316 + $urlHash = array();
317 +
318 + // Allow {url} notation for namespaces. URLs will contain : and /, so...
319 + preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
320 + foreach ($match as $ref) :
321 + $urlHash[md5($ref[1])] = $ref[1];
322 + endforeach;
323 +
324 + foreach ($urlHash as $hash => $url) :
325 + $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
326 + endforeach;
327 +
328 + $path = explode('/', $path);
329 + foreach ($path as $index => $node) :
330 + if (preg_match('/{#([^}]+)}/', $node, $ref)) :
331 + if (isset($urlHash[$ref[1]])) :
332 + $path[$index] = str_replace(
333 + '{#'.$ref[1].'}',
334 + '{'.$urlHash[$ref[1]].'}',
335 + $node
336 + );
337 + endif;
338 + endif;
339 + endforeach;
340 +
341 + // Start out with a get_item_tags query.
342 + $node = '';
343 + while (strlen($node)==0 and !is_null($node)) :
344 + $node = array_shift($path);
345 + endwhile;
346 +
347 + switch ($node) :
348 + case 'feed' :
349 + case 'channel' :
350 + $method = "get_${node}_tags";
351 + $node = array_shift($path);
352 + break;
353 + case 'item' :
354 + $node = array_shift($path);
355 + default :
356 + $method = NULL;
357 + endswitch;
358 +
359 + $data = array();
360 + if (!is_null($node)) :
361 + list($namespaces, $element) = $this->xpath_extended_name($node);
362 +
363 + $matches = array();
364 + foreach ($namespaces as $ns) :
365 + if (!is_null($method)) :
366 + $el = $this->link->simplepie->{$method}($ns, $element);
367 + else :
368 + $el = $this->entry->get_item_tags($ns, $element);
369 + endif;
370 +
371 + if (!is_null($el)) :
372 + $matches = array_merge($matches, $el);
373 + endif;
374 + endforeach;
375 + $data = $matches;
376 +
377 + $node = array_shift($path);
378 + endif;
379 +
380 + while (!is_null($node)) :
381 + if (strlen($node) > 0) :
382 + $matches = array();
383 +
384 + list($ns, $element) = $this->xpath_extended_name($node);
385 +
386 + if (preg_match('/^@(.*)$/', $element, $ref)) :
387 + $element = $ref[1];
388 + $axis = 'attribs';
389 + else :
390 + $axis = 'child';
391 + endif;
392 +
393 + foreach ($data as $datum) :
394 + foreach ($namespaces as $ns) :
395 + if (!is_string($datum)
396 + and isset($datum[$axis][$ns][$element])) :
397 + if (is_string($datum[$axis][$ns][$element])) :
398 + $matches[] = $datum[$axis][$ns][$element];
399 + else :
400 + $matches = array_merge($matches, $datum[$axis][$ns][$element]);
401 + endif;
402 + endif;
403 + endforeach;
404 + endforeach;
405 +
406 + $data = $matches;
407 + endif;
408 + $node = array_shift($path);
409 + endwhile;
410 +
411 + $matches = array();
412 + foreach ($data as $datum) :
413 + if (is_string($datum)) :
414 + $matches[] = $datum;
415 + elseif (isset($datum['data'])) :
416 + $matches[] = $datum['data'];
417 + endif;
418 + endforeach;
419 + return $matches;
420 + } /* SyndicatedPost::query() */
421 +
422 + function xpath_default_namespace () {
423 + // Get the default namespace.
424 + $type = $this->link->simplepie->get_type();
425 + if ($type & SIMPLEPIE_TYPE_ATOM_10) :
426 + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
427 + elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
428 + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
429 + elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
430 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
431 + elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
432 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
433 + elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
434 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
435 + else :
436 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
437 + endif;
438 + return $defaultNS;
439 + } /* SyndicatedPost::xpath_default_namespace() */
440 +
441 + function xpath_extended_name ($node) {
442 + $ns = NULL; $element = NULL;
443 +
444 + if (substr($node, 0, 1)=='@') :
445 + $attr = '@'; $node = substr($node, 1);
446 + else :
447 + $attr = '';
448 + endif;
449 +
450 + if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
451 + $ns = array($ref[1]); $element = $ref[2];
452 + elseif (strpos($node, ':') !== FALSE) :
453 + list($xmlns, $element) = explode(':', $node, 2);
454 + if (isset($this->xmlns['reverse'][$xmlns])) :
455 + $ns = $this->xmlns['reverse'][$xmlns];
456 + else :
457 + $ns = array($xmlns);
458 + endif;
459 +
460 + // Fucking SimplePie. For attributes in default xmlns.
461 + if ($xmlns==$this->xmlns['forward'][$defaultNS[0]]) :
462 + $ns[] = '';
463 + endif;
464 + else :
465 + // Often in SimplePie, the default namespace gets stored
466 + // as an empty string rather than a URL.
467 + $ns = array($this->xpath_default_namespace(), '');
468 + $element = $node;
469 + endif;
470 + return array(array_unique($ns), $attr.$element);
471 + } /* SyndicatedPost::xpath_extended_name () */
472 +
473 + function content () {
474 + $content = NULL;
475 + if (isset($this->item['atom_content'])) :
476 + $content = $this->item['atom_content'];
477 + elseif (isset($this->item['xhtml']['body'])) :
478 + $content = $this->item['xhtml']['body'];
479 + elseif (isset($this->item['xhtml']['div'])) :
480 + $content = $this->item['xhtml']['div'];
481 + elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
482 + $content = $this->item['content']['encoded'];
483 + else:
484 + $content = $this->item['description'];
485 + endif;
486 + return $content;
487 + } /* SyndicatedPost::content() */
488 +
489 + function excerpt () {
490 + # Identify and sanitize excerpt: atom:summary, or rss:description
491 + $excerpt = $this->entry->get_description();
492 +
493 + # Many RSS feeds use rss:description, inadvisably, to
494 + # carry the entire post (typically with escaped HTML).
495 + # If that's what happened, we don't want the full
496 + # content for the excerpt.
497 + $content = $this->content();
498 + if ( is_null($excerpt) or $excerpt == $content ) :
499 + # If content is available, generate an excerpt.
500 + if ( strlen(trim($content)) > 0 ) :
501 + $excerpt = strip_tags($content);
502 + if (strlen($excerpt) > 255) :
503 + $excerpt = substr($excerpt,0,252).'...';
504 + endif;
505 + endif;
506 + endif;
507 + return $excerpt;
508 + } /* SyndicatedPost::excerpt() */
509 +
510 + function permalink () {
511 + // Handles explicit <link> elements and also RSS 2.0 cases with
512 + // <guid isPermaLink="true">, etc. Hooray!
513 + $permalink = $this->entry->get_link();
514 + return $permalink;
515 + }
516 +
517 + function created () {
518 + $date = '';
519 + if (isset($this->item['dc']['created'])) :
520 + $date = $this->item['dc']['created'];
521 + elseif (isset($this->item['dcterms']['created'])) :
522 + $date = $this->item['dcterms']['created'];
523 + elseif (isset($this->item['created'])): // Atom 0.3
524 + $date = $this->item['created'];
525 + endif;
526 +
527 + $epoch = new FeedTime($date);
528 + return $epoch->timestamp();
529 + } /* SyndicatedPost::created() */
530 +
531 + function published ($fallback = true, $default = NULL) {
532 + $date = '';
533 +
534 + # RSS is a fucking mess. Figure out whether we have a date in
535 + # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
536 + # epoch format for reformatting. If we can't find anything,
537 + # we'll use the last-updated time.
538 + if (isset($this->item['dc']['date'])): // Dublin Core
539 + $date = $this->item['dc']['date'];
540 + elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
541 + $date = $this->item['dcterms']['issued'];
542 + elseif (isset($this->item['published'])) : // Atom 1.0
543 + $date = $this->item['published'];
544 + elseif (isset($this->item['issued'])): // Atom 0.3
545 + $date = $this->item['issued'];
546 + elseif (isset($this->item['pubdate'])): // RSS 2.0
547 + $date = $this->item['pubdate'];
548 + endif;
549 +
550 + if (strlen($date) > 0) :
551 + $time = new FeedTime($date);
552 + $epoch = $time->timestamp();
553 + elseif ($fallback) : // Fall back to <updated> / <modified> if present
554 + $epoch = $this->updated(/*fallback=*/ false, /*default=*/ $default);
555 + endif;
556 +
557 + # If everything failed, then default to the current time.
558 + if (is_null($epoch)) :
559 + if (-1 == $default) :
560 + $epoch = time();
561 + else :
562 + $epoch = $default;
563 + endif;
564 + endif;
565 +
566 + return $epoch;
567 + } /* SyndicatedPost::published() */
568 +
569 + function updated ($fallback = true, $default = -1) {
570 + $date = '';
571 +
572 + # As far as I know, only dcterms and Atom have reliable ways to
573 + # specify when something was *modified* last. If neither is
574 + # available, then we'll try to get the time of publication.
575 + if (isset($this->item['dc']['modified'])) : // Not really correct
576 + $date = $this->item['dc']['modified'];
577 + elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
578 + $date = $this->item['dcterms']['modified'];
579 + elseif (isset($this->item['modified'])): // Atom 0.3
580 + $date = $this->item['modified'];
581 + elseif (isset($this->item['updated'])): // Atom 1.0
582 + $date = $this->item['updated'];
583 + endif;
584 +
585 + if (strlen($date) > 0) :
586 + $time = new FeedTime($date);
587 + $epoch = $time->timestamp();
588 + elseif ($fallback) : // Fall back to issued / dc:date
589 + $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
590 + endif;
591 +
592 + # If everything failed, then default to the current time.
593 + if (is_null($epoch)) :
594 + if (-1 == $default) :
595 + $epoch = time();
596 + else :
597 + $epoch = $default;
598 + endif;
599 + endif;
600 +
601 + return $epoch;
602 + } /* SyndicatedPost::updated() */
603 +
604 + function update_hash () {
605 + return md5(serialize($this->item));
606 + } /* SyndicatedPost::update_hash() */
607 +
608 + function guid () {
609 + $guid = null;
610 + if (isset($this->item['id'])): // Atom 0.3 / 1.0
611 + $guid = $this->item['id'];
612 + elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
613 + $guid = $this->item['atom']['id'];
614 + elseif (isset($this->item['guid'])) : // RSS 2.0
615 + $guid = $this->item['guid'];
616 + elseif (isset($this->item['dc']['identifier'])) :// yeah, right
617 + $guid = $this->item['dc']['identifier'];
618 + else :
619 + // The feed does not seem to have provided us with a
620 + // unique identifier, so we'll have to cobble together
621 + // a tag: URI that might work for us. The base of the
622 + // URI will be the host name of the feed source ...
623 + $bits = parse_url($this->feedmeta['link/uri']);
624 + $guid = 'tag:'.$bits['host'];
625 +
626 + // If we have a date of creation, then we can use that
627 + // to uniquely identify the item. (On the other hand, if
628 + // the feed producer was consicentious enough to
629 + // generate dates of creation, she probably also was
630 + // conscientious enough to generate unique identifiers.)
631 + if (!is_null($this->created())) :
632 + $guid .= '://post.'.date('YmdHis', $this->created());
633 +
634 + // Otherwise, use both the URI of the item, *and* the
635 + // item's title. We have to use both because titles are
636 + // often not unique, and sometimes links aren't unique
637 + // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
638 + // some podcasts). But it's rare to have *both* the same
639 + // title *and* the same link for two different items. So
640 + // this is about the best we can do.
641 + else :
642 + $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
643 + endif;
644 + endif;
645 + return $guid;
646 + } /* SyndicatedPost::guid() */
647 +
648 + function author () {
649 + $author = array ();
650 +
651 + if (isset($this->item['author_name'])):
652 + $author['name'] = $this->item['author_name'];
653 + elseif (isset($this->item['dc']['creator'])):
654 + $author['name'] = $this->item['dc']['creator'];
655 + elseif (isset($this->item['dc']['contributor'])):
656 + $author['name'] = $this->item['dc']['contributor'];
657 + elseif (isset($this->feed->channel['dc']['creator'])) :
658 + $author['name'] = $this->feed->channel['dc']['creator'];
659 + elseif (isset($this->feed->channel['dc']['contributor'])) :
660 + $author['name'] = $this->feed->channel['dc']['contributor'];
661 + elseif (isset($this->feed->channel['author_name'])) :
662 + $author['name'] = $this->feed->channel['author_name'];
663 + elseif ($this->feed->is_rss() and isset($this->item['author'])) :
664 + // The author element in RSS is allegedly an
665 + // e-mail address, but lots of people don't use
666 + // it that way. So let's make of it what we can.
667 + $author = parse_email_with_realname($this->item['author']);
668 +
669 + if (!isset($author['name'])) :
670 + if (isset($author['email'])) :
671 + $author['name'] = $author['email'];
672 + else :
673 + $author['name'] = $this->feed->channel['title'];
674 + endif;
675 + endif;
676 + else :
677 + $author['name'] = $this->feed->channel['title'];
678 + endif;
679 +
680 + if (isset($this->item['author_email'])):
681 + $author['email'] = $this->item['author_email'];
682 + elseif (isset($this->feed->channel['author_email'])) :
683 + $author['email'] = $this->feed->channel['author_email'];
684 + endif;
685 +
686 + if (isset($this->item['author_url'])):
687 + $author['uri'] = $this->item['author_url'];
688 + elseif (isset($this->feed->channel['author_url'])) :
689 + $author['uri'] = $this->item['author_url'];
690 + else:
691 + $author['uri'] = $this->feed->channel['link'];
692 + endif;
693 +
694 + return $author;
695 + } /* SyndicatedPost::author() */
696 +
697 + /**
698 + * SyndicatedPost::isTaggedAs: Test whether a feed item is
699 + * tagged / categorized with a given string. Case and leading and
700 + * trailing whitespace are ignored.
701 + *
702 + * @param string $tag Tag to check for
703 + *
704 + * @return bool Whether or not at least one of the categories / tags on
705 + * $this->item is set to $tag (modulo case and leading and trailing
706 + * whitespace)
707 + */
708 + function isTaggedAs ($tag) {
709 + $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
710 +
711 + // Check to see if this is tagged with $tag
712 + $currentCategory = 'category';
713 + $currentCategoryNumber = 1;
714 +
715 + // If we have the new MagpieRSS, the number of category elements
716 + // on this item is stored under index "category#".
717 + if (isset($this->item['category#'])) :
718 + $numberOfCategories = (int) $this->item['category#'];
719 +
720 + // We REALLY shouldn't have the old and busted MagpieRSS, but in
721 + // case we do, it doesn't support multiple categories, but there
722 + // might still be a single value under the "category" index.
723 + elseif (isset($this->item['category'])) :
724 + $numberOfCategories = 1;
725 +
726 + // No standard category or tag elements on this feed item.
727 + else :
728 + $numberOfCategories = 0;
729 +
730 + endif;
731 +
732 + $isSoTagged = false; // Innocent until proven guilty
733 +
734 + // Loop through category elements; if there are multiple
735 + // elements, they are indexed as category, category#2,
736 + // category#3, ... category#N
737 + while ($currentCategoryNumber <= $numberOfCategories) :
738 + if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
739 + $isSoTagged = true; // Got it!
740 + break;
741 + endif;
742 +
743 + $currentCategoryNumber += 1;
744 + $currentCategory = 'category#'.$currentCategoryNumber;
745 + endwhile;
746 +
747 + return $isSoTagged;
748 + } /* SyndicatedPost::isTaggedAs() */
749 +
750 + /**
751 + * SyndicatedPost::enclosures: returns an array with any enclosures
752 + * that may be attached to this syndicated item.
753 + *
754 + * @param string $type If you only want enclosures that match a certain
755 + * MIME type or group of MIME types, you can limit the enclosures
756 + * that will be returned to only those with a MIME type which
757 + * matches this regular expression.
758 + * @return array
759 + */
760 + function enclosures ($type = '/.*/') {
761 + $enclosures = array();
762 +
763 + if (isset($this->item['enclosure#'])) :
764 + // Loop through enclosure, enclosure#2, enclosure#3, ....
765 + for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
766 + $eid = (($i > 1) ? "#{$id}" : "");
767 +
768 + // Does it match the type we want?
769 + if (preg_match($type, $this->item["enclosure{$eid}@type"])) :
770 + $enclosures[] = array(
771 + "url" => $this->item["enclosure{$eid}@url"],
772 + "type" => $this->item["enclosure{$eid}@type"],
773 + "length" => $this->item["enclosure{$eid}@length"],
774 + );
775 + endif;
776 + endfor;
777 + endif;
778 + return $enclosures;
779 + } /* SyndicatedPost::enclosures() */
780 +
781 + function comment_link () {
782 + $url = null;
783 +
784 + // RSS 2.0 has a standard <comments> element:
785 + // "<comments> is an optional sub-element of <item>. If present,
786 + // it is the url of the comments page for the item."
787 + // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
788 + if (isset($this->item['comments'])) :
789 + $url = $this->item['comments'];
790 + endif;
791 +
792 + // The convention in Atom feeds is to use a standard <link>
793 + // element with @rel="replies" and @type="text/html".
794 + // Unfortunately, SimplePie_Item::get_links() allows us to filter
795 + // by the value of @rel, but not by the value of @type. *sigh*
796 +
797 + // Try Atom 1.0 first
798 + $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
799 +
800 + // Fall back and try Atom 0.3
801 + if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
802 +
803 + // Now loop through the elements, screening by @rel and @type
804 + if (is_array($linkElements)) : foreach ($linkElements as $link) :
805 + $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
806 + $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
807 + $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL);
808 +
809 + if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) :
810 + $url = $href;
811 + endif;
812 + endforeach; endif;
813 +
814 + return $url;
815 + }
816 +
817 + function comment_feed () {
818 + $feed = null;
819 +
820 + // Well Formed Web comment feeds extension for RSS 2.0
821 + // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments>
822 + //
823 + // N.B.: Correct capitalization is wfw:commentRss, but
824 + // wfw:commentRSS is common in the wild (partly due to a typo in
825 + // the original spec). In any case, our item array is normalized
826 + // to all lowercase anyways.
827 + if (isset($this->item['wfw']['commentrss'])) :
828 + $feed = $this->item['wfw']['commentrss'];
829 + endif;
830 +
831 + // In Atom 1.0, the convention is to use a standard link element
832 + // with @rel="replies". Sometimes this is also used to pass a
833 + // link to the human-readable comments page, so we also need to
834 + // check link/@type for a feed MIME type.
835 + //
836 + // Which is why I'm not using the SimplePie_Item::get_links()
837 + // method here, incidentally: it doesn't allow you to filter by
838 + // @type. *sigh*
839 + if (isset($this->item['link_replies'])) :
840 + // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
841 + $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
842 + for ($i = 1; $i <= $N; $i++) :
843 + $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
844 + if (isset($this->item[$currentElement.'@type'])
845 + and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
846 + $feed = $this->item[$currentElement];
847 + endif;
848 + endfor;
849 + endif;
850 + return $feed;
851 + } /* SyndicatedPost::comment_feed() */
852 +
853 + ##################################
854 + #### BUILT-IN CONTENT FILTERS ####
855 + ##################################
856 +
857 + var $uri_attrs = array (
858 + array('a', 'href'),
859 + array('applet', 'codebase'),
860 + array('area', 'href'),
861 + array('blockquote', 'cite'),
862 + array('body', 'background'),
863 + array('del', 'cite'),
864 + array('form', 'action'),
865 + array('frame', 'longdesc'),
866 + array('frame', 'src'),
867 + array('iframe', 'longdesc'),
868 + array('iframe', 'src'),
869 + array('head', 'profile'),
870 + array('img', 'longdesc'),
871 + array('img', 'src'),
872 + array('img', 'usemap'),
873 + array('input', 'src'),
874 + array('input', 'usemap'),
875 + array('ins', 'cite'),
876 + array('link', 'href'),
877 + array('object', 'classid'),
878 + array('object', 'codebase'),
879 + array('object', 'data'),
880 + array('object', 'usemap'),
881 + array('q', 'cite'),
882 + array('script', 'src')
883 + ); /* var SyndicatedPost::$uri_attrs */
884 +
885 + var $_base = null;
886 +
887 + function resolve_single_relative_uri ($refs) {
888 + $tag = FeedWordPressHTML::attributeMatch($refs);
889 + $url = Relative_URI::resolve($tag['value'], $this->_base);
890 + return $tag['prefix'] . $url . $tag['suffix'];
891 + } /* function SyndicatedPost::resolve_single_relative_uri() */
892 +
893 + function resolve_relative_uris ($content, $obj) {
894 + $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
895 + if ($set and $set != 'no') :
896 + // Fallback: if we don't have anything better, use the
897 + // item link from the feed
898 + $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
899 +
900 + // What we should do here, properly, is to use
901 + // SimplePie_Item::get_base() -- but that method is
902 + // currently broken. Or getting down and dirty in the
903 + // SimplePie representation of the content tags and
904 + // grabbing the xml_base member for the content element.
905 + // Maybe someday...
906 +
907 + foreach ($obj->uri_attrs as $pair) :
908 + list($tag, $attr) = $pair;
909 + $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
910 + $content = preg_replace_callback (
911 + $pattern,
912 + array(&$obj, 'resolve_single_relative_uri'),
913 + $content
914 + );
915 + endforeach;
916 + endif;
917 +
918 + return $content;
919 + } /* function SyndicatedPost::resolve_relative_uris () */
920 +
921 + var $strip_attrs = array (
922 + array('[a-z]+', 'target'),
923 +// array('[a-z]+', 'style'),
924 +// array('[a-z]+', 'on[a-z]+'),
925 + );
926 +
927 + function strip_attribute_from_tag ($refs) {
928 + $tag = FeedWordPressHTML::attributeMatch($refs);
929 + return $tag['before_attribute'].$tag['after_attribute'];
930 + }
931 +
932 + function sanitize_content ($content, $obj) {
933 + # This kind of sucks. I intend to replace it with
934 + # lib_filter sometime soon.
935 + foreach ($obj->strip_attrs as $pair):
936 + list($tag,$attr) = $pair;
937 + $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
938 +
939 + $content = preg_replace_callback (
940 + $pattern,
941 + array(&$obj, 'strip_attribute_from_tag'),
942 + $content
943 + );
944 + endforeach;
945 + return $content;
946 + } /* SyndicatedPost::sanitize() */
947 +
948 + #####################
949 + #### POST STATUS ####
950 + #####################
951 +
952 + /**
953 + * SyndicatedPost::filtered: check whether or not this post has been
954 + * screened out by a registered filter.
955 + *
956 + * @return bool TRUE iff post has been filtered out by a previous filter
957 + */
254 958 function filtered () {
255 959 return is_null($this->post);
256 - }
960 + } /* SyndicatedPost::filtered() */
257 961
962 + /**
963 + * SyndicatedPost::freshness: check whether post is a new post to be
964 + * inserted, a previously syndicated post that needs to be updated to
965 + * match the latest revision, or a previously syndicated post that is
966 + * still up-to-date.
967 + *
968 + * @return int A status code representing the freshness of the post
969 + * 0 = post already syndicated; no update needed
970 + * 1 = post already syndicated, but needs to be updated to latest
971 + * 2 = post has not yet been syndicated; needs to be created
972 + */
258 973 function freshness () {
259 974 global $wpdb;
260 975
261 976 if ($this->filtered()) : // This should never happen.
@@ -284,13 +999,30 @@
284 999 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
285 1000
286 1001 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
287 1002 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
288 - $updated = ((
1003 +
1004 + $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
1005 + $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
1006 + $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
1007 +
1008 + // Check timestamps...
1009 + $updated = (
289 1010 !is_null($updated_ts)
290 1011 and ($updated_ts > $last_rev_ts)
291 - ) or $update_hash_changed);
292 -
1012 + );
1013 +
1014 +
1015 + // Or the hash...
1016 + $updated = ($updated or $update_hash_changed);
1017 +
1018 + // But only if the post is not frozen.
1019 + $updated = (
1020 + $updated
1021 + and !$frozen_post
1022 + and !$frozen_feed
1023 + );
1024 +
293 1025 if ($updated) :
294 1026 $this->_freshness = 1; // Updated content
295 1027 $this->_wp_id = $result->id;
296 1028 else :
@@ -301,8 +1033,12 @@
301 1033 endif;
302 1034 return $this->_freshness;
303 1035 }
304 1036
1037 + #################################################
1038 + #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1039 + #################################################
1040 +
305 1041 function wp_id () {
306 1042 if ($this->filtered()) : // This should never happen.
307 1043 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
308 1044 endif;
@@ -364,30 +1100,49 @@
364 1100
365 1101 if (!$this->filtered() and $freshness > 0) :
366 1102 unset($this->post['named']);
367 1103 $this->post = apply_filters('syndicated_post', $this->post, $this);
1104 +
1105 + // Allow for feed-specific syndicated_post filters.
1106 + $this->post = apply_filters(
1107 + "syndicated_post_".$this->link->uri(),
1108 + $this->post,
1109 + $this
1110 + );
368 1111 endif;
369 1112
1113 + // Hook in early to make sure these get inserted if at all possible
1114 + add_action(
1115 + /*hook=*/ 'transition_post_status',
1116 + /*callback=*/ array($this, 'add_rss_meta'),
1117 + /*priority=*/ -10000, /* very early */
1118 + /*arguments=*/ 3
1119 + );
1120 +
370 1121 if (!$this->filtered() and $freshness == 2) :
371 1122 // The item has not yet been added. So let's add it.
372 1123 $this->insert_new();
373 - $this->add_rss_meta();
374 - do_action('post_syndicated_item', $this->wp_id());
1124 + do_action('post_syndicated_item', $this->wp_id(), $this);
375 1125
376 1126 $ret = 'new';
377 1127 elseif (!$this->filtered() and $freshness == 1) :
378 1128 $this->post['ID'] = $this->wp_id();
379 1129 $this->update_existing();
380 - $this->add_rss_meta();
381 - do_action('update_syndicated_item', $this->wp_id());
1130 + do_action('update_syndicated_item', $this->wp_id(), $this);
382 1131
383 1132 $ret = 'updated';
384 1133 else :
385 1134 $ret = false;
386 1135 endif;
387 -
1136 +
1137 + // Remove add_rss_meta hook
1138 + remove_action(
1139 + /*hook=*/ 'transition_post_status',
1140 + /*callback=*/ array($this, 'add_rss_meta')
1141 + );
1142 +
388 1143 return $ret;
389 - } // function SyndicatedPost::store ()
1144 + } /* function SyndicatedPost::store () */
390 1145
391 1146 function insert_new () {
392 1147 global $wpdb, $wp_db_version;
393 1148
@@ -668,9 +1423,9 @@
668 1423 // for by standard WP meta-data (i.e., any interesting data about the
669 1424 // syndicated post other than author, title, timestamp, categories, and
670 1425 // guid). It's also used to hook into WordPress's support for
671 1426 // enclosures.
672 - function add_rss_meta () {
1427 + function add_rss_meta ($new_status, $old_status, $post) {
673 1428 global $wpdb;
674 1429 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
675 1430 $postId = $this->wp_id();
676 1431
@@ -684,34 +1439,21 @@
684 1439 ");
685 1440
686 1441 foreach ( $this->post['meta'] as $key => $values ) :
687 1442
688 - $key = $wpdb->escape($key);
1443 + $eKey = $wpdb->escape($key);
689 1444
690 1445 // If this is an update, clear out the old
691 1446 // values to avoid duplication.
692 1447 $result = $wpdb->query("
693 1448 DELETE FROM $wpdb->postmeta
694 - WHERE post_id='$postId' AND meta_key='$key'
1449 + WHERE post_id='$postId' AND meta_key='$eKey'
695 1450 ");
696 1451
697 1452 // Allow for either a single value or an array
698 1453 if (!is_array($values)) $values = array($values);
699 1454 foreach ( $values as $value ) :
700 - $value = $wpdb->escape($value);
701 - $result = $wpdb->query("
702 - INSERT INTO $wpdb->postmeta
703 - SET
704 - post_id='$postId',
705 - meta_key='$key',
706 - meta_value='$value'
707 - ");
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;
1455 + add_post_meta($postId, $key, $value, /*unique=*/ false);
714 1456 endforeach;
715 1457 endforeach;
716 1458 endif;
717 1459 } /* SyndicatedPost::add_rss_meta () */
@@ -722,10 +1464,10 @@
722 1464 global $wpdb;
723 1465
724 1466 $a = $this->author();
725 1467 $author = $a['name'];
726 - $email = $a['email'];
727 - $url = $a['uri'];
1468 + $email = (isset($a['email']) ? $a['email'] : NULL);
1469 + $url = (isset($a['uri']) ? $a['uri'] : NULL);
728 1470
729 1471 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
730 1472 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
731 1473 $test_email = $email;
@@ -763,65 +1505,36 @@
763 1505
764 1506 else :
765 1507 // Check the database for an existing author record that might fit
766 1508
767 - #-- WordPress 2.0+
768 - if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
769 -
770 - // First try the user core data table.
1509 + // First try the user core data table.
1510 + $id = $wpdb->get_var(
1511 + "SELECT ID FROM $wpdb->users
1512 + WHERE
1513 + TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1514 + OR (
1515 + LENGTH(TRIM(LCASE(user_email))) > 0
1516 + AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1517 + )
1518 + OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
1519 + ");
1520 +
1521 + // If that fails, look for aliases in the user meta data table
1522 + if (is_null($id)) :
771 1523 $id = $wpdb->get_var(
772 - "SELECT ID FROM $wpdb->users
1524 + "SELECT user_id FROM $wpdb->usermeta
773 1525 WHERE
774 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1526 + (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
775 1527 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)
1528 + meta_key = 'description'
1529 + AND TRIM(LCASE(meta_value))
816 1530 RLIKE CONCAT(
817 1531 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
818 - LCASE('$reg_author'),
1532 + TRIM(LCASE('$reg_author')),
819 1533 '( |\\t|\\r)*(\\n|\$)'
820 1534 )
821 1535 )
822 1536 ");
823 -
824 1537 endif;
825 1538
826 1539 // ... if you don't find one, then do what you need to do
827 1540 if (is_null($id)) :
@@ -827,8 +1540,16 @@
827 1540 if (is_null($id)) :
828 1541 if ($unfamiliar_author === 'create') :
829 1542 $userdata = array();
830 1543
1544 + // WordPress 3 is going to pitch a fit if we attempt to register
1545 + // more than one user account with an empty e-mail address, so we
1546 + // need *something* here. Ugh.
1547 + if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
1548 + $url = parse_url($this->feed->channel['link']);
1549 + $email = $nice_author.'@'.$url['host'];
1550 + endif;
1551 +
831 1552 #-- user table data
832 1553 $userdata['ID'] = NULL; // new user
833 1554 $userdata['user_login'] = $login;
834 1555 $userdata['user_nicename'] = $nice_author;
@@ -835,9 +1556,9 @@
835 1556 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
836 1557 $userdata['user_email'] = $email;
837 1558 $userdata['user_url'] = $url;
838 1559 $userdata['display_name'] = $author;
839 -
1560 +
840 1561 $id = wp_insert_user($userdata);
841 1562 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
842 1563 $id = (int) $unfamiliar_author;
843 1564 elseif ($unfamiliar_author === 'default') :
@@ -968,311 +1689,6 @@
968 1689 endswitch;
969 1690 return $ret;
970 1691 } // function SyndicatedPost::use_api ()
971 1692
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
1693 +} /* class SyndicatedPost */
1278 1694