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 +680 -140 2009.0612 → 2013.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,120 +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 - 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 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 +
145 128 function poll ($crash_ts = NULL) {
146 129 global $wpdb;
147 130
148 - $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 +
149 136 $new_count = NULL;
150 137
151 - $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
138 + $resume = ('yes'==$this->setting('update/unfinished'));
152 139 if ($resume) :
153 140 // pick up where we left off
154 - $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
141 + $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
155 142 else :
156 143 // begin at the beginning
157 144 $processed = array();
158 145 endif;
159 146
160 - 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 +
161 184 $new_count = array('new' => 0, 'updated' => 0);
162 185
163 186 # -- Update Link metadata live from feed
164 187 $channel = $this->magpie->channel;
@@ -182,25 +205,36 @@
182 205 elseif (isset($channel['description'])) :
183 206 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
184 207 endif;
185 208 endif;
186 -
187 - $this->settings = array_merge($this->settings, $this->flatten_array($channel));
188 209
189 - $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 +
190 215 if (!is_null($ttl)) :
191 - $this->settings['update/ttl'] = $ttl;
192 - $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');
193 219 else :
194 - $this->settings['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates
195 - $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');
196 224 endif;
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 + ));
197 231
198 - if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
199 - $this->settings['update/hold'] = 'scheduled';
232 + if (!$this->setting('update/hold') != 'ping') :
233 + $this->update_setting('update/hold', 'scheduled');
200 234 endif;
201 235
202 - $this->settings['update/unfinished'] = 'yes';
236 + $this->update_setting('update/unfinished', 'yes');
203 237
204 238 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
205 239
206 240 $update_set = implode(',', $update);
@@ -210,15 +244,36 @@
210 244 UPDATE $wpdb->links
211 245 SET $update_set
212 246 WHERE link_id='$this->id'
213 247 ");
248 + do_action('update_syndicated_feed', $this->id, $this);
214 249
215 250 # -- Add new posts from feed and update any updated posts
216 251 $crashed = false;
217 252
218 - if (is_array($this->magpie->items)) :
219 - foreach ($this->magpie->items as $item) :
220 - $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 +
221 276 if (!$resume or !in_array(trim($post->guid()), $processed)) :
222 277 $processed[] = $post->guid();
223 278 if (!$post->filtered()) :
224 279 $new = $post->store();
@@ -229,19 +284,56 @@
229 284 $crashed = true;
230 285 break;
231 286 endif;
232 287 endif;
288 + unset($post);
233 289 endforeach;
234 290 endif;
235 291
236 - // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
237 - $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);
238 327
239 - $this->settings['update/processed'] = $processed;
328 + $this->update_setting('update/processed', $processed);
240 329 if (!$crashed) :
241 - $this->settings['update/unfinished'] = 'no';
330 + $this->update_setting('update/unfinished', 'no');
242 331 endif;
332 + $this->update_setting('link/item count', count($posts));
243 333
334 + // Copy back any changes to feed settings made in the
335 + // course of updating (e.g. new author rules)
244 336 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
245 337
246 338 // Update the properties of the link from the feed information
247 339 $result = $wpdb->query("
@@ -248,34 +340,132 @@
248 340 UPDATE $wpdb->links
249 341 SET $update_set
250 342 WHERE link_id='$this->id'
251 343 ");
344 +
345 + do_action("update_syndicated_feed_completed", $this->id, $this);
252 346 endif;
253 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 +
254 358 return $new_count;
255 359 } /* SyndicatedLink::poll() */
256 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 +
383 + /**
384 + * Updates the URL for the feed syndicated by this link.
385 + *
386 + * @param string $url The new feed URL to use for this source.
387 + * @return bool TRUE on success, FALSE on failure.
388 + */
389 + function set_uri ($url) {
390 + global $wpdb;
391 +
392 + if ($this->found()) :
393 + // Update link_rss
394 + $result = $wpdb->query("
395 + UPDATE $wpdb->links
396 + SET
397 + link_rss = '".$wpdb->escape($url)."'
398 + WHERE link_id = '".$wpdb->escape($this->id)."'
399 + ");
400 +
401 + $ret = ($result ? true : false);
402 + else :
403 + $ret = false;
404 + endif;
405 + return $ret;
406 + } /* SyndicatedLink::set_uri () */
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 +
257 456 function map_name_to_new_user ($name, $newuser_name) {
258 457 global $wpdb;
259 458
260 459 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);
460 + $newuser_id = fwp_insert_new_user($newuser_name);
273 461 if (is_numeric($newuser_id)) :
274 462 if (is_null($name)) : // Unfamiliar author
275 - $this->settings['unfamiliar author'] = $newuser_id;
463 + $this->update_setting('unfamiliar author', $newuser_id);
276 464 else :
277 - $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);
278 468 endif;
279 469 else :
280 470 // TODO: Add some error detection and reporting
281 471 endif;
@@ -283,8 +473,113 @@
283 473 // TODO: Add some error reporting
284 474 endif;
285 475 } /* SyndicatedLink::map_name_to_new_user () */
286 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 +
287 582 function settings_to_notes () {
288 583 $to_notes = $this->settings;
289 584
290 585 unset($to_notes['link/id']); // Magic setting; don't save
@@ -297,15 +592,22 @@
297 592 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
298 593 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
299 594 endif;
300 595
301 - if (is_array($to_notes['cats'])) :
302 - $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']);
303 608 endif;
304 - if (is_array($to_notes['tags'])) :
305 - $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
306 - endif;
307 -
609 +
308 610 // Collapse the author mapping rule structure back into a flat string
309 611 if (isset($to_notes['map authors'])) :
310 612 $ma = array();
311 613 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
@@ -322,17 +624,232 @@
322 624 endforeach;
323 625 return $notes;
324 626 } /* SyndicatedLink::settings_to_notes () */
325 627
326 - function uri () {
327 - return (is_object($this->link) ? $this->link->link_rss : NULL);
628 + function save_settings ($reload = false) {
629 + global $wpdb;
630 +
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 +
642 + $result = $wpdb->query("
643 + UPDATE $wpdb->links
644 + SET $update_set
645 + WHERE link_id='$this->id'
646 + ");
647 +
648 + if ($reload) :
649 + // force reload of link information from DB
650 + if (function_exists('clean_bookmark_cache')) :
651 + clean_bookmark_cache($this->id);
652 + endif;
653 + endif;
654 + } /* SyndicatedLink::save_settings () */
655 +
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;
328 738 } /* SyndicatedLink::uri () */
329 739
330 - function homepage () {
331 - return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
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;
803 + if ($fromFeed) :
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;
811 + else :
812 + $value = $this->link->{$link_field};
813 + endif;
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');
332 819 } /* SyndicatedLink::homepage () */
333 820
334 - function ttl () {
821 + function name ($fromFeed = true) {
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;
847 + endif;
848 + return $ret;
849 + }
850 +
851 + function ttl ($return_element = false) {
335 852 if (is_object($this->magpie)) :
336 853 $channel = $this->magpie->channel;
337 854 else :
338 855 $channel = array();
@@ -342,8 +859,9 @@
342 859 // "ttl stands for time to live. It's a number of
343 860 // minutes that indicates how long a channel can be
344 861 // cached before refreshing from the source."
345 862 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
863 + $xml = 'rss:ttl';
346 864 $ret = $channel['ttl'];
347 865 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
348 866 $period_minutes = array (
349 867 'hourly' => 60, /* minutes in an hour */
@@ -369,15 +887,38 @@
369 887 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
370 888 else : $freq = 1;
371 889 endif;
372 890
891 + $xml = 'sy:updateFrequency';
373 892 $ret = (int) ($period_minutes[$period] / $freq);
374 893 else :
894 + $xml = NULL;
375 895 $ret = NULL;
376 896 endif;
377 - 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);
378 906 } /* SyndicatedLink::ttl() */
379 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 +
380 921 // SyndicatedLink::flatten_array (): flatten an array. Useful for
381 922 // hierarchical and namespaced elements.
382 923 //
383 924 // Given an array which may contain array or object elements in it,
@@ -400,32 +941,31 @@
400 941 return $ret;
401 942 } /* SyndicatedLink::flatten_array () */
402 943
403 944 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");
945 +
946 + $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
947 +
948 + if ('yes' == $ret) :
949 + $ret = true;
410 950 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");
951 + $ret = false;
415 952 endif;
416 953 return $ret;
417 954 } /* SyndicatedLink::hardcode () */
418 955
419 - function syndicated_status ($what, $default) {
956 + function syndicated_status ($what, $default, $fallback = true) {
420 957 global $wpdb;
421 958
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;
959 + $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
960 + $ret = $this->setting($what.' status', $g_set, $default);
961 +
428 962 return $wpdb->escape(trim(strtolower($ret)));
429 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 +
430 970 } // class SyndicatedLink
431 971