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