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