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