PluginProbe
FeedWordPress / 2022.0203
FeedWordPress v2022.0203
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 +898 -244 2010.05282022.0203 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 #
@@ -33,17 +33,18 @@
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 36 require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php');
37 +require_once(dirname(__FILE__).'/feedwordpressparsedpostmeta.class.php');
37 38
38 39 class SyndicatedLink {
39 40 var $id = null;
40 41 var $link = null;
41 42 var $settings = array ();
42 - var $simplepie = null;
43 + public $simplepie = null;
43 44 var $magpie = null;
44 45
45 - function SyndicatedLink ($link) {
46 + public function __construct( $link ) {
46 47 global $wpdb;
47 48
48 49 if (is_object($link)) :
49 50 $this->link = $link;
@@ -49,126 +50,103 @@
49 50 $this->link = $link;
50 51 $this->id = $link->link_id;
51 52 else :
52 53 $this->id = $link;
53 - if (function_exists('get_bookmark')) : // WP 2.1+
54 - $this->link = get_bookmark($link);
55 - else :
56 - $this->link = $wpdb->get_row("
57 - SELECT * FROM $wpdb->links
58 - WHERE (link_id = '".$wpdb->escape($link)."')"
59 - );
60 - endif;
54 + $this->link = get_bookmark($link);
61 55 endif;
62 56
63 - if (strlen($this->link->link_rss) > 0) :
64 - // Read off feed settings from link_notes
65 - $notes = explode("\n", $this->link->link_notes);
66 - foreach ($notes as $note):
67 - $pair = explode(": ", $note, 2);
68 - $key = (isset($pair[0]) ? $pair[0] : null);
69 - $value = (isset($pair[1]) ? $pair[1] : null);
70 - if (!is_null($key) and !is_null($value)) :
71 - // Unescape and trim() off the whitespace.
72 - // Thanks to Ray Lischner for pointing out the
73 - // need to trim off whitespace.
74 - $this->settings[$key] = stripcslashes (trim($value));
75 - endif;
76 - endforeach;
77 -
78 - // "Magic" feed settings
79 - $this->settings['link/uri'] = $this->link->link_rss;
80 - $this->settings['link/name'] = $this->link->link_name;
81 - $this->settings['link/id'] = $this->link->link_id;
82 -
83 - // `hardcode categories` and `unfamiliar categories` are deprecated in favor of `unfamiliar category`
84 - if (
85 - isset($this->settings['unfamiliar categories'])
86 - and !isset($this->settings['unfamiliar category'])
87 - ) :
88 - $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
57 + if (is_object($this->link)) :
58 + if (strlen($this->link->link_rss) > 0) :
59 + $this->get_settings_from_notes();
89 60 endif;
90 - if (
91 - FeedWordPress::affirmative($this->settings, 'hardcode categories')
92 - and !isset($this->settings['unfamiliar category'])
93 - ) :
94 - $this->settings['unfamiliar category'] = 'default';
95 - endif;
61 + endif;
96 62
97 - // Set this up automagically for del.icio.us
98 - $bits = parse_url($this->link->link_rss);
99 - $tagspacers = array('del.icio.us', 'feeds.delicious.com');
100 - if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
101 - $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
102 - endif;
63 + add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
64 + } /* SyndicatedLink::__construct () */
103 65
104 - if (isset($this->settings['cats'])):
105 - $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
106 - endif;
107 - if (isset($this->settings['tags'])):
108 - $this->settings['tags'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['tags']);
109 - endif;
110 -
111 - if (isset($this->settings['map authors'])) :
112 - $author_rules = explode("\n\n", $this->settings['map authors']);
113 - $ma = array();
114 - foreach ($author_rules as $rule) :
115 - list($rule_type, $author_name, $author_action) = explode("\n", $rule);
116 -
117 - // Normalize for case and whitespace
118 - $rule_type = strtolower(trim($rule_type));
119 - $author_name = strtolower(trim($author_name));
120 - $author_action = strtolower(trim($author_action));
121 -
122 - $ma[$rule_type][$author_name] = $author_action;
123 - endforeach;
124 - $this->settings['map authors'] = $ma;
125 - endif;
126 - endif;
127 - } /* SyndicatedLink::SyndicatedLink () */
128 -
129 - function found () {
66 + public function found () {
130 67 return is_object($this->link) and !is_wp_error($this->link);
131 68 } /* SyndicatedLink::found () */
132 69
133 - function stale () {
70 + public function id () {
71 + return (is_object($this->link) ? $this->link->link_id : NULL);
72 + }
73 +
74 + public function stale () {
75 + global $feedwordpress;
76 +
134 77 $stale = true;
135 - if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
78 + if ($this->setting('update/hold')=='ping') :
136 79 $stale = false; // don't update on any timed updates; pings only
137 - elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
80 + elseif ($this->setting('update/hold')=='next') :
138 81 $stale = true; // update on the next timed update
139 - elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
82 + elseif ( !$this->setting('update/last') ) :
140 83 $stale = true; // initial update
84 + elseif ($feedwordpress->force_update_all()) :
85 + $stale = true; // forced general updating
141 86 else :
142 - $after = ((int) $this->settings['update/last'])
143 - +((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 + );
144 92 $stale = (time() >= $after);
145 93 endif;
146 94 return $stale;
147 95 } /* SyndicatedLink::stale () */
148 96
149 - function poll ($crash_ts = NULL) {
150 - global $wpdb;
97 + public function fetch () {
98 + $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
151 99
152 100 $this->simplepie = apply_filters(
153 101 'syndicated_feed',
154 - FeedWordPress::fetch($this->link->link_rss),
102 + FeedWordPress::fetch($this, array('timeout' => $timeout)),
155 103 $this
156 104 );
157 -
105 +
158 106 // Filter compatibility mode
159 107 if (is_wp_error($this->simplepie)) :
160 108 $this->magpie = $this->simplepie;
161 109 else :
162 - $this->magpie = new MagpieFromSimplePie($this->simplepie);
110 + $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
163 111 endif;
112 + } /* SyndicatedLink::fetch () */
113 +
114 + public function live_posts () {
115 + if (!is_object($this->simplepie)) :
116 + $this->fetch();
117 + endif;
164 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) {
136 + global $wpdb;
137 +
138 + $url = $this->uri(array('add_params' => true, 'fetch' => true));
139 + FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
140 +
141 + $this->fetch();
142 +
165 143 $new_count = NULL;
166 144
167 - $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
145 + $resume = ('yes'==$this->setting('update/unfinished'));
168 146 if ($resume) :
169 147 // pick up where we left off
170 - $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
148 + $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
171 149 else :
172 150 // begin at the beginning
173 151 $processed = array();
174 152 endif;
@@ -174,11 +152,46 @@
174 152 endif;
175 153
176 154 if (is_wp_error($this->simplepie)) :
177 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;
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 +
178 187 elseif (is_object($this->simplepie)) :
179 - $new_count = array('new' => 0, 'updated' => 0);
180 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 +
181 194 # -- Update Link metadata live from feed
182 195 $channel = $this->magpie->channel;
183 196
184 197 if (!isset($channel['id'])) :
@@ -183,59 +196,43 @@
183 196
184 197 if (!isset($channel['id'])) :
185 198 $channel['id'] = $this->link->link_rss;
186 199 endif;
187 -
200 +
188 201 $update = array();
189 202 if (!$this->hardcode('url') and isset($channel['link'])) :
190 - $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
203 + $update[] = "link_url = '".esc_sql($channel['link'])."'";
191 204 endif;
192 -
205 +
193 206 if (!$this->hardcode('name') and isset($channel['title'])) :
194 - $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
207 + $update[] = "link_name = '".esc_sql($channel['title'])."'";
195 208 endif;
196 -
209 +
197 210 if (!$this->hardcode('description')) :
198 211 if (isset($channel['tagline'])) :
199 - $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
212 + $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
200 213 elseif (isset($channel['description'])) :
201 - $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
214 + $update[] = "link_description = '".esc_sql($channel['description'])."'";
202 215 endif;
203 216 endif;
204 -
205 - $this->settings = array_merge($this->settings, $this->flatten_array($channel));
206 217
207 - $this->settings['update/last'] = time(); $ttl = $this->ttl();
208 - if (!is_null($ttl)) :
209 - $this->settings['update/ttl'] = $ttl;
210 - $this->settings['update/timed'] = 'feed';
211 - else :
212 - // spread out over a time interval for staggered updates
213 - $updateWindow = $this->setting(
214 - 'update/window',
215 - 'update_window',
216 - DEFAULT_UPDATE_PERIOD
217 - );
218 - if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
219 - $updateWindow = DEFAULT_UPDATE_PERIOD;
220 - endif;
221 -
222 - $fudgedInterval = $updateWindow+rand(0, 2*($updateWindow/3));
223 - $this->settings['update/ttl'] = apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
224 - $this->settings['update/timed'] = 'automatically';
225 - endif;
226 - $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());
227 219
228 - if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
229 - $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');
230 227 endif;
231 228
232 - $this->settings['update/unfinished'] = 'yes';
229 + $this->update_setting('update/unfinished', 'yes');
233 230
234 - $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
231 + $update[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
235 232
236 233 $update_set = implode(',', $update);
237 -
234 +
238 235 // Update the properties of the link from the feed information
239 236 $result = $wpdb->query("
240 237 UPDATE $wpdb->links
241 238 SET $update_set
@@ -245,50 +242,96 @@
245 242
246 243 # -- Add new posts from feed and update any updated posts
247 244 $crashed = false;
248 245
249 - $posts = apply_filters(
250 - 'syndicated_feed_items',
251 - $this->magpie->originals,
252 - $this
253 - );
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 +
254 265 if (is_array($posts)) :
255 - foreach ($posts as $key => $original) :
256 - $item = $this->magpie->items[$key];
257 - $post = new SyndicatedPost(array(
258 - 'simplepie' => $original,
259 - 'magpie' => $item,
260 - ), $this);
266 + foreach ($posts as $key => $item) :
267 + if (!$this->pause_updates()) :
268 + $post = new SyndicatedPost($item, $this);
261 269
262 - if (!$resume or !in_array(trim($post->guid()), $processed)) :
263 - $processed[] = $post->guid();
264 - if (!$post->filtered()) :
265 - $new = $post->store();
266 - if ( $new !== false ) $new_count[$new]++;
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;
267 281 endif;
268 282
269 - if (!is_null($crash_ts) and (time() > $crash_ts)) :
270 - $crashed = true;
271 - break;
272 - endif;
273 - endif;
274 - unset($post);
283 + unset($post);
284 + endif;
275 285 endforeach;
276 286 endif;
287 +
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 +
277 320 $suffix = ($crashed ? 'crashed' : 'completed');
278 321 do_action('update_syndicated_feed_items', $this->id, $this);
279 322 do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
280 323
281 - // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
282 - $to_notes = $this->settings;
283 -
284 - $this->settings['update/processed'] = $processed;
324 + $this->update_setting('update/processed', $processed);
285 325 if (!$crashed) :
286 - $this->settings['update/unfinished'] = 'no';
326 + $this->update_setting('update/unfinished', 'no');
287 327 endif;
328 + $this->update_setting('link/item count', count($posts));
288 329
289 - $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
290 -
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 +
291 334 // Update the properties of the link from the feed information
292 335 $result = $wpdb->query("
293 336 UPDATE $wpdb->links
294 337 SET $update_set
@@ -293,15 +336,72 @@
293 336 UPDATE $wpdb->links
294 337 SET $update_set
295 338 WHERE link_id='$this->id'
296 339 ");
297 -
340 +
298 341 do_action("update_syndicated_feed_completed", $this->id, $this);
299 342 endif;
300 -
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 +
301 354 return $new_count;
302 355 } /* SyndicatedLink::poll() */
303 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 +
304 404 /**
305 405 * Updates the URL for the feed syndicated by this link.
306 406 *
307 407 * @param string $url The new feed URL to use for this source.
@@ -306,9 +406,9 @@
306 406 *
307 407 * @param string $url The new feed URL to use for this source.
308 408 * @return bool TRUE on success, FALSE on failure.
309 409 */
310 - function set_uri ($url) {
410 + public function set_uri ($url) {
311 411 global $wpdb;
312 412
313 413 if ($this->found()) :
314 414 // Update link_rss
@@ -314,12 +414,12 @@
314 414 // Update link_rss
315 415 $result = $wpdb->query("
316 416 UPDATE $wpdb->links
317 417 SET
318 - link_rss = '".$wpdb->escape($url)."'
319 - WHERE link_id = '".$wpdb->escape($this->id)."'
418 + link_rss = '".esc_sql($url)."'
419 + WHERE link_id = '".esc_sql($this->id)."'
320 420 ");
321 -
421 +
322 422 $ret = ($result ? true : false);
323 423 else :
324 424 $ret = false;
325 425 endif;
@@ -324,30 +424,89 @@
324 424 $ret = false;
325 425 endif;
326 426 return $ret;
327 427 } /* SyndicatedLink::set_uri () */
328 -
329 - function map_name_to_new_user ($name, $newuser_name) {
428 +
429 + public function deactivate () {
330 430 global $wpdb;
331 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 +
332 500 if (strlen($newuser_name) > 0) :
333 - $userdata = array();
334 - $userdata['ID'] = NULL;
335 -
336 - $userdata['user_login'] = sanitize_user($newuser_name);
337 - $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
338 -
339 - $userdata['user_nicename'] = sanitize_title($newuser_name);
340 - $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
341 -
342 - $userdata['display_name'] = $wpdb->escape($newuser_name);
343 -
344 - $newuser_id = wp_insert_user($userdata);
501 + $newuser_id = fwp_insert_new_user($newuser_name);
345 502 if (is_numeric($newuser_id)) :
346 503 if (is_null($name)) : // Unfamiliar author
347 - $this->settings['unfamiliar author'] = $newuser_id;
504 + $this->update_setting('unfamiliar author', $newuser_id);
348 505 else :
349 - $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);
350 509 endif;
351 510 else :
352 511 // TODO: Add some error detection and reporting
353 512 endif;
@@ -355,9 +514,114 @@
355 514 // TODO: Add some error reporting
356 515 endif;
357 516 } /* SyndicatedLink::map_name_to_new_user () */
358 517
359 - 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 () {
360 624 $to_notes = $this->settings;
361 625
362 626 unset($to_notes['link/id']); // Magic setting; don't save
363 627 unset($to_notes['link/uri']); // Magic setting; don't save
@@ -369,14 +633,21 @@
369 633 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
370 634 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
371 635 endif;
372 636
373 - if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
374 - $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']);
375 649 endif;
376 - if (isset($to_notes['tags']) and is_array($to_notes['tags'])) :
377 - $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
378 - endif;
379 650
380 651 // Collapse the author mapping rule structure back into a flat string
381 652 if (isset($to_notes['map authors'])) :
382 653 $ma = array();
@@ -394,18 +665,18 @@
394 665 endforeach;
395 666 return $notes;
396 667 } /* SyndicatedLink::settings_to_notes () */
397 668
398 - function save_settings ($reload = false) {
669 + public function save_settings ($reload = false) {
399 670 global $wpdb;
400 671
401 672 // Save channel-level meta-data
402 673 foreach (array('link_name', 'link_description', 'link_url') as $what) :
403 - $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
674 + $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
404 675 endforeach;
405 676
406 677 // Save settings to the notes field
407 - $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
678 + $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
408 679
409 680 // Update the properties of the link from settings changes, etc.
410 681 $update_set = implode(", ", $alter);
411 682
@@ -413,9 +684,9 @@
413 684 UPDATE $wpdb->links
414 685 SET $update_set
415 686 WHERE link_id='$this->id'
416 687 ");
417 -
688 +
418 689 if ($reload) :
419 690 // force reload of link information from DB
420 691 if (function_exists('clean_bookmark_cache')) :
421 692 clean_bookmark_cache($this->id);
@@ -431,26 +702,34 @@
431 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.
432 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.
433 704 * @return bool TRUE on success, FALSE on failure.
434 705 */
435 - function setting ($name, $fallback_global = NULL, $fallback_value = NULL) {
706 + public function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
436 707 $ret = NULL;
437 708 if (isset($this->settings[$name])) :
438 709 $ret = $this->settings[$name];
439 710 endif;
440 -
711 +
441 712 $no_value = (
442 713 is_null($ret)
443 - or (is_string($ret) and strtolower($ret)=='default')
714 + or (is_string($ret) and strtolower($ret)==$default)
444 715 );
445 716
446 717 if ($no_value and !is_null($fallback_global)) :
447 - $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
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);
448 727 endif;
449 728
450 729 $no_value = (
451 730 is_null($ret)
452 - or (is_string($ret) and strtolower($ret)=='default')
731 + or (is_string($ret) and strtolower($ret)==$default)
453 732 );
454 733
455 734 if ($no_value and !is_null($fallback_value)) :
456 735 $ret = $fallback_value;
@@ -457,31 +736,270 @@
457 736 endif;
458 737 return $ret;
459 738 } /* SyndicatedLink::setting () */
460 739
461 - function uri () {
462 - return (is_object($this->link) ? $this->link->link_rss : NULL);
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;
463 833 } /* SyndicatedLink::uri () */
464 834
465 - 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;
466 898 if ($fromFeed) :
467 - $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;
468 906 else :
469 - $url = $this->link->link_url;
907 + $value = $this->link->{$link_field};
470 908 endif;
471 - 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');
472 914 } /* SyndicatedLink::homepage () */
473 915
474 - function name ($fromFeed = true) {
475 - if ($fromFeed) :
476 - $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
477 - else :
478 - $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;
479 943 endif;
480 - return $name;
481 - } /* SyndicatedLink::name () */
944 + return $ret;
945 + }
482 946
483 - 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) {
484 1002 if (is_object($this->magpie)) :
485 1003 $channel = $this->magpie->channel;
486 1004 else :
487 1005 $channel = array();
@@ -490,9 +1008,10 @@
490 1008 if (isset($channel['ttl'])) :
491 1009 // "ttl stands for time to live. It's a number of
492 1010 // minutes that indicates how long a channel can be
493 1011 // cached before refreshing from the source."
494 - // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
1012 + // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
1013 + $xml = 'rss:ttl';
495 1014 $ret = $channel['ttl'];
496 1015 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
497 1016 $period_minutes = array (
498 1017 'hourly' => 60, /* minutes in an hour */
@@ -505,13 +1024,13 @@
505 1024 // "sy:updatePeriod: Describes the period over which the
506 1025 // channel format is updated. Acceptable values are:
507 1026 // hourly, daily, weekly, monthly, yearly. If omitted,
508 1027 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
509 - if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
1028 + if (isset($channel['sy']['updateperiod'])) : $period = trim($channel['sy']['updateperiod']);
510 1029 else : $period = 'daily';
511 1030 endif;
512 -
513 - // "sy:updateFrequency: Used to describe the frequency
1031 +
1032 + // "sy:updateFrequency: Used to describe the frequency
514 1033 // of updates in relation to the update period. A
515 1034 // positive integer indicates how many times in that
516 1035 // period the channel is updated. ... If omitted a value
517 1036 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
@@ -517,26 +1036,64 @@
517 1036 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
518 1037 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
519 1038 else : $freq = 1;
520 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);
521 1047
522 - $ret = (int) ($period_minutes[$period] / $freq);
1048 + $xml = 'sy:updateFrequency';
1049 + $ret = (int) ($mins / $freq);
1050 +
523 1051 else :
1052 + $xml = NULL;
524 1053 $ret = NULL;
525 1054 endif;
526 - 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);
527 1064 } /* SyndicatedLink::ttl() */
528 1065
529 - // SyndicatedLink::flatten_array (): flatten an array. Useful for
530 - // hierarchical and namespaced elements.
531 - //
532 - // Given an array which may contain array or object elements in it,
533 - // return a "flattened" array: a one-dimensional array of scalars
534 - // containing each of the scalar elements contained within the array
535 - // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
536 - // returned array for FeedWordPress::flatten_array($a) will contain a key
537 - // $a['feed/b/c/d'] with value 'e'.
538 - 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 = '/') {
539 1096 $ret = array ();
540 1097 if (is_array($arr)) :
541 1098 foreach ($arr as $key => $value) :
542 1099 if (is_scalar($value)) :
@@ -548,41 +1105,138 @@
548 1105 endif;
549 1106 return $ret;
550 1107 } /* SyndicatedLink::flatten_array () */
551 1108
552 - function hardcode ($what) {
553 - $default = get_option("feedwordpress_hardcode_$what");
554 - if ( $default === 'yes' ) :
555 - // If the default is to hardcode, then we want the
556 - // negation of negative(): TRUE by default and FALSE if
557 - // the setting is explicitly "no"
558 - $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;
559 1115 else :
560 - // If the default is NOT to hardcode, then we want
561 - // affirmative(): FALSE by default and TRUE if the
562 - // setting is explicitly "yes"
563 - $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
1116 + $ret = false;
564 1117 endif;
565 1118 return $ret;
566 1119 } /* SyndicatedLink::hardcode () */
567 1120
568 - function syndicated_status ($what, $default, $fallback = true) {
1121 + public function syndicated_status ($what, $default, $fallback = true) {
569 1122 global $wpdb;
570 1123
571 - // Use local setting if we have it
572 - if ( isset($this->settings["$what status"]) ) :
573 - $ret = $this->settings["$what status"];
574 -
575 - // Or fall back to global default if we can
576 - elseif ($fallback) :
577 - $ret = FeedWordPress::syndicated_status($what, $default);
1124 + $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
1125 + $ret = $this->setting($what.' status', $g_set, $default);
578 1126
579 - // Or use default value if we can't.
580 - else :
581 - $ret = $default;
1127 + return esc_sql(trim(strtolower($ret)));
1128 + } /* SyndicatedLink:syndicated_status () */
582 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');
583 1152 endif;
584 1153
585 - return $wpdb->escape(trim(strtolower($ret)));
586 - } /* SyndicatedLink:syndicated_status () */
587 -} // 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 */
588 1242