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 +907 -407 2009.07072010.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');
@@ -106,9 +143,9 @@
106 143 $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
107 144
108 145 // User-supplied custom settings to apply to each post. Do first so that FWP-generated custom settings will overwrite if necessary; thus preventing any munging
109 146 $default_custom_settings = get_option('feedwordpress_custom_settings');
110 - if ($default_custom_settings) :
147 + if ($default_custom_settings and !is_array($default_custom_settings)) :
111 148 $default_custom_settings = unserialize($default_custom_settings);
112 149 endif;
113 150 if (!is_array($default_custom_settings)) :
114 151 $default_custom_settings = array();
@@ -114,26 +151,55 @@
114 151 $default_custom_settings = array();
115 152 endif;
116 153
117 154 $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null);
118 - if ($custom_settings) :
155 + if ($custom_settings and !is_array($custom_settings)) :
119 156 $custom_settings = unserialize($custom_settings);
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,25 +222,33 @@
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']);
226 +
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;
230 +
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.
236 +
237 + // Store information to identify the feed that this came from
238 + if (isset($this->feedmeta['link/uri'])) :
239 + $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
162 240 endif;
163 - if (isset($this->item['wfw']['commentrss'])) :
164 - $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
241 + if (isset($this->feedmeta['link/id'])) :
242 + $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
165 243 endif;
166 244
167 - // Store information to identify the feed that this came from
168 - $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
169 - $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
170 -
171 245 if (isset($this->item['source_link_self'])) :
172 246 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
173 247 endif;
174 248
175 249 // In case you want to know the external permalink...
176 - $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->item['link']);
250 + $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink());
177 251
178 252 // Store a hash of the post content for checking whether something needs to be updated
179 253 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
180 254
@@ -221,16 +295,682 @@
221 295
222 296 if (isset($this->feedmeta['tags']) and is_array($this->feedmeta['tags'])) :
223 297 $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
224 298 endif;
299 + $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
300 + endif;
301 + } /* SyndicatedPost::SyndicatedPost() */
225 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);
226 378 endif;
227 - } // SyndicatedPost::SyndicatedPost()
228 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 + */
229 958 function filtered () {
230 959 return is_null($this->post);
231 - }
960 + } /* SyndicatedPost::filtered() */
232 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 + */
233 973 function freshness () {
234 974 global $wpdb;
235 975
236 976 if ($this->filtered()) : // This should never happen.
@@ -259,13 +999,30 @@
259 999 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
260 1000
261 1001 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
262 1002 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
263 - $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 = (
264 1010 !is_null($updated_ts)
265 1011 and ($updated_ts > $last_rev_ts)
266 - ) or $update_hash_changed);
267 -
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 +
268 1025 if ($updated) :
269 1026 $this->_freshness = 1; // Updated content
270 1027 $this->_wp_id = $result->id;
271 1028 else :
@@ -276,8 +1033,12 @@
276 1033 endif;
277 1034 return $this->_freshness;
278 1035 }
279 1036
1037 + #################################################
1038 + #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1039 + #################################################
1040 +
280 1041 function wp_id () {
281 1042 if ($this->filtered()) : // This should never happen.
282 1043 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
283 1044 endif;
@@ -339,30 +1100,49 @@
339 1100
340 1101 if (!$this->filtered() and $freshness > 0) :
341 1102 unset($this->post['named']);
342 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 + );
343 1111 endif;
344 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 +
345 1121 if (!$this->filtered() and $freshness == 2) :
346 1122 // The item has not yet been added. So let's add it.
347 1123 $this->insert_new();
348 - $this->add_rss_meta();
349 - do_action('post_syndicated_item', $this->wp_id());
1124 + do_action('post_syndicated_item', $this->wp_id(), $this);
350 1125
351 1126 $ret = 'new';
352 1127 elseif (!$this->filtered() and $freshness == 1) :
353 1128 $this->post['ID'] = $this->wp_id();
354 1129 $this->update_existing();
355 - $this->add_rss_meta();
356 - do_action('update_syndicated_item', $this->wp_id());
1130 + do_action('update_syndicated_item', $this->wp_id(), $this);
357 1131
358 1132 $ret = 'updated';
359 1133 else :
360 1134 $ret = false;
361 1135 endif;
362 -
1136 +
1137 + // Remove add_rss_meta hook
1138 + remove_action(
1139 + /*hook=*/ 'transition_post_status',
1140 + /*callback=*/ array($this, 'add_rss_meta')
1141 + );
1142 +
363 1143 return $ret;
364 - } // function SyndicatedPost::store ()
1144 + } /* function SyndicatedPost::store () */
365 1145
366 1146 function insert_new () {
367 1147 global $wpdb, $wp_db_version;
368 1148
@@ -643,9 +1423,9 @@
643 1423 // for by standard WP meta-data (i.e., any interesting data about the
644 1424 // syndicated post other than author, title, timestamp, categories, and
645 1425 // guid). It's also used to hook into WordPress's support for
646 1426 // enclosures.
647 - function add_rss_meta () {
1427 + function add_rss_meta ($new_status, $old_status, $post) {
648 1428 global $wpdb;
649 1429 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
650 1430 $postId = $this->wp_id();
651 1431
@@ -659,28 +1439,21 @@
659 1439 ");
660 1440
661 1441 foreach ( $this->post['meta'] as $key => $values ) :
662 1442
663 - $key = $wpdb->escape($key);
1443 + $eKey = $wpdb->escape($key);
664 1444
665 1445 // If this is an update, clear out the old
666 1446 // values to avoid duplication.
667 1447 $result = $wpdb->query("
668 1448 DELETE FROM $wpdb->postmeta
669 - WHERE post_id='$postId' AND meta_key='$key'
1449 + WHERE post_id='$postId' AND meta_key='$eKey'
670 1450 ");
671 1451
672 1452 // Allow for either a single value or an array
673 1453 if (!is_array($values)) $values = array($values);
674 1454 foreach ( $values as $value ) :
675 - $value = $wpdb->escape($value);
676 - $result = $wpdb->query("
677 - INSERT INTO $wpdb->postmeta
678 - SET
679 - post_id='$postId',
680 - meta_key='$key',
681 - meta_value='$value'
682 - ");
1455 + add_post_meta($postId, $key, $value, /*unique=*/ false);
683 1456 endforeach;
684 1457 endforeach;
685 1458 endif;
686 1459 } /* SyndicatedPost::add_rss_meta () */
@@ -691,10 +1464,10 @@
691 1464 global $wpdb;
692 1465
693 1466 $a = $this->author();
694 1467 $author = $a['name'];
695 - $email = $a['email'];
696 - $url = $a['uri'];
1468 + $email = (isset($a['email']) ? $a['email'] : NULL);
1469 + $url = (isset($a['uri']) ? $a['uri'] : NULL);
697 1470
698 1471 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
699 1472 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
700 1473 $test_email = $email;
@@ -732,65 +1505,36 @@
732 1505
733 1506 else :
734 1507 // Check the database for an existing author record that might fit
735 1508
736 - #-- WordPress 2.0+
737 - if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
738 -
739 - // 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)) :
740 1523 $id = $wpdb->get_var(
741 - "SELECT ID FROM $wpdb->users
1524 + "SELECT user_id FROM $wpdb->usermeta
742 1525 WHERE
743 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1526 + (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
744 1527 OR (
745 - LENGTH(TRIM(LCASE(user_email))) > 0
746 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
747 - )
748 - OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
749 - ");
750 -
751 - // If that fails, look for aliases in the user meta data table
752 - if (is_null($id)) :
753 - $id = $wpdb->get_var(
754 - "SELECT user_id FROM $wpdb->usermeta
755 - WHERE
756 - (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
757 - OR (
758 - meta_key = 'description'
759 - AND TRIM(LCASE(meta_value))
760 - RLIKE CONCAT(
761 - '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
762 - TRIM(LCASE('$reg_author')),
763 - '( |\\t|\\r)*(\\n|\$)'
764 - )
765 - )
766 - ");
767 - endif;
768 -
769 - #-- WordPress 1.5.x
770 - else :
771 - $id = $wpdb->get_var(
772 - "SELECT ID from $wpdb->users
773 - WHERE
774 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
775 - (
776 - LENGTH(TRIM(LCASE(user_email))) > 0
777 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
778 - ) OR
779 - TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
780 - TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
781 - TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
782 - TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
783 - (
784 - LOWER(user_description)
1528 + meta_key = 'description'
1529 + AND TRIM(LCASE(meta_value))
785 1530 RLIKE CONCAT(
786 1531 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
787 - LCASE('$reg_author'),
1532 + TRIM(LCASE('$reg_author')),
788 1533 '( |\\t|\\r)*(\\n|\$)'
789 1534 )
790 1535 )
791 1536 ");
792 -
793 1537 endif;
794 1538
795 1539 // ... if you don't find one, then do what you need to do
796 1540 if (is_null($id)) :
@@ -796,8 +1540,16 @@
796 1540 if (is_null($id)) :
797 1541 if ($unfamiliar_author === 'create') :
798 1542 $userdata = array();
799 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 +
800 1552 #-- user table data
801 1553 $userdata['ID'] = NULL; // new user
802 1554 $userdata['user_login'] = $login;
803 1555 $userdata['user_nicename'] = $nice_author;
@@ -804,9 +1556,9 @@
804 1556 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
805 1557 $userdata['user_email'] = $email;
806 1558 $userdata['user_url'] = $url;
807 1559 $userdata['display_name'] = $author;
808 -
1560 +
809 1561 $id = wp_insert_user($userdata);
810 1562 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
811 1563 $id = (int) $unfamiliar_author;
812 1564 elseif ($unfamiliar_author === 'default') :
@@ -890,9 +1642,9 @@
890 1642 $cat_ids[] = (int) $term->cat_ID;
891 1643 endforeach;
892 1644 elseif ('create'===$unfamiliar_category) :
893 1645 if (function_exists('wp_insert_category')) :
894 - $cat_id = wp_insert_category(array('cat_name' => $cat_name));
1646 + $cat_id = wp_insert_category(array('cat_name' => $esc));
895 1647 // And into the database we go.
896 1648 else :
897 1649 $nice_kitty = sanitize_title($cat_name);
898 1650 $wpdb->query(sprintf("
@@ -899,9 +1651,9 @@
899 1651 INSERT INTO $wpdb->categories
900 1652 SET
901 1653 cat_name='%s',
902 1654 category_nicename='%s'
903 - ", $wpdb->escape($cat_name), $nice_kitty
1655 + ", $esc, $nice_kitty
904 1656 ));
905 1657 $cat_id = $wpdb->insert_id;
906 1658 endif;
907 1659 $cat_ids[] = $cat_id;
@@ -937,258 +1689,6 @@
937 1689 endswitch;
938 1690 return $ret;
939 1691 } // function SyndicatedPost::use_api ()
940 1692
941 - #### EXTRACT DATA FROM FEED ITEM ####
942 -
943 - function created () {
944 - $epoch = null;
945 - if (isset($this->item['dc']['created'])) :
946 - $epoch = @parse_w3cdtf($this->item['dc']['created']);
947 - elseif (isset($this->item['dcterms']['created'])) :
948 - $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
949 - elseif (isset($this->item['created'])): // Atom 0.3
950 - $epoch = @parse_w3cdtf($this->item['created']);
951 - endif;
952 - return $epoch;
953 - }
954 - function published ($fallback = true) {
955 - $epoch = null;
956 -
957 - # RSS is a fucking mess. Figure out whether we have a date in
958 - # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
959 - # epoch format for reformatting. If we can't find anything,
960 - # we'll use the last-updated time.
961 - if (isset($this->item['dc']['date'])): // Dublin Core
962 - $epoch = @parse_w3cdtf($this->item['dc']['date']);
963 - elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
964 - $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
965 - elseif (isset($this->item['published'])) : // Atom 1.0
966 - $epoch = @parse_w3cdtf($this->item['published']);
967 - elseif (isset($this->item['issued'])): // Atom 0.3
968 - $epoch = @parse_w3cdtf($this->item['issued']);
969 - elseif (isset($this->item['pubdate'])): // RSS 2.0
970 - $epoch = strtotime($this->item['pubdate']);
971 - elseif ($fallback) : // Fall back to <updated> / <modified> if present
972 - $epoch = $this->updated(/*fallback=*/ false);
973 - endif;
974 -
975 - # If everything failed, then default to the current time.
976 - if (is_null($epoch)) :
977 - if (-1 == $default) :
978 - $epoch = time();
979 - else :
980 - $epoch = $default;
981 - endif;
982 - endif;
983 -
984 - return $epoch;
985 - }
986 - function updated ($fallback = true, $default = -1) {
987 - $epoch = null;
988 -
989 - # As far as I know, only dcterms and Atom have reliable ways to
990 - # specify when something was *modified* last. If neither is
991 - # available, then we'll try to get the time of publication.
992 - if (isset($this->item['dc']['modified'])) : // Not really correct
993 - $epoch = @parse_w3cdtf($this->item['dc']['modified']);
994 - elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
995 - $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
996 - elseif (isset($this->item['modified'])): // Atom 0.3
997 - $epoch = @parse_w3cdtf($this->item['modified']);
998 - elseif (isset($this->item['updated'])): // Atom 1.0
999 - $epoch = @parse_w3cdtf($this->item['updated']);
1000 - elseif ($fallback) : // Fall back to issued / dc:date
1001 - $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
1002 - endif;
1003 -
1004 - # If everything failed, then default to the current time.
1005 - if (is_null($epoch)) :
1006 - if (-1 == $default) :
1007 - $epoch = time();
1008 - else :
1009 - $epoch = $default;
1010 - endif;
1011 - endif;
1012 -
1013 - return $epoch;
1014 - }
1015 -
1016 - function update_hash () {
1017 - return md5(serialize($this->item));
1018 - }
1019 -
1020 - function guid () {
1021 - $guid = null;
1022 - if (isset($this->item['id'])): // Atom 0.3 / 1.0
1023 - $guid = $this->item['id'];
1024 - elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
1025 - $guid = $this->item['atom']['id'];
1026 - elseif (isset($this->item['guid'])) : // RSS 2.0
1027 - $guid = $this->item['guid'];
1028 - elseif (isset($this->item['dc']['identifier'])) :// yeah, right
1029 - $guid = $this->item['dc']['identifier'];
1030 - else :
1031 - // The feed does not seem to have provided us with a
1032 - // unique identifier, so we'll have to cobble together
1033 - // a tag: URI that might work for us. The base of the
1034 - // URI will be the host name of the feed source ...
1035 - $bits = parse_url($this->feedmeta['link/uri']);
1036 - $guid = 'tag:'.$bits['host'];
1037 -
1038 - // If we have a date of creation, then we can use that
1039 - // to uniquely identify the item. (On the other hand, if
1040 - // the feed producer was consicentious enough to
1041 - // generate dates of creation, she probably also was
1042 - // conscientious enough to generate unique identifiers.)
1043 - if (!is_null($this->created())) :
1044 - $guid .= '://post.'.date('YmdHis', $this->created());
1045 -
1046 - // Otherwise, use both the URI of the item, *and* the
1047 - // item's title. We have to use both because titles are
1048 - // often not unique, and sometimes links aren't unique
1049 - // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
1050 - // some podcasts). But it's rare to have *both* the same
1051 - // title *and* the same link for two different items. So
1052 - // this is about the best we can do.
1053 - else :
1054 - $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
1055 - endif;
1056 - endif;
1057 - return $guid;
1058 - }
1059 -
1060 - function author () {
1061 - $author = array ();
1062 -
1063 - if (isset($this->item['author_name'])):
1064 - $author['name'] = $this->item['author_name'];
1065 - elseif (isset($this->item['dc']['creator'])):
1066 - $author['name'] = $this->item['dc']['creator'];
1067 - elseif (isset($this->item['dc']['contributor'])):
1068 - $author['name'] = $this->item['dc']['contributor'];
1069 - elseif (isset($this->feed->channel['dc']['creator'])) :
1070 - $author['name'] = $this->feed->channel['dc']['creator'];
1071 - elseif (isset($this->feed->channel['dc']['contributor'])) :
1072 - $author['name'] = $this->feed->channel['dc']['contributor'];
1073 - elseif (isset($this->feed->channel['author_name'])) :
1074 - $author['name'] = $this->feed->channel['author_name'];
1075 - elseif ($this->feed->is_rss() and isset($this->item['author'])) :
1076 - // The author element in RSS is allegedly an
1077 - // e-mail address, but lots of people don't use
1078 - // it that way. So let's make of it what we can.
1079 - $author = parse_email_with_realname($this->item['author']);
1080 -
1081 - if (!isset($author['name'])) :
1082 - if (isset($author['email'])) :
1083 - $author['name'] = $author['email'];
1084 - else :
1085 - $author['name'] = $this->feed->channel['title'];
1086 - endif;
1087 - endif;
1088 - else :
1089 - $author['name'] = $this->feed->channel['title'];
1090 - endif;
1091 -
1092 - if (isset($this->item['author_email'])):
1093 - $author['email'] = $this->item['author_email'];
1094 - elseif (isset($this->feed->channel['author_email'])) :
1095 - $author['email'] = $this->feed->channel['author_email'];
1096 - endif;
1097 -
1098 - if (isset($this->item['author_url'])):
1099 - $author['uri'] = $this->item['author_url'];
1100 - elseif (isset($this->feed->channel['author_url'])) :
1101 - $author['uri'] = $this->item['author_url'];
1102 - else:
1103 - $author['uri'] = $this->feed->channel['link'];
1104 - endif;
1105 -
1106 - return $author;
1107 - } // SyndicatedPost::author()
1108 -
1109 - var $uri_attrs = array (
1110 - array('a', 'href'),
1111 - array('applet', 'codebase'),
1112 - array('area', 'href'),
1113 - array('blockquote', 'cite'),
1114 - array('body', 'background'),
1115 - array('del', 'cite'),
1116 - array('form', 'action'),
1117 - array('frame', 'longdesc'),
1118 - array('frame', 'src'),
1119 - array('iframe', 'longdesc'),
1120 - array('iframe', 'src'),
1121 - array('head', 'profile'),
1122 - array('img', 'longdesc'),
1123 - array('img', 'src'),
1124 - array('img', 'usemap'),
1125 - array('input', 'src'),
1126 - array('input', 'usemap'),
1127 - array('ins', 'cite'),
1128 - array('link', 'href'),
1129 - array('object', 'classid'),
1130 - array('object', 'codebase'),
1131 - array('object', 'data'),
1132 - array('object', 'usemap'),
1133 - array('q', 'cite'),
1134 - array('script', 'src')
1135 - ); /* var SyndicatedPost::$uri_attrs */
1136 -
1137 - var $_base = null;
1138 -
1139 - function resolve_single_relative_uri ($refs) {
1140 - $tag = FeedWordPressHTML::attributeMatch($refs);
1141 - $url = Relative_URI::resolve($tag['value'], $this->_base);
1142 - return $tag['prefix'] . $url . $tag['suffix'];
1143 - } /* function SyndicatedPost::resolve_single_relative_uri() */
1144 -
1145 - function resolve_relative_uris ($content, $obj) {
1146 - # The MagpieRSS upgrade has some `xml:base` support baked in.
1147 - # However, sometimes people do silly things, like putting
1148 - # relative URIs out on a production RSS 2.0 feed or other feeds
1149 - # with no good support for `xml:base`. So we'll do our best to
1150 - # try to catch any remaining relative URIs and resolve them as
1151 - # best we can.
1152 - $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
1153 -
1154 - foreach ($obj->uri_attrs as $pair) :
1155 - list($tag, $attr) = $pair;
1156 - $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1157 - $content = preg_replace_callback (
1158 - $pattern,
1159 - array(&$obj, 'resolve_single_relative_uri'),
1160 - $content
1161 - );
1162 - endforeach;
1163 -
1164 - return $content;
1165 - } /* function SyndicatedPost::resolve_relative_uris () */
1166 -
1167 - var $strip_attrs = array (
1168 - array('[a-z]+', 'target'),
1169 -// array('[a-z]+', 'style'),
1170 -// array('[a-z]+', 'on[a-z]+'),
1171 - );
1172 -
1173 - function strip_attribute_from_tag ($refs) {
1174 - $tag = FeedWordPressHTML::attributeMatch($refs);
1175 - return $tag['before_attribute'].$tag['after_attribute'];
1176 - }
1177 -
1178 - function sanitize_content ($content, $obj) {
1179 - # This kind of sucks. I intend to replace it with
1180 - # lib_filter sometime soon.
1181 - foreach ($obj->strip_attrs as $pair):
1182 - list($tag,$attr) = $pair;
1183 - $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1184 -
1185 - $content = preg_replace_callback (
1186 - $pattern,
1187 - array(&$obj, 'strip_attribute_from_tag'),
1188 - $content
1189 - );
1190 - endforeach;
1191 - return $content;
1192 - }
1193 -} // class SyndicatedPost
1693 +} /* class SyndicatedPost */
1194 1694