PluginProbe
FeedWordPress / 2021.0713
FeedWordPress v2021.0713
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 +960 -230 2009.1111 → 2021.0713 View file →
@@ -24,9 +24,9 @@
24 24 # Values have linebreak characters escaped with C-style
25 25 # backslashes (so, for example, a newline becomes "\n").
26 26 #
27 27 # The value of `cats` is used as a newline-separated list of
28 -# default categories for any post coming from a particular feed.
28 +# default categories for any post coming from a particular feed.
29 29 # (In the example above, any posts from this feed will be placed
30 30 # in the "computers" and "web" categories--*in addition to* any
31 31 # categories that may already be applied to the posts.)
32 32 #
@@ -32,15 +32,19 @@
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 + public $simplepie = null;
40 44 var $magpie = null;
41 45
42 - function SyndicatedLink ($link) {
46 + public function __construct( $link ) {
43 47 global $wpdb;
44 48
45 49 if (is_object($link)) :
46 50 $this->link = $link;
@@ -46,122 +50,148 @@
46 50 $this->link = $link;
47 51 $this->id = $link->link_id;
48 52 else :
49 53 $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 - );
54 + $this->link = get_bookmark($link);
55 + endif;
56 +
57 + if (is_object($this->link)) :
58 + if (strlen($this->link->link_rss) > 0) :
59 + $this->get_settings_from_notes();
57 60 endif;
58 61 endif;
59 62
60 - 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;
63 + add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
64 + } /* SyndicatedLink::__construct () */
74 65
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;
66 + public function found () {
67 + return is_object($this->link) and !is_wp_error($this->link);
68 + } /* SyndicatedLink::found () */
93 69
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;
70 + public function id () {
71 + return (is_object($this->link) ? $this->link->link_id : NULL);
72 + }
100 73
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;
123 - endif;
124 - } /* SyndicatedLink::SyndicatedLink () */
125 -
126 - function found () {
127 - return is_object($this->link);
128 - } /* SyndicatedLink::found () */
74 + public function stale () {
75 + global $feedwordpress;
129 76
130 - function stale () {
131 77 $stale = true;
132 - if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
78 + if ($this->setting('update/hold')=='ping') :
133 79 $stale = false; // don't update on any timed updates; pings only
134 - elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
80 + elseif ($this->setting('update/hold')=='next') :
135 81 $stale = true; // update on the next timed update
136 - elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
82 + elseif ( !$this->setting('update/last') ) :
137 83 $stale = true; // initial update
84 + elseif ($feedwordpress->force_update_all()) :
85 + $stale = true; // forced general updating
138 86 else :
139 - $after = ((int) $this->settings['update/last'])
140 - +((int) $this->settings['update/ttl'] * 60);
87 + $after = (
88 + (int) $this->setting('update/last')
89 + + (int) $this->setting('update/fudge')
90 + + ((int) $this->setting('update/ttl') * 60)
91 + );
141 92 $stale = (time() >= $after);
142 93 endif;
143 94 return $stale;
144 95 } /* SyndicatedLink::stale () */
145 96
146 - function poll ($crash_ts = NULL) {
97 + public function fetch () {
98 + $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
99 +
100 + $this->simplepie = apply_filters(
101 + 'syndicated_feed',
102 + FeedWordPress::fetch($this, array('timeout' => $timeout)),
103 + $this
104 + );
105 +
106 + // Filter compatibility mode
107 + if (is_wp_error($this->simplepie)) :
108 + $this->magpie = $this->simplepie;
109 + else :
110 + $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
111 + endif;
112 + } /* SyndicatedLink::fetch () */
113 +
114 + public function live_posts () {
115 + if (!is_object($this->simplepie)) :
116 + $this->fetch();
117 + endif;
118 +
119 + if (is_object($this->simplepie) and method_exists($this->simplepie, 'get_items')) :
120 + $ret = apply_filters(
121 + 'syndicated_feed_items',
122 + $this->simplepie->get_items(),
123 + $this
124 + );
125 + else :
126 + $ret = $this->simplepie;
127 + endif;
128 + return $ret;
129 + } /* SyndicatedLink::live_posts () */
130 +
131 + protected function pause_updates () {
132 + return ('yes'==$this->setting("update/pause", "update_pause", 'no'));
133 + } /* SyndicatedLink::pause_updates () */
134 +
135 + public function poll ($crash_ts = NULL) {
147 136 global $wpdb;
148 137
149 - $this->magpie = fetch_rss($this->link->link_rss);
138 + $url = $this->uri(array('add_params' => true, 'fetch' => true));
139 + FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
140 +
141 + $this->fetch();
142 +
150 143 $new_count = NULL;
151 144
152 - $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
145 + $resume = ('yes'==$this->setting('update/unfinished'));
153 146 if ($resume) :
154 147 // pick up where we left off
155 - $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
148 + $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
156 149 else :
157 150 // begin at the beginning
158 151 $processed = array();
159 152 endif;
160 153
161 - if (is_object($this->magpie)) :
162 - $new_count = array('new' => 0, 'updated' => 0);
154 + if (is_wp_error($this->simplepie)) :
155 + $new_count = $this->simplepie;
156 + // Error; establish an error setting.
157 + $theError = array();
158 + $theError['ts'] = time();
159 + $theError['since'] = time();
160 + $theError['object'] = $this->simplepie;
163 161
162 + $oldError = $this->setting('update/error', NULL, NULL);
163 + if (is_string($oldError)) :
164 + $oldError = unserialize($oldError);
165 + endif;
166 +
167 + if (!is_null($oldError)) :
168 + // Copy over the in-error-since timestamp
169 + $theError['since'] = $oldError['since'];
170 +
171 + // If this is a repeat error, then we should
172 + // take a step back before we try to fetch it
173 + // again.
174 + $this->update_setting('update/last', time(), NULL);
175 + $ttl = $this->automatic_ttl();
176 + $ttl = apply_filters('syndicated_feed_ttl', $ttl, $this);
177 + $ttl = apply_filters('syndicated_feed_ttl_from_error', $ttl, $this);
178 + $this->update_setting('update/ttl', $ttl);
179 + $this->update_setting('update/timed', 'automatically');
180 + endif;
181 +
182 + do_action('syndicated_feed_error', $theError, $oldError, $this);
183 +
184 + $this->update_setting('update/error', serialize($theError));
185 + $this->save_settings(/*reload=*/ true);
186 +
187 + elseif (is_object($this->simplepie)) :
188 +
189 + // Success; clear out error setting, if any.
190 + $this->update_setting('update/error', NULL);
191 +
192 + $new_count = array('new' => 0, 'updated' => 0, 'stored' => 0);
193 +
164 194 # -- Update Link metadata live from feed
165 195 $channel = $this->magpie->channel;
166 196
167 197 if (!isset($channel['id'])) :
@@ -166,60 +196,43 @@
166 196
167 197 if (!isset($channel['id'])) :
168 198 $channel['id'] = $this->link->link_rss;
169 199 endif;
170 -
200 +
171 201 $update = array();
172 202 if (!$this->hardcode('url') and isset($channel['link'])) :
173 - $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
203 + $update[] = "link_url = '".esc_sql($channel['link'])."'";
174 204 endif;
175 -
205 +
176 206 if (!$this->hardcode('name') and isset($channel['title'])) :
177 - $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
207 + $update[] = "link_name = '".esc_sql($channel['title'])."'";
178 208 endif;
179 -
209 +
180 210 if (!$this->hardcode('description')) :
181 211 if (isset($channel['tagline'])) :
182 - $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
212 + $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
183 213 elseif (isset($channel['description'])) :
184 - $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
214 + $update[] = "link_description = '".esc_sql($channel['description'])."'";
185 215 endif;
186 216 endif;
187 -
188 - $this->settings = array_merge($this->settings, $this->flatten_array($channel));
189 217
190 - $this->settings['update/last'] = time(); $ttl = $this->ttl();
191 - if (!is_null($ttl)) :
192 - $this->settings['update/ttl'] = $ttl;
193 - $this->settings['update/timed'] = 'feed';
194 - 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';
209 - endif;
210 - $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this);
218 + $this->update_setting('link/feed_type', $this->simplepie->get_type());
211 219
212 - if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
213 - $this->settings['update/hold'] = 'scheduled';
220 + $this->merge_settings($channel, 'feed/');
221 +
222 + $this->update_setting('update/last', time());
223 + $this->do_update_ttl();
224 +
225 + if (!$this->setting('update/hold') != 'ping') :
226 + $this->update_setting('update/hold', 'scheduled');
214 227 endif;
215 228
216 - $this->settings['update/unfinished'] = 'yes';
229 + $this->update_setting('update/unfinished', 'yes');
217 230
218 - $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
231 + $update[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
219 232
220 233 $update_set = implode(',', $update);
221 -
234 +
222 235 // Update the properties of the link from the feed information
223 236 $result = $wpdb->query("
224 237 UPDATE $wpdb->links
225 238 SET $update_set
@@ -224,40 +237,101 @@
224 237 UPDATE $wpdb->links
225 238 SET $update_set
226 239 WHERE link_id='$this->id'
227 240 ");
241 + do_action('update_syndicated_feed', $this->id, $this);
228 242
229 243 # -- Add new posts from feed and update any updated posts
230 244 $crashed = false;
231 245
232 - if (is_array($this->magpie->items)) :
233 - foreach ($this->magpie->items as $item) :
234 - $post =& new SyndicatedPost($item, $this);
235 - if (!$resume or !in_array(trim($post->guid()), $processed)) :
236 - $processed[] = $post->guid();
237 - if (!$post->filtered()) :
238 - $new = $post->store();
239 - if ( $new !== false ) $new_count[$new]++;
246 + $posts = $this->live_posts();
247 +
248 + $this->magpie->originals = $posts;
249 +
250 + // If this is a complete feed, rather than an incremental feed, we
251 + // need to prepare to mark everything for presumptive retirement.
252 + if ($this->is_non_incremental()) :
253 + $q = new WP_Query(array(
254 + 'fields' => '_synfrom',
255 + 'post_status__not' => 'fwpretired',
256 + 'ignore_sticky_posts' => true,
257 + 'meta_key' => 'syndication_feed_id',
258 + 'meta_value' => $this->id,
259 + ));
260 + foreach ($q->posts as $p) :
261 + update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
262 + endforeach;
263 + endif;
264 +
265 + if (is_array($posts)) :
266 + foreach ($posts as $key => $item) :
267 + if (!$this->pause_updates()) :
268 + $post = new SyndicatedPost($item, $this);
269 +
270 + if (!$resume or !in_array(trim($post->guid()), $processed)) :
271 + $processed[] = $post->guid();
272 + if (!$post->filtered()) :
273 + $new = $post->store();
274 + if ( $new !== false ) $new_count[$new]++;
275 + endif;
276 +
277 + if (!is_null($crash_ts) and (time() > $crash_ts)) :
278 + $crashed = true;
279 + break;
280 + endif;
240 281 endif;
241 282
242 - if (!is_null($crash_ts) and (time() > $crash_ts)) :
243 - $crashed = true;
244 - break;
245 - endif;
246 - endif;
283 + unset($post);
284 + endif;
247 285 endforeach;
248 286 endif;
249 -
250 - // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
251 - $to_notes = $this->settings;
252 287
253 - $this->settings['update/processed'] = $processed;
288 + if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
289 + // Check for use of Atom tombstones. Spec:
290 + // <http://tools.ietf.org/html/draft-snell-atompub-tombstones-18>
291 + $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
292 + if (!is_null($tombstones) && count($tombstones) > 0) :
293 + foreach ($tombstones as $tombstone) :
294 + $ref = NULL;
295 + foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
296 + if (isset($tombstone['attribs'][$ns])
297 + and isset($tombstone['attribs'][$ns]['ref'])) :
298 + $ref = $tombstone['attribs'][$ns]['ref'];
299 + endif;
300 + endforeach;
301 +
302 + $q = new WP_Query(array(
303 + 'ignore_sticky_posts' => true,
304 + 'guid' => $ref,
305 + 'meta_key' => 'syndication_feed_id',
306 + 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
307 + ));
308 +
309 + foreach ($q->posts as $p) :
310 + $old_status = $p->post_status;
311 + FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
312 + set_post_field('post_status', 'fwpretired', $p->ID);
313 + wp_transition_post_status('fwpretired', $old_status, $p);
314 + endforeach;
315 +
316 + endforeach;
317 + endif;
318 + endif;
319 +
320 + $suffix = ($crashed ? 'crashed' : 'completed');
321 + do_action('update_syndicated_feed_items', $this->id, $this);
322 + do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
323 +
324 + $this->update_setting('update/processed', $processed);
254 325 if (!$crashed) :
255 - $this->settings['update/unfinished'] = 'no';
326 + $this->update_setting('update/unfinished', 'no');
256 327 endif;
328 + $this->update_setting('link/item count', count($posts));
257 329
258 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
259 -
330 + // Copy back any changes to feed settings made in the
331 + // course of updating (e.g. new author rules)
332 + $update_set = "link_notes = '".esc_sql($this->settings_to_notes())."'";
333 +
260 334 // Update the properties of the link from the feed information
261 335 $result = $wpdb->query("
262 336 UPDATE $wpdb->links
263 337 SET $update_set
@@ -262,13 +336,72 @@
262 336 UPDATE $wpdb->links
263 337 SET $update_set
264 338 WHERE link_id='$this->id'
265 339 ");
340 +
341 + do_action("update_syndicated_feed_completed", $this->id, $this);
266 342 endif;
267 -
343 +
344 + // All done; let's clean up.
345 + $this->magpie = NULL;
346 +
347 + // Avoid circular-reference memory leak in PHP < 5.3.
348 + // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks>
349 + if (method_exists($this->simplepie, '__destruct')) :
350 + $this->simplepie->__destruct();
351 + endif;
352 + $this->simplepie = NULL;
353 +
268 354 return $new_count;
269 355 } /* SyndicatedLink::poll() */
270 356
357 + public function do_update_ttl () {
358 + list($ttl, $xml) = $this->ttl(/*return element=*/ true);
359 +
360 + if (!is_null($ttl)) :
361 + $this->update_setting('update/ttl', $ttl);
362 + $this->update_setting('update/xml', $xml);
363 + $this->update_setting('update/timed', 'feed');
364 + else :
365 + $ttl = $this->automatic_ttl();
366 + $this->update_setting('update/ttl', $ttl);
367 + $this->update_setting('update/xml', NULL);
368 + $this->update_setting('update/timed', 'automatically');
369 + endif;
370 +
371 + $this->update_setting('update/fudge', rand(0, ($ttl/3))*60);
372 +
373 + $this->update_setting('update/ttl', apply_filters(
374 + 'syndicated_feed_ttl',
375 + $this->setting('update/ttl'),
376 + $this
377 + ));
378 +
379 + } /* SyndicatedLink::do_update_ttl () */
380 +
381 + public function process_retirements ($delta) {
382 + global $post;
383 +
384 + $q = new WP_Query(array(
385 + 'fields' => '_synfrom',
386 + 'post_status__not' => 'fwpretired',
387 + 'ignore_sticky_posts' => true,
388 + 'meta_key' => '_feedwordpress_retire_me_'.$this->id,
389 + 'meta_value' => '1',
390 + ));
391 + if ($q->have_posts()) :
392 + foreach ($q->posts as $p) :
393 + $old_status = $p->post_status;
394 + FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to absence from a non-incremental feed.');
395 + set_post_field('post_status', 'fwpretired', $p->ID);
396 + wp_transition_post_status('fwpretired', $old_status, $p);
397 + delete_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id);
398 + endforeach;
399 + endif;
400 +
401 + return $delta;
402 + } /* SyndicatedLink::process_retirements () */
403 +
271 404 /**
272 405 * Updates the URL for the feed syndicated by this link.
273 406 *
274 407 * @param string $url The new feed URL to use for this source.
@@ -273,9 +406,9 @@
273 406 *
274 407 * @param string $url The new feed URL to use for this source.
275 408 * @return bool TRUE on success, FALSE on failure.
276 409 */
277 - function set_uri ($url) {
410 + public function set_uri ($url) {
278 411 global $wpdb;
279 412
280 413 if ($this->found()) :
281 414 // Update link_rss
@@ -281,12 +414,12 @@
281 414 // Update link_rss
282 415 $result = $wpdb->query("
283 416 UPDATE $wpdb->links
284 417 SET
285 - link_rss = '".$wpdb->escape($url)."'
286 - WHERE link_id = '".$wpdb->escape($this->id)."'
418 + link_rss = '".esc_sql($url)."'
419 + WHERE link_id = '".esc_sql($this->id)."'
287 420 ");
288 -
421 +
289 422 $ret = ($result ? true : false);
290 423 else :
291 424 $ret = false;
292 425 endif;
@@ -291,30 +424,89 @@
291 424 $ret = false;
292 425 endif;
293 426 return $ret;
294 427 } /* SyndicatedLink::set_uri () */
295 -
296 - function map_name_to_new_user ($name, $newuser_name) {
428 +
429 + public function deactivate () {
297 430 global $wpdb;
298 431
432 + $wpdb->query($wpdb->prepare("
433 + UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
434 + ", (int) $this->id));
435 + } /* SyndicatedLink::deactivate () */
436 +
437 + /**
438 + * SyndicatedLink::delete() deletes a subscription from the WordPress links
439 + * table. Any posts that were syndicated through that subscription will still
440 + * be present in the wp_posts table; but postmeta fields that refer to the
441 + * syndication feed's numeric id (which will no longer be valid) will be
442 + * deleted. For most purposes, the posts remaining will be treated as if they
443 + * were locally authored posts rather than syndicated posts.
444 + *
445 + * @global $wpdb
446 + * @uses wpdb::query
447 + */
448 + public function delete () {
449 + global $wpdb;
450 +
451 + $wpdb->query($wpdb->prepare("
452 + DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
453 + AND meta_value = '%s'
454 + ", $this->id));
455 +
456 + $wpdb->query($wpdb->prepare("
457 + DELETE FROM $wpdb->links WHERE link_id = %d
458 + ", (int) $this->id));
459 +
460 + $this->id = NULL;
461 + } /* SyndicatedLink::delete () */
462 +
463 + /**
464 + * SyndicatedLink::nuke() deletes a subscription AND all of the
465 + * posts syndicated through that subscription.
466 + *
467 + * @global $wpdb
468 + * @uses wpdb::get_col
469 + * @uses wp_delete_post
470 + * @uses SyndicatedLink::delete
471 + */
472 + public function nuke () {
473 + global $wpdb;
474 +
475 + // Make a list of the items syndicated from this feed...
476 + $post_ids = $wpdb->get_col($wpdb->prepare("
477 + SELECT post_id FROM $wpdb->postmeta
478 + WHERE meta_key = 'syndication_feed_id'
479 + AND meta_value = '%s'
480 + ", $this->id));
481 +
482 + // ... and kill them all
483 + if (count($post_ids) > 0) :
484 + foreach ($post_ids as $post_id) :
485 + // Force scrubbing of deleted post
486 + // rather than sending to Trashcan
487 + wp_delete_post(
488 + /*postid=*/ $post_id,
489 + /*force_delete=*/ true
490 + );
491 + endforeach;
492 + endif;
493 +
494 + $this->delete();
495 + } /* SyndicatedLink::nuke () */
496 +
497 + public function map_name_to_new_user ($name, $newuser_name) {
498 + global $wpdb;
499 +
299 500 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);
501 + $newuser_id = fwp_insert_new_user($newuser_name);
312 502 if (is_numeric($newuser_id)) :
313 503 if (is_null($name)) : // Unfamiliar author
314 - $this->settings['unfamiliar author'] = $newuser_id;
504 + $this->update_setting('unfamiliar author', $newuser_id);
315 505 else :
316 - $this->settings['map authors']['name'][$name] = $newuser_id;
506 + $map = $this->setting('map authors');
507 + $map['name'][$name] = $newuser_id;
508 + $this->update_setting('map authors', $map);
317 509 endif;
318 510 else :
319 511 // TODO: Add some error detection and reporting
320 512 endif;
@@ -322,9 +514,114 @@
322 514 // TODO: Add some error reporting
323 515 endif;
324 516 } /* SyndicatedLink::map_name_to_new_user () */
325 517
326 - function settings_to_notes () {
518 + protected function imploded_settings () {
519 + return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
520 + } /* SyndicatedLink::imploded_settings () */
521 +
522 + protected function get_settings_from_notes () {
523 + // Read off feed settings from link_notes
524 + $notes = explode("\n", $this->link->link_notes);
525 + foreach ($notes as $note):
526 + $pair = explode(": ", $note, 2);
527 + $key = (isset($pair[0]) ? $pair[0] : null);
528 + $value = (isset($pair[1]) ? $pair[1] : null);
529 + if (!is_null($key) and !is_null($value)) :
530 + // Unescape and trim() off the whitespace.
531 + // Thanks to Ray Lischner for pointing out the
532 + // need to trim off whitespace.
533 + $this->settings[$key] = stripcslashes (trim($value));
534 + endif;
535 + endforeach;
536 +
537 + // "Magic" feed settings
538 + $this->settings['link/uri'] = $this->link->link_rss;
539 + $this->settings['link/name'] = $this->link->link_name;
540 + $this->settings['link/id'] = $this->link->link_id;
541 +
542 + // `hardcode categories` and `unfamiliar categories` are
543 + // deprecated in favor of `unfamiliar category`
544 + if (
545 + isset($this->settings['unfamiliar categories'])
546 + and !isset($this->settings['unfamiliar category'])
547 + ) :
548 + $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
549 + endif;
550 + if (
551 + FeedWordPress::affirmative($this->settings, 'hardcode categories')
552 + and !isset($this->settings['unfamiliar category'])
553 + ) :
554 + $this->settings['unfamiliar category'] = 'default';
555 + endif;
556 +
557 + // Set this up automagically for del.icio.us
558 + $bits = parse_url($this->link->link_rss);
559 + $tagspacers = array('del.icio.us', 'feeds.delicious.com');
560 + if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
561 + $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
562 + endif;
563 +
564 + // Simple lists
565 + foreach ($this->imploded_settings() as $what) :
566 + if (isset($this->settings[$what])):
567 + $this->settings[$what] = explode(
568 + FEEDWORDPRESS_CAT_SEPARATOR,
569 + $this->settings[$what]
570 + );
571 + endif;
572 + endforeach;
573 +
574 + if (isset($this->settings['terms'])) :
575 + // Look for new format
576 + $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
577 +
578 + if (!is_array($this->settings['terms'])) :
579 + // Deal with old format instead. Ugh.
580 +
581 + // Split on two *or more* consecutive breaks
582 + // because in the old format, a taxonomy
583 + // without any associated terms would
584 + // produce tax_name#1\n\n\ntax_name#2\nterm,
585 + // and the naive split on the first \n\n
586 + // would screw up the tax_name#2 list.
587 + //
588 + // Props to David Morris for pointing this
589 + // out.
590 +
591 + $this->settings['terms'] = preg_split(
592 + "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
593 + $this->settings['terms']
594 + );
595 + $terms = array();
596 + foreach ($this->settings['terms'] as $line) :
597 + $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
598 + $tax = array_shift($line);
599 + $terms[$tax] = $line;
600 + endforeach;
601 + $this->settings['terms'] = $terms;
602 + endif;
603 + endif;
604 +
605 + if (isset($this->settings['map authors'])) :
606 + $author_rules = explode("\n\n", $this->settings['map authors']);
607 + $ma = array();
608 + foreach ($author_rules as $rule) :
609 + list($rule_type, $author_name, $author_action) = explode("\n", $rule);
610 +
611 + // Normalize for case and whitespace
612 + $rule_type = strtolower(trim($rule_type));
613 + $author_name = strtolower(trim($author_name));
614 + $author_action = strtolower(trim($author_action));
615 +
616 + $ma[$rule_type][$author_name] = $author_action;
617 + endforeach;
618 + $this->settings['map authors'] = $ma;
619 + endif;
620 +
621 + } /* SyndicatedLink::get_settings_from_notes () */
622 +
623 + protected function settings_to_notes () {
327 624 $to_notes = $this->settings;
328 625
329 626 unset($to_notes['link/id']); // Magic setting; don't save
330 627 unset($to_notes['link/uri']); // Magic setting; don't save
@@ -336,14 +633,21 @@
336 633 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
337 634 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
338 635 endif;
339 636
340 - if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
341 - $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
637 + foreach ($this->imploded_settings() as $what) :
638 + if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
639 + $to_notes[$what] = implode(
640 + FEEDWORDPRESS_CAT_SEPARATOR,
641 + $to_notes[$what]
642 + );
643 + endif;
644 + endforeach;
645 +
646 + if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
647 + // Serialize it.
648 + $to_notes['terms'] = serialize($to_notes['terms']);
342 649 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 650
347 651 // Collapse the author mapping rule structure back into a flat string
348 652 if (isset($to_notes['map authors'])) :
349 653 $ma = array();
@@ -361,22 +665,30 @@
361 665 endforeach;
362 666 return $notes;
363 667 } /* SyndicatedLink::settings_to_notes () */
364 668
365 - function save_settings ($reload = false) {
669 + public function save_settings ($reload = false) {
366 670 global $wpdb;
367 671
368 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
369 -
370 - // Update the properties of the link from the feed information
672 + // Save channel-level meta-data
673 + foreach (array('link_name', 'link_description', 'link_url') as $what) :
674 + $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
675 + endforeach;
676 +
677 + // Save settings to the notes field
678 + $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
679 +
680 + // Update the properties of the link from settings changes, etc.
681 + $update_set = implode(", ", $alter);
682 +
371 683 $result = $wpdb->query("
372 684 UPDATE $wpdb->links
373 685 SET $update_set
374 686 WHERE link_id='$this->id'
375 687 ");
376 -
688 +
377 689 if ($reload) :
378 - // reload link information from DB
690 + // force reload of link information from DB
379 691 if (function_exists('clean_bookmark_cache')) :
380 692 clean_bookmark_cache($this->id);
381 693 endif;
382 694 endif;
@@ -381,31 +693,313 @@
381 693 endif;
382 694 endif;
383 695 } /* SyndicatedLink::save_settings () */
384 696
385 - function uri () {
386 - return (is_object($this->link) ? $this->link->link_rss : NULL);
697 + /**
698 + * Retrieves the value of a setting, allowing for a global setting to be
699 + * used as a fallback, or a constant value, or both.
700 + *
701 + * @param string $name The link setting key
702 + * @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.
703 + * @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.
704 + * @return bool TRUE on success, FALSE on failure.
705 + */
706 + public function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
707 + $ret = NULL;
708 + if (isset($this->settings[$name])) :
709 + $ret = $this->settings[$name];
710 + endif;
711 +
712 + $no_value = (
713 + is_null($ret)
714 + or (is_string($ret) and strtolower($ret)==$default)
715 + );
716 +
717 + if ($no_value and !is_null($fallback_global)) :
718 + // Avoid duplication of this correction
719 + $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
720 +
721 + // Occasionally we'll get an array back. Convert it to a string
722 + if ( is_array($fallback_global) && sizeof($fallback_global) )
723 + $fallback_global = reset($fallback_global);
724 +
725 + if ( !empty($fallback_global) )
726 + $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
727 + endif;
728 +
729 + $no_value = (
730 + is_null($ret)
731 + or (is_string($ret) and strtolower($ret)==$default)
732 + );
733 +
734 + if ($no_value and !is_null($fallback_value)) :
735 + $ret = $fallback_value;
736 + endif;
737 + return $ret;
738 + } /* SyndicatedLink::setting () */
739 +
740 + public function merge_settings ($data, $prefix, $separator = '/') {
741 + $dd = $this->flatten_array($data, $prefix, $separator);
742 + $this->settings = array_merge($this->settings, $dd);
743 + } /* SyndicatedLink::merge_settings () */
744 +
745 + public function update_setting ($name, $value, $default = 'default') {
746 + if (!is_null($value) and $value != $default) :
747 + $this->settings[$name] = $value;
748 + else : // Zap it.
749 + unset($this->settings[$name]);
750 + endif;
751 + } /* SyndicatedLink::update_setting () */
752 +
753 + public function is_non_incremental () {
754 + return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
755 + } /* SyndicatedLink::is_non_incremental () */
756 +
757 + public function get_feed_type () {
758 + $type_code = $this->setting('link/feed_type');
759 +
760 + // list derived from: <http://simplepie.org/api/class-SimplePie.html>, retrieved 2020/01/18
761 + $bitmasks = array(
762 + SIMPLEPIE_TYPE_RSS_090 => 'RSS 0.90',
763 + SIMPLEPIE_TYPE_RSS_091_NETSCAPE => 'RSS 0.91 (Netscape)',
764 + SIMPLEPIE_TYPE_RSS_091_USERLAND => 'RSS 0.91 (Userland)',
765 + SIMPLEPIE_TYPE_RSS_091 => 'RSS 0.91',
766 + SIMPLEPIE_TYPE_RSS_092 => 'RSS 0.92',
767 + SIMPLEPIE_TYPE_RSS_093 => 'RSS 0.93',
768 + SIMPLEPIE_TYPE_RSS_094 => 'RSS 0.94',
769 + SIMPLEPIE_TYPE_RSS_10 => 'RSS 1.0',
770 + SIMPLEPIE_TYPE_RSS_20 => 'RSS 2.0.x',
771 + SIMPLEPIE_TYPE_RSS_RDF => 'RDF-based RSS',
772 + SIMPLEPIE_TYPE_RSS_SYNDICATION => 'Non-RDF-based RSS',
773 + SIMPLEPIE_TYPE_RSS_ALL => 'Any version of RSS',
774 + SIMPLEPIE_TYPE_ATOM_03 => 'Atom 0.3',
775 + SIMPLEPIE_TYPE_ATOM_10 => 'Atom 1.0',
776 + SIMPLEPIE_TYPE_ATOM_ALL => 'Atom (any version)',
777 + SIMPLEPIE_TYPE_ALL => 'Supported Feed (unspecified format)',
778 + );
779 +
780 + $type = "Unknown or unsupported format";
781 + foreach ($bitmasks as $format_flag => $format_string) :
782 + if (is_numeric($format_flag)) : // Guard against failure of constants to be defined.
783 + if ($type_code & $format_flag) :
784 + $type = $format_string;
785 + break; // foreach
786 + endif;
787 + endif;
788 + endforeach;
789 +
790 + return $type;
791 + } /* SyndicatedLink::get_feed_type () */
792 +
793 + public function uri ($params = array()) {
794 + $params = wp_parse_args($params, array(
795 + 'add_params' => false,
796 + 'fetch' => false,
797 + ));
798 +
799 + // Initialize $qp (= array for added query parameters, if any)
800 + $qp = array();
801 +
802 + $link_rss = (is_object($this->link) ? $this->link->link_rss : NULL);
803 +
804 + // $link_rss stores the URI for the subscription as stored in the feed's record.
805 + // $uri stores the effective URI of the request including any/all added query parameters
806 + $uri = $link_rss;
807 + if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
808 + $qp = maybe_unserialize($this->setting('query parameters', array()));
809 +
810 + // For high-tech HTTP feed request kung fu
811 + $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
812 +
813 + // $qp is an array of key-value pairs stored as arrays of format [$key, $value]
814 + $q = array();
815 + if (is_array($qp) and count($qp) > 0) :
816 + foreach ($qp as $pair) :
817 + $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
818 + endforeach;
819 +
820 + // Are we appending to a URI that already has params?
821 + $sep = ((strpos($uri, "?")===false) ? '?' : '&');
822 +
823 + // Tack it on
824 + $uri .= $sep . implode("&", $q);
825 + endif;
826 + endif;
827 +
828 + // Do we have any filters that apply here?
829 + $uri = apply_filters('syndicated_link_uri', $uri, $link_rss, $qp, $params, $this);
830 +
831 + // Return the filtered link URI.
832 + return $uri;
387 833 } /* SyndicatedLink::uri () */
388 834
389 - function homepage ($fromFeed = true) {
835 + public function username () {
836 + return $this->setting('http username', 'http_username', NULL);
837 + } /* SyndicatedLink::username () */
838 +
839 + public function password () {
840 + return $this->setting('http password', 'http_password', NULL);
841 + } /* SyndicatedLink::password () */
842 +
843 + public function authentication_method () {
844 + $auth = $this->setting('http auth method', NULL);
845 + if (('-' == $auth) or (strlen($auth)==0)) :
846 + $auth = NULL;
847 + endif;
848 + return $auth;
849 + } /* SyndicatedLink::authentication_method () */
850 +
851 + var $postmeta = array();
852 + public function postmeta ($params = array()) {
853 + $params = wp_parse_args($params, /*defaults=*/ array(
854 + "field" => NULL,
855 + "parsed" => false,
856 + "force" => false,
857 + ));
858 +
859 + if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
860 + // First, get the global settings.
861 + $default_custom_settings = get_option('feedwordpress_custom_settings');
862 + if ($default_custom_settings and !is_array($default_custom_settings)) :
863 + $default_custom_settings = unserialize($default_custom_settings);
864 + endif;
865 + if (!is_array($default_custom_settings)) :
866 + $default_custom_settings = array();
867 + endif;
868 +
869 + // Next, get the settings for this particular feed.
870 + $custom_settings = $this->setting('postmeta', NULL, NULL);
871 + if ($custom_settings and !is_array($custom_settings)) :
872 + $custom_settings = unserialize($custom_settings);
873 + endif;
874 + if (!is_array($custom_settings)) :
875 + $custom_settings = array();
876 + endif;
877 +
878 + $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
879 + $this->postmeta[/*parsed=*/ true] = array();
880 +
881 + // Now, run through and parse them all.
882 + foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
883 + $meta = apply_filters("syndicated_link_post_meta_${key}_pre", $meta, $this);
884 + $this->postmeta[/*parsed=*/ false][$key] = $meta;
885 + $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
886 + endforeach;
887 + endif;
888 +
889 + $ret = $this->postmeta[!!$params['parsed']];
890 + if (is_string($params['field'])) :
891 + $ret = $ret[$params['field']];
892 + endif;
893 + return $ret;
894 + } /* SyndicatedLink::postmeta () */
895 +
896 + public function property_cascade ($fromFeed, $link_field, $setting, $method) {
897 + $value = NULL;
390 898 if ($fromFeed) :
391 - $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
899 + $value = $this->setting($setting, NULL, NULL, NULL);
900 +
901 + $s = $this->simplepie;
902 + $callable = (is_object($s) and method_exists($s, $method));
903 + if (is_null($value) and $callable) :
904 + $fallback = $s->{$method}();
905 + endif;
392 906 else :
393 - $url = $this->link->link_url;
907 + $value = $this->link->{$link_field};
394 908 endif;
395 - return $url;
909 + return $value;
910 + } /* SyndicatedLink::property_cascade () */
911 +
912 + public function homepage ($fromFeed = true) {
913 + return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
396 914 } /* SyndicatedLink::homepage () */
397 915
398 - 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;
916 + public function name ($fromFeed = true) {
917 + return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
918 + } /* SyndicatedLink::name () */
919 +
920 + public function guid () {
921 + $ret = $this->setting('feed/id', NULL, $this->uri());
922 +
923 + // If we can get it live from the feed, do so.
924 + if (is_object($this->simplepie)) :
925 + $search = array(
926 + array('', 'id'),
927 + array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
928 + array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
929 + array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
930 + array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
931 + array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
932 + );
933 +
934 + foreach ($search as $pair) :
935 + if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
936 + $ret = $id_tags[0]['data'];
937 + break;
938 + elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
939 + $ret = $id_tags[0]['data'];
940 + break;
941 + endif;
942 + endforeach;
403 943 endif;
404 - return $name;
405 - } /* SyndicatedLink::name () */
944 + return $ret;
945 + }
406 946
407 - function ttl () {
947 + public function links ($params = array()) {
948 + $params = wp_parse_args($params, array(
949 + "rel" => NULL,
950 + ));
951 +
952 + $fLinks = array();
953 + $search = array(
954 + array('', 'link'),
955 + array(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'),
956 + array(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'),
957 + );
958 +
959 + foreach ($search as $pair) :
960 + if ($link_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
961 + $fLinks = array_merge($fLinks, $link_tags);
962 + endif;
963 + if ($link_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
964 + $fLinks = array_merge($fLinks, $link_tags);
965 + endif;
966 + endforeach;
967 +
968 + $ret = array();
969 + foreach ($fLinks as $link) :
970 + $filter = false;
971 + if (!is_null($params['rel'])) :
972 + $filter = true;
973 +
974 + if (isset($link['attribs'])) :
975 + // Get a list of NSes from the search
976 + foreach ($search as $pair) :
977 + $ns = $pair[0];
978 +
979 + if (isset($link['attribs'][$ns])
980 + and isset($link['attribs'][$ns]['rel'])
981 + ) :
982 + $rel = strtolower(trim($link['attribs'][$ns]['rel']));
983 + $fRel = strtolower(trim($params['rel']));
984 +
985 + if ($rel == $fRel) :
986 + $filter = false;
987 + endif;
988 + endif;
989 + endforeach;
990 + endif;
991 + endif;
992 +
993 + if (!$filter) :
994 + $ret[] = $link;
995 + endif;
996 + endforeach;
997 +
998 + return $ret;
999 + }
1000 +
1001 + public function ttl ($return_element = false) {
408 1002 if (is_object($this->magpie)) :
409 1003 $channel = $this->magpie->channel;
410 1004 else :
411 1005 $channel = array();
@@ -414,9 +1008,10 @@
414 1008 if (isset($channel['ttl'])) :
415 1009 // "ttl stands for time to live. It's a number of
416 1010 // minutes that indicates how long a channel can be
417 1011 // cached before refreshing from the source."
418 - // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
1012 + // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
1013 + $xml = 'rss:ttl';
419 1014 $ret = $channel['ttl'];
420 1015 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
421 1016 $period_minutes = array (
422 1017 'hourly' => 60, /* minutes in an hour */
@@ -429,13 +1024,13 @@
429 1024 // "sy:updatePeriod: Describes the period over which the
430 1025 // channel format is updated. Acceptable values are:
431 1026 // hourly, daily, weekly, monthly, yearly. If omitted,
432 1027 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
433 - if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
1028 + if (isset($channel['sy']['updateperiod'])) : $period = trim($channel['sy']['updateperiod']);
434 1029 else : $period = 'daily';
435 1030 endif;
436 -
437 - // "sy:updateFrequency: Used to describe the frequency
1031 +
1032 + // "sy:updateFrequency: Used to describe the frequency
438 1033 // of updates in relation to the update period. A
439 1034 // positive integer indicates how many times in that
440 1035 // period the channel is updated. ... If omitted a value
441 1036 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
@@ -441,26 +1036,64 @@
441 1036 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
442 1037 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
443 1038 else : $freq = 1;
444 1039 endif;
1040 +
1041 + // normalize the period name...
1042 + $period = strtolower(trim($period));
1043 +
1044 + // do we recognize the alphanumeric period name? if not, then guess
1045 + // a responsible default, e.g. roughly hourly
1046 + $mins = (isset($period_minutes[$period]) ? $period_minutes[$period] : 67);
445 1047
446 - $ret = (int) ($period_minutes[$period] / $freq);
1048 + $xml = 'sy:updateFrequency';
1049 + $ret = (int) ($mins / $freq);
1050 +
447 1051 else :
1052 + $xml = NULL;
448 1053 $ret = NULL;
449 1054 endif;
450 - return $ret;
1055 +
1056 + if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
1057 + $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
1058 +
1059 + if ($min > $ret) :
1060 + $ret = NULL;
1061 + endif;
1062 + endif;
1063 + return ($return_element ? array($ret, $xml) : $ret);
451 1064 } /* SyndicatedLink::ttl() */
452 1065
453 - // SyndicatedLink::flatten_array (): flatten an array. Useful for
454 - // hierarchical and namespaced elements.
455 - //
456 - // Given an array which may contain array or object elements in it,
457 - // return a "flattened" array: a one-dimensional array of scalars
458 - // containing each of the scalar elements contained within the array
459 - // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
460 - // returned array for FeedWordPress::flatten_array($a) will contain a key
461 - // $a['feed/b/c/d'] with value 'e'.
462 - function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
1066 + public function automatic_ttl () {
1067 + // spread out over a time interval for staggered updates
1068 + $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
1069 + if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
1070 + $updateWindow = DEFAULT_UPDATE_PERIOD;
1071 + endif;
1072 +
1073 + // We get a fudge of 1/3 of window from elsewhere. We'll do some more
1074 + // fudging here.
1075 + $fudgedInterval = $updateWindow+rand(-($updateWindow/6), 5*($updateWindow/12));
1076 + return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
1077 + } /* SyndicatedLink::automatic_ttl () */
1078 +
1079 + /**
1080 + * SyndicatedLink::flatten_array (): flatten an array. Useful for
1081 + * hierarchical and namespaced elements.
1082 + *
1083 + * Given an array which may contain array or object elements in it,
1084 + * return a "flattened" array: a one-dimensional array of scalars
1085 + * containing each of the scalar elements contained within the array
1086 + * structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
1087 + * returned array for FeedWordPress::flatten_array($a) will contain a key
1088 + * $a['feed/b/c/d'] with value 'e'.
1089 + *
1090 + * @param array $arr
1091 + * @param string $prefix
1092 + * @param string $separator
1093 + * @return array
1094 + */
1095 + public function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
463 1096 $ret = array ();
464 1097 if (is_array($arr)) :
465 1098 foreach ($arr as $key => $value) :
466 1099 if (is_scalar($value)) :
@@ -472,41 +1105,138 @@
472 1105 endif;
473 1106 return $ret;
474 1107 } /* SyndicatedLink::flatten_array () */
475 1108
476 - 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");
1109 + public function hardcode ($what) {
1110 +
1111 + $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
1112 +
1113 + if ('yes' == $ret) :
1114 + $ret = true;
483 1115 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");
1116 + $ret = false;
488 1117 endif;
489 1118 return $ret;
490 1119 } /* SyndicatedLink::hardcode () */
491 1120
492 - function syndicated_status ($what, $default, $fallback = true) {
1121 + public function syndicated_status ($what, $default, $fallback = true) {
493 1122 global $wpdb;
494 1123
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);
1124 + $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
1125 + $ret = $this->setting($what.' status', $g_set, $default);
502 1126
503 - // Or use default value if we can't.
504 - else :
505 - $ret = $default;
1127 + return esc_sql(trim(strtolower($ret)));
1128 + } /* SyndicatedLink:syndicated_status () */
506 1129
1130 + public function taxonomies () {
1131 + $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
1132 + return get_object_taxonomies(array('object_type' => $post_type), 'names');
1133 + } /* SyndicatedLink::taxonomies () */
1134 +
1135 + /**
1136 + * category_ids: look up (and create) category ids from a list of
1137 + * categories
1138 + *
1139 + * @param array $cats
1140 + * @param string $unfamiliar_category
1141 + * @param array|null $taxonomies
1142 + * @return array
1143 + */
1144 + public function category_ids ($post, $cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
1145 + $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
1146 + $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
1147 +
1148 + $catTax = 'category';
1149 +
1150 + if (is_null($taxonomies)) :
1151 + $taxonomies = array('category');
507 1152 endif;
508 1153
509 - return $wpdb->escape(trim(strtolower($ret)));
510 - } /* SyndicatedLink:syndicated_status () */
511 -} // class SyndicatedLink
1154 + // We need to normalize whitespace because (1) trailing
1155 + // whitespace can cause PHP and MySQL not to see eye to eye on
1156 + // VARCHAR comparisons for some versions of MySQL (cf.
1157 + // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1158 + // because I doubt most people want to make a semantic
1159 + // distinction between 'Computers' and 'Computers '
1160 + $cats = array_map('trim', $cats);
1161 +
1162 + $terms = array();
1163 + foreach ($taxonomies as $tax) :
1164 + $terms[$tax] = array();
1165 + endforeach;
1166 +
1167 + foreach ($cats as $cat_name) :
1168 + if (strlen(trim($cat_name)) < 1) :
1169 + continue;
1170 + endif;
1171 +
1172 + $oTerm = new SyndicatedPostTerm($cat_name, $taxonomies, $post);
1173 +
1174 + if ($oTerm->is_familiar()) :
1175 +
1176 + $tax = $oTerm->taxonomy();
1177 + if (!isset($terms[$tax])) :
1178 + $terms[$tax] = array();
1179 + endif;
1180 + $terms[$tax][] = $oTerm->id();
1181 +
1182 + else :
1183 +
1184 + if ('tag'==$unfamiliar_category) :
1185 + $unfamiliar_category = 'create:post_tag';
1186 + endif;
1187 +
1188 + if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
1189 + $tax = $catTax; // Default
1190 +
1191 + if (isset($ref[2])
1192 + and strlen($ref[2]) > 2) :
1193 + $tax = $ref[2];
1194 + endif;
1195 +
1196 + $inserted = $oTerm->insert($tax);
1197 + if (!is_null($inserted)) :
1198 + if (!isset($terms[$tax])) :
1199 + $terms[$tax] = array();
1200 + endif;
1201 + $terms[$tax][] = $inserted;
1202 + else :
1203 +
1204 + endif; // !is_null($inserted)
1205 + endif; // preg_match(...)
1206 +
1207 + endif; /* ($oTerm->is_familiar()) */
1208 + endforeach;
1209 +
1210 + $filtersOn = $allowFilters;
1211 + if ($allowFilters) :
1212 + $filters = array_filter(
1213 + $this->setting('match/filter', 'match_filter', array()),
1214 + 'remove_dummy_zero'
1215 + );
1216 + $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
1217 + endif;
1218 +
1219 + // Check for filter conditions
1220 + foreach ($terms as $tax => $term_ids) :
1221 + if ($filtersOn
1222 + and (count($term_ids)==0)
1223 + and in_array($tax, $filters)) :
1224 + $terms = NULL; // Drop the post
1225 + break;
1226 + else :
1227 + $terms[$tax] = array_unique($term_ids);
1228 + endif;
1229 + endforeach;
1230 +
1231 + if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
1232 + $terms = end($terms);
1233 + endif;
1234 +
1235 + FeedWordPress::diagnostic(
1236 + 'syndicated_posts:categories',
1237 + 'Category: MAPPED term names '.json_encode($cats).' to IDs: '.json_encode($terms)
1238 + );
1239 + return $terms;
1240 + } /* SyndicatedLink::category_ids () */
1241 +} /* class SyndicatedLink */
512 1242