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

1,303 lines 46.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 class SyndicatedPost {
3 var $item = null;
4
5 var $link = null;
6 var $feed = null;
7 var $feedmeta = null;
8
9 var $post = array ();
10
11 var $_freshness = null;
12 var $_wp_id = null;
13
14 function SyndicatedPost ($item, $link) {
15 global $wpdb;
16
17 $this->link = $link;
18 $feedmeta = $link->settings;
19 $feed = $link->magpie;
20
21 # This is ugly as all hell. I'd like to use apply_filters()'s
22 # alleged support for a variable argument count, but this seems
23 # to have been broken in WordPress 1.5. It'll be fixed somehow
24 # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
25 # the board here.
26 #
27 # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
28 global $fwp_channel, $fwp_feedmeta;
29 $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
30
31 $this->feed = $feed;
32 $this->feedmeta = $feedmeta;
33
34 $this->item = $item;
35 $this->item = apply_filters('syndicated_item', $this->item, $this);
36
37 # Filters can halt further processing by returning NULL
38 if (is_null($this->item)) :
39 $this->post = NULL;
40 else :
41 # Note that nothing is run through $wpdb->escape() here.
42 # That's deliberate. The escaping is done at the point
43 # of insertion, not here, to avoid double-escaping and
44 # to avoid screwing with syndicated_post filters
45
46 $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
47
48 // This just gives us an alphanumeric representation of
49 // the author. We will look up (or create) the numeric
50 // ID for the author in SyndicatedPost::add()
51 $this->post['named']['author'] = apply_filters('syndicated_item_author', $this->author(), $this);
52
53 # Identify content and sanitize it.
54 # ---------------------------------
55 if (isset($this->item['atom_content'])) :
56 $content = $this->item['atom_content'];
57 elseif (isset($this->item['xhtml']['body'])) :
58 $content = $this->item['xhtml']['body'];
59 elseif (isset($this->item['xhtml']['div'])) :
60 $content = $this->item['xhtml']['div'];
61 elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
62 $content = $this->item['content']['encoded'];
63 else:
64 $content = $this->item['description'];
65 endif;
66 $this->post['post_content'] = apply_filters('syndicated_item_content', $content, $this);
67
68 # Identify and sanitize excerpt
69 $excerpt = NULL;
70 if ( isset($this->item['description']) and $this->item['description'] ) :
71 $excerpt = $this->item['description'];
72 elseif ( isset($content) and $content ) :
73 $excerpt = strip_tags($content);
74 if (strlen($excerpt) > 255) :
75 $excerpt = substr($excerpt,0,252).'...';
76 endif;
77 endif;
78 $excerpt = apply_filters('syndicated_item_excerpt', $excerpt, $this);
79
80 if (!is_null($excerpt)):
81 $this->post['post_excerpt'] = $excerpt;
82 endif;
83
84 // This is unnecessary if we use wp_insert_post
85 if (!$this->use_api('wp_insert_post')) :
86 $this->post['post_name'] = sanitize_title($this->post['post_title']);
87 endif;
88
89 $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
90 $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
91 $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
92
93 // Dealing with timestamps in WordPress is so fucking fucked.
94 $offset = (int) get_option('gmt_offset') * 60 * 60;
95 $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
96 $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
97 $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
98 $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
99
100 // Use feed-level preferences or the global default.
101 $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
102 $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
103 $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
104
105 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
106 $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
107
108 // User-supplied custom settings to apply to each post. Do first so that FWP-generated custom settings will overwrite if necessary; thus preventing any munging
109 $default_custom_settings = get_option('feedwordpress_custom_settings');
110 if ($default_custom_settings and !is_array($default_custom_settings)) :
111 $default_custom_settings = unserialize($default_custom_settings);
112 endif;
113 if (!is_array($default_custom_settings)) :
114 $default_custom_settings = array();
115 endif;
116
117 $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null);
118 if ($custom_settings and !is_array($custom_settings)) :
119 $custom_settings = unserialize($custom_settings);
120 endif;
121 if (!is_array($custom_settings)) :
122 $custom_settings = array();
123 endif;
124 $this->post['meta'] = array_merge($default_custom_settings, $custom_settings);
125
126 // RSS 2.0 / Atom 1.0 enclosure support
127 if ( isset($this->item['enclosure#']) ) :
128 for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
129 $eid = (($i > 1) ? "#{$id}" : "");
130 $this->post['meta']['enclosure'][] =
131 apply_filters('syndicated_item_enclosure_url', $this->item["enclosure{$eid}@url"], $this)."\n".
132 apply_filters('syndicated_item_enclosure_length', $this->item["enclosure{$eid}@length"], $this)."\n".
133 apply_filters('syndicated_item_enclosure_type', $this->item["enclosure{$eid}@type"], $this);
134 endfor;
135 endif;
136
137 // In case you want to point back to the blog this was syndicated from
138 if (isset($this->feed->channel['title'])) :
139 $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
140 endif;
141
142 if (isset($this->feed->channel['link'])) :
143 $this->post['meta']['syndication_source_uri'] = apply_filters('syndicated_item_source_link', $this->feed->channel['link'], $this);
144 endif;
145
146 // Make use of atom:source data, if present in an aggregated feed
147 if (isset($this->item['source_title'])) :
148 $this->post['meta']['syndication_source_original'] = $this->item['source_title'];
149 endif;
150
151 if (isset($this->item['source_link'])) :
152 $this->post['meta']['syndication_source_uri_original'] = $this->item['source_link'];
153 endif;
154
155 if (isset($this->item['source_id'])) :
156 $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
157 endif;
158
159 // Store information on human-readable and machine-readable comment URIs
160 if (isset($this->item['comments'])) :
161 $this->post['meta']['rss:comments'] = apply_filters('syndicated_item_comments', $this->item['comments']);
162 endif;
163
164 // RSS 2.0 comment feeds extension
165 if (isset($this->item['wfw']['commentrss'])) :
166 $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
167 endif;
168
169 // Atom 1.0 comment feeds link-rel
170 if (isset($this->item['link_replies'])) :
171 // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
172 $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
173 for ($i = 1; $i <= $N; $i++) :
174 $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
175 if (isset($this->item[$currentElement.'@type'])
176 and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
177 $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item[$currentElement]);
178 endif;
179 endfor;
180 endif;
181
182 // Store information to identify the feed that this came from
183 if (isset($this->feedmeta['link/uri'])) :
184 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
185 endif;
186 if (isset($this->feedmeta['link/id'])) :
187 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
188 endif;
189
190 if (isset($this->item['source_link_self'])) :
191 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
192 endif;
193
194 // In case you want to know the external permalink...
195 if (isset($this->item['link'])) :
196 $permalink = $this->item['link'];
197
198 // No <link> element. See if this feed has <guid isPermalink="true"> ....
199 elseif (isset($this->item['guid'])) :
200 if (isset($this->item['guid@ispermalink']) and strtolower(trim($this->item['guid@ispermalink'])) != 'false') :
201 $permalink = $this->item['guid'];
202 endif;
203 endif;
204
205 $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $permalink);
206
207 // Store a hash of the post content for checking whether something needs to be updated
208 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
209
210 // Feed-by-feed options for author and category creation
211 $this->post['named']['unfamiliar']['author'] = (isset($this->feedmeta['unfamiliar author']) ? $this->feedmeta['unfamiliar author'] : null);
212 $this->post['named']['unfamiliar']['category'] = (isset($this->feedmeta['unfamiliar category']) ? $this->feedmeta['unfamiliar category'] : null);
213
214 // Categories: start with default categories, if any
215 $fc = get_option("feedwordpress_syndication_cats");
216 if ($fc) :
217 $this->post['named']['preset/category'] = explode("\n", $fc);
218 else :
219 $this->post['named']['preset/category'] = array();
220 endif;
221
222 if (isset($this->feedmeta['cats']) and is_array($this->feedmeta['cats'])) :
223 $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
224 endif;
225
226 // Now add categories from the post, if we have 'em
227 $this->post['named']['category'] = array();
228 if ( isset($this->item['category#']) ) :
229 for ($i = 1; $i <= $this->item['category#']; $i++) :
230 $cat_idx = (($i > 1) ? "#{$i}" : "");
231 $cat = $this->item["category{$cat_idx}"];
232
233 if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
234 $pcre = "\007".$this->feedmeta['cat_split']."\007";
235 $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
236 else :
237 $this->post['named']['category'][] = $cat;
238 endif;
239 endfor;
240 endif;
241 $this->post['named']['category'] = apply_filters('syndicated_item_categories', $this->post['named']['category'], $this);
242
243 // Tags: start with default tags, if any
244 $ft = get_option("feedwordpress_syndication_tags");
245 if ($ft) :
246 $this->post['tags_input'] = explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft);
247 else :
248 $this->post['tags_input'] = array();
249 endif;
250
251 if (isset($this->feedmeta['tags']) and is_array($this->feedmeta['tags'])) :
252 $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
253 endif;
254 $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
255 endif;
256 } // SyndicatedPost::SyndicatedPost()
257
258 function filtered () {
259 return is_null($this->post);
260 }
261
262 function freshness () {
263 global $wpdb;
264
265 if ($this->filtered()) : // This should never happen.
266 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
267 endif;
268
269 if (is_null($this->_freshness)) :
270 $guid = $wpdb->escape($this->guid());
271
272 $result = $wpdb->get_row("
273 SELECT id, guid, post_modified_gmt
274 FROM $wpdb->posts WHERE guid='$guid'
275 ");
276
277 if (!$result) :
278 $this->_freshness = 2; // New content
279 else:
280 $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
281 if (count($stored_update_hashes) > 0) :
282 $stored_update_hash = $stored_update_hashes[0];
283 $update_hash_changed = ($stored_update_hash != $this->update_hash());
284 else :
285 $update_hash_changed = false;
286 endif;
287
288 preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
289
290 $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
291 $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
292
293 $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
294 $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
295 $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
296
297 // Check timestamps...
298 $updated = (
299 !is_null($updated_ts)
300 and ($updated_ts > $last_rev_ts)
301 );
302
303
304 // Or the hash...
305 $updated = ($updated or $update_hash_changed);
306
307 // But only if the post is not frozen.
308 $updated = (
309 $updated
310 and !$frozen_post
311 and !$frozen_feed
312 );
313
314 if ($updated) :
315 $this->_freshness = 1; // Updated content
316 $this->_wp_id = $result->id;
317 else :
318 $this->_freshness = 0; // Same old, same old
319 $this->_wp_id = $result->id;
320 endif;
321 endif;
322 endif;
323 return $this->_freshness;
324 }
325
326 function wp_id () {
327 if ($this->filtered()) : // This should never happen.
328 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
329 endif;
330
331 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
332 $fresh = $this->freshness(); // sets WP DB id in the process
333 endif;
334 return $this->_wp_id;
335 }
336
337 function store () {
338 global $wpdb;
339
340 if ($this->filtered()) : // This should never happen.
341 FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
342 endif;
343
344 $freshness = $this->freshness();
345 if ($freshness > 0) :
346 # -- Look up, or create, numeric ID for author
347 $this->post['post_author'] = $this->author_id (
348 FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
349 );
350
351 if (is_null($this->post['post_author'])) :
352 $this->post = NULL;
353 endif;
354 endif;
355
356 if (!$this->filtered() and $freshness > 0) :
357 # -- Look up, or create, numeric ID for categories
358 list($pcats, $ptags) = $this->category_ids (
359 $this->post['named']['category'],
360 FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
361 /*tags_too=*/ true
362 );
363
364 $this->post['post_category'] = $pcats;
365 $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
366
367 if (is_null($this->post['post_category'])) :
368 // filter mode on, no matching categories; drop the post
369 $this->post = NULL;
370 else :
371 // filter mode off or at least one match; now add on the feed and global presets
372 $this->post['post_category'] = array_merge (
373 $this->post['post_category'],
374 $this->category_ids (
375 $this->post['named']['preset/category'],
376 'default'
377 )
378 );
379
380 if (count($this->post['post_category']) < 1) :
381 $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
382 endif;
383 endif;
384 endif;
385
386 if (!$this->filtered() and $freshness > 0) :
387 unset($this->post['named']);
388 $this->post = apply_filters('syndicated_post', $this->post, $this);
389 endif;
390
391 if (!$this->filtered() and $freshness == 2) :
392 // The item has not yet been added. So let's add it.
393 $this->insert_new();
394 $this->add_rss_meta();
395 do_action('post_syndicated_item', $this->wp_id(), $this);
396
397 $ret = 'new';
398 elseif (!$this->filtered() and $freshness == 1) :
399 $this->post['ID'] = $this->wp_id();
400 $this->update_existing();
401 $this->add_rss_meta();
402 do_action('update_syndicated_item', $this->wp_id(), $this);
403
404 $ret = 'updated';
405 else :
406 $ret = false;
407 endif;
408
409 return $ret;
410 } // function SyndicatedPost::store ()
411
412 function insert_new () {
413 global $wpdb, $wp_db_version;
414
415 $dbpost = $this->normalize_post(/*new=*/ true);
416 if (!is_null($dbpost)) :
417 if ($this->use_api('wp_insert_post')) :
418 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
419
420 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
421 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
422
423 // Kludge to prevent kses filters from stripping the
424 // content of posts when updating without a logged in
425 // user who has `unfiltered_html` capability.
426 add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
427
428 $this->_wp_id = wp_insert_post($dbpost);
429
430 // Turn off ridiculous fucking kludges #1 and #2
431 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
432 remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
433
434 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
435
436 // Unfortunately, as of WordPress 2.3, wp_insert_post()
437 // *still* offers no way to use a guid of your choice,
438 // and munges your post modified timestamp, too.
439 $result = $wpdb->query("
440 UPDATE $wpdb->posts
441 SET
442 guid='{$dbpost['guid']}',
443 post_modified='{$dbpost['post_modified']}',
444 post_modified_gmt='{$dbpost['post_modified_gmt']}'
445 WHERE ID='{$this->_wp_id}'
446 ");
447 else :
448 # The right way to do this is the above. But, alas,
449 # in earlier versions of WordPress, wp_insert_post has
450 # too much behavior (mainly related to pings) that can't
451 # be overridden. In WordPress 1.5, it's enough of a
452 # resource hog to make PHP segfault after inserting
453 # 50-100 posts. This can get pretty annoying, especially
454 # if you are trying to update your feeds for the first
455 # time.
456
457 $result = $wpdb->query("
458 INSERT INTO $wpdb->posts
459 SET
460 guid = '{$dbpost['guid']}',
461 post_author = '{$dbpost['post_author']}',
462 post_date = '{$dbpost['post_date']}',
463 post_date_gmt = '{$dbpost['post_date_gmt']}',
464 post_content = '{$dbpost['post_content']}',"
465 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
466 post_title = '{$dbpost['post_title']}',
467 post_name = '{$dbpost['post_name']}',
468 post_modified = '{$dbpost['post_modified']}',
469 post_modified_gmt = '{$dbpost['post_modified_gmt']}',
470 comment_status = '{$dbpost['comment_status']}',
471 ping_status = '{$dbpost['ping_status']}',
472 post_status = '{$dbpost['post_status']}'
473 ");
474 $this->_wp_id = $wpdb->insert_id;
475
476 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
477
478 // WordPress 1.5.x - 2.0.x
479 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
480
481 // Since we are not going through official channels, we need to
482 // manually tell WordPress that we've published a new post.
483 // We need to make sure to do this in order for FeedWordPress
484 // to play well with the staticize-reloaded plugin (something
485 // that a large aggregator website is going to *want* to be
486 // able to use).
487 do_action('publish_post', $this->_wp_id);
488 endif;
489 endif;
490 } /* SyndicatedPost::insert_new() */
491
492 function update_existing () {
493 global $wpdb;
494
495 // Why the fuck doesn't wp_insert_post already do this?
496 $dbpost = $this->normalize_post(/*new=*/ false);
497 if (!is_null($dbpost)) :
498 if ($this->use_api('wp_insert_post')) :
499 $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
500
501 // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
502 add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
503
504 // Kludge to prevent kses filters from stripping the
505 // content of posts when updating without a logged in
506 // user who has `unfiltered_html` capability.
507 add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
508
509 // Don't munge status fields that the user may have reset manually
510 if (function_exists('get_post_field')) :
511 $doNotMunge = array('post_status', 'comment_status', 'ping_status');
512 foreach ($doNotMunge as $field) :
513 $dbpost[$field] = get_post_field($field, $this->wp_id());
514 endforeach;
515 endif;
516
517 $this->_wp_id = wp_insert_post($dbpost);
518
519 // Turn off ridiculous fucking kludges #1 and #2
520 remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
521 remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
522
523 $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
524
525 // Unfortunately, as of WordPress 2.3, wp_insert_post()
526 // munges your post modified timestamp.
527 $result = $wpdb->query("
528 UPDATE $wpdb->posts
529 SET
530 post_modified='{$dbpost['post_modified']}',
531 post_modified_gmt='{$dbpost['post_modified_gmt']}'
532 WHERE ID='{$this->_wp_id}'
533 ");
534 else :
535
536 $result = $wpdb->query("
537 UPDATE $wpdb->posts
538 SET
539 post_author = '{$dbpost['post_author']}',
540 post_content = '{$dbpost['post_content']}',"
541 .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
542 post_title = '{$dbpost['post_title']}',
543 post_name = '{$dbpost['post_name']}',
544 post_modified = '{$dbpost['post_modified']}',
545 post_modified_gmt = '{$dbpost['post_modified_gmt']}'
546 WHERE guid='{$dbpost['guid']}'
547 ");
548
549 // WordPress 2.1.x and up
550 if (function_exists('wp_set_post_categories')) :
551 wp_set_post_categories($this->wp_id(), $this->post['post_category']);
552 // WordPress 1.5.x - 2.0.x
553 elseif (function_exists('wp_set_post_cats')) :
554 wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
555 // This should never happen.
556 else :
557 FeedWordPress::critical_bug(__CLASS__.'::'.__FUNCTION.'(): no post categorizing function', array("dbpost" => $dbpost, "this" => $this), __LINE__);
558 endif;
559
560 // Since we are not going through official channels, we need to
561 // manually tell WordPress that we've published a new post.
562 // We need to make sure to do this in order for FeedWordPress
563 // to play well with the staticize-reloaded plugin (something
564 // that a large aggregator website is going to *want* to be
565 // able to use).
566 do_action('edit_post', $this->post['ID']);
567 endif;
568 endif;
569 } /* SyndicatedPost::update_existing() */
570
571 /**
572 * SyndicatedPost::normalize_post()
573 *
574 * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
575 * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
576 */
577 function normalize_post ($new = true) {
578 global $wpdb;
579
580 $out = array();
581
582 // Why the fuck doesn't wp_insert_post already do this?
583 foreach ($this->post as $key => $value) :
584 if (is_string($value)) :
585 $out[$key] = $wpdb->escape($value);
586 else :
587 $out[$key] = $value;
588 endif;
589 endforeach;
590
591 if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
592 // FIXME: Option for filtering out empty posts
593 endif;
594 if (strlen($out['post_title'])==0) :
595 $offset = (int) get_option('gmt_offset') * 60 * 60;
596 $out['post_title'] =
597 $this->post['meta']['syndication_source']
598 .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
599 // FIXME: Option for what to fill a blank title with...
600 endif;
601
602 return $out;
603 }
604
605 /**
606 * SyndicatedPost::validate_post_id()
607 *
608 * @param array $dbpost An array representing the post we attempted to insert or update
609 * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
610 */
611 function validate_post_id ($dbpost, $ns) {
612 if (is_array($ns)) : $ns = implode('::', $ns);
613 else : $ns = (string) $ns; endif;
614
615 // This should never happen.
616 if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
617 FeedWordPress::critical_bug(
618 /*name=*/ $ns.'::_wp_id',
619 /*var =*/ array(
620 "\$this->_wp_id" => $this->_wp_id,
621 "\$dbpost" => $dbpost,
622 "\$this" => $this
623 ),
624 /*line # =*/ __LINE__
625 );
626 endif;
627 } /* SyndicatedPost::validate_post_id() */
628
629 /**
630 * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
631 * meta-data (authorship, etc.) when storing revisions of an updated
632 * syndicated post.
633 *
634 * In their infinite wisdom, the WordPress coders have made it completely
635 * impossible for a plugin that uses wp_insert_post() to set certain
636 * meta-data (such as the author) when you store an old revision of an
637 * updated post. Instead, it uses the WordPress defaults (= currently
638 * active user ID if the process is running with a user logged in, or
639 * = #0 if there is no user logged in). This results in bogus authorship
640 * data for revisions that are syndicated from off the feed, unless we
641 * use a ridiculous kludge like this to end-run the munging of meta-data
642 * by _wp_put_post_revision.
643 *
644 * @param int $revision_id The revision ID to fix up meta-data
645 */
646 function fix_revision_meta ($revision_id) {
647 global $wpdb;
648
649 $post_author = (int) $this->post['post_author'];
650
651 $revision_id = (int) $revision_id;
652 $wpdb->query("
653 UPDATE $wpdb->posts
654 SET post_author={$this->post['post_author']}
655 WHERE post_type = 'revision' AND ID='$revision_id'
656 ");
657 } /* SyndicatedPost::fix_revision_meta () */
658
659 /**
660 * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
661 * an automatic update, that generally means that wp_insert_post() is
662 * being called under the user credentials of whoever is viewing the
663 * blog at the time -- usually meaning no user at all. But if WordPress
664 * gets a wp_insert_post() when current_user_can('unfiltered_html') is
665 * false, it will run the content of the post through a kses function
666 * that strips out lots of HTML tags -- notably <object> and some others.
667 * This causes problems for syndicating (for example) feeds that contain
668 * YouTube videos. It also produces an unexpected asymmetry between
669 * automatically-initiated updates and updates initiated manually from
670 * the WordPress Dashboard (which are usually initiated under the
671 * credentials of a logged-in admin, and so don't get run through the
672 * kses function). So, to avoid the whole mess, what we do here is
673 * just forcibly disable the kses munging for a single syndicated post,
674 * by restoring the contents of the `post_content` field.
675 *
676 * @param string $content The content of the post, after other filters have gotten to it
677 * @return string The original content of the post, before other filters had a chance to munge it.
678 */
679 function avoid_kses_munge ($content) {
680 global $wpdb;
681 return $wpdb->escape($this->post['post_content']);
682 }
683
684 // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
685 // using the space for custom keys. The set of keys and values to add is
686 // specified by the keys and values of $post['meta']. This is used to
687 // store anything that the WordPress user might want to access from a
688 // template concerning the post's original source that isn't provided
689 // for by standard WP meta-data (i.e., any interesting data about the
690 // syndicated post other than author, title, timestamp, categories, and
691 // guid). It's also used to hook into WordPress's support for
692 // enclosures.
693 function add_rss_meta () {
694 global $wpdb;
695 if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
696 $postId = $this->wp_id();
697
698 // Aggregated posts should NOT send out pingbacks.
699 // WordPress 2.1-2.2 claim you can tell them not to
700 // using $post_pingback, but they don't listen, so we
701 // make sure here.
702 $result = $wpdb->query("
703 DELETE FROM $wpdb->postmeta
704 WHERE post_id='$postId' AND meta_key='_pingme'
705 ");
706
707 foreach ( $this->post['meta'] as $key => $values ) :
708
709 $key = $wpdb->escape($key);
710
711 // If this is an update, clear out the old
712 // values to avoid duplication.
713 $result = $wpdb->query("
714 DELETE FROM $wpdb->postmeta
715 WHERE post_id='$postId' AND meta_key='$key'
716 ");
717
718 // Allow for either a single value or an array
719 if (!is_array($values)) $values = array($values);
720 foreach ( $values as $value ) :
721 $value = $wpdb->escape($value);
722 $result = $wpdb->query("
723 INSERT INTO $wpdb->postmeta
724 SET
725 post_id='$postId',
726 meta_key='$key',
727 meta_value='$value'
728 ");
729 if (!$result) :
730 $err = mysql_error();
731 if (FEEDWORDPRESS_DEBUG) :
732 echo "[DEBUG:".date('Y-m-d H:i:S')."][feedwordpress]: post metadata insertion FAILED for field '$key' := '$value': [$err]";
733 endif;
734 endif;
735 endforeach;
736 endforeach;
737 endif;
738 } /* SyndicatedPost::add_rss_meta () */
739
740 // SyndicatedPost::author_id (): get the ID for an author name from
741 // the feed. Create the author if necessary.
742 function author_id ($unfamiliar_author = 'create') {
743 global $wpdb;
744
745 $a = $this->author();
746 $author = $a['name'];
747 $email = (isset($a['email']) ? $a['email'] : NULL);
748 $url = (isset($a['uri']) ? $a['uri'] : NULL);
749
750 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
751 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
752 $test_email = $email;
753 else :
754 $test_email = NULL;
755 endif;
756
757 // Never can be too careful...
758 $login = sanitize_user($author, /*strict=*/ true);
759 $login = apply_filters('pre_user_login', $login);
760
761 $nice_author = sanitize_title($author);
762 $nice_author = apply_filters('pre_user_nicename', $nice_author);
763
764 $reg_author = $wpdb->escape(preg_quote($author));
765 $author = $wpdb->escape($author);
766 $email = $wpdb->escape($email);
767 $test_email = $wpdb->escape($test_email);
768 $url = $wpdb->escape($url);
769
770 // Check for an existing author rule....
771 if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
772 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
773 else :
774 $author_rule = NULL;
775 endif;
776
777 // User name is mapped to a particular author. If that author ID exists, use it.
778 if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
779 $id = (int) $author_rule;
780
781 // User name is filtered out
782 elseif ('filter' == $author_rule) :
783 $id = NULL;
784
785 else :
786 // Check the database for an existing author record that might fit
787
788 #-- WordPress 2.0+
789 if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
790
791 // First try the user core data table.
792 $id = $wpdb->get_var(
793 "SELECT ID FROM $wpdb->users
794 WHERE
795 TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
796 OR (
797 LENGTH(TRIM(LCASE(user_email))) > 0
798 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
799 )
800 OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
801 ");
802
803 // If that fails, look for aliases in the user meta data table
804 if (is_null($id)) :
805 $id = $wpdb->get_var(
806 "SELECT user_id FROM $wpdb->usermeta
807 WHERE
808 (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
809 OR (
810 meta_key = 'description'
811 AND TRIM(LCASE(meta_value))
812 RLIKE CONCAT(
813 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
814 TRIM(LCASE('$reg_author')),
815 '( |\\t|\\r)*(\\n|\$)'
816 )
817 )
818 ");
819 endif;
820
821 #-- WordPress 1.5.x
822 else :
823 $id = $wpdb->get_var(
824 "SELECT ID from $wpdb->users
825 WHERE
826 TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
827 (
828 LENGTH(TRIM(LCASE(user_email))) > 0
829 AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
830 ) OR
831 TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
832 TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
833 TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
834 TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
835 (
836 LOWER(user_description)
837 RLIKE CONCAT(
838 '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
839 LCASE('$reg_author'),
840 '( |\\t|\\r)*(\\n|\$)'
841 )
842 )
843 ");
844
845 endif;
846
847 // ... if you don't find one, then do what you need to do
848 if (is_null($id)) :
849 if ($unfamiliar_author === 'create') :
850 $userdata = array();
851
852 #-- user table data
853 $userdata['ID'] = NULL; // new user
854 $userdata['user_login'] = $login;
855 $userdata['user_nicename'] = $nice_author;
856 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
857 $userdata['user_email'] = $email;
858 $userdata['user_url'] = $url;
859 $userdata['display_name'] = $author;
860
861 $id = wp_insert_user($userdata);
862 elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
863 $id = (int) $unfamiliar_author;
864 elseif ($unfamiliar_author === 'default') :
865 $id = 1;
866 endif;
867 endif;
868 endif;
869
870 if ($id) :
871 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
872 endif;
873 return $id;
874 } // function SyndicatedPost::author_id ()
875
876 // look up (and create) category ids from a list of categories
877 function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
878 global $wpdb;
879
880 // We need to normalize whitespace because (1) trailing
881 // whitespace can cause PHP and MySQL not to see eye to eye on
882 // VARCHAR comparisons for some versions of MySQL (cf.
883 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
884 // because I doubt most people want to make a semantic
885 // distinction between 'Computers' and 'Computers '
886 $cats = array_map('trim', $cats);
887
888 $tags = array();
889
890 $cat_ids = array ();
891 foreach ($cats as $cat_name) :
892 if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
893 $cat_id = (int) $backref[1];
894 if (function_exists('is_term') and is_term($cat_id, 'category')) :
895 $cat_ids[] = $cat_id;
896 elseif (get_category($cat_id)) :
897 $cat_ids[] = $cat_id;
898 endif;
899 elseif (strlen($cat_name) > 0) :
900 $esc = $wpdb->escape($cat_name);
901 $resc = $wpdb->escape(preg_quote($cat_name));
902
903 // WordPress 2.3+
904 if (function_exists('is_term')) :
905 $cat_id = is_term($cat_name, 'category');
906 if ($cat_id) :
907 $cat_ids[] = $cat_id['term_id'];
908 // There must be a better way to do this...
909 elseif ($results = $wpdb->get_results(
910 "SELECT term_id
911 FROM $wpdb->term_taxonomy
912 WHERE
913 LOWER(description) RLIKE
914 CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
915 )) :
916 foreach ($results AS $term) :
917 $cat_ids[] = (int) $term->term_id;
918 endforeach;
919 elseif ('tag'==$unfamiliar_category) :
920 $tags[] = $cat_name;
921 elseif ('create'===$unfamiliar_category) :
922 $term = wp_insert_term($cat_name, 'category');
923 if (is_wp_error($term)) :
924 FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
925 else :
926 $cat_ids[] = $term['term_id'];
927 endif;
928 endif;
929
930 // WordPress 1.5.x - 2.2.x
931 else :
932 $results = $wpdb->get_results(
933 "SELECT cat_ID
934 FROM $wpdb->categories
935 WHERE
936 (LOWER(cat_name) = LOWER('$esc'))
937 OR (LOWER(category_description)
938 RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
939 ");
940 if ($results) :
941 foreach ($results as $term) :
942 $cat_ids[] = (int) $term->cat_ID;
943 endforeach;
944 elseif ('create'===$unfamiliar_category) :
945 if (function_exists('wp_insert_category')) :
946 $cat_id = wp_insert_category(array('cat_name' => $esc));
947 // And into the database we go.
948 else :
949 $nice_kitty = sanitize_title($cat_name);
950 $wpdb->query(sprintf("
951 INSERT INTO $wpdb->categories
952 SET
953 cat_name='%s',
954 category_nicename='%s'
955 ", $esc, $nice_kitty
956 ));
957 $cat_id = $wpdb->insert_id;
958 endif;
959 $cat_ids[] = $cat_id;
960 endif;
961 endif;
962 endif;
963 endforeach;
964
965 if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
966 $cat_ids = NULL; // Drop the post
967 else :
968 $cat_ids = array_unique($cat_ids);
969 endif;
970
971 if ($tags_too) : $ret = array($cat_ids, $tags);
972 else : $ret = $cat_ids;
973 endif;
974
975 return $ret;
976 } // function SyndicatedPost::category_ids ()
977
978 function use_api ($tag) {
979 global $wp_db_version;
980 switch ($tag) :
981 case 'wp_insert_post':
982 // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
983 // In 1.5 it was such a resource hog it would make PHP segfault on big updates
984 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
985 break;
986 case 'post_status_pending':
987 $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
988 break;
989 endswitch;
990 return $ret;
991 } // function SyndicatedPost::use_api ()
992
993 #### EXTRACT DATA FROM FEED ITEM ####
994
995 function created () {
996 $epoch = null;
997 if (isset($this->item['dc']['created'])) :
998 $epoch = @parse_w3cdtf($this->item['dc']['created']);
999 elseif (isset($this->item['dcterms']['created'])) :
1000 $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
1001 elseif (isset($this->item['created'])): // Atom 0.3
1002 $epoch = @parse_w3cdtf($this->item['created']);
1003 endif;
1004 return $epoch;
1005 }
1006 function published ($fallback = true) {
1007 $epoch = null;
1008
1009 # RSS is a fucking mess. Figure out whether we have a date in
1010 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
1011 # epoch format for reformatting. If we can't find anything,
1012 # we'll use the last-updated time.
1013 if (isset($this->item['dc']['date'])): // Dublin Core
1014 $epoch = @parse_w3cdtf($this->item['dc']['date']);
1015 elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
1016 $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
1017 elseif (isset($this->item['published'])) : // Atom 1.0
1018 $epoch = @parse_w3cdtf($this->item['published']);
1019 elseif (isset($this->item['issued'])): // Atom 0.3
1020 $epoch = @parse_w3cdtf($this->item['issued']);
1021 elseif (isset($this->item['pubdate'])): // RSS 2.0
1022 $epoch = strtotime($this->item['pubdate']);
1023 elseif ($fallback) : // Fall back to <updated> / <modified> if present
1024 $epoch = $this->updated(/*fallback=*/ false);
1025 endif;
1026
1027 # If everything failed, then default to the current time.
1028 if (is_null($epoch)) :
1029 if (-1 == $default) :
1030 $epoch = time();
1031 else :
1032 $epoch = $default;
1033 endif;
1034 endif;
1035
1036 return $epoch;
1037 }
1038 function updated ($fallback = true, $default = -1) {
1039 $epoch = null;
1040
1041 # As far as I know, only dcterms and Atom have reliable ways to
1042 # specify when something was *modified* last. If neither is
1043 # available, then we'll try to get the time of publication.
1044 if (isset($this->item['dc']['modified'])) : // Not really correct
1045 $epoch = @parse_w3cdtf($this->item['dc']['modified']);
1046 elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
1047 $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
1048 elseif (isset($this->item['modified'])): // Atom 0.3
1049 $epoch = @parse_w3cdtf($this->item['modified']);
1050 elseif (isset($this->item['updated'])): // Atom 1.0
1051 $epoch = @parse_w3cdtf($this->item['updated']);
1052 elseif ($fallback) : // Fall back to issued / dc:date
1053 $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
1054 endif;
1055
1056 # If everything failed, then default to the current time.
1057 if (is_null($epoch)) :
1058 if (-1 == $default) :
1059 $epoch = time();
1060 else :
1061 $epoch = $default;
1062 endif;
1063 endif;
1064
1065 return $epoch;
1066 }
1067
1068 function update_hash () {
1069 return md5(serialize($this->item));
1070 }
1071
1072 function guid () {
1073 $guid = null;
1074 if (isset($this->item['id'])): // Atom 0.3 / 1.0
1075 $guid = $this->item['id'];
1076 elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
1077 $guid = $this->item['atom']['id'];
1078 elseif (isset($this->item['guid'])) : // RSS 2.0
1079 $guid = $this->item['guid'];
1080 elseif (isset($this->item['dc']['identifier'])) :// yeah, right
1081 $guid = $this->item['dc']['identifier'];
1082 else :
1083 // The feed does not seem to have provided us with a
1084 // unique identifier, so we'll have to cobble together
1085 // a tag: URI that might work for us. The base of the
1086 // URI will be the host name of the feed source ...
1087 $bits = parse_url($this->feedmeta['link/uri']);
1088 $guid = 'tag:'.$bits['host'];
1089
1090 // If we have a date of creation, then we can use that
1091 // to uniquely identify the item. (On the other hand, if
1092 // the feed producer was consicentious enough to
1093 // generate dates of creation, she probably also was
1094 // conscientious enough to generate unique identifiers.)
1095 if (!is_null($this->created())) :
1096 $guid .= '://post.'.date('YmdHis', $this->created());
1097
1098 // Otherwise, use both the URI of the item, *and* the
1099 // item's title. We have to use both because titles are
1100 // often not unique, and sometimes links aren't unique
1101 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
1102 // some podcasts). But it's rare to have *both* the same
1103 // title *and* the same link for two different items. So
1104 // this is about the best we can do.
1105 else :
1106 $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
1107 endif;
1108 endif;
1109 return $guid;
1110 }
1111
1112 function author () {
1113 $author = array ();
1114
1115 if (isset($this->item['author_name'])):
1116 $author['name'] = $this->item['author_name'];
1117 elseif (isset($this->item['dc']['creator'])):
1118 $author['name'] = $this->item['dc']['creator'];
1119 elseif (isset($this->item['dc']['contributor'])):
1120 $author['name'] = $this->item['dc']['contributor'];
1121 elseif (isset($this->feed->channel['dc']['creator'])) :
1122 $author['name'] = $this->feed->channel['dc']['creator'];
1123 elseif (isset($this->feed->channel['dc']['contributor'])) :
1124 $author['name'] = $this->feed->channel['dc']['contributor'];
1125 elseif (isset($this->feed->channel['author_name'])) :
1126 $author['name'] = $this->feed->channel['author_name'];
1127 elseif ($this->feed->is_rss() and isset($this->item['author'])) :
1128 // The author element in RSS is allegedly an
1129 // e-mail address, but lots of people don't use
1130 // it that way. So let's make of it what we can.
1131 $author = parse_email_with_realname($this->item['author']);
1132
1133 if (!isset($author['name'])) :
1134 if (isset($author['email'])) :
1135 $author['name'] = $author['email'];
1136 else :
1137 $author['name'] = $this->feed->channel['title'];
1138 endif;
1139 endif;
1140 else :
1141 $author['name'] = $this->feed->channel['title'];
1142 endif;
1143
1144 if (isset($this->item['author_email'])):
1145 $author['email'] = $this->item['author_email'];
1146 elseif (isset($this->feed->channel['author_email'])) :
1147 $author['email'] = $this->feed->channel['author_email'];
1148 endif;
1149
1150 if (isset($this->item['author_url'])):
1151 $author['uri'] = $this->item['author_url'];
1152 elseif (isset($this->feed->channel['author_url'])) :
1153 $author['uri'] = $this->item['author_url'];
1154 else:
1155 $author['uri'] = $this->feed->channel['link'];
1156 endif;
1157
1158 return $author;
1159 } // SyndicatedPost::author()
1160
1161 /**
1162 * SyndicatedPost::isTaggedAs: Test whether a feed item is
1163 * tagged / categorized with a given string. Case and leading and
1164 * trailing whitespace are ignored.
1165 *
1166 * @param string $tag Tag to check for
1167 *
1168 * @return bool Whether or not at least one of the categories / tags on
1169 * $this->item is set to $tag (modulo case and leading and trailing
1170 * whitespace)
1171 */
1172 function isTaggedAs ($tag) {
1173 $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
1174
1175 // Check to see if this is tagged with $tag
1176 $currentCategory = 'category';
1177 $currentCategoryNumber = 1;
1178
1179 // If we have the new MagpieRSS, the number of category elements
1180 // on this item is stored under index "category#".
1181 if (isset($this->item['category#'])) :
1182 $numberOfCategories = (int) $this->item['category#'];
1183
1184 // We REALLY shouldn't have the old and busted MagpieRSS, but in
1185 // case we do, it doesn't support multiple categories, but there
1186 // might still be a single value under the "category" index.
1187 elseif (isset($this->item['category'])) :
1188 $numberOfCategories = 1;
1189
1190 // No standard category or tag elements on this feed item.
1191 else :
1192 $numberOfCategories = 0;
1193
1194 endif;
1195
1196 $isSoTagged = false; // Innocent until proven guilty
1197
1198 // Loop through category elements; if there are multiple
1199 // elements, they are indexed as category, category#2,
1200 // category#3, ... category#N
1201 while ($currentCategoryNumber <= $numberOfCategories) :
1202 if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
1203 $isSoTagged = true; // Got it!
1204 break;
1205 endif;
1206
1207 $currentCategoryNumber += 1;
1208 $currentCategory = 'category#'.$currentCategoryNumber;
1209 endwhile;
1210
1211 return $isSoTagged;
1212 } /* SyndicatedPost::isTaggedAs() */
1213
1214 var $uri_attrs = array (
1215 array('a', 'href'),
1216 array('applet', 'codebase'),
1217 array('area', 'href'),
1218 array('blockquote', 'cite'),
1219 array('body', 'background'),
1220 array('del', 'cite'),
1221 array('form', 'action'),
1222 array('frame', 'longdesc'),
1223 array('frame', 'src'),
1224 array('iframe', 'longdesc'),
1225 array('iframe', 'src'),
1226 array('head', 'profile'),
1227 array('img', 'longdesc'),
1228 array('img', 'src'),
1229 array('img', 'usemap'),
1230 array('input', 'src'),
1231 array('input', 'usemap'),
1232 array('ins', 'cite'),
1233 array('link', 'href'),
1234 array('object', 'classid'),
1235 array('object', 'codebase'),
1236 array('object', 'data'),
1237 array('object', 'usemap'),
1238 array('q', 'cite'),
1239 array('script', 'src')
1240 ); /* var SyndicatedPost::$uri_attrs */
1241
1242 var $_base = null;
1243
1244 function resolve_single_relative_uri ($refs) {
1245 $tag = FeedWordPressHTML::attributeMatch($refs);
1246 $url = Relative_URI::resolve($tag['value'], $this->_base);
1247 return $tag['prefix'] . $url . $tag['suffix'];
1248 } /* function SyndicatedPost::resolve_single_relative_uri() */
1249
1250 function resolve_relative_uris ($content, $obj) {
1251 $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
1252 if ($set and $set != 'no') :
1253 # The MagpieRSS upgrade has some `xml:base` support baked in.
1254 # However, sometimes people do silly things, like putting
1255 # relative URIs out on a production RSS 2.0 feed or other feeds
1256 # with no good support for `xml:base`. So we'll do our best to
1257 # try to catch any remaining relative URIs and resolve them as
1258 # best we can.
1259 $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
1260
1261 foreach ($obj->uri_attrs as $pair) :
1262 list($tag, $attr) = $pair;
1263 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1264 $content = preg_replace_callback (
1265 $pattern,
1266 array(&$obj, 'resolve_single_relative_uri'),
1267 $content
1268 );
1269 endforeach;
1270 endif;
1271
1272 return $content;
1273 } /* function SyndicatedPost::resolve_relative_uris () */
1274
1275 var $strip_attrs = array (
1276 array('[a-z]+', 'target'),
1277 // array('[a-z]+', 'style'),
1278 // array('[a-z]+', 'on[a-z]+'),
1279 );
1280
1281 function strip_attribute_from_tag ($refs) {
1282 $tag = FeedWordPressHTML::attributeMatch($refs);
1283 return $tag['before_attribute'].$tag['after_attribute'];
1284 }
1285
1286 function sanitize_content ($content, $obj) {
1287 # This kind of sucks. I intend to replace it with
1288 # lib_filter sometime soon.
1289 foreach ($obj->strip_attrs as $pair):
1290 list($tag,$attr) = $pair;
1291 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1292
1293 $content = preg_replace_callback (
1294 $pattern,
1295 array(&$obj, 'strip_attribute_from_tag'),
1296 $content
1297 );
1298 endforeach;
1299 return $content;
1300 }
1301 } // class SyndicatedPost
1302
1303