PluginProbe
FeedWordPress / 2011.0211
FeedWordPress v2011.0211
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.0211, at syndicatedpost.class.php

2,066 lines 68.4 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 2010.0905
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 function update_hash () {
759 return md5(serialize($this->item));
760 } /* SyndicatedPost::update_hash() */
761
762 function guid () {
763 $guid = null;
764 if (isset($this->item['id'])): // Atom 0.3 / 1.0
765 $guid = $this->item['id'];
766 elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
767 $guid = $this->item['atom']['id'];
768 elseif (isset($this->item['guid'])) : // RSS 2.0
769 $guid = $this->item['guid'];
770 elseif (isset($this->item['dc']['identifier'])) : // yeah, right
771 $guid = $this->item['dc']['identifier'];
772 endif;
773
774 // Un-set or too long to use as-is. Generate a tag: URI.
775 if (is_null($guid) or strlen($guid) > 250) :
776 // In case we need to check this again
777 $original_guid = $guid;
778
779 // The feed does not seem to have provided us with a
780 // usable unique identifier, so we'll have to cobble
781 // together a tag: URI that might work for us. The base
782 // of the URI will be the host name of the feed source ...
783 $bits = parse_url($this->link->uri());
784 $guid = 'tag:'.$bits['host'];
785
786 // Some ill-mannered feeds (for example, certain feeds
787 // coming from Google Calendar) have extraordinarily long
788 // guids -- so long that they exceed the 255 character
789 // width of the WordPress guid field. But if the string
790 // gets clipped by MySQL, uniqueness tests will fail
791 // forever after and the post will be endlessly
792 // reduplicated. So, instead, Guids Of A Certain Length
793 // are hashed down into a nice, manageable tag: URI.
794 if (!is_null($original_guid)) :
795 $guid .= ',2010-12-03:id.'.md5($original_guid);
796
797 // If we have a date of creation, then we can use that
798 // to uniquely identify the item. (On the other hand, if
799 // the feed producer was consicentious enough to
800 // generate dates of creation, she probably also was
801 // conscientious enough to generate unique identifiers.)
802 elseif (!is_null($this->created())) :
803 $guid .= '://post.'.date('YmdHis', $this->created());
804
805 // Otherwise, use both the URI of the item, *and* the
806 // item's title. We have to use both because titles are
807 // often not unique, and sometimes links aren't unique
808 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
809 // some podcasts). But it's rare to have *both* the same
810 // title *and* the same link for two different items. So
811 // this is about the best we can do.
812 else :
813 $link = $this->permalink();
814 if (is_null($link)) : $link = $this->link->uri(); endif;
815 $guid .= '://'.md5($link.'/'.$this->item['title']);
816 endif;
817 endif;
818 return $guid;
819 } /* SyndicatedPost::guid() */
820
821 function author () {
822 $author = array ();
823
824 if (isset($this->item['author_name'])):
825 $author['name'] = $this->item['author_name'];
826 elseif (isset($this->item['dc']['creator'])):
827 $author['name'] = $this->item['dc']['creator'];
828 elseif (isset($this->item['dc']['contributor'])):
829 $author['name'] = $this->item['dc']['contributor'];
830 elseif (isset($this->feed->channel['dc']['creator'])) :
831 $author['name'] = $this->feed->channel['dc']['creator'];
832 elseif (isset($this->feed->channel['dc']['contributor'])) :
833 $author['name'] = $this->feed->channel['dc']['contributor'];
834 elseif (isset($this->feed->channel['author_name'])) :
835 $author['name'] = $this->feed->channel['author_name'];
836 elseif ($this->feed->is_rss() and isset($this->item['author'])) :
837 // The author element in RSS is allegedly an
838 // e-mail address, but lots of people don't use
839 // it that way. So let's make of it what we can.
840 $author = parse_email_with_realname($this->item['author']);
841
842 if (!isset($author['name'])) :
843 if (isset($author['email'])) :
844 $author['name'] = $author['email'];
845 else :
846 $author['name'] = $this->feed->channel['title'];
847 endif;
848 endif;
849 elseif ($this->link->name()) :
850 $author['name'] = $this->link->name();
851 else :
852 $url = parse_url($this->link->uri());
853 $author['name'] = $url['host'];
854 endif;
855
856 if (isset($this->item['author_email'])):
857 $author['email'] = $this->item['author_email'];
858 elseif (isset($this->feed->channel['author_email'])) :
859 $author['email'] = $this->feed->channel['author_email'];
860 endif;
861
862 if (isset($this->item['author_url'])):
863 $author['uri'] = $this->item['author_url'];
864 elseif (isset($this->feed->channel['author_url'])) :
865 $author['uri'] = $this->item['author_url'];
866 elseif (isset($this->feed->channel['link'])) :
867 $author['uri'] = $this->feed->channel['link'];
868 endif;
869
870 return $author;
871 } /* SyndicatedPost::author() */
872
873 /**
874 * SyndicatedPost::inline_tags: Return a list of all the tags embedded
875 * in post content using the a[@rel="tag"] microformat.
876 *
877 * @since 2010.0630
878 * @return array of string values containing the name of each tag
879 */
880 function inline_tags () {
881 $tags = array();
882 $content = $this->content();
883 $pattern = FeedWordPressHTML::tagWithAttributeRegex('a', 'rel', 'tag');
884 preg_match_all($pattern, $content, $refs, PREG_SET_ORDER);
885 if (count($refs) > 0) :
886 foreach ($refs as $ref) :
887 $tag = FeedWordPressHTML::tagWithAttributeMatch($ref);
888 $tags[] = $tag['content'];
889 endforeach;
890 endif;
891 return $tags;
892 }
893
894 /**
895 * SyndicatedPost::isTaggedAs: Test whether a feed item is
896 * tagged / categorized with a given string. Case and leading and
897 * trailing whitespace are ignored.
898 *
899 * @param string $tag Tag to check for
900 *
901 * @return bool Whether or not at least one of the categories / tags on
902 * $this->item is set to $tag (modulo case and leading and trailing
903 * whitespace)
904 */
905 function isTaggedAs ($tag) {
906 $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
907
908 // Check to see if this is tagged with $tag
909 $currentCategory = 'category';
910 $currentCategoryNumber = 1;
911
912 // If we have the new MagpieRSS, the number of category elements
913 // on this item is stored under index "category#".
914 if (isset($this->item['category#'])) :
915 $numberOfCategories = (int) $this->item['category#'];
916
917 // We REALLY shouldn't have the old and busted MagpieRSS, but in
918 // case we do, it doesn't support multiple categories, but there
919 // might still be a single value under the "category" index.
920 elseif (isset($this->item['category'])) :
921 $numberOfCategories = 1;
922
923 // No standard category or tag elements on this feed item.
924 else :
925 $numberOfCategories = 0;
926
927 endif;
928
929 $isSoTagged = false; // Innocent until proven guilty
930
931 // Loop through category elements; if there are multiple
932 // elements, they are indexed as category, category#2,
933 // category#3, ... category#N
934 while ($currentCategoryNumber <= $numberOfCategories) :
935 if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
936 $isSoTagged = true; // Got it!
937 break;
938 endif;
939
940 $currentCategoryNumber += 1;
941 $currentCategory = 'category#'.$currentCategoryNumber;
942 endwhile;
943
944 return $isSoTagged;
945 } /* SyndicatedPost::isTaggedAs() */
946
947 /**
948 * SyndicatedPost::enclosures: returns an array with any enclosures
949 * that may be attached to this syndicated item.
950 *
951 * @param string $type If you only want enclosures that match a certain
952 * MIME type or group of MIME types, you can limit the enclosures
953 * that will be returned to only those with a MIME type which
954 * matches this regular expression.
955 * @return array
956 */
957 function enclosures ($type = '/.*/') {
958 $enclosures = array();
959
960 if (isset($this->item['enclosure#'])) :
961 // Loop through enclosure, enclosure#2, enclosure#3, ....
962 for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
963 $eid = (($i > 1) ? "#{$id}" : "");
964
965 // Does it match the type we want?
966 if (preg_match($type, $this->item["enclosure{$eid}@type"])) :
967 $enclosures[] = array(
968 "url" => $this->item["enclosure{$eid}@url"],
969 "type" => $this->item["enclosure{$eid}@type"],
970 "length" => $this->item["enclosure{$eid}@length"],
971 );
972 endif;
973 endfor;
974 endif;
975 return $enclosures;
976 } /* SyndicatedPost::enclosures() */
977
978 function source ($what = NULL) {
979 $ret = NULL;
980 $source = $this->entry->get_source();
981 if ($source) :
982 $ret = array();
983 $ret['title'] = $source->get_title();
984 $ret['uri'] = $source->get_link();
985 $ret['feed'] = $source->get_link(0, 'self');
986
987 if ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) :
988 $ret['id'] = $id_tags[0]['data'];
989 elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) :
990 $ret['id'] = $id_tags[0]['data'];
991 elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) :
992 $ret['id'] = $id_tags[0]['data'];
993 elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'guid')) :
994 $ret['id'] = $id_tags[0]['data'];
995 elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'guid')) :
996 $ret['id'] = $id_tags[0]['data'];
997 endif;
998 endif;
999
1000 if (!is_null($what) and is_scalar($what)) :
1001 $ret = $ret[$what];
1002 endif;
1003 return $ret;
1004 }
1005
1006 function comment_link () {
1007 $url = null;
1008
1009 // RSS 2.0 has a standard <comments> element:
1010 // "<comments> is an optional sub-element of <item>. If present,
1011 // it is the url of the comments page for the item."
1012 // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
1013 if (isset($this->item['comments'])) :
1014 $url = $this->item['comments'];
1015 endif;
1016
1017 // The convention in Atom feeds is to use a standard <link>
1018 // element with @rel="replies" and @type="text/html".
1019 // Unfortunately, SimplePie_Item::get_links() allows us to filter
1020 // by the value of @rel, but not by the value of @type. *sigh*
1021
1022 // Try Atom 1.0 first
1023 $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
1024
1025 // Fall back and try Atom 0.3
1026 if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
1027
1028 // Now loop through the elements, screening by @rel and @type
1029 if (is_array($linkElements)) : foreach ($linkElements as $link) :
1030 $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
1031 $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
1032 $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL);
1033
1034 if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) :
1035 $url = $href;
1036 endif;
1037 endforeach; endif;
1038
1039 return $url;
1040 }
1041
1042 function comment_feed () {
1043 $feed = null;
1044
1045 // Well Formed Web comment feeds extension for RSS 2.0
1046 // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments>
1047 //
1048 // N.B.: Correct capitalization is wfw:commentRss, but
1049 // wfw:commentRSS is common in the wild (partly due to a typo in
1050 // the original spec). In any case, our item array is normalized
1051 // to all lowercase anyways.
1052 if (isset($this->item['wfw']['commentrss'])) :
1053 $feed = $this->item['wfw']['commentrss'];
1054 endif;
1055
1056 // In Atom 1.0, the convention is to use a standard link element
1057 // with @rel="replies". Sometimes this is also used to pass a
1058 // link to the human-readable comments page, so we also need to
1059 // check link/@type for a feed MIME type.
1060 //
1061 // Which is why I'm not using the SimplePie_Item::get_links()
1062 // method here, incidentally: it doesn't allow you to filter by
1063 // @type. *sigh*
1064 if (isset($this->item['link_replies'])) :
1065 // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
1066 $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
1067 for ($i = 1; $i <= $N; $i++) :
1068 $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
1069 if (isset($this->item[$currentElement.'@type'])
1070 and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
1071 $feed = $this->item[$currentElement];
1072 endif;
1073 endfor;
1074 endif;
1075 return $feed;
1076 } /* SyndicatedPost::comment_feed() */
1077
1078 ##################################
1079 #### BUILT-IN CONTENT FILTERS ####
1080 ##################################
1081
1082 var $uri_attrs = array (
1083 array('a', 'href'),
1084 array('applet', 'codebase'),
1085 array('area', 'href'),
1086 array('blockquote', 'cite'),
1087 array('body', 'background'),
1088 array('del', 'cite'),
1089 array('form', 'action'),
1090 array('frame', 'longdesc'),
1091 array('frame', 'src'),
1092 array('iframe', 'longdesc'),
1093 array('iframe', 'src'),
1094 array('head', 'profile'),
1095 array('img', 'longdesc'),
1096 array('img', 'src'),
1097 array('img', 'usemap'),
1098 array('input', 'src'),
1099 array('input', 'usemap'),
1100 array('ins', 'cite'),
1101 array('link', 'href'),
1102 array('object', 'classid'),
1103 array('object', 'codebase'),
1104 array('object', 'data'),
1105 array('object', 'usemap'),
1106 array('q', 'cite'),
1107 array('script', 'src')
1108 ); /* var SyndicatedPost::$uri_attrs */
1109
1110 var $_base = null;
1111
1112 function resolve_single_relative_uri ($refs) {
1113 $tag = FeedWordPressHTML::attributeMatch($refs);
1114 $url = SimplePie_Misc::absolutize_url($tag['value'], $this->_base);
1115 return $tag['prefix'] . $url . $tag['suffix'];
1116 } /* function SyndicatedPost::resolve_single_relative_uri() */
1117
1118 function resolve_relative_uris ($content, $obj) {
1119 $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
1120 if ($set and $set != 'no') :
1121 // Fallback: if we don't have anything better, use the
1122 // item link from the feed
1123 $obj->_base = $obj->permalink(); // Reset the base for resolving relative URIs
1124
1125 // What we should do here, properly, is to use
1126 // SimplePie_Item::get_base() -- but that method is
1127 // currently broken. Or getting down and dirty in the
1128 // SimplePie representation of the content tags and
1129 // grabbing the xml_base member for the content element.
1130 // Maybe someday...
1131
1132 foreach ($obj->uri_attrs as $pair) :
1133 list($tag, $attr) = $pair;
1134 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1135 $content = preg_replace_callback (
1136 $pattern,
1137 array(&$obj, 'resolve_single_relative_uri'),
1138 $content
1139 );
1140 endforeach;
1141 endif;
1142
1143 return $content;
1144 } /* function SyndicatedPost::resolve_relative_uris () */
1145
1146 var $strip_attrs = array (
1147 array('[a-z]+', 'target'),
1148 // array('[a-z]+', 'style'),
1149 // array('[a-z]+', 'on[a-z]+'),
1150 );
1151
1152 function strip_attribute_from_tag ($refs) {
1153 $tag = FeedWordPressHTML::attributeMatch($refs);
1154 return $tag['before_attribute'].$tag['after_attribute'];
1155 }
1156
1157 function sanitize_content ($content, $obj) {
1158 # This kind of sucks. I intend to replace it with
1159 # lib_filter sometime soon.
1160 foreach ($obj->strip_attrs as $pair):
1161 list($tag,$attr) = $pair;
1162 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1163
1164 $content = preg_replace_callback (
1165 $pattern,
1166 array(&$obj, 'strip_attribute_from_tag'),
1167 $content
1168 );
1169 endforeach;
1170 return $content;
1171 } /* SyndicatedPost::sanitize() */
1172
1173 #####################
1174 #### POST STATUS ####
1175 #####################
1176
1177 /**
1178 * SyndicatedPost::filtered: check whether or not this post has been
1179 * screened out by a registered filter.
1180 *
1181 * @return bool TRUE iff post has been filtered out by a previous filter
1182 */
1183 function filtered () {
1184 return is_null($this->post);
1185 } /* SyndicatedPost::filtered() */
1186
1187 /**
1188 * SyndicatedPost::freshness: check whether post is a new post to be
1189 * inserted, a previously syndicated post that needs to be updated to
1190 * match the latest revision, or a previously syndicated post that is
1191 * still up-to-date.
1192 *
1193 * @return int A status code representing the freshness of the post
1194 * 0 = post already syndicated; no update needed
1195 * 1 = post already syndicated, but needs to be updated to latest
1196 * 2 = post has not yet been syndicated; needs to be created
1197 */
1198 function freshness () {
1199 global $wpdb;
1200
1201 if ($this->filtered()) : // This should never happen.
1202 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1203 endif;
1204
1205 if (is_null($this->_freshness)) :
1206 $guid = $wpdb->escape($this->guid());
1207
1208 $result = $wpdb->get_row("
1209 SELECT id, guid, post_modified_gmt
1210 FROM $wpdb->posts WHERE guid='$guid'
1211 ");
1212
1213 if (!$result) :
1214 $this->_wp_id = NULL;
1215 $this->_freshness = 2; // New content
1216 else:
1217 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
1218
1219 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
1220 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1221
1222 // Check timestamps...
1223 $updated = (
1224 !is_null($updated_ts)
1225 and ($updated_ts > $last_rev_ts)
1226 );
1227
1228 if (!$updated) :
1229 // Or the hash...
1230 $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
1231 if (count($stored_update_hashes) > 0) :
1232 $stored_update_hash = $stored_update_hashes[0];
1233 $updated = ($stored_update_hash != $this->update_hash());
1234 else :
1235 $updated = true; // Can't find syndication meta-data
1236 endif;
1237 endif;
1238
1239 $frozen = false;
1240 if ($updated) : // Ignore if the post is frozen
1241 $frozen = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
1242 if (!$frozen) :
1243 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
1244 $frozen = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
1245 endif;
1246 endif;
1247 $updated = ($updated and !$frozen);
1248
1249 if ($updated) :
1250 $this->_freshness = 1; // Updated content
1251 $this->_wp_id = $result->id;
1252 else :
1253 $this->_freshness = 0; // Same old, same old
1254 $this->_wp_id = $result->id;
1255 endif;
1256 endif;
1257 endif;
1258 return $this->_freshness;
1259 }
1260
1261 #################################################
1262 #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1263 #################################################
1264
1265 function wp_id () {
1266 if ($this->filtered()) : // This should never happen.
1267 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1268 endif;
1269
1270 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1271 $fresh = $this->freshness(); // sets WP DB id in the process
1272 endif;
1273 return $this->_wp_id;
1274 }
1275
1276 function store () {
1277 global $wpdb;
1278
1279 if ($this->filtered()) : // This should never happen.
1280 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1281 endif;
1282
1283 $freshness = $this->freshness();
1284 if ($freshness > 0) :
1285 # -- Look up, or create, numeric ID for author
1286 $this->post['post_author'] = $this->author_id (
1287 $this->link->setting('unfamiliar author', 'unfamiliar_author', 'create')
1288 );
1289
1290 if (is_null($this->post['post_author'])) :
1291 $this->post = NULL;
1292 endif;
1293 endif;
1294
1295 if (!$this->filtered() and $freshness > 0) :
1296 $consider = array(
1297 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')),
1298 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')),
1299 );
1300
1301 $termSet = array(); $valid = null;
1302 foreach ($consider as $what => $taxes) :
1303 if (!is_null($this->post)) : // Not filtered out yet
1304 # -- Look up, or create, numeric ID for categories
1305 $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
1306
1307 // Eliminate dummy variables
1308 $taxonomies = array_filter($taxonomies, 'remove_dummy_zero');
1309
1310 $terms = $this->category_ids (
1311 $this->feed_terms[$what],
1312 $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what),
1313 /*taxonomies=*/ $taxonomies,
1314 array(
1315 'singleton' => false, // I don't like surprises
1316 'filters' => true,
1317 )
1318 );
1319
1320 if (is_null($terms) or is_null($termSet)) :
1321 // filtered out -- no matches
1322 else :
1323 $valid = true;
1324
1325 // filter mode off, or at least one match
1326 foreach ($terms as $tax => $term_ids) :
1327 if (!isset($termSet[$tax])) :
1328 $termSet[$tax] = array();
1329 endif;
1330 $termSet[$tax] = array_merge($termSet[$tax], $term_ids);
1331 endforeach;
1332 endif;
1333 endif;
1334 endforeach;
1335
1336 if (is_null($valid)) : // Plonked
1337 $this->post = NULL;
1338 else : // We can proceed
1339 $this->post['tax_input'] = array();
1340 foreach ($termSet as $tax => $term_ids) :
1341 if (!isset($this->post['tax_input'][$tax])) :
1342 $this->post['tax_input'][$tax] = array();
1343 endif;
1344 $this->post['tax_input'][$tax] = array_merge(
1345 $this->post['tax_input'][$tax],
1346 $term_ids
1347 );
1348 endforeach;
1349
1350 // Now let's add on the feed and global presets
1351 foreach ($this->preset_terms as $tax => $term_ids) :
1352 if (!isset($this->post['tax_input'][$tax])) :
1353 $this->post['tax_input'][$tax] = array();
1354 endif;
1355
1356 $this->post['tax_input'][$tax] = array_merge (
1357 $this->post['tax_input'][$tax],
1358 $this->category_ids (
1359 /*terms=*/ $term_ids,
1360 /*unfamiliar=*/ 'create:'.$tax, // These are presets; for those added in a tagbox editor, the tag may not yet exist
1361 /*taxonomies=*/ array($tax),
1362 array(
1363 'singleton' => true,
1364 ))
1365 );
1366 endforeach;
1367 endif;
1368 endif;
1369
1370 if (!$this->filtered() and $freshness > 0) :
1371 // Filter some individual fields
1372
1373 // Allow filters to set post slug. Props niska.
1374 $post_name = apply_filters('syndicated_post_slug', NULL, $this);
1375 if (!empty($post_name)) :
1376 $this->post['post_name'] = $post_name;
1377 endif;
1378
1379 $this->post = apply_filters('syndicated_post', $this->post, $this);
1380
1381 // Allow for feed-specific syndicated_post filters.
1382 $this->post = apply_filters(
1383 "syndicated_post_".$this->link->uri(),
1384 $this->post,
1385 $this
1386 );
1387 endif;
1388
1389 // Hook in early to make sure these get inserted if at all possible
1390 add_action(
1391 /*hook=*/ 'transition_post_status',
1392 /*callback=*/ array(&$this, 'add_rss_meta'),
1393 /*priority=*/ -10000, /* very early */
1394 /*arguments=*/ 3
1395 );
1396
1397 $retval = array(1 => 'updated', 2 => 'new');
1398
1399 $ret = false;
1400 if (!$this->filtered() and isset($retval[$freshness])) :
1401 $diag = array(
1402 1 => 'Updating existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"',
1403 2 => 'Inserting new post "'.$this->post['post_title'].'"',
1404 );
1405 FeedWordPress::diagnostic('syndicated_posts', $diag[$freshness]);
1406
1407 $this->insert_post(/*update=*/ ($freshness == 1));
1408
1409 $hook = array( 1 => 'update_syndicated_item', 2 => 'post_syndicated_item' );
1410 do_action($hook[$freshness], $this->wp_id(), $this);
1411
1412 $ret = $retval[$freshness];
1413 endif;
1414
1415 // Remove add_rss_meta hook
1416 remove_action(
1417 /*hook=*/ 'transition_post_status',
1418 /*callback=*/ array(&$this, 'add_rss_meta'),
1419 /*priority=*/ -10000, /* very early */
1420 /*arguments=*/ 3
1421 );
1422
1423 return $ret;
1424 } /* function SyndicatedPost::store () */
1425
1426 function insert_post ($update = false) {
1427 global $wpdb;
1428
1429 $dbpost = $this->normalize_post(/*new=*/ true);
1430 if (!is_null($dbpost)) :
1431 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1432
1433 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1434 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1435
1436 // Kludge to prevent kses filters from stripping the
1437 // content of posts when updating without a logged in
1438 // user who has `unfiltered_html` capability.
1439 add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1440
1441 if ($update and function_exists('get_post_field')) :
1442 // Don't munge status fields that the user may
1443 // have reset manually
1444 $doNotMunge = array('post_status', 'comment_status', 'ping_status');
1445
1446 foreach ($doNotMunge as $field) :
1447 $dbpost[$field] = get_post_field($field, $this->wp_id());
1448 endforeach;
1449 endif;
1450
1451 // WP3's wp_insert_post scans current_user_can() for the
1452 // tax_input, with no apparent way to override. Ugh.
1453 add_action(
1454 /*hook=*/ 'transition_post_status',
1455 /*callback=*/ array(&$this, 'add_terms'),
1456 /*priority=*/ -10001, /* very early */
1457 /*arguments=*/ 3
1458 );
1459
1460 // WP3 appears to override whatever you give it for
1461 // post_modified. Ugh.
1462 add_action(
1463 /*hook=*/ 'transition_post_status',
1464 /*callback=*/ array(&$this, 'fix_post_modified_ts'),
1465 /*priority=*/ -10000, /* very early */
1466 /*arguments=*/ 3
1467 );
1468
1469 if ($update) :
1470 $this->post['ID'] = $this->wp_id();
1471 $dbpost['ID'] = $this->post['ID'];
1472 endif;
1473 $this->_wp_id = wp_insert_post($dbpost);
1474
1475 remove_action(
1476 /*hook=*/ 'transition_post_status',
1477 /*callback=*/ array(&$this, 'add_terms'),
1478 /*priority=*/ -10001, /* very early */
1479 /*arguments=*/ 3
1480 );
1481
1482 remove_action(
1483 /*hook=*/ 'transition_post_status',
1484 /*callback=*/ array(&$this, 'fix_post_modified_ts'),
1485 /*priority=*/ -10000, /* very early */
1486 /*arguments=*/ 3
1487 );
1488
1489 // Turn off ridiculous fucking kludges #1 and #2
1490 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1491 remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1492
1493 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1494 endif;
1495 } /* function SyndicatedPost::insert_post () */
1496
1497 function insert_new () {
1498 $this->insert_post(/*update=*/ false);
1499 } /* SyndicatedPost::insert_new() */
1500
1501 function update_existing () {
1502 $this->insert_post(/*update=*/ true);
1503 } /* SyndicatedPost::update_existing() */
1504
1505 /**
1506 * SyndicatedPost::normalize_post()
1507 *
1508 * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
1509 * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1510 */
1511 function normalize_post ($new = true) {
1512 global $wpdb;
1513
1514 $out = array();
1515
1516 // Why the fuck doesn't wp_insert_post already do this?
1517 foreach ($this->post as $key => $value) :
1518 if (is_string($value)) :
1519 $out[$key] = $wpdb->escape($value);
1520 else :
1521 $out[$key] = $value;
1522 endif;
1523 endforeach;
1524
1525 $fullPost = $out['post_title'].$out['post_content'];
1526 $fullPost .= (isset($out['post_excerpt']) ? $out['post_excerpt'] : '');
1527 if (strlen($fullPost) < 1) :
1528 // FIXME: Option for filtering out empty posts
1529 endif;
1530 if (strlen($out['post_title'])==0) :
1531 $offset = (int) get_option('gmt_offset') * 60 * 60;
1532 if (isset($this->post['meta']['syndication_source'])) :
1533 $source_title = $this->post['meta']['syndication_source'];
1534 else :
1535 $feed_url = parse_url($this->post['meta']['syndication_feed']);
1536 $source_title = $feed_url['host'];
1537 endif;
1538
1539 $out['post_title'] = $source_title
1540 .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1541 // FIXME: Option for what to fill a blank title with...
1542 endif;
1543
1544 return $out;
1545 }
1546
1547 /**
1548 * SyndicatedPost::validate_post_id()
1549 *
1550 * @param array $dbpost An array representing the post we attempted to insert or update
1551 * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1552 */
1553 function validate_post_id ($dbpost, $ns) {
1554 if (is_array($ns)) : $ns = implode('::', $ns);
1555 else : $ns = (string) $ns; endif;
1556
1557 // This should never happen.
1558 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1559 FeedWordPress::critical_bug(
1560 /*name=*/ $ns.'::_wp_id',
1561 /*var =*/ array(
1562 "\$this->_wp_id" => $this->_wp_id,
1563 "\$dbpost" => $dbpost,
1564 "\$this" => $this
1565 ),
1566 /*line # =*/ __LINE__
1567 );
1568 endif;
1569 } /* SyndicatedPost::validate_post_id() */
1570
1571 /**
1572 * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
1573 * meta-data (authorship, etc.) when storing revisions of an updated
1574 * syndicated post.
1575 *
1576 * In their infinite wisdom, the WordPress coders have made it completely
1577 * impossible for a plugin that uses wp_insert_post() to set certain
1578 * meta-data (such as the author) when you store an old revision of an
1579 * updated post. Instead, it uses the WordPress defaults (= currently
1580 * active user ID if the process is running with a user logged in, or
1581 * = #0 if there is no user logged in). This results in bogus authorship
1582 * data for revisions that are syndicated from off the feed, unless we
1583 * use a ridiculous kludge like this to end-run the munging of meta-data
1584 * by _wp_put_post_revision.
1585 *
1586 * @param int $revision_id The revision ID to fix up meta-data
1587 */
1588 function fix_revision_meta ($revision_id) {
1589 global $wpdb;
1590
1591 $post_author = (int) $this->post['post_author'];
1592
1593 $revision_id = (int) $revision_id;
1594 $wpdb->query("
1595 UPDATE $wpdb->posts
1596 SET post_author={$this->post['post_author']}
1597 WHERE post_type = 'revision' AND ID='$revision_id'
1598 ");
1599 } /* SyndicatedPost::fix_revision_meta () */
1600
1601 /**
1602 * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
1603 * an automatic update, that generally means that wp_insert_post() is
1604 * being called under the user credentials of whoever is viewing the
1605 * blog at the time -- usually meaning no user at all. But if WordPress
1606 * gets a wp_insert_post() when current_user_can('unfiltered_html') is
1607 * false, it will run the content of the post through a kses function
1608 * that strips out lots of HTML tags -- notably <object> and some others.
1609 * This causes problems for syndicating (for example) feeds that contain
1610 * YouTube videos. It also produces an unexpected asymmetry between
1611 * automatically-initiated updates and updates initiated manually from
1612 * the WordPress Dashboard (which are usually initiated under the
1613 * credentials of a logged-in admin, and so don't get run through the
1614 * kses function). So, to avoid the whole mess, what we do here is
1615 * just forcibly disable the kses munging for a single syndicated post,
1616 * by restoring the contents of the `post_content` field.
1617 *
1618 * @param string $content The content of the post, after other filters have gotten to it
1619 * @return string The original content of the post, before other filters had a chance to munge it.
1620 */
1621 function avoid_kses_munge ($content) {
1622 global $wpdb;
1623 return $wpdb->escape($this->post['post_content']);
1624 }
1625
1626 /**
1627 * SyndicatedPost::add_terms() -- if FeedWordPress is processing an
1628 * automatic update, that generally means that wp_insert_post() is being
1629 * called under the user credentials of whoever is viewing the blog at
1630 * the time -- which usually means no user at all. But wp_insert_post()
1631 * checks current_user_can() before assigning any of the terms in a
1632 * post's tax_input structure -- which is unfortunate, since
1633 * current_user_can() always returns FALSE when there is no current user
1634 * logged in. Meaning that automatic updates get no terms assigned.
1635 *
1636 * So, wp_insert_post() is not going to do the term assignments for us.
1637 * If you want something done right....
1638 *
1639 * @param string $new_status Unused action parameter.
1640 * @param string $old_status Unused action parameter.
1641 * @param object $post The database record for the post just inserted.
1642 */
1643 function add_terms ($new_status, $old_status, $post) {
1644 if ( is_array($this->post) and isset($this->post['tax_input']) and is_array($this->post['tax_input']) ) :
1645 foreach ($this->post['tax_input'] as $taxonomy => $terms) :
1646 if (is_array($terms)) :
1647 $terms = array_filter($terms); // strip out empties
1648 endif;
1649
1650 wp_set_post_terms($post->ID, $terms, $taxonomy);
1651 endforeach;
1652 endif;
1653 } /* SyndicatedPost::add_terms () */
1654
1655 /**
1656 * SyndicatedPost::fix_post_modified_ts() -- We would like to set
1657 * post_modified and post_modified_gmt to reflect the value of
1658 * <atom:updated> or equivalent elements on the feed. Unfortunately,
1659 * wp_insert_post() refuses to acknowledge explicitly-set post_modified
1660 * fields and overwrites them, either with the post_date (if new) or the
1661 * current timestamp (if updated).
1662 *
1663 * So, wp_insert_post() is not going to do the last-modified assignments
1664 * for us. If you want something done right....
1665 *
1666 * @param string $new_status Unused action parameter.
1667 * @param string $old_status Unused action parameter.
1668 * @param object $post The database record for the post just inserted.
1669 */
1670 function fix_post_modified_ts ($new_status, $old_status, $post) {
1671 global $wpdb;
1672 $wpdb->update( $wpdb->posts, /*data=*/ array(
1673 'post_modified' => $this->post['post_modified'],
1674 'post_modified_gmt' => $this->post['post_modified_gmt'],
1675 ), /*where=*/ array('ID' => $post->ID) );
1676 } /* SyndicatedPost::fix_post_modified_ts () */
1677
1678 /**
1679 * SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
1680 * using the space for custom keys. The set of keys and values to add is
1681 * specified by the keys and values of $post['meta']. This is used to
1682 * store anything that the WordPress user might want to access from a
1683 * template concerning the post's original source that isn't provided
1684 * for by standard WP meta-data (i.e., any interesting data about the
1685 * syndicated post other than author, title, timestamp, categories, and
1686 * guid). It's also used to hook into WordPress's support for
1687 * enclosures.
1688 *
1689 * @param string $new_status Unused action parameter.
1690 * @param string $old_status Unused action parameter.
1691 * @param object $post The database record for the post just inserted.
1692 */
1693 function add_rss_meta ($new_status, $old_status, $post) {
1694 FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}');
1695
1696 global $wpdb;
1697 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
1698 $postId = $post->ID;
1699
1700 // Aggregated posts should NOT send out pingbacks.
1701 // WordPress 2.1-2.2 claim you can tell them not to
1702 // using $post_pingback, but they don't listen, so we
1703 // make sure here.
1704 $result = $wpdb->query("
1705 DELETE FROM $wpdb->postmeta
1706 WHERE post_id='$postId' AND meta_key='_pingme'
1707 ");
1708
1709 foreach ( $this->post['meta'] as $key => $values ) :
1710 $eKey = $wpdb->escape($key);
1711
1712 // If this is an update, clear out the old
1713 // values to avoid duplication.
1714 $result = $wpdb->query("
1715 DELETE FROM $wpdb->postmeta
1716 WHERE post_id='$postId' AND meta_key='$eKey'
1717 ");
1718
1719 // Allow for either a single value or an array
1720 if (!is_array($values)) $values = array($values);
1721 foreach ( $values as $value ) :
1722 FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($value, /*no newlines=*/ true));
1723 add_post_meta($postId, $key, $value, /*unique=*/ false);
1724 endforeach;
1725 endforeach;
1726 endif;
1727 } /* SyndicatedPost::add_rss_meta () */
1728
1729 /**
1730 * SyndicatedPost::author_id (): get the ID for an author name from
1731 * the feed. Create the author if necessary.
1732 *
1733 * @param string $unfamiliar_author
1734 *
1735 * @return NULL|int The numeric ID of the author to attribute the post to
1736 * NULL if the post should be filtered out.
1737 */
1738 function author_id ($unfamiliar_author = 'create') {
1739 global $wpdb;
1740
1741 $a = $this->named['author'];
1742
1743 $source = $this->source();
1744 $forbidden = apply_filters('feedwordpress_forbidden_author_names',
1745 array('admin', 'administrator', 'www', 'root'));
1746
1747 $candidates = array();
1748 $candidates[] = $a['name'];
1749 if (!is_null($source)) : $candidates[] = $source['title']; endif;
1750 $candidates[] = $this->link->name(/*fromFeed=*/ true);
1751 $candidates[] = $this->link->name(/*fromFeed=*/ false);
1752 if (strlen($this->link->homepage()) > 0) : $candidates[] = feedwordpress_display_url($this->link->homepage()); endif;
1753 $candidates[] = feedwordpress_display_url($this->link->uri());
1754 $candidates[] = 'unknown author';
1755
1756 $author = NULL;
1757 while (is_null($author) and ($candidate = each($candidates))) :
1758 if (!is_null($candidate['value'])
1759 and (strlen(trim($candidate['value'])) > 0)
1760 and !in_array(strtolower(trim($candidate['value'])), $forbidden)) :
1761 $author = $candidate['value'];
1762 endif;
1763 endwhile;
1764
1765 $email = (isset($a['email']) ? $a['email'] : NULL);
1766 $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL);
1767
1768
1769 $hostUrl = $this->link->homepage();
1770 if (is_null($hostUrl) or (strlen($hostUrl) < 0)) :
1771 $hostUrl = $this->link->uri();
1772 endif;
1773
1774 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
1775 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
1776 $test_email = $email;
1777 else :
1778 $test_email = NULL;
1779 endif;
1780
1781 // Never can be too careful...
1782 $login = sanitize_user($author, /*strict=*/ true);
1783
1784 // Possible for, e.g., foreign script author names
1785 if (strlen($login) < 1) :
1786 // No usable characters in author name for a login.
1787 // (Sometimes results from, e.g., foreign scripts.)
1788 //
1789 // We just need *something* in Western alphanumerics,
1790 // so let's try the domain name.
1791 //
1792 // Uniqueness will be guaranteed below if necessary.
1793
1794 $url = parse_url($hostUrl);
1795
1796 $login = sanitize_user($url['host'], /*strict=*/ true);
1797 if (strlen($login) < 1) :
1798 // This isn't working. Frak it.
1799 $login = 'syndicated';
1800 endif;
1801 endif;
1802
1803 $login = apply_filters('pre_user_login', $login);
1804
1805 $nice_author = sanitize_title($author);
1806 $nice_author = apply_filters('pre_user_nicename', $nice_author);
1807
1808 $reg_author = $wpdb->escape(preg_quote($author));
1809 $author = $wpdb->escape($author);
1810 $email = $wpdb->escape($email);
1811 $test_email = $wpdb->escape($test_email);
1812 $authorUrl = $wpdb->escape($authorUrl);
1813
1814 // Check for an existing author rule....
1815 if (isset($this->link->settings['map authors']['name']['*'])) :
1816 $author_rule = $this->link->settings['map authors']['name']['*'];
1817 elseif (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
1818 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
1819 else :
1820 $author_rule = NULL;
1821 endif;
1822
1823 // User name is mapped to a particular author. If that author ID exists, use it.
1824 if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
1825 $id = (int) $author_rule;
1826
1827 // User name is filtered out
1828 elseif ('filter' == $author_rule) :
1829 $id = NULL;
1830
1831 else :
1832 // Check the database for an existing author record that might fit
1833
1834 // First try the user core data table.
1835 $id = $wpdb->get_var(
1836 "SELECT ID FROM $wpdb->users
1837 WHERE TRIM(LCASE(display_name)) = TRIM(LCASE('$author'))
1838 OR (
1839 LENGTH(TRIM(LCASE(user_email))) > 0
1840 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1841 )");
1842
1843 // If that fails, look for aliases in the user meta data table
1844 if (is_null($id)) :
1845 $id = $wpdb->get_var(
1846 "SELECT user_id FROM $wpdb->usermeta
1847 WHERE
1848 (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
1849 OR (
1850 meta_key = 'description'
1851 AND TRIM(LCASE(meta_value))
1852 RLIKE CONCAT(
1853 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1854 TRIM(LCASE('$reg_author')),
1855 '( |\\t|\\r)*(\\n|\$)'
1856 )
1857 )
1858 ");
1859 endif;
1860
1861 // ... if you don't find one, then do what you need to do
1862 if (is_null($id)) :
1863 if ($unfamiliar_author === 'create') :
1864 $userdata = array();
1865
1866 // WordPress 3 is going to pitch a fit if we attempt to register
1867 // more than one user account with an empty e-mail address, so we
1868 // need *something* here. Ugh.
1869 if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
1870 $url = parse_url($hostUrl);
1871 $email = $nice_author.'@'.$url['host'];
1872 endif;
1873
1874 #-- user table data
1875 $userdata['ID'] = NULL; // new user
1876 $userdata['user_login'] = $login;
1877 $userdata['user_nicename'] = $nice_author;
1878 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
1879 $userdata['user_email'] = $email;
1880 $userdata['user_url'] = $authorUrl;
1881 $userdata['display_name'] = $author;
1882 $userdata['role'] = 'contributor';
1883
1884 do { // Keep trying until you get it right. Or until PHP crashes, I guess.
1885 $id = wp_insert_user($userdata);
1886 if (is_wp_error($id)) :
1887 $codes = $id->get_error_code();
1888 switch ($codes) :
1889 case 'empty_user_login' :
1890 case 'existing_user_login' :
1891 // Add a random disambiguator
1892 $userdata['user_login'] .= substr(md5(uniqid(microtime())), 0, 6);
1893 break;
1894 case 'existing_user_email' :
1895 // No disassemble!
1896 $parts = explode('@', $userdata['user_email'], 2);
1897
1898 // Add a random disambiguator as a gmail-style username extension
1899 $parts[0] .= '+'.substr(md5(uniqid(microtime())), 0, 6);
1900
1901 // Reassemble
1902 $userdata['user_email'] = $parts[0].'@'.$parts[1];
1903 break;
1904 endswitch;
1905 endif;
1906 } while (is_wp_error($id));
1907 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
1908 $id = (int) $unfamiliar_author;
1909 elseif ($unfamiliar_author === 'default') :
1910 $id = 1;
1911 endif;
1912 endif;
1913 endif;
1914
1915 if ($id) :
1916 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
1917
1918 // Multisite: Check whether the author has been recorded
1919 // on *this* blog before. If not, put her down as a
1920 // Contributor for *this* blog.
1921 $user = new WP_User((int) $id);
1922 if (empty($user->roles)) :
1923 $user->add_role('contributor');
1924 endif;
1925 endif;
1926 return $id;
1927 } // function SyndicatedPost::author_id ()
1928
1929 /**
1930 * category_ids: look up (and create) category ids from a list of categories
1931 *
1932 * @param array $cats
1933 * @param string $unfamiliar_category
1934 * @param array|null $taxonomies
1935 * @return array
1936 */
1937 function category_ids ($cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
1938 $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
1939 $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
1940
1941 $catTax = 'category';
1942
1943 if (is_null($taxonomies)) :
1944 $taxonomies = array('category');
1945 endif;
1946
1947 // We need to normalize whitespace because (1) trailing
1948 // whitespace can cause PHP and MySQL not to see eye to eye on
1949 // VARCHAR comparisons for some versions of MySQL (cf.
1950 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1951 // because I doubt most people want to make a semantic
1952 // distinction between 'Computers' and 'Computers '
1953 $cats = array_map('trim', $cats);
1954
1955 $terms = array();
1956 foreach ($taxonomies as $tax) :
1957 $terms[$tax] = array();
1958 endforeach;
1959
1960 foreach ($cats as $cat_name) :
1961 if (preg_match('/^{([^#}]*)#([0-9]+)}$/', $cat_name, $backref)) :
1962 $cat_id = (int) $backref[2];
1963 $tax = $backref[1];
1964 if (strlen($tax) < 1) :
1965 $tax = $catTax;
1966 endif;
1967
1968 $term = term_exists($cat_id, $tax);
1969 if (!is_wp_error($term) and !!$term) :
1970 if (!isset($terms[$tax])) :
1971 $terms[$tax] = array();
1972 endif;
1973 $terms[$tax][] = $cat_id;
1974 endif;
1975 elseif (strlen($cat_name) > 0) :
1976 $familiar = false;
1977 foreach ($taxonomies as $tax) :
1978 if ($tax!='category' or strtolower($cat_name)!='uncategorized') :
1979 $term = term_exists($cat_name, $tax);
1980 if (!is_wp_error($term) and !!$term) :
1981 $familiar = true;
1982
1983 if (is_array($term)) :
1984 $term_id = (int) $term['term_id'];
1985 else :
1986 $term_id = (int) $term;
1987 endif;
1988
1989 if (!isset($terms[$tax])) :
1990 $terms[$tax] = array();
1991 endif;
1992 $terms[$tax][] = $term_id;
1993 break; // We're done here.
1994 endif;
1995 endif;
1996 endforeach;
1997
1998 if (!$familiar) :
1999 if ('tag'==$unfamiliar_category) :
2000 $unfamiliar_category = 'create:post_tag';
2001 endif;
2002
2003 if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
2004 $tax = $catTax; // Default
2005 if (isset($ref[2]) and strlen($ref[2]) > 2) :
2006 $tax = $ref[2];
2007 endif;
2008 $term = wp_insert_term($cat_name, $tax);
2009 if (is_wp_error($term)) :
2010 FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
2011 else :
2012 if (!isset($terms[$tax])) :
2013 $terms[$tax] = array();
2014 endif;
2015 $terms[$tax][] = (int) $term['term_id'];
2016 endif;
2017 endif;
2018 endif;
2019 endif;
2020 endforeach;
2021
2022 $filtersOn = $allowFilters;
2023 if ($allowFilters) :
2024 $filters = array_filter(
2025 $this->link->setting('match/filter', 'match_filter', array()),
2026 'remove_dummy_zero'
2027 );
2028 $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
2029 endif;
2030
2031 // Check for filter conditions
2032 foreach ($terms as $tax => $term_ids) :
2033 if ($filtersOn
2034 and (count($term_ids)==0)
2035 and in_array($tax, $filters)) :
2036 $terms = NULL; // Drop the post
2037 break;
2038 else :
2039 $terms[$tax] = array_unique($term_ids);
2040 endif;
2041 endforeach;
2042
2043 if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
2044 $terms = end($terms);
2045 endif;
2046 return $terms;
2047 } // function SyndicatedPost::category_ids ()
2048
2049 function use_api ($tag) {
2050 global $wp_db_version;
2051 switch ($tag) :
2052 case 'wp_insert_post':
2053 // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
2054 // In 1.5 it was such a resource hog it would make PHP segfault on big updates
2055 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
2056 break;
2057 case 'post_status_pending':
2058 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
2059 break;
2060 endswitch;
2061 return $ret;
2062 } // function SyndicatedPost::use_api ()
2063
2064 } /* class SyndicatedPost */
2065
2066