PluginProbe
FeedWordPress / 2011.1018
FeedWordPress v2011.1018
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
feedwordpress / syndicatedpost.class.php

syndicatedpost.class.php in FeedWordPress 2011.1018, at syndicatedpost.class.php

2,151 lines 71.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 require_once(dirname(__FILE__).'/feedtime.class.php');
3
4 /**
5 * class SyndicatedPost: FeedWordPress uses to manage the conversion of
6 * incoming items from the feed parser into posts for the WordPress
7 * database. It contains several internal management methods primarily
8 * of interest to someone working on the FeedWordPress source, as well
9 * as some utility methods for extracting useful data from many
10 * different feed formats, which may be useful to FeedWordPress users
11 * who make use of feed data in PHP add-ons and filters.
12 *
13 * @version 2011.0831
14 */
15 class SyndicatedPost {
16 var $item = null; // MagpieRSS representation
17 var $entry = null; // SimplePie_Item representation
18
19 var $link = null;
20 var $feed = null;
21 var $feedmeta = null;
22
23 var $xmlns = array ();
24
25 var $post = array ();
26
27 var $named = array ();
28 var $preset_terms = array ();
29 var $feed_terms = array ();
30
31 var $_freshness = null;
32 var $_wp_id = null;
33
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) {
44 global $wpdb;
45
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;
64
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.
88 #
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
96 global $fwp_channel, $fwp_feedmeta;
97 $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta;
98
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
111 # Filters can halt further processing by returning NULL
112 if (is_null($this->item)) :
113 $this->post = NULL;
114 else :
115 # Note that nothing is run through $wpdb->escape() here.
116 # That's deliberate. The escaping is done at the point
117 # of insertion, not here, to avoid double-escaping and
118 # to avoid screwing with syndicated_post filters
119
120 $this->post['post_title'] = apply_filters(
121 'syndicated_item_title',
122 $this->entry->get_title(), $this
123 );
124
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().
132
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)):
140 $this->post['post_excerpt'] = $excerpt;
141 endif;
142
143 // Dealing with timestamps in WordPress is so fucking fucked.
144 $offset = (int) get_option('gmt_offset') * 60 * 60;
145 $post_date_gmt = $this->published(array('default' => -1));
146 $post_modified_gmt = $this->updated(array('default' => -1));
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
153 // Use feed-level preferences or the global default.
154 $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
155 $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
156 $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
157
158 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
159 $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
160
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
162 $default_custom_settings = get_option('feedwordpress_custom_settings');
163 if ($default_custom_settings and !is_array($default_custom_settings)) :
164 $default_custom_settings = unserialize($default_custom_settings);
165 endif;
166 if (!is_array($default_custom_settings)) :
167 $default_custom_settings = array();
168 endif;
169
170 $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null);
171 if ($custom_settings and !is_array($custom_settings)) :
172 $custom_settings = unserialize($custom_settings);
173 endif;
174 if (!is_array($custom_settings)) :
175 $custom_settings = array();
176 endif;
177
178 $postMetaIn = array_merge($default_custom_settings, $custom_settings);
179 $postMetaOut = array();
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
210 // RSS 2.0 / Atom 1.0 enclosure support
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;
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;
262
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'];
278 endif;
279 if (isset($this->feedmeta['link/id'])) :
280 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
281 endif;
282
283 if (isset($this->item['source_link_self'])) :
284 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
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;
307
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));
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());
353
354 $specials = array('category' => 'cats', 'post_tag' => 'tags');
355 foreach ($taxonomies as $tax) :
356 if (!isset($specials[$tax])) :
357 $terms = array();
358
359 // See if we should get the globals
360 if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) :
361 if (isset($globalTerms[$tax])) :
362 $terms = $globalTerms[$tax];
363 endif;
364 endif;
365
366 // Now merge in the locals
367 if (isset($feedTerms[$tax])) :
368 $terms = array_merge($terms, $feedTerms[$tax]);
369 endif;
370
371 // That's all, folks.
372 $this->preset_terms[$tax] = $terms;
373 endif;
374 endforeach;
375
376 $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this);
377 endif;
378 } /* SyndicatedPost::SyndicatedPost() */
379
380 #####################################
381 #### EXTRACT DATA FROM FEED ITEM ####
382 #####################################
383
384 /**
385 * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
386 * elements within the syndicated item.
387 *
388 * @param string $path
389 * @returns array of string values representing contents of matching
390 * elements or attributes
391 */
392 function query ($path) {
393 $urlHash = array();
394
395 // Allow {url} notation for namespaces. URLs will contain : and /, so...
396 preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
397 foreach ($match as $ref) :
398 $urlHash[md5($ref[1])] = $ref[1];
399 endforeach;
400
401 foreach ($urlHash as $hash => $url) :
402 $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
403 endforeach;
404
405 $path = explode('/', $path);
406 foreach ($path as $index => $node) :
407 if (preg_match('/{#([^}]+)}/', $node, $ref)) :
408 if (isset($urlHash[$ref[1]])) :
409 $path[$index] = str_replace(
410 '{#'.$ref[1].'}',
411 '{'.$urlHash[$ref[1]].'}',
412 $node
413 );
414 endif;
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);
566
567 if (isset($this->xmlns['reverse'][$xmlns])) :
568 $ns = $this->xmlns['reverse'][$xmlns];
569 else :
570 $ns = array($xmlns);
571 endif;
572
573 // Fucking SimplePie. For attributes in default xmlns.
574 $defaultNS = $this->xpath_default_namespace();
575 if (isset($this->xmlns['forward'][$defaultNS])
576 and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
577 $ns[] = '';
578 endif;
579
580 if (isset($datum['xmlns'])) :
581 if (isset($datum['xmlns'][$xmlns])) :
582 $ns[] = $datum['xmlns'][$xmlns];
583 endif;
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() */
592
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() */
608
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;
630 endif;
631 endif;
632 return $excerpt;
633 } /* SyndicatedPost::excerpt() */
634
635 function permalink () {
636 // Handles explicit <link> elements and also RSS 2.0 cases with
637 // <guid isPermaLink="true">, etc. Hooray!
638 $permalink = $this->entry->get_link();
639 return $permalink;
640 }
641
642 function created ($params = array()) {
643 $unfiltered = false; $default = NULL;
644 extract($params);
645
646 $date = '';
647 if (isset($this->item['dc']['created'])) :
648 $date = $this->item['dc']['created'];
649 elseif (isset($this->item['dcterms']['created'])) :
650 $date = $this->item['dcterms']['created'];
651 elseif (isset($this->item['created'])): // Atom 0.3
652 $date = $this->item['created'];
653 endif;
654
655 $time = new FeedTime($date);
656 $ts = $time->timestamp();
657 if (!$unfiltered) :
658 apply_filters('syndicated_item_created', $ts, $this);
659 endif;
660 return $ts;
661 } /* SyndicatedPost::created() */
662
663 function published ($params = array(), $default = NULL) {
664 $fallback = true; $unfiltered = false;
665 if (!is_array($params)) : // Old style
666 $fallback = $params;
667 else : // New style
668 extract($params);
669 endif;
670
671 $date = '';
672 $ts = null;
673
674 # RSS is a fucking mess. Figure out whether we have a date in
675 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
676 # epoch format for reformatting. If we can't find anything,
677 # we'll use the last-updated time.
678 if (isset($this->item['dc']['date'])): // Dublin Core
679 $date = $this->item['dc']['date'];
680 elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
681 $date = $this->item['dcterms']['issued'];
682 elseif (isset($this->item['published'])) : // Atom 1.0
683 $date = $this->item['published'];
684 elseif (isset($this->item['issued'])): // Atom 0.3
685 $date = $this->item['issued'];
686 elseif (isset($this->item['pubdate'])): // RSS 2.0
687 $date = $this->item['pubdate'];
688 endif;
689
690 if (strlen($date) > 0) :
691 $time = new FeedTime($date);
692 $ts = $time->timestamp();
693 elseif ($fallback) : // Fall back to <updated> / <modified> if present
694 $ts = $this->updated(/*fallback=*/ false, /*default=*/ $default);
695 endif;
696
697 # If everything failed, then default to the current time.
698 if (is_null($ts)) :
699 if (-1 == $default) :
700 $ts = time();
701 else :
702 $ts = $default;
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() */
711
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;
749 endif;
750 endif;
751
752 if (!$unfiltered) :
753 apply_filters('syndicated_item_updated', $ts, $this);
754 endif;
755 return $ts;
756 } /* SyndicatedPost::updated() */
757
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();
770 endif;
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;
810
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.
844 else :
845 $link = $this->permalink();
846 if (is_null($link)) : $link = $this->link->uri(); endif;
847 $guid .= '://'.md5($link.'/'.$this->item['title']);
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']);
873
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;
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;
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;
926 endif;
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
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 */
1219 function filtered () {
1220 return is_null($this->post);
1221 } /* SyndicatedPost::filtered() */
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 */
1234 function freshness () {
1235 global $wpdb;
1236
1237 if ($this->filtered()) : // This should never happen.
1238 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1239 endif;
1240
1241 if (is_null($this->_freshness)) : // Not yet checked and cached.
1242 $guid = $this->post['guid'];
1243 $eguid = $wpdb->escape($this->post['guid']);
1244
1245 $q = new WP_Query(array(
1246 'fields' => '_synfresh', // id, guid, post_modified_gmt
1247 'ignore_sticky_posts' => true,
1248 'guid' => $guid,
1249 ));
1250
1251 $old_post = NULL;
1252 if ($q->have_posts()) :
1253 while ($q->have_posts()) : $q->the_post();
1254 $old_post = $q->post;
1255 endwhile;
1256 endif;
1257
1258 if (is_null($old_post)) : // No post with this guid
1259 FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a NEW POST.');
1260 $this->_wp_id = NULL;
1261 $this->_freshness = 2; // New content
1262 else :
1263 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $old_post->post_modified_gmt, $backref);
1264
1265 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
1266 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1267
1268 // Check timestamps...
1269 $updated = (
1270 !is_null($updated_ts)
1271 and ($updated_ts > $last_rev_ts)
1272 );
1273
1274
1275 if (!$updated) :
1276 // Or the hash...
1277 $hash = $this->update_hash();
1278 $seen = $this->stored_hashes($old_post->ID);
1279 if (count($seen) > 0) :
1280 $updated = !in_array($hash, $seen); // Not seen yet?
1281 else :
1282 $updated = true; // Can't find syndication meta-data
1283 endif;
1284 endif;
1285
1286 $frozen = false;
1287 if ($updated) : // Ignore if the post is frozen
1288 $frozen = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
1289 if (!$frozen) :
1290 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $old_post->ID);
1291 $frozen = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
1292 endif;
1293 endif;
1294 $updated = ($updated and !$frozen);
1295
1296 if ($updated) :
1297 FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is an update of an existing post.');
1298 $this->_freshness = 1; // Updated content
1299 $this->_wp_id = $old_post->ID;
1300
1301 // We want this to keep a running list of all the
1302 // processed update hashes.
1303 $this->post['meta']['syndication_item_hash'] = array_merge(
1304 $this->stored_hashes(),
1305 array($this->update_hash())
1306 );
1307 else :
1308 FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a duplicate of an existing post.');
1309 $this->_freshness = 0; // Same old, same old
1310 $this->_wp_id = $old_post->ID;
1311 endif;
1312 endif;
1313 endif;
1314 return $this->_freshness;
1315 }
1316
1317 #################################################
1318 #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1319 #################################################
1320
1321 function wp_id () {
1322 if ($this->filtered()) : // This should never happen.
1323 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1324 endif;
1325
1326 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1327 $fresh = $this->freshness(); // sets WP DB id in the process
1328 endif;
1329 return $this->_wp_id;
1330 }
1331
1332 function store () {
1333 global $wpdb;
1334
1335 if ($this->filtered()) : // This should never happen.
1336 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1337 endif;
1338
1339 $freshness = $this->freshness();
1340 if ($freshness > 0) :
1341 # -- Look up, or create, numeric ID for author
1342 $this->post['post_author'] = $this->author_id (
1343 $this->link->setting('unfamiliar author', 'unfamiliar_author', 'create')
1344 );
1345
1346 if (is_null($this->post['post_author'])) :
1347 FeedWordPress::diagnostic('feed_items:rejected', 'Filtered out item ['.$this->guid().'] without syndication: no author available');
1348 $this->post = NULL;
1349 endif;
1350 endif;
1351
1352 if (!$this->filtered() and $freshness > 0) :
1353 $consider = array(
1354 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')),
1355 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')),
1356 );
1357
1358 $termSet = array(); $valid = null;
1359 foreach ($consider as $what => $taxes) :
1360 if (!is_null($this->post)) : // Not filtered out yet
1361 # -- Look up, or create, numeric ID for categories
1362 $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
1363
1364 // Eliminate dummy variables
1365 $taxonomies = array_filter($taxonomies, 'remove_dummy_zero');
1366
1367 $terms = $this->category_ids (
1368 $this->feed_terms[$what],
1369 $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what),
1370 /*taxonomies=*/ $taxonomies,
1371 array(
1372 'singleton' => false, // I don't like surprises
1373 'filters' => true,
1374 )
1375 );
1376
1377 if (is_null($terms) or is_null($termSet)) :
1378 // filtered out -- no matches
1379 else :
1380 $valid = true;
1381
1382 // filter mode off, or at least one match
1383 foreach ($terms as $tax => $term_ids) :
1384 if (!isset($termSet[$tax])) :
1385 $termSet[$tax] = array();
1386 endif;
1387 $termSet[$tax] = array_merge($termSet[$tax], $term_ids);
1388 endforeach;
1389 endif;
1390 endif;
1391 endforeach;
1392
1393 if (is_null($valid)) : // Plonked
1394 $this->post = NULL;
1395 else : // We can proceed
1396 $this->post['tax_input'] = array();
1397 foreach ($termSet as $tax => $term_ids) :
1398 if (!isset($this->post['tax_input'][$tax])) :
1399 $this->post['tax_input'][$tax] = array();
1400 endif;
1401 $this->post['tax_input'][$tax] = array_merge(
1402 $this->post['tax_input'][$tax],
1403 $term_ids
1404 );
1405 endforeach;
1406
1407 // Now let's add on the feed and global presets
1408 foreach ($this->preset_terms as $tax => $term_ids) :
1409 if (!isset($this->post['tax_input'][$tax])) :
1410 $this->post['tax_input'][$tax] = array();
1411 endif;
1412
1413 $this->post['tax_input'][$tax] = array_merge (
1414 $this->post['tax_input'][$tax],
1415 $this->category_ids (
1416 /*terms=*/ $term_ids,
1417 /*unfamiliar=*/ 'create:'.$tax, // These are presets; for those added in a tagbox editor, the tag may not yet exist
1418 /*taxonomies=*/ array($tax),
1419 array(
1420 'singleton' => true,
1421 ))
1422 );
1423 endforeach;
1424 endif;
1425 endif;
1426
1427 if (!$this->filtered() and $freshness > 0) :
1428 // Filter some individual fields
1429
1430 // Allow filters to set post slug. Props niska.
1431 $post_name = apply_filters('syndicated_post_slug', NULL, $this);
1432 if (!empty($post_name)) :
1433 $this->post['post_name'] = $post_name;
1434 endif;
1435
1436 $this->post = apply_filters('syndicated_post', $this->post, $this);
1437
1438 // Allow for feed-specific syndicated_post filters.
1439 $this->post = apply_filters(
1440 "syndicated_post_".$this->link->uri(),
1441 $this->post,
1442 $this
1443 );
1444 endif;
1445
1446 // Hook in early to make sure these get inserted if at all possible
1447 add_action(
1448 /*hook=*/ 'transition_post_status',
1449 /*callback=*/ array(&$this, 'add_rss_meta'),
1450 /*priority=*/ -10000, /* very early */
1451 /*arguments=*/ 3
1452 );
1453
1454 $retval = array(1 => 'updated', 2 => 'new');
1455
1456 $ret = false;
1457 if (!$this->filtered() and isset($retval[$freshness])) :
1458 $diag = array(
1459 1 => 'Updating existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"',
1460 2 => 'Inserting new post "'.$this->post['post_title'].'"',
1461 );
1462 FeedWordPress::diagnostic('syndicated_posts', $diag[$freshness]);
1463
1464 $this->insert_post(/*update=*/ ($freshness == 1));
1465
1466 $hook = array( 1 => 'update_syndicated_item', 2 => 'post_syndicated_item' );
1467 do_action($hook[$freshness], $this->wp_id(), $this);
1468
1469 $ret = $retval[$freshness];
1470 endif;
1471
1472 // Remove add_rss_meta hook
1473 remove_action(
1474 /*hook=*/ 'transition_post_status',
1475 /*callback=*/ array(&$this, 'add_rss_meta'),
1476 /*priority=*/ -10000, /* very early */
1477 /*arguments=*/ 3
1478 );
1479
1480 return $ret;
1481 } /* function SyndicatedPost::store () */
1482
1483 function insert_post ($update = false) {
1484 global $wpdb;
1485
1486 $dbpost = $this->normalize_post(/*new=*/ true);
1487 if (!is_null($dbpost)) :
1488 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1489
1490 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1491 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1492
1493 // Kludge to prevent kses filters from stripping the
1494 // content of posts when updating without a logged in
1495 // user who has `unfiltered_html` capability.
1496 $mungers = array('wp_filter_kses', 'wp_filter_post_kses');
1497 $removed = array();
1498 foreach ($mungers as $munger) :
1499 if (has_filter('content_save_pre', $munger)) :
1500 remove_filter('content_save_pre', $munger);
1501 $removed[] = $munger;
1502 endif;
1503 endforeach;
1504
1505 if ($update and function_exists('get_post_field')) :
1506 // Don't munge status fields that the user may
1507 // have reset manually
1508 $doNotMunge = array('post_status', 'comment_status', 'ping_status');
1509
1510 foreach ($doNotMunge as $field) :
1511 $dbpost[$field] = get_post_field($field, $this->wp_id());
1512 endforeach;
1513 endif;
1514
1515 // WP3's wp_insert_post scans current_user_can() for the
1516 // tax_input, with no apparent way to override. Ugh.
1517 add_action(
1518 /*hook=*/ 'transition_post_status',
1519 /*callback=*/ array(&$this, 'add_terms'),
1520 /*priority=*/ -10001, /* very early */
1521 /*arguments=*/ 3
1522 );
1523
1524 // WP3 appears to override whatever you give it for
1525 // post_modified. Ugh.
1526 add_action(
1527 /*hook=*/ 'transition_post_status',
1528 /*callback=*/ array(&$this, 'fix_post_modified_ts'),
1529 /*priority=*/ -10000, /* very early */
1530 /*arguments=*/ 3
1531 );
1532
1533 if ($update) :
1534 $this->post['ID'] = $this->wp_id();
1535 $dbpost['ID'] = $this->post['ID'];
1536 endif;
1537
1538 $this->_wp_id = wp_insert_post($dbpost, /*return wp_error=*/ true);
1539
1540 remove_action(
1541 /*hook=*/ 'transition_post_status',
1542 /*callback=*/ array(&$this, 'add_terms'),
1543 /*priority=*/ -10001, /* very early */
1544 /*arguments=*/ 3
1545 );
1546
1547 remove_action(
1548 /*hook=*/ 'transition_post_status',
1549 /*callback=*/ array(&$this, 'fix_post_modified_ts'),
1550 /*priority=*/ -10000, /* very early */
1551 /*arguments=*/ 3
1552 );
1553
1554 // Turn off ridiculous fucking kludges #1 and #2
1555 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1556 foreach ($removed as $filter) :
1557 add_filter('content_save_pre', $removed);
1558 endforeach;
1559
1560 $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__));
1561 endif;
1562 } /* function SyndicatedPost::insert_post () */
1563
1564 function insert_new () {
1565 $this->insert_post(/*update=*/ false);
1566 } /* SyndicatedPost::insert_new() */
1567
1568 function update_existing () {
1569 $this->insert_post(/*update=*/ true);
1570 } /* SyndicatedPost::update_existing() */
1571
1572 /**
1573 * SyndicatedPost::normalize_post()
1574 *
1575 * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
1576 * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1577 */
1578 function normalize_post ($new = true) {
1579 global $wpdb;
1580
1581 $out = $this->post;
1582
1583 $fullPost = $out['post_title'].$out['post_content'];
1584 $fullPost .= (isset($out['post_excerpt']) ? $out['post_excerpt'] : '');
1585 if (strlen($fullPost) < 1) :
1586 // FIXME: Option for filtering out empty posts
1587 endif;
1588 if (strlen($out['post_title'])==0) :
1589 $offset = (int) get_option('gmt_offset') * 60 * 60;
1590 if (isset($this->post['meta']['syndication_source'])) :
1591 $source_title = $this->post['meta']['syndication_source'];
1592 else :
1593 $feed_url = parse_url($this->post['meta']['syndication_feed']);
1594 $source_title = $feed_url['host'];
1595 endif;
1596
1597 $out['post_title'] = $source_title
1598 .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1599 // FIXME: Option for what to fill a blank title with...
1600 endif;
1601
1602 // Normalize the guid if necessary.
1603 $out['guid'] = SyndicatedPost::normalize_guid($out['guid']);
1604
1605 // Why the fuck doesn't wp_insert_post already do this?
1606 foreach ($out as $key => $value) :
1607 if (is_string($value)) :
1608 $out[$key] = $wpdb->escape($value);
1609 else :
1610 $out[$key] = $value;
1611 endif;
1612 endforeach;
1613
1614 return $out;
1615 }
1616
1617 /**
1618 * SyndicatedPost::validate_post_id()
1619 *
1620 * @param array $dbpost An array representing the post we attempted to insert or update
1621 * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1622 */
1623 function validate_post_id ($dbpost, $is_update, $ns) {
1624 if (is_array($ns)) : $ns = implode('::', $ns);
1625 else : $ns = (string) $ns; endif;
1626
1627 // This should never happen.
1628 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1629 $verb = ($is_update ? 'update existing' : 'insert new');
1630 $guid = $this->guid();
1631 $url = $this->permalink();
1632 $feed = $this->link->uri(array('add_params' => true));
1633
1634 // wp_insert_post failed. Diagnostics, or barf up a critical bug
1635 // notice if we are in debug mode.
1636 $mesg = "Failed to $verb item [$guid]. WordPress API returned no valid post ID.\n"
1637 ."\t\tID = ".serialize($this->_wp_id)."\n"
1638 ."\t\tURL = ".FeedWordPress::val($url)
1639 ."\t\tFeed = ".FeedWordPress::val($feed);
1640 FeedWordPress::diagnostic('updated_feeds:errors', "WordPress API error: $mesg");
1641 FeedWordPress::diagnostic('feed_items:rejected', $mesg);
1642
1643 $mesg = <<<EOM
1644 The WordPress API returned an invalid post ID
1645 when FeedWordPress tried to $verb item $guid
1646 [URL: $url]
1647 from the feed at $feed
1648
1649 $ns::_wp_id
1650 EOM;
1651 FeedWordPress::noncritical_bug(
1652 /*message=*/ $mesg,
1653 /*var =*/ array(
1654 "\$this->_wp_id" => $this->_wp_id,
1655 "\$dbpost" => $dbpost,
1656 ),
1657 /*line # =*/ __LINE__, /*filename=*/ __FILE__
1658 );
1659 endif;
1660 } /* SyndicatedPost::validate_post_id() */
1661
1662 /**
1663 * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
1664 * meta-data (authorship, etc.) when storing revisions of an updated
1665 * syndicated post.
1666 *
1667 * In their infinite wisdom, the WordPress coders have made it completely
1668 * impossible for a plugin that uses wp_insert_post() to set certain
1669 * meta-data (such as the author) when you store an old revision of an
1670 * updated post. Instead, it uses the WordPress defaults (= currently
1671 * active user ID if the process is running with a user logged in, or
1672 * = #0 if there is no user logged in). This results in bogus authorship
1673 * data for revisions that are syndicated from off the feed, unless we
1674 * use a ridiculous kludge like this to end-run the munging of meta-data
1675 * by _wp_put_post_revision.
1676 *
1677 * @param int $revision_id The revision ID to fix up meta-data
1678 */
1679 function fix_revision_meta ($revision_id) {
1680 global $wpdb;
1681
1682 $post_author = (int) $this->post['post_author'];
1683
1684 $revision_id = (int) $revision_id;
1685 $wpdb->query("
1686 UPDATE $wpdb->posts
1687 SET post_author={$this->post['post_author']}
1688 WHERE post_type = 'revision' AND ID='$revision_id'
1689 ");
1690 } /* SyndicatedPost::fix_revision_meta () */
1691
1692 /**
1693 * SyndicatedPost::add_terms() -- if FeedWordPress is processing an
1694 * automatic update, that generally means that wp_insert_post() is being
1695 * called under the user credentials of whoever is viewing the blog at
1696 * the time -- which usually means no user at all. But wp_insert_post()
1697 * checks current_user_can() before assigning any of the terms in a
1698 * post's tax_input structure -- which is unfortunate, since
1699 * current_user_can() always returns FALSE when there is no current user
1700 * logged in. Meaning that automatic updates get no terms assigned.
1701 *
1702 * So, wp_insert_post() is not going to do the term assignments for us.
1703 * If you want something done right....
1704 *
1705 * @param string $new_status Unused action parameter.
1706 * @param string $old_status Unused action parameter.
1707 * @param object $post The database record for the post just inserted.
1708 */
1709 function add_terms ($new_status, $old_status, $post) {
1710 if ($new_status!='inherit') : // Bail if we are creating a revision.
1711 if ( is_array($this->post) and isset($this->post['tax_input']) and is_array($this->post['tax_input']) ) :
1712 foreach ($this->post['tax_input'] as $taxonomy => $terms) :
1713 if (is_array($terms)) :
1714 $terms = array_filter($terms); // strip out empties
1715 endif;
1716 wp_set_post_terms($post->ID, $terms, $taxonomy);
1717 endforeach;
1718 endif;
1719 endif;
1720 } /* SyndicatedPost::add_terms () */
1721
1722 /**
1723 * SyndicatedPost::fix_post_modified_ts() -- We would like to set
1724 * post_modified and post_modified_gmt to reflect the value of
1725 * <atom:updated> or equivalent elements on the feed. Unfortunately,
1726 * wp_insert_post() refuses to acknowledge explicitly-set post_modified
1727 * fields and overwrites them, either with the post_date (if new) or the
1728 * current timestamp (if updated).
1729 *
1730 * So, wp_insert_post() is not going to do the last-modified assignments
1731 * for us. If you want something done right....
1732 *
1733 * @param string $new_status Unused action parameter.
1734 * @param string $old_status Unused action parameter.
1735 * @param object $post The database record for the post just inserted.
1736 */
1737 function fix_post_modified_ts ($new_status, $old_status, $post) {
1738 global $wpdb;
1739 if ($new_status!='inherit') : // Bail if we are creating a revision.
1740 $wpdb->update( $wpdb->posts, /*data=*/ array(
1741 'post_modified' => $this->post['post_modified'],
1742 'post_modified_gmt' => $this->post['post_modified_gmt'],
1743 ), /*where=*/ array('ID' => $post->ID) );
1744 endif;
1745 } /* SyndicatedPost::fix_post_modified_ts () */
1746
1747 /**
1748 * SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
1749 * using the space for custom keys. The set of keys and values to add is
1750 * specified by the keys and values of $post['meta']. This is used to
1751 * store anything that the WordPress user might want to access from a
1752 * template concerning the post's original source that isn't provided
1753 * for by standard WP meta-data (i.e., any interesting data about the
1754 * syndicated post other than author, title, timestamp, categories, and
1755 * guid). It's also used to hook into WordPress's support for
1756 * enclosures.
1757 *
1758 * @param string $new_status Unused action parameter.
1759 * @param string $old_status Unused action parameter.
1760 * @param object $post The database record for the post just inserted.
1761 */
1762 function add_rss_meta ($new_status, $old_status, $post) {
1763 global $wpdb;
1764 if ($new_status!='inherit') : // Bail if we are creating a revision.
1765 FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}');
1766
1767 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
1768 $postId = $post->ID;
1769
1770 // Aggregated posts should NOT send out pingbacks.
1771 // WordPress 2.1-2.2 claim you can tell them not to
1772 // using $post_pingback, but they don't listen, so we
1773 // make sure here.
1774 $result = $wpdb->query("
1775 DELETE FROM $wpdb->postmeta
1776 WHERE post_id='$postId' AND meta_key='_pingme'
1777 ");
1778
1779 foreach ( $this->post['meta'] as $key => $values ) :
1780 $eKey = $wpdb->escape($key);
1781
1782 // If this is an update, clear out the old
1783 // values to avoid duplication.
1784 $result = $wpdb->query("
1785 DELETE FROM $wpdb->postmeta
1786 WHERE post_id='$postId' AND meta_key='$eKey'
1787 ");
1788
1789 // Allow for either a single value or an array
1790 if (!is_array($values)) $values = array($values);
1791 foreach ( $values as $value ) :
1792 FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($value, /*no newlines=*/ true));
1793 add_post_meta($postId, $key, $value, /*unique=*/ false);
1794 endforeach;
1795 endforeach;
1796 endif;
1797 endif;
1798 } /* SyndicatedPost::add_rss_meta () */
1799
1800 /**
1801 * SyndicatedPost::author_id (): get the ID for an author name from
1802 * the feed. Create the author if necessary.
1803 *
1804 * @param string $unfamiliar_author
1805 *
1806 * @return NULL|int The numeric ID of the author to attribute the post to
1807 * NULL if the post should be filtered out.
1808 */
1809 function author_id ($unfamiliar_author = 'create') {
1810 global $wpdb;
1811
1812 $a = $this->named['author'];
1813
1814 $source = $this->source();
1815 $forbidden = apply_filters('feedwordpress_forbidden_author_names',
1816 array('admin', 'administrator', 'www', 'root'));
1817
1818 // Prepare the list of candidates to try for author name: name from
1819 // feed, original source title (if any), immediate source title live
1820 // from feed, subscription title, prettied version of feed homepage URL,
1821 // prettied version of feed URL, or, failing all, use "unknown author"
1822 // as last resort
1823
1824 $candidates = array();
1825 $candidates[] = $a['name'];
1826 if (!is_null($source)) : $candidates[] = $source['title']; endif;
1827 $candidates[] = $this->link->name(/*fromFeed=*/ true);
1828 $candidates[] = $this->link->name(/*fromFeed=*/ false);
1829 if (strlen($this->link->homepage()) > 0) : $candidates[] = feedwordpress_display_url($this->link->homepage()); endif;
1830 $candidates[] = feedwordpress_display_url($this->link->uri());
1831 $candidates[] = 'unknown author';
1832
1833 // Pick the first one that works from the list, screening against empty
1834 // or forbidden names.
1835
1836 $author = NULL;
1837 while (is_null($author) and ($candidate = each($candidates))) :
1838 if (!is_null($candidate['value'])
1839 and (strlen(trim($candidate['value'])) > 0)
1840 and !in_array(strtolower(trim($candidate['value'])), $forbidden)) :
1841 $author = $candidate['value'];
1842 endif;
1843 endwhile;
1844
1845 $email = (isset($a['email']) ? $a['email'] : NULL);
1846 $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL);
1847
1848
1849 $hostUrl = $this->link->homepage();
1850 if (is_null($hostUrl) or (strlen($hostUrl) < 0)) :
1851 $hostUrl = $this->link->uri();
1852 endif;
1853
1854 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
1855 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
1856 $test_email = $email;
1857 else :
1858 $test_email = NULL;
1859 endif;
1860
1861 // Never can be too careful...
1862 $login = sanitize_user($author, /*strict=*/ true);
1863
1864 // Possible for, e.g., foreign script author names
1865 if (strlen($login) < 1) :
1866 // No usable characters in author name for a login.
1867 // (Sometimes results from, e.g., foreign scripts.)
1868 //
1869 // We just need *something* in Western alphanumerics,
1870 // so let's try the domain name.
1871 //
1872 // Uniqueness will be guaranteed below if necessary.
1873
1874 $url = parse_url($hostUrl);
1875
1876 $login = sanitize_user($url['host'], /*strict=*/ true);
1877 if (strlen($login) < 1) :
1878 // This isn't working. Frak it.
1879 $login = 'syndicated';
1880 endif;
1881 endif;
1882
1883 $login = apply_filters('pre_user_login', $login);
1884
1885 $nice_author = sanitize_title($author);
1886 $nice_author = apply_filters('pre_user_nicename', $nice_author);
1887
1888 $reg_author = $wpdb->escape(preg_quote($author));
1889 $author = $wpdb->escape($author);
1890 $email = $wpdb->escape($email);
1891 $test_email = $wpdb->escape($test_email);
1892 $authorUrl = $wpdb->escape($authorUrl);
1893
1894 // Check for an existing author rule....
1895 if (isset($this->link->settings['map authors']['name']['*'])) :
1896 $author_rule = $this->link->settings['map authors']['name']['*'];
1897 elseif (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
1898 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
1899 else :
1900 $author_rule = NULL;
1901 endif;
1902
1903 // User name is mapped to a particular author. If that author ID exists, use it.
1904 if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
1905 $id = (int) $author_rule;
1906
1907 // User name is filtered out
1908 elseif ('filter' == $author_rule) :
1909 $id = NULL;
1910
1911 else :
1912 // Check the database for an existing author record that might fit
1913
1914 // First try the user core data table.
1915 $id = $wpdb->get_var(
1916 "SELECT ID FROM $wpdb->users
1917 WHERE TRIM(LCASE(display_name)) = TRIM(LCASE('$author'))
1918 OR TRIM(LCASE(user_login)) = TRIM(LCASE('$author'))
1919 OR (
1920 LENGTH(TRIM(LCASE(user_email))) > 0
1921 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1922 )");
1923
1924 // If that fails, look for aliases in the user meta data table
1925 if (is_null($id)) :
1926 $id = $wpdb->get_var(
1927 "SELECT user_id FROM $wpdb->usermeta
1928 WHERE
1929 (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
1930 OR (
1931 meta_key = 'description'
1932 AND TRIM(LCASE(meta_value))
1933 RLIKE CONCAT(
1934 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1935 TRIM(LCASE('$reg_author')),
1936 '( |\\t|\\r)*(\\n|\$)'
1937 )
1938 )
1939 ");
1940 endif;
1941
1942 // ... if you don't find one, then do what you need to do
1943 if (is_null($id)) :
1944 if ($unfamiliar_author === 'create') :
1945 $userdata = array();
1946
1947 // WordPress 3 is going to pitch a fit if we attempt to register
1948 // more than one user account with an empty e-mail address, so we
1949 // need *something* here. Ugh.
1950 if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
1951 $url = parse_url($hostUrl);
1952 $email = $nice_author.'@'.$url['host'];
1953 endif;
1954
1955 #-- user table data
1956 $userdata['ID'] = NULL; // new user
1957 $userdata['user_login'] = $login;
1958 $userdata['user_nicename'] = $nice_author;
1959 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
1960 $userdata['user_email'] = $email;
1961 $userdata['user_url'] = $authorUrl;
1962 $userdata['nickname'] = $author;
1963 $parts = preg_split('/\s+/', trim($author), 2);
1964 if (isset($parts[0])) : $userdata['first_name'] = $parts[0]; endif;
1965 if (isset($parts[1])) : $userdata['last_name'] = $parts[1]; endif;
1966 $userdata['display_name'] = $author;
1967 $userdata['role'] = 'contributor';
1968
1969 do { // Keep trying until you get it right. Or until PHP crashes, I guess.
1970 $id = wp_insert_user($userdata);
1971 if (is_wp_error($id)) :
1972 $codes = $id->get_error_code();
1973 switch ($codes) :
1974 case 'empty_user_login' :
1975 case 'existing_user_login' :
1976 // Add a random disambiguator
1977 $userdata['user_login'] .= substr(md5(uniqid(microtime())), 0, 6);
1978 break;
1979 case 'existing_user_email' :
1980 // No disassemble!
1981 $parts = explode('@', $userdata['user_email'], 2);
1982
1983 // Add a random disambiguator as a gmail-style username extension
1984 $parts[0] .= '+'.substr(md5(uniqid(microtime())), 0, 6);
1985
1986 // Reassemble
1987 $userdata['user_email'] = $parts[0].'@'.$parts[1];
1988 break;
1989 endswitch;
1990 endif;
1991 } while (is_wp_error($id));
1992 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
1993 $id = (int) $unfamiliar_author;
1994 elseif ($unfamiliar_author === 'default') :
1995 $id = 1;
1996 endif;
1997 endif;
1998 endif;
1999
2000 if ($id) :
2001 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
2002
2003 // Multisite: Check whether the author has been recorded
2004 // on *this* blog before. If not, put her down as a
2005 // Contributor for *this* blog.
2006 $user = new WP_User((int) $id);
2007 if (empty($user->roles)) :
2008 $user->add_role('contributor');
2009 endif;
2010 endif;
2011 return $id;
2012 } /* function SyndicatedPost::author_id () */
2013
2014 /**
2015 * category_ids: look up (and create) category ids from a list of categories
2016 *
2017 * @param array $cats
2018 * @param string $unfamiliar_category
2019 * @param array|null $taxonomies
2020 * @return array
2021 */
2022 function category_ids ($cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
2023 $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
2024 $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
2025
2026 $catTax = 'category';
2027
2028 if (is_null($taxonomies)) :
2029 $taxonomies = array('category');
2030 endif;
2031
2032 // We need to normalize whitespace because (1) trailing
2033 // whitespace can cause PHP and MySQL not to see eye to eye on
2034 // VARCHAR comparisons for some versions of MySQL (cf.
2035 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
2036 // because I doubt most people want to make a semantic
2037 // distinction between 'Computers' and 'Computers '
2038 $cats = array_map('trim', $cats);
2039
2040 $terms = array();
2041 foreach ($taxonomies as $tax) :
2042 $terms[$tax] = array();
2043 endforeach;
2044
2045 foreach ($cats as $cat_name) :
2046 if (preg_match('/^{([^#}]*)#([0-9]+)}$/', $cat_name, $backref)) :
2047 $cat_id = (int) $backref[2];
2048 $tax = $backref[1];
2049 if (strlen($tax) < 1) :
2050 $tax = $catTax;
2051 endif;
2052
2053 $term = term_exists($cat_id, $tax);
2054 if (!is_wp_error($term) and !!$term) :
2055 if (!isset($terms[$tax])) :
2056 $terms[$tax] = array();
2057 endif;
2058 $terms[$tax][] = $cat_id;
2059 endif;
2060 elseif (strlen($cat_name) > 0) :
2061 $familiar = false;
2062 foreach ($taxonomies as $tax) :
2063 if ($tax!='category' or strtolower($cat_name)!='uncategorized') :
2064 $term = term_exists($cat_name, $tax);
2065 if (!is_wp_error($term) and !!$term) :
2066 $familiar = true;
2067
2068 if (is_array($term)) :
2069 $term_id = (int) $term['term_id'];
2070 else :
2071 $term_id = (int) $term;
2072 endif;
2073
2074 if (!isset($terms[$tax])) :
2075 $terms[$tax] = array();
2076 endif;
2077 $terms[$tax][] = $term_id;
2078 break; // We're done here.
2079 endif;
2080 endif;
2081 endforeach;
2082
2083 if (!$familiar) :
2084 if ('tag'==$unfamiliar_category) :
2085 $unfamiliar_category = 'create:post_tag';
2086 endif;
2087
2088 if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
2089 $tax = $catTax; // Default
2090 if (isset($ref[2]) and strlen($ref[2]) > 2) :
2091 $tax = $ref[2];
2092 endif;
2093 $term = wp_insert_term($cat_name, $tax);
2094 if (is_wp_error($term)) :
2095 FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__, __FILE__);
2096 else :
2097 if (!isset($terms[$tax])) :
2098 $terms[$tax] = array();
2099 endif;
2100 $terms[$tax][] = (int) $term['term_id'];
2101 endif;
2102 endif;
2103 endif;
2104 endif;
2105 endforeach;
2106
2107 $filtersOn = $allowFilters;
2108 if ($allowFilters) :
2109 $filters = array_filter(
2110 $this->link->setting('match/filter', 'match_filter', array()),
2111 'remove_dummy_zero'
2112 );
2113 $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
2114 endif;
2115
2116 // Check for filter conditions
2117 foreach ($terms as $tax => $term_ids) :
2118 if ($filtersOn
2119 and (count($term_ids)==0)
2120 and in_array($tax, $filters)) :
2121 $terms = NULL; // Drop the post
2122 break;
2123 else :
2124 $terms[$tax] = array_unique($term_ids);
2125 endif;
2126 endforeach;
2127
2128 if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
2129 $terms = end($terms);
2130 endif;
2131 return $terms;
2132 } // function SyndicatedPost::category_ids ()
2133
2134 function use_api ($tag) {
2135 global $wp_db_version;
2136 switch ($tag) :
2137 case 'wp_insert_post':
2138 // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
2139 // In 1.5 it was such a resource hog it would make PHP segfault on big updates
2140 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
2141 break;
2142 case 'post_status_pending':
2143 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
2144 break;
2145 endswitch;
2146 return $ret;
2147 } // function SyndicatedPost::use_api ()
2148
2149 } /* class SyndicatedPost */
2150
2151