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