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