PluginProbe
FeedWordPress / 2010.0903
FeedWordPress v2010.0903
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 2010.0903, at syndicatedpost.class.php

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