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