PluginProbe
FeedWordPress / 2011.0211
FeedWordPress v2011.0211
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
← All changes | syndicatedlink.class.php +316 -62 2010.0127 → 2011.0211 View file →
@@ -32,12 +32,15 @@
32 32 #
33 33 # Values of keys in link_notes are accessible from templates using
34 34 # the function `get_feed_meta($key)` if this plugin is activated.
35 35
36 +require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php');
37 +
36 38 class SyndicatedLink {
37 39 var $id = null;
38 40 var $link = null;
39 41 var $settings = array ();
42 + var $simplepie = null;
40 43 var $magpie = null;
41 44
42 45 function SyndicatedLink ($link) {
43 46 global $wpdb;
@@ -97,14 +100,48 @@
97 100 if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
98 101 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
99 102 endif;
100 103
101 - if (isset($this->settings['cats'])):
102 - $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
104 + // Simple lists
105 + foreach ($this->imploded_settings() as $what) :
106 + if (isset($this->settings[$what])):
107 + $this->settings[$what] = explode(
108 + FEEDWORDPRESS_CAT_SEPARATOR,
109 + $this->settings[$what]
110 + );
111 + endif;
112 + endforeach;
113 +
114 + if (isset($this->settings['terms'])) :
115 + // Look for new format
116 + $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
117 +
118 + if (!is_array($this->settings['terms'])) :
119 + // Deal with old format instead. Ugh.
120 +
121 + // Split on two *or more* consecutive breaks
122 + // because in the old format, a taxonomy
123 + // without any associated terms would
124 + // produce tax_name#1\n\n\ntax_name#2\nterm,
125 + // and the naive split on the first \n\n
126 + // would screw up the tax_name#2 list.
127 + //
128 + // Props to David Morris for pointing this
129 + // out.
130 +
131 + $this->settings['terms'] = preg_split(
132 + "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
133 + $this->settings['terms']
134 + );
135 + $terms = array();
136 + foreach ($this->settings['terms'] as $line) :
137 + $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
138 + $tax = array_shift($line);
139 + $terms[$tax] = $line;
140 + endforeach;
141 + $this->settings['terms'] = $terms;
142 + endif;
103 143 endif;
104 - if (isset($this->settings['tags'])):
105 - $this->settings['tags'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['tags']);
106 - endif;
107 144
108 145 if (isset($this->settings['map authors'])) :
109 146 $author_rules = explode("\n\n", $this->settings['map authors']);
110 147 $ma = array();
@@ -123,9 +160,9 @@
123 160 endif;
124 161 } /* SyndicatedLink::SyndicatedLink () */
125 162
126 163 function found () {
127 - return is_object($this->link);
164 + return is_object($this->link) and !is_wp_error($this->link);
128 165 } /* SyndicatedLink::found () */
129 166
130 167 function stale () {
131 168 $stale = true;
@@ -145,9 +182,26 @@
145 182
146 183 function poll ($crash_ts = NULL) {
147 184 global $wpdb;
148 185
149 - $this->magpie = fetch_rss($this->link->link_rss);
186 + $url = $this->uri(array('add_params' => true));
187 + FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
188 +
189 + $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
190 +
191 + $this->simplepie = apply_filters(
192 + 'syndicated_feed',
193 + FeedWordPress::fetch($url, array('timeout' => $timeout)),
194 + $this
195 + );
196 +
197 + // Filter compatibility mode
198 + if (is_wp_error($this->simplepie)) :
199 + $this->magpie = $this->simplepie;
200 + else :
201 + $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
202 + endif;
203 +
150 204 $new_count = NULL;
151 205
152 206 $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
153 207 if ($resume) :
@@ -157,9 +211,46 @@
157 211 // begin at the beginning
158 212 $processed = array();
159 213 endif;
160 214
161 - if (is_object($this->magpie)) :
215 + if (is_wp_error($this->simplepie)) :
216 + $new_count = $this->simplepie;
217 + // Error; establish an error setting.
218 + $theError = array();
219 + $theError['ts'] = time();
220 + $theError['since'] = time();
221 + $theError['object'] = $this->simplepie;
222 +
223 + $oldError = $this->setting('update/error', NULL, NULL);
224 + if (is_string($oldError)) :
225 + $oldError = unserialize($oldError);
226 + endif;
227 +
228 + if (!is_null($oldError)) :
229 + // Copy over the in-error-since timestamp
230 + $theError['since'] = $oldError['since'];
231 +
232 + // If this is a repeat error, then we should take
233 + // a step back before we try to fetch it again.
234 + $this->settings['update/last'] = time();
235 + $this->settings['update/ttl'] = $this->automatic_ttl();
236 + $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this);
237 + $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl_from_error', $this->settings['update/ttl'], $this);
238 +
239 + $this->settings['update/timed'] = 'automatically';
240 + endif;
241 +
242 + do_action('syndicated_feed_error', $theError, $oldError, $this);
243 +
244 + $this->settings['update/error'] = serialize($theError);
245 + $this->save_settings(/*reload=*/ true);
246 +
247 + elseif (is_object($this->simplepie)) :
248 + // Success; clear out error setting, if any.
249 + if (isset($this->settings['update/error'])) :
250 + unset($this->settings['update/error']);
251 + endif;
252 +
162 253 $new_count = array('new' => 0, 'updated' => 0);
163 254
164 255 # -- Update Link metadata live from feed
165 256 $channel = $this->magpie->channel;
@@ -191,21 +282,9 @@
191 282 if (!is_null($ttl)) :
192 283 $this->settings['update/ttl'] = $ttl;
193 284 $this->settings['update/timed'] = 'feed';
194 285 else :
195 - // spread out over a time interval for staggered updates
196 - if (isset($this->settings['update/window'])) :
197 - $updateWindow = $this->settings['update/window'];
198 - else :
199 - $updateWindow = get_option('feedwordpress_update_window');
200 - endif;
201 -
202 - if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
203 - $updateWindow = DEFAULT_UPDATE_PERIOD;
204 - endif;
205 -
206 - $fudgedInterval = $updateWindow+rand(0, 2*($updateWindow/3));
207 - $this->settings['update/ttl'] = apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
286 + $this->settings['update/ttl'] = $this->automatic_ttl();
208 287 $this->settings['update/timed'] = 'automatically';
209 288 endif;
210 289 $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this);
211 290
@@ -224,15 +303,25 @@
224 303 UPDATE $wpdb->links
225 304 SET $update_set
226 305 WHERE link_id='$this->id'
227 306 ");
307 + do_action('update_syndicated_feed', $this->id, $this);
228 308
229 309 # -- Add new posts from feed and update any updated posts
230 310 $crashed = false;
231 311
232 - if (is_array($this->magpie->items)) :
233 - foreach ($this->magpie->items as $item) :
234 - $post =& new SyndicatedPost($item, $this);
312 + $posts = apply_filters(
313 + 'syndicated_feed_items',
314 + $this->simplepie->get_items(),
315 + &$this
316 + );
317 +
318 + $this->magpie->originals = $posts;
319 +
320 + if (is_array($posts)) :
321 + foreach ($posts as $key => $item) :
322 + $post = new SyndicatedPost($item, $this);
323 +
235 324 if (!$resume or !in_array(trim($post->guid()), $processed)) :
236 325 $processed[] = $post->guid();
237 326 if (!$post->filtered()) :
238 327 $new = $post->store();
@@ -243,11 +332,15 @@
243 332 $crashed = true;
244 333 break;
245 334 endif;
246 335 endif;
336 + unset($post);
247 337 endforeach;
248 338 endif;
249 -
339 + $suffix = ($crashed ? 'crashed' : 'completed');
340 + do_action('update_syndicated_feed_items', $this->id, $this);
341 + do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
342 +
250 343 // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
251 344 $to_notes = $this->settings;
252 345
253 346 $this->settings['update/processed'] = $processed;
@@ -262,10 +355,22 @@
262 355 UPDATE $wpdb->links
263 356 SET $update_set
264 357 WHERE link_id='$this->id'
265 358 ");
359 +
360 + do_action("update_syndicated_feed_completed", $this->id, $this);
266 361 endif;
267 362
363 + // All done; let's clean up.
364 + $this->magpie = NULL;
365 +
366 + // Avoid circular-reference memory leak in PHP < 5.3.
367 + // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks>
368 + if (method_exists($this->simplepie, '__destruct')) :
369 + $this->simplepie->__destruct();
370 + endif;
371 + $this->simplepie = NULL;
372 +
268 373 return $new_count;
269 374 } /* SyndicatedLink::poll() */
270 375
271 376 /**
@@ -292,24 +397,61 @@
292 397 endif;
293 398 return $ret;
294 399 } /* SyndicatedLink::set_uri () */
295 400
401 + function deactivate () {
402 + global $wpdb;
403 +
404 + $wpdb->query($wpdb->prepare("
405 + UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
406 + ", (int) $this->id));
407 + } /* SyndicatedLink::deactivate () */
408 +
409 + function delete () {
410 + global $wpdb;
411 +
412 + $wpdb->query($wpdb->prepare("
413 + DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
414 + AND meta_value = '%s'
415 + ", $this->id));
416 +
417 + $wpdb->query($wpdb->prepare("
418 + DELETE FROM $wpdb->links WHERE link_id = %d
419 + ", (int) $this->id));
420 +
421 + $this->id = NULL;
422 + } /* SyndicatedLink::delete () */
423 +
424 + function nuke () {
425 + global $wpdb;
426 +
427 + // Make a list of the items syndicated from this feed...
428 + $post_ids = $wpdb->get_col($wpdb->prepare("
429 + SELECT post_id FROM $wpdb->postmeta
430 + WHERE meta_key = 'syndication_feed_id'
431 + AND meta_value = '%s'
432 + ", $this->id));
433 +
434 + // ... and kill them all
435 + if (count($post_ids) > 0) :
436 + foreach ($post_ids as $post_id) :
437 + // Force scrubbing of deleted post
438 + // rather than sending to Trashcan
439 + wp_delete_post(
440 + /*postid=*/ $post_id,
441 + /*force_delete=*/ true
442 + );
443 + endforeach;
444 + endif;
445 +
446 + $this->delete();
447 + } /* SyndicatedLink::nuke () */
448 +
296 449 function map_name_to_new_user ($name, $newuser_name) {
297 450 global $wpdb;
298 451
299 452 if (strlen($newuser_name) > 0) :
300 - $userdata = array();
301 - $userdata['ID'] = NULL;
302 -
303 - $userdata['user_login'] = sanitize_user($newuser_name);
304 - $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
305 -
306 - $userdata['user_nicename'] = sanitize_title($newuser_name);
307 - $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
308 -
309 - $userdata['display_name'] = $wpdb->escape($newuser_name);
310 -
311 - $newuser_id = wp_insert_user($userdata);
453 + $newuser_id = fwp_insert_new_user($newuser_name);
312 454 if (is_numeric($newuser_id)) :
313 455 if (is_null($name)) : // Unfamiliar author
314 456 $this->settings['unfamiliar author'] = $newuser_id;
315 457 else :
@@ -322,8 +464,11 @@
322 464 // TODO: Add some error reporting
323 465 endif;
324 466 } /* SyndicatedLink::map_name_to_new_user () */
325 467
468 + function imploded_settings () {
469 + return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
470 + }
326 471 function settings_to_notes () {
327 472 $to_notes = $this->settings;
328 473
329 474 unset($to_notes['link/id']); // Magic setting; don't save
@@ -336,15 +481,22 @@
336 481 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
337 482 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
338 483 endif;
339 484
340 - if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
341 - $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
485 + foreach ($this->imploded_settings() as $what) :
486 + if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
487 + $to_notes[$what] = implode(
488 + FEEDWORDPRESS_CAT_SEPARATOR,
489 + $to_notes[$what]
490 + );
491 + endif;
492 + endforeach;
493 +
494 + if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
495 + // Serialize it.
496 + $to_notes['terms'] = serialize($to_notes['terms']);
342 497 endif;
343 - if (isset($to_notes['tags']) and is_array($to_notes['tags'])) :
344 - $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
345 - endif;
346 -
498 +
347 499 // Collapse the author mapping rule structure back into a flat string
348 500 if (isset($to_notes['map authors'])) :
349 501 $ma = array();
350 502 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
@@ -364,11 +516,19 @@
364 516
365 517 function save_settings ($reload = false) {
366 518 global $wpdb;
367 519
368 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
369 -
370 - // Update the properties of the link from the feed information
520 + // Save channel-level meta-data
521 + foreach (array('link_name', 'link_description', 'link_url') as $what) :
522 + $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
523 + endforeach;
524 +
525 + // Save settings to the notes field
526 + $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
527 +
528 + // Update the properties of the link from settings changes, etc.
529 + $update_set = implode(", ", $alter);
530 +
371 531 $result = $wpdb->query("
372 532 UPDATE $wpdb->links
373 533 SET $update_set
374 534 WHERE link_id='$this->id'
@@ -374,9 +534,9 @@
374 534 WHERE link_id='$this->id'
375 535 ");
376 536
377 537 if ($reload) :
378 - // reload link information from DB
538 + // force reload of link information from DB
379 539 if (function_exists('clean_bookmark_cache')) :
380 540 clean_bookmark_cache($this->id);
381 541 endif;
382 542 endif;
@@ -390,45 +550,122 @@
390 550 * @param mixed $fallback_global If the link setting is nonexistent or marked as a use-default value, fall back to the value of this global setting.
391 551 * @param mixed $fallback_value If the link setting and the global setting are nonexistent or marked as a use-default value, fall back to this constant value.
392 552 * @return bool TRUE on success, FALSE on failure.
393 553 */
394 - function setting ($name, $fallback_global = NULL, $fallback_value = NULL) {
554 + function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
395 555 $ret = NULL;
396 556 if (isset($this->settings[$name])) :
397 557 $ret = $this->settings[$name];
398 558 endif;
399 559
400 - if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_global)) :
560 + $no_value = (
561 + is_null($ret)
562 + or (is_string($ret) and strtolower($ret)==$default)
563 + );
564 +
565 + if ($no_value and !is_null($fallback_global)) :
566 + // Avoid duplication of this correction
567 + $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
568 +
401 569 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
402 570 endif;
403 571
404 - if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_value)) :
572 + $no_value = (
573 + is_null($ret)
574 + or (is_string($ret) and strtolower($ret)==$default)
575 + );
576 +
577 + if ($no_value and !is_null($fallback_value)) :
405 578 $ret = $fallback_value;
406 579 endif;
407 580 return $ret;
408 581 } /* SyndicatedLink::setting () */
409 582
410 - function uri () {
411 - return (is_object($this->link) ? $this->link->link_rss : NULL);
583 + function update_setting ($name, $value, $default = 'default') {
584 + if (!is_null($value) and $value != $default) :
585 + $this->settings[$name] = $value;
586 + else : // Zap it.
587 + unset($this->settings[$name]);
588 + endif;
589 + } /* SyndicatedLink::update_setting () */
590 +
591 + function uri ($params = array()) {
592 + $params = shortcode_atts(array(
593 + 'add_params' => false,
594 + ), $params);
595 +
596 + $uri = (is_object($this->link) ? $this->link->link_rss : NULL);
597 + if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
598 + $qp = maybe_unserialize($this->setting('query parameters', array()));
599 +
600 + // For high-tech HTTP feed request kung fu
601 + $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
602 +
603 + $q = array();
604 + if (is_array($qp) and count($qp) > 0) :
605 + foreach ($qp as $pair) :
606 + $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
607 + endforeach;
608 +
609 + // Are we appending to a URI that already has params?
610 + $sep = ((strpos('?', $uri)===false) ? '?' : '&');
611 +
612 + // Tack it on
613 + $uri .= $sep . implode("&", $q);
614 + endif;
615 + endif;
616 +
617 + return $uri;
412 618 } /* SyndicatedLink::uri () */
413 619
414 - function homepage ($fromFeed = true) {
620 + function property_cascade ($fromFeed, $link_field, $setting, $simplepie_method) {
621 + $value = NULL;
415 622 if ($fromFeed) :
416 - $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
623 + if (isset($this->settings[$setting])) :
624 + $value = $this->settings[$setting];
625 + elseif (is_object($this->simplepie)
626 + and method_exists($this->simplepie, $simplepie_method)) :
627 + $value = $this->simplepie->{$simplepie_method}();
628 + endif;
417 629 else :
418 - $url = $this->link->link_url;
630 + $value = $this->link->{$link_field};
419 631 endif;
420 - return $url;
632 + return $value;
633 + } /* SyndicatedLink::property_cascade () */
634 +
635 + function homepage ($fromFeed = true) {
636 + return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
421 637 } /* SyndicatedLink::homepage () */
422 638
423 639 function name ($fromFeed = true) {
424 - if ($fromFeed) :
425 - $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
426 - else :
427 - $name = $this->link->link_name;
640 + return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
641 + } /* SyndicatedLink::name () */
642 +
643 + function guid () {
644 + $ret = $this->setting('feed/id', NULL, $this->uri());
645 +
646 + // If we can get it live from the feed, do so.
647 + if (is_object($this->simplepie)) :
648 + $search = array(
649 + array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
650 + array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
651 + array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
652 + array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
653 + array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
654 + );
655 +
656 + foreach ($search as $pair) :
657 + if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
658 + $ret = $id_tags[0]['data'];
659 + break;
660 + elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
661 + $ret = $id_tags[0]['data'];
662 + break;
663 + endif;
664 + endforeach;
428 665 endif;
429 - return $name;
430 - } /* SyndicatedLink::name () */
666 + return $ret;
667 + }
431 668
432 669 function ttl () {
433 670 if (is_object($this->magpie)) :
434 671 $channel = $this->magpie->channel;
@@ -474,8 +711,19 @@
474 711 endif;
475 712 return $ret;
476 713 } /* SyndicatedLink::ttl() */
477 714
715 + function automatic_ttl () {
716 + // spread out over a time interval for staggered updates
717 + $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
718 + if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
719 + $updateWindow = DEFAULT_UPDATE_PERIOD;
720 + endif;
721 +
722 + $fudgedInterval = $updateWindow+rand(0, 2*($updateWindow/3));
723 + return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
724 + } /* SyndicatedLink::automatic_ttl () */
725 +
478 726 // SyndicatedLink::flatten_array (): flatten an array. Useful for
479 727 // hierarchical and namespaced elements.
480 728 //
481 729 // Given an array which may contain array or object elements in it,
@@ -532,6 +780,12 @@
532 780 endif;
533 781
534 782 return $wpdb->escape(trim(strtolower($ret)));
535 783 } /* SyndicatedLink:syndicated_status () */
784 +
785 + function taxonomies () {
786 + $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
787 + return get_object_taxonomies(array('object_type' => $post_type), 'names');
788 + } /* SyndicatedLink::taxonomies () */
789 +
536 790 } // class SyndicatedLink
537 791