PluginProbe
FeedWordPress / 2013.0504
FeedWordPress v2013.0504
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 +631 -172 2009.11112013.0504 View file →
@@ -32,12 +32,16 @@
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 +require_once(dirname(__FILE__).'/feedwordpressparsedpostmeta.class.php');
38 +
36 39 class SyndicatedLink {
37 40 var $id = null;
38 41 var $link = null;
39 42 var $settings = array ();
43 + var $simplepie = null;
40 44 var $magpie = null;
41 45
42 46 function SyndicatedLink ($link) {
43 47 global $wpdb;
@@ -45,121 +49,139 @@
45 49 if (is_object($link)) :
46 50 $this->link = $link;
47 51 $this->id = $link->link_id;
48 52 else :
49 - $this->id = $link;
50 - if (function_exists('get_bookmark')) : // WP 2.1+
51 - $this->link = get_bookmark($link);
52 - else :
53 - $this->link = $wpdb->get_row("
54 - SELECT * FROM $wpdb->links
55 - WHERE (link_id = '".$wpdb->escape($link)."')"
56 - );
57 - endif;
53 + $this->id = $link;
54 + $this->link = get_bookmark($link);
58 55 endif;
59 56
60 57 if (strlen($this->link->link_rss) > 0) :
61 - // Read off feed settings from link_notes
62 - $notes = explode("\n", $this->link->link_notes);
63 - foreach ($notes as $note):
64 - $pair = explode(": ", $note, 2);
65 - $key = (isset($pair[0]) ? $pair[0] : null);
66 - $value = (isset($pair[1]) ? $pair[1] : null);
67 - if (!is_null($key) and !is_null($value)) :
68 - // Unescape and trim() off the whitespace.
69 - // Thanks to Ray Lischner for pointing out the
70 - // need to trim off whitespace.
71 - $this->settings[$key] = stripcslashes (trim($value));
72 - endif;
73 - endforeach;
74 -
75 - // "Magic" feed settings
76 - $this->settings['link/uri'] = $this->link->link_rss;
77 - $this->settings['link/name'] = $this->link->link_name;
78 - $this->settings['link/id'] = $this->link->link_id;
79 -
80 - // `hardcode categories` and `unfamiliar categories` are deprecated in favor of `unfamiliar category`
81 - if (
82 - isset($this->settings['unfamiliar categories'])
83 - and !isset($this->settings['unfamiliar category'])
84 - ) :
85 - $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
86 - endif;
87 - if (
88 - FeedWordPress::affirmative($this->settings, 'hardcode categories')
89 - and !isset($this->settings['unfamiliar category'])
90 - ) :
91 - $this->settings['unfamiliar category'] = 'default';
92 - endif;
93 -
94 - // Set this up automagically for del.icio.us
95 - $bits = parse_url($this->link->link_rss);
96 - $tagspacers = array('del.icio.us', 'feeds.delicious.com');
97 - if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
98 - $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
99 - endif;
100 -
101 - if (isset($this->settings['cats'])):
102 - $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
103 - endif;
104 - if (isset($this->settings['tags'])):
105 - $this->settings['tags'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['tags']);
106 - endif;
107 -
108 - if (isset($this->settings['map authors'])) :
109 - $author_rules = explode("\n\n", $this->settings['map authors']);
110 - $ma = array();
111 - foreach ($author_rules as $rule) :
112 - list($rule_type, $author_name, $author_action) = explode("\n", $rule);
113 -
114 - // Normalize for case and whitespace
115 - $rule_type = strtolower(trim($rule_type));
116 - $author_name = strtolower(trim($author_name));
117 - $author_action = strtolower(trim($author_action));
118 -
119 - $ma[$rule_type][$author_name] = $author_action;
120 - endforeach;
121 - $this->settings['map authors'] = $ma;
122 - endif;
58 + $this->get_settings_from_notes();
123 59 endif;
60 +
61 + add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
124 62 } /* SyndicatedLink::SyndicatedLink () */
125 63
126 64 function found () {
127 - return is_object($this->link);
65 + return is_object($this->link) and !is_wp_error($this->link);
128 66 } /* SyndicatedLink::found () */
129 67
68 + function id () {
69 + return (is_object($this->link) ? $this->link->link_id : NULL);
70 + }
71 +
130 72 function stale () {
73 + global $feedwordpress;
74 +
131 75 $stale = true;
132 - if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
76 + if ($this->setting('update/hold')=='ping') :
133 77 $stale = false; // don't update on any timed updates; pings only
134 - elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
78 + elseif ($this->setting('update/hold')=='next') :
135 79 $stale = true; // update on the next timed update
136 - elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
80 + elseif ( !$this->setting('update/last') ) :
137 81 $stale = true; // initial update
82 + elseif ($feedwordpress->force_update_all()) :
83 + $stale = true; // forced general updating
138 84 else :
139 - $after = ((int) $this->settings['update/last'])
140 - +((int) $this->settings['update/ttl'] * 60);
85 + $after = (
86 + (int) $this->setting('update/last')
87 + + (int) $this->setting('update/fudge')
88 + + ((int) $this->setting('update/ttl') * 60)
89 + );
141 90 $stale = (time() >= $after);
142 91 endif;
143 92 return $stale;
144 93 } /* SyndicatedLink::stale () */
145 94
95 + function fetch () {
96 + $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
97 +
98 + $this->simplepie = apply_filters(
99 + 'syndicated_feed',
100 + FeedWordPress::fetch($this, array('timeout' => $timeout)),
101 + $this
102 + );
103 +
104 + // Filter compatibility mode
105 + if (is_wp_error($this->simplepie)) :
106 + $this->magpie = $this->simplepie;
107 + else :
108 + $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
109 + endif;
110 + }
111 + function live_posts () {
112 + if (!is_object($this->simplepie)) :
113 + $this->fetch();
114 + endif;
115 +
116 + if (is_object($this->simplepie) and method_exists($this->simplepie, 'get_items')) :
117 + $ret = apply_filters(
118 + 'syndicated_feed_items',
119 + $this->simplepie->get_items(),
120 + $this
121 + );
122 + else :
123 + $ret = $this->simplepie;
124 + endif;
125 + return $ret;
126 + }
127 +
146 128 function poll ($crash_ts = NULL) {
147 129 global $wpdb;
148 130
149 - $this->magpie = fetch_rss($this->link->link_rss);
131 + $url = $this->uri(array('add_params' => true));
132 + FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
133 +
134 + $this->fetch();
135 +
150 136 $new_count = NULL;
151 137
152 - $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
138 + $resume = ('yes'==$this->setting('update/unfinished'));
153 139 if ($resume) :
154 140 // pick up where we left off
155 - $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
141 + $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
156 142 else :
157 143 // begin at the beginning
158 144 $processed = array();
159 145 endif;
160 146
161 - if (is_object($this->magpie)) :
147 + if (is_wp_error($this->simplepie)) :
148 + $new_count = $this->simplepie;
149 + // Error; establish an error setting.
150 + $theError = array();
151 + $theError['ts'] = time();
152 + $theError['since'] = time();
153 + $theError['object'] = $this->simplepie;
154 +
155 + $oldError = $this->setting('update/error', NULL, NULL);
156 + if (is_string($oldError)) :
157 + $oldError = unserialize($oldError);
158 + endif;
159 +
160 + if (!is_null($oldError)) :
161 + // Copy over the in-error-since timestamp
162 + $theError['since'] = $oldError['since'];
163 +
164 + // If this is a repeat error, then we should
165 + // take a step back before we try to fetch it
166 + // again.
167 + $this->update_setting('update/last', time(), NULL);
168 + $ttl = $this->automatic_ttl();
169 + $ttl = apply_filters('syndicated_feed_ttl', $ttl, $this);
170 + $ttl = apply_filters('syndicated_feed_ttl_from_error', $ttl, $this);
171 + $this->update_setting('update/ttl', $ttl, $this);
172 + $this->update_setting('update/timed', 'automatically');
173 + endif;
174 +
175 + do_action('syndicated_feed_error', $theError, $oldError, $this);
176 +
177 + $this->update_setting('update/error', serialize($theError));
178 + $this->save_settings(/*reload=*/ true);
179 +
180 + elseif (is_object($this->simplepie)) :
181 + // Success; clear out error setting, if any.
182 + $this->update_setting('update/error', NULL);
183 +
162 184 $new_count = array('new' => 0, 'updated' => 0);
163 185
164 186 # -- Update Link metadata live from feed
165 187 $channel = $this->magpie->channel;
@@ -183,38 +205,36 @@
183 205 elseif (isset($channel['description'])) :
184 206 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
185 207 endif;
186 208 endif;
187 -
188 - $this->settings = array_merge($this->settings, $this->flatten_array($channel));
189 209
190 - $this->settings['update/last'] = time(); $ttl = $this->ttl();
210 + $this->merge_settings($channel, 'feed/');
211 +
212 + $this->update_setting('update/last', time());
213 + list($ttl, $xml) = $this->ttl(/*return element=*/ true);
214 +
191 215 if (!is_null($ttl)) :
192 - $this->settings['update/ttl'] = $ttl;
193 - $this->settings['update/timed'] = 'feed';
216 + $this->update_setting('update/ttl', $ttl);
217 + $this->update_setting('update/xml', $xml);
218 + $this->update_setting('update/timed', 'feed');
194 219 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);
208 - $this->settings['update/timed'] = 'automatically';
220 + $ttl = $this->automatic_ttl();
221 + $this->update_setting('update/ttl', $ttl);
222 + $this->update_setting('update/xml', NULL);
223 + $this->update_setting('update/timed', 'automatically');
209 224 endif;
210 - $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this);
225 + $this->update_setting('update/fudge', rand(0, ($ttl/3))*60);
226 + $this->update_setting('update/ttl', apply_filters(
227 + 'syndicated_feed_ttl',
228 + $this->setting('update/ttl'),
229 + $this
230 + ));
211 231
212 - if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
213 - $this->settings['update/hold'] = 'scheduled';
232 + if (!$this->setting('update/hold') != 'ping') :
233 + $this->update_setting('update/hold', 'scheduled');
214 234 endif;
215 235
216 - $this->settings['update/unfinished'] = 'yes';
236 + $this->update_setting('update/unfinished', 'yes');
217 237
218 238 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
219 239
220 240 $update_set = implode(',', $update);
@@ -224,15 +244,36 @@
224 244 UPDATE $wpdb->links
225 245 SET $update_set
226 246 WHERE link_id='$this->id'
227 247 ");
248 + do_action('update_syndicated_feed', $this->id, $this);
228 249
229 250 # -- Add new posts from feed and update any updated posts
230 251 $crashed = false;
231 252
232 - if (is_array($this->magpie->items)) :
233 - foreach ($this->magpie->items as $item) :
234 - $post =& new SyndicatedPost($item, $this);
253 + $posts = $this->live_posts();
254 +
255 + $this->magpie->originals = $posts;
256 +
257 + // If this is a complete feed, rather than an incremental feed, we
258 + // need to prepare to mark everything for presumptive retirement.
259 + if ($this->is_incremental()) :
260 + $q = new WP_Query(array(
261 + 'fields' => '_synfrom',
262 + 'post_status__not' => 'fwpretired',
263 + 'ignore_sticky_posts' => true,
264 + 'meta_key' => 'syndication_feed_id',
265 + 'meta_value' => $this->id,
266 + ));
267 + foreach ($q->posts as $p) :
268 + update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
269 + endforeach;
270 + endif;
271 +
272 + if (is_array($posts)) :
273 + foreach ($posts as $key => $item) :
274 + $post = new SyndicatedPost($item, $this);
275 +
235 276 if (!$resume or !in_array(trim($post->guid()), $processed)) :
236 277 $processed[] = $post->guid();
237 278 if (!$post->filtered()) :
238 279 $new = $post->store();
@@ -243,19 +284,56 @@
243 284 $crashed = true;
244 285 break;
245 286 endif;
246 287 endif;
288 + unset($post);
247 289 endforeach;
248 290 endif;
249 291
250 - // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
251 - $to_notes = $this->settings;
292 + if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
293 + // Check for use of Atom tombstones. Spec:
294 + // <http://tools.ietf.org/html/draft-snell-atompub-tombstones-18>
295 + $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
296 + if (count($tombstones) > 0) :
297 + foreach ($tombstones as $tombstone) :
298 + $ref = NULL;
299 + foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
300 + if (isset($tombstone['attribs'][$ns])
301 + and isset($tombstone['attribs'][$ns]['ref'])) :
302 + $ref = $tombstone['attribs'][$ns]['ref'];
303 + endif;
304 + endforeach;
305 +
306 + $q = new WP_Query(array(
307 + 'ignore_sticky_posts' => true,
308 + 'guid' => $ref,
309 + 'meta_key' => 'syndication_feed_id',
310 + 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
311 + ));
312 +
313 + foreach ($q->posts as $p) :
314 + $old_status = $p->post_status;
315 + FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
316 + set_post_field('post_status', 'fwpretired', $p->ID);
317 + wp_transition_post_status('fwpretired', $old_status, $p);
318 + endforeach;
319 +
320 + endforeach;
321 + endif;
322 + endif;
323 +
324 + $suffix = ($crashed ? 'crashed' : 'completed');
325 + do_action('update_syndicated_feed_items', $this->id, $this);
326 + do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
252 327
253 - $this->settings['update/processed'] = $processed;
328 + $this->update_setting('update/processed', $processed);
254 329 if (!$crashed) :
255 - $this->settings['update/unfinished'] = 'no';
330 + $this->update_setting('update/unfinished', 'no');
256 331 endif;
332 + $this->update_setting('link/item count', count($posts));
257 333
334 + // Copy back any changes to feed settings made in the
335 + // course of updating (e.g. new author rules)
258 336 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
259 337
260 338 // Update the properties of the link from the feed information
261 339 $result = $wpdb->query("
@@ -262,13 +340,47 @@
262 340 UPDATE $wpdb->links
263 341 SET $update_set
264 342 WHERE link_id='$this->id'
265 343 ");
344 +
345 + do_action("update_syndicated_feed_completed", $this->id, $this);
266 346 endif;
267 347
348 + // All done; let's clean up.
349 + $this->magpie = NULL;
350 +
351 + // Avoid circular-reference memory leak in PHP < 5.3.
352 + // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks>
353 + if (method_exists($this->simplepie, '__destruct')) :
354 + $this->simplepie->__destruct();
355 + endif;
356 + $this->simplepie = NULL;
357 +
268 358 return $new_count;
269 359 } /* SyndicatedLink::poll() */
270 360
361 + function process_retirements ($delta) {
362 + global $post;
363 +
364 + $q = new WP_Query(array(
365 + 'fields' => '_synfrom',
366 + 'post_status__not' => 'fwpretired',
367 + 'ignore_sticky_posts' => true,
368 + 'meta_key' => '_feedwordpress_retire_me_'.$this->id,
369 + 'meta_value' => '1',
370 + ));
371 + if ($q->have_posts()) :
372 + foreach ($q->posts as $p) :
373 + $old_status = $p->post_status;
374 + FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to absence from a non-incremental feed.');
375 + set_post_field('post_status', 'fwpretired', $p->ID);
376 + wp_transition_post_status('fwpretired', $old_status, $p);
377 + delete_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id);
378 + endforeach;
379 + endif;
380 + return $delta;
381 + }
382 +
271 383 /**
272 384 * Updates the URL for the feed syndicated by this link.
273 385 *
274 386 * @param string $url The new feed URL to use for this source.
@@ -292,29 +404,68 @@
292 404 endif;
293 405 return $ret;
294 406 } /* SyndicatedLink::set_uri () */
295 407
408 + function deactivate () {
409 + global $wpdb;
410 +
411 + $wpdb->query($wpdb->prepare("
412 + UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
413 + ", (int) $this->id));
414 + } /* SyndicatedLink::deactivate () */
415 +
416 + function delete () {
417 + global $wpdb;
418 +
419 + $wpdb->query($wpdb->prepare("
420 + DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
421 + AND meta_value = '%s'
422 + ", $this->id));
423 +
424 + $wpdb->query($wpdb->prepare("
425 + DELETE FROM $wpdb->links WHERE link_id = %d
426 + ", (int) $this->id));
427 +
428 + $this->id = NULL;
429 + } /* SyndicatedLink::delete () */
430 +
431 + function nuke () {
432 + global $wpdb;
433 +
434 + // Make a list of the items syndicated from this feed...
435 + $post_ids = $wpdb->get_col($wpdb->prepare("
436 + SELECT post_id FROM $wpdb->postmeta
437 + WHERE meta_key = 'syndication_feed_id'
438 + AND meta_value = '%s'
439 + ", $this->id));
440 +
441 + // ... and kill them all
442 + if (count($post_ids) > 0) :
443 + foreach ($post_ids as $post_id) :
444 + // Force scrubbing of deleted post
445 + // rather than sending to Trashcan
446 + wp_delete_post(
447 + /*postid=*/ $post_id,
448 + /*force_delete=*/ true
449 + );
450 + endforeach;
451 + endif;
452 +
453 + $this->delete();
454 + } /* SyndicatedLink::nuke () */
455 +
296 456 function map_name_to_new_user ($name, $newuser_name) {
297 457 global $wpdb;
298 458
299 459 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);
460 + $newuser_id = fwp_insert_new_user($newuser_name);
312 461 if (is_numeric($newuser_id)) :
313 462 if (is_null($name)) : // Unfamiliar author
314 - $this->settings['unfamiliar author'] = $newuser_id;
463 + $this->update_setting('unfamiliar author', $newuser_id);
315 464 else :
316 - $this->settings['map authors']['name'][$name] = $newuser_id;
465 + $map = $this->setting('map authors');
466 + $map['name'][$name] = $newuser_id;
467 + $this->update_setting('map authors', $map);
317 468 endif;
318 469 else :
319 470 // TODO: Add some error detection and reporting
320 471 endif;
@@ -322,8 +473,113 @@
322 473 // TODO: Add some error reporting
323 474 endif;
324 475 } /* SyndicatedLink::map_name_to_new_user () */
325 476
477 + function imploded_settings () {
478 + return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
479 + }
480 +
481 + function get_settings_from_notes () {
482 + // Read off feed settings from link_notes
483 + $notes = explode("\n", $this->link->link_notes);
484 + foreach ($notes as $note):
485 + $pair = explode(": ", $note, 2);
486 + $key = (isset($pair[0]) ? $pair[0] : null);
487 + $value = (isset($pair[1]) ? $pair[1] : null);
488 + if (!is_null($key) and !is_null($value)) :
489 + // Unescape and trim() off the whitespace.
490 + // Thanks to Ray Lischner for pointing out the
491 + // need to trim off whitespace.
492 + $this->settings[$key] = stripcslashes (trim($value));
493 + endif;
494 + endforeach;
495 +
496 + // "Magic" feed settings
497 + $this->settings['link/uri'] = $this->link->link_rss;
498 + $this->settings['link/name'] = $this->link->link_name;
499 + $this->settings['link/id'] = $this->link->link_id;
500 +
501 + // `hardcode categories` and `unfamiliar categories` are
502 + // deprecated in favor of `unfamiliar category`
503 + if (
504 + isset($this->settings['unfamiliar categories'])
505 + and !isset($this->settings['unfamiliar category'])
506 + ) :
507 + $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
508 + endif;
509 + if (
510 + FeedWordPress::affirmative($this->settings, 'hardcode categories')
511 + and !isset($this->settings['unfamiliar category'])
512 + ) :
513 + $this->settings['unfamiliar category'] = 'default';
514 + endif;
515 +
516 + // Set this up automagically for del.icio.us
517 + $bits = parse_url($this->link->link_rss);
518 + $tagspacers = array('del.icio.us', 'feeds.delicious.com');
519 + if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
520 + $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
521 + endif;
522 +
523 + // Simple lists
524 + foreach ($this->imploded_settings() as $what) :
525 + if (isset($this->settings[$what])):
526 + $this->settings[$what] = explode(
527 + FEEDWORDPRESS_CAT_SEPARATOR,
528 + $this->settings[$what]
529 + );
530 + endif;
531 + endforeach;
532 +
533 + if (isset($this->settings['terms'])) :
534 + // Look for new format
535 + $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
536 +
537 + if (!is_array($this->settings['terms'])) :
538 + // Deal with old format instead. Ugh.
539 +
540 + // Split on two *or more* consecutive breaks
541 + // because in the old format, a taxonomy
542 + // without any associated terms would
543 + // produce tax_name#1\n\n\ntax_name#2\nterm,
544 + // and the naive split on the first \n\n
545 + // would screw up the tax_name#2 list.
546 + //
547 + // Props to David Morris for pointing this
548 + // out.
549 +
550 + $this->settings['terms'] = preg_split(
551 + "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
552 + $this->settings['terms']
553 + );
554 + $terms = array();
555 + foreach ($this->settings['terms'] as $line) :
556 + $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
557 + $tax = array_shift($line);
558 + $terms[$tax] = $line;
559 + endforeach;
560 + $this->settings['terms'] = $terms;
561 + endif;
562 + endif;
563 +
564 + if (isset($this->settings['map authors'])) :
565 + $author_rules = explode("\n\n", $this->settings['map authors']);
566 + $ma = array();
567 + foreach ($author_rules as $rule) :
568 + list($rule_type, $author_name, $author_action) = explode("\n", $rule);
569 +
570 + // Normalize for case and whitespace
571 + $rule_type = strtolower(trim($rule_type));
572 + $author_name = strtolower(trim($author_name));
573 + $author_action = strtolower(trim($author_action));
574 +
575 + $ma[$rule_type][$author_name] = $author_action;
576 + endforeach;
577 + $this->settings['map authors'] = $ma;
578 + endif;
579 +
580 + } /* SyndicatedLink::get_settings_from_notes () */
581 +
326 582 function settings_to_notes () {
327 583 $to_notes = $this->settings;
328 584
329 585 unset($to_notes['link/id']); // Magic setting; don't save
@@ -336,15 +592,22 @@
336 592 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
337 593 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
338 594 endif;
339 595
340 - if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
341 - $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
596 + foreach ($this->imploded_settings() as $what) :
597 + if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
598 + $to_notes[$what] = implode(
599 + FEEDWORDPRESS_CAT_SEPARATOR,
600 + $to_notes[$what]
601 + );
602 + endif;
603 + endforeach;
604 +
605 + if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
606 + // Serialize it.
607 + $to_notes['terms'] = serialize($to_notes['terms']);
342 608 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 -
609 +
347 610 // Collapse the author mapping rule structure back into a flat string
348 611 if (isset($to_notes['map authors'])) :
349 612 $ma = array();
350 613 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
@@ -364,11 +627,19 @@
364 627
365 628 function save_settings ($reload = false) {
366 629 global $wpdb;
367 630
368 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
369 -
370 - // Update the properties of the link from the feed information
631 + // Save channel-level meta-data
632 + foreach (array('link_name', 'link_description', 'link_url') as $what) :
633 + $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
634 + endforeach;
635 +
636 + // Save settings to the notes field
637 + $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
638 +
639 + // Update the properties of the link from settings changes, etc.
640 + $update_set = implode(", ", $alter);
641 +
371 642 $result = $wpdb->query("
372 643 UPDATE $wpdb->links
373 644 SET $update_set
374 645 WHERE link_id='$this->id'
@@ -374,9 +645,9 @@
374 645 WHERE link_id='$this->id'
375 646 ");
376 647
377 648 if ($reload) :
378 - // reload link information from DB
649 + // force reload of link information from DB
379 650 if (function_exists('clean_bookmark_cache')) :
380 651 clean_bookmark_cache($this->id);
381 652 endif;
382 653 endif;
@@ -381,31 +652,204 @@
381 652 endif;
382 653 endif;
383 654 } /* SyndicatedLink::save_settings () */
384 655
385 - function uri () {
386 - return (is_object($this->link) ? $this->link->link_rss : NULL);
656 + /**
657 + * Retrieves the value of a setting, allowing for a global setting to be
658 + * used as a fallback, or a constant value, or both.
659 + *
660 + * @param string $name The link setting key
661 + * @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.
662 + * @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.
663 + * @return bool TRUE on success, FALSE on failure.
664 + */
665 + function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
666 + $ret = NULL;
667 + if (isset($this->settings[$name])) :
668 + $ret = $this->settings[$name];
669 + endif;
670 +
671 + $no_value = (
672 + is_null($ret)
673 + or (is_string($ret) and strtolower($ret)==$default)
674 + );
675 +
676 + if ($no_value and !is_null($fallback_global)) :
677 + // Avoid duplication of this correction
678 + $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
679 +
680 + $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
681 + endif;
682 +
683 + $no_value = (
684 + is_null($ret)
685 + or (is_string($ret) and strtolower($ret)==$default)
686 + );
687 +
688 + if ($no_value and !is_null($fallback_value)) :
689 + $ret = $fallback_value;
690 + endif;
691 + return $ret;
692 + } /* SyndicatedLink::setting () */
693 +
694 + function merge_settings ($data, $prefix, $separator = '/') {
695 + $dd = $this->flatten_array($data, $prefix, $separator);
696 + $this->settings = array_merge($this->settings, $dd);
697 + } /* SyndicatedLink::merge_settings () */
698 +
699 + function update_setting ($name, $value, $default = 'default') {
700 + if (!is_null($value) and $value != $default) :
701 + $this->settings[$name] = $value;
702 + else : // Zap it.
703 + unset($this->settings[$name]);
704 + endif;
705 + } /* SyndicatedLink::update_setting () */
706 +
707 + function is_incremental () {
708 + return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
709 + } /* SyndicatedLink::is_incremental () */
710 +
711 + function uri ($params = array()) {
712 + $params = wp_parse_args($params, array(
713 + 'add_params' => false,
714 + ));
715 +
716 + $uri = (is_object($this->link) ? $this->link->link_rss : NULL);
717 + if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
718 + $qp = maybe_unserialize($this->setting('query parameters', array()));
719 +
720 + // For high-tech HTTP feed request kung fu
721 + $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
722 +
723 + $q = array();
724 + if (is_array($qp) and count($qp) > 0) :
725 + foreach ($qp as $pair) :
726 + $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
727 + endforeach;
728 +
729 + // Are we appending to a URI that already has params?
730 + $sep = ((strpos($uri, "?")===false) ? '?' : '&');
731 +
732 + // Tack it on
733 + $uri .= $sep . implode("&", $q);
734 + endif;
735 + endif;
736 +
737 + return $uri;
387 738 } /* SyndicatedLink::uri () */
388 739
389 - function homepage ($fromFeed = true) {
740 + function username () {
741 + return $this->setting('http username', 'http_username', NULL);
742 + } /* SyndicatedLink::username () */
743 +
744 + function password () {
745 + return $this->setting('http password', 'http_password', NULL);
746 + } /* SyndicatedLink::password () */
747 +
748 + function authentication_method () {
749 + $auth = $this->setting('http auth method', NULL);
750 + if (('-' == $auth) or (strlen($auth)==0)) :
751 + $auth = NULL;
752 + endif;
753 + return $auth;
754 + } /* SyndicatedLink::authentication_method () */
755 +
756 + var $postmeta = array();
757 + function postmeta ($params = array()) {
758 + $params = wp_parse_args($params, /*defaults=*/ array(
759 + "field" => NULL,
760 + "parsed" => false,
761 + "force" => false,
762 + ));
763 +
764 + if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
765 + // First, get the global settings.
766 + $default_custom_settings = get_option('feedwordpress_custom_settings');
767 + if ($default_custom_settings and !is_array($default_custom_settings)) :
768 + $default_custom_settings = unserialize($default_custom_settings);
769 + endif;
770 + if (!is_array($default_custom_settings)) :
771 + $default_custom_settings = array();
772 + endif;
773 +
774 + // Next, get the settings for this particular feed.
775 + $custom_settings = $this->setting('postmeta', NULL, NULL);
776 + if ($custom_settings and !is_array($custom_settings)) :
777 + $custom_settings = unserialize($custom_settings);
778 + endif;
779 + if (!is_array($custom_settings)) :
780 + $custom_settings = array();
781 + endif;
782 +
783 + $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
784 + $this->postmeta[/*parsed=*/ true] = array();
785 +
786 + // Now, run through and parse them all.
787 + foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
788 + $meta = apply_filters("syndicated_link_post_meta_${key}_pre", $meta, $this);
789 + $this->postmeta[/*parsed=*/ false][$key] = $meta;
790 + $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
791 + endforeach;
792 + endif;
793 +
794 + $ret = $this->postmeta[!!$params['parsed']];
795 + if (is_string($params['field'])) :
796 + $ret = $ret[$params['field']];
797 + endif;
798 + return $ret;
799 + } /* SyndicatedLink::postmeta () */
800 +
801 + function property_cascade ($fromFeed, $link_field, $setting, $method) {
802 + $value = NULL;
390 803 if ($fromFeed) :
391 - $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
804 + $value = $this->setting($setting, NULL, NULL, NULL);
805 +
806 + $s = $this->simplepie;
807 + $callable = (is_object($s) and method_exists($s, $method));
808 + if (is_null($value) and $callable) :
809 + $fallback = $s->{$method}();
810 + endif;
392 811 else :
393 - $url = $this->link->link_url;
812 + $value = $this->link->{$link_field};
394 813 endif;
395 - return $url;
814 + return $value;
815 + } /* SyndicatedLink::property_cascade () */
816 +
817 + function homepage ($fromFeed = true) {
818 + return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
396 819 } /* SyndicatedLink::homepage () */
397 820
398 821 function name ($fromFeed = true) {
399 - if ($fromFeed) :
400 - $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
401 - else :
402 - $name = $this->link->link_name;
822 + return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
823 + } /* SyndicatedLink::name () */
824 +
825 + function guid () {
826 + $ret = $this->setting('feed/id', NULL, $this->uri());
827 +
828 + // If we can get it live from the feed, do so.
829 + if (is_object($this->simplepie)) :
830 + $search = array(
831 + array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
832 + array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
833 + array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
834 + array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
835 + array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
836 + );
837 +
838 + foreach ($search as $pair) :
839 + if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
840 + $ret = $id_tags[0]['data'];
841 + break;
842 + elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
843 + $ret = $id_tags[0]['data'];
844 + break;
845 + endif;
846 + endforeach;
403 847 endif;
404 - return $name;
405 - } /* SyndicatedLink::name () */
848 + return $ret;
849 + }
406 850
407 - function ttl () {
851 + function ttl ($return_element = false) {
408 852 if (is_object($this->magpie)) :
409 853 $channel = $this->magpie->channel;
410 854 else :
411 855 $channel = array();
@@ -415,8 +859,9 @@
415 859 // "ttl stands for time to live. It's a number of
416 860 // minutes that indicates how long a channel can be
417 861 // cached before refreshing from the source."
418 862 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
863 + $xml = 'rss:ttl';
419 864 $ret = $channel['ttl'];
420 865 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
421 866 $period_minutes = array (
422 867 'hourly' => 60, /* minutes in an hour */
@@ -442,15 +887,38 @@
442 887 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
443 888 else : $freq = 1;
444 889 endif;
445 890
891 + $xml = 'sy:updateFrequency';
446 892 $ret = (int) ($period_minutes[$period] / $freq);
447 893 else :
894 + $xml = NULL;
448 895 $ret = NULL;
449 896 endif;
450 - return $ret;
897 +
898 + if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
899 + $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
900 +
901 + if ($min > $ret) :
902 + $ret = NULL;
903 + endif;
904 + endif;
905 + return ($return_element ? array($ret, $xml) : $ret);
451 906 } /* SyndicatedLink::ttl() */
452 907
908 + function automatic_ttl () {
909 + // spread out over a time interval for staggered updates
910 + $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
911 + if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
912 + $updateWindow = DEFAULT_UPDATE_PERIOD;
913 + endif;
914 +
915 + // We get a fudge of 1/3 of window from elsewhere. We'll do some more
916 + // fudging here.
917 + $fudgedInterval = $updateWindow+rand(-($updateWindow/6), 5*($updateWindow/12));
918 + return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
919 + } /* SyndicatedLink::automatic_ttl () */
920 +
453 921 // SyndicatedLink::flatten_array (): flatten an array. Useful for
454 922 // hierarchical and namespaced elements.
455 923 //
456 924 // Given an array which may contain array or object elements in it,
@@ -473,19 +941,15 @@
473 941 return $ret;
474 942 } /* SyndicatedLink::flatten_array () */
475 943
476 944 function hardcode ($what) {
477 - $default = get_option("feedwordpress_hardcode_$what");
478 - if ( $default === 'yes' ) :
479 - // If the default is to hardcode, then we want the
480 - // negation of negative(): TRUE by default and FALSE if
481 - // the setting is explicitly "no"
482 - $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
945 +
946 + $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
947 +
948 + if ('yes' == $ret) :
949 + $ret = true;
483 950 else :
484 - // If the default is NOT to hardcode, then we want
485 - // affirmative(): FALSE by default and TRUE if the
486 - // setting is explicitly "yes"
487 - $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
951 + $ret = false;
488 952 endif;
489 953 return $ret;
490 954 } /* SyndicatedLink::hardcode () */
491 955
@@ -491,22 +955,17 @@
491 955
492 956 function syndicated_status ($what, $default, $fallback = true) {
493 957 global $wpdb;
494 958
495 - // Use local setting if we have it
496 - if ( isset($this->settings["$what status"]) ) :
497 - $ret = $this->settings["$what status"];
498 -
499 - // Or fall back to global default if we can
500 - elseif ($fallback) :
501 - $ret = FeedWordPress::syndicated_status($what, $default);
959 + $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
960 + $ret = $this->setting($what.' status', $g_set, $default);
502 961
503 - // Or use default value if we can't.
504 - else :
505 - $ret = $default;
506 -
507 - endif;
508 -
509 962 return $wpdb->escape(trim(strtolower($ret)));
510 963 } /* SyndicatedLink:syndicated_status () */
964 +
965 + function taxonomies () {
966 + $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
967 + return get_object_taxonomies(array('object_type' => $post_type), 'names');
968 + } /* SyndicatedLink::taxonomies () */
969 +
511 970 } // class SyndicatedLink
512 971