PluginProbe
FeedWordPress / 2009.1112
FeedWordPress v2009.1112
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
feedwordpress / syndicatedlink.class.php

syndicatedlink.class.php in FeedWordPress 2009.1112, at syndicatedlink.class.php

513 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 # class SyndicatedLink: represents a syndication feed stored within the
3 # WordPress database
4 #
5 # To keep things compact and editable from within WordPress, we use all the
6 # links under a particular category in the WordPress "Blogroll" for the list of
7 # feeds to syndicate. "Contributors" is the category used by default; you can
8 # configure that under Options --> Syndication.
9 #
10 # Fields used are:
11 #
12 # * link_rss: the URI of the Atom/RSS feed to syndicate
13 #
14 # * link_notes: user-configurable options, with keys and values
15 # like so:
16 #
17 # key: value
18 # cats: computers\nweb
19 # feed/key: value
20 #
21 # Keys that start with "feed/" are gleaned from the data supplied
22 # by the feed itself, and will be overwritten with each update.
23 #
24 # Values have linebreak characters escaped with C-style
25 # backslashes (so, for example, a newline becomes "\n").
26 #
27 # The value of `cats` is used as a newline-separated list of
28 # default categories for any post coming from a particular feed.
29 # (In the example above, any posts from this feed will be placed
30 # in the "computers" and "web" categories--*in addition to* any
31 # categories that may already be applied to the posts.)
32 #
33 # Values of keys in link_notes are accessible from templates using
34 # the function `get_feed_meta($key)` if this plugin is activated.
35
36 class SyndicatedLink {
37 var $id = null;
38 var $link = null;
39 var $settings = array ();
40 var $magpie = null;
41
42 function SyndicatedLink ($link) {
43 global $wpdb;
44
45 if (is_object($link)) :
46 $this->link = $link;
47 $this->id = $link->link_id;
48 else :
49 $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 );
57 endif;
58 endif;
59
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;
74
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;
93
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;
100
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 () */
129
130 function stale () {
131 $stale = true;
132 if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
133 $stale = false; // don't update on any timed updates; pings only
134 elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
135 $stale = true; // update on the next timed update
136 elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
137 $stale = true; // initial update
138 else :
139 $after = ((int) $this->settings['update/last'])
140 +((int) $this->settings['update/ttl'] * 60);
141 $stale = (time() >= $after);
142 endif;
143 return $stale;
144 } /* SyndicatedLink::stale () */
145
146 function poll ($crash_ts = NULL) {
147 global $wpdb;
148
149 $this->magpie = fetch_rss($this->link->link_rss);
150 $new_count = NULL;
151
152 $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
153 if ($resume) :
154 // pick up where we left off
155 $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
156 else :
157 // begin at the beginning
158 $processed = array();
159 endif;
160
161 if (is_object($this->magpie)) :
162 $new_count = array('new' => 0, 'updated' => 0);
163
164 # -- Update Link metadata live from feed
165 $channel = $this->magpie->channel;
166
167 if (!isset($channel['id'])) :
168 $channel['id'] = $this->link->link_rss;
169 endif;
170
171 $update = array();
172 if (!$this->hardcode('url') and isset($channel['link'])) :
173 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
174 endif;
175
176 if (!$this->hardcode('name') and isset($channel['title'])) :
177 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
178 endif;
179
180 if (!$this->hardcode('description')) :
181 if (isset($channel['tagline'])) :
182 $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
183 elseif (isset($channel['description'])) :
184 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
185 endif;
186 endif;
187
188 $this->settings = array_merge($this->settings, $this->flatten_array($channel));
189
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);
211
212 if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
213 $this->settings['update/hold'] = 'scheduled';
214 endif;
215
216 $this->settings['update/unfinished'] = 'yes';
217
218 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
219
220 $update_set = implode(',', $update);
221
222 // Update the properties of the link from the feed information
223 $result = $wpdb->query("
224 UPDATE $wpdb->links
225 SET $update_set
226 WHERE link_id='$this->id'
227 ");
228
229 # -- Add new posts from feed and update any updated posts
230 $crashed = false;
231
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]++;
240 endif;
241
242 if (!is_null($crash_ts) and (time() > $crash_ts)) :
243 $crashed = true;
244 break;
245 endif;
246 endif;
247 endforeach;
248 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
253 $this->settings['update/processed'] = $processed;
254 if (!$crashed) :
255 $this->settings['update/unfinished'] = 'no';
256 endif;
257
258 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
259
260 // Update the properties of the link from the feed information
261 $result = $wpdb->query("
262 UPDATE $wpdb->links
263 SET $update_set
264 WHERE link_id='$this->id'
265 ");
266 endif;
267
268 return $new_count;
269 } /* SyndicatedLink::poll() */
270
271 /**
272 * Updates the URL for the feed syndicated by this link.
273 *
274 * @param string $url The new feed URL to use for this source.
275 * @return bool TRUE on success, FALSE on failure.
276 */
277 function set_uri ($url) {
278 global $wpdb;
279
280 if ($this->found()) :
281 // Update link_rss
282 $result = $wpdb->query("
283 UPDATE $wpdb->links
284 SET
285 link_rss = '".$wpdb->escape($url)."'
286 WHERE link_id = '".$wpdb->escape($this->id)."'
287 ");
288
289 $ret = ($result ? true : false);
290 else :
291 $ret = false;
292 endif;
293 return $ret;
294 } /* SyndicatedLink::set_uri () */
295
296 function map_name_to_new_user ($name, $newuser_name) {
297 global $wpdb;
298
299 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);
312 if (is_numeric($newuser_id)) :
313 if (is_null($name)) : // Unfamiliar author
314 $this->settings['unfamiliar author'] = $newuser_id;
315 else :
316 $this->settings['map authors']['name'][$name] = $newuser_id;
317 endif;
318 else :
319 // TODO: Add some error detection and reporting
320 endif;
321 else :
322 // TODO: Add some error reporting
323 endif;
324 } /* SyndicatedLink::map_name_to_new_user () */
325
326 function settings_to_notes () {
327 $to_notes = $this->settings;
328
329 unset($to_notes['link/id']); // Magic setting; don't save
330 unset($to_notes['link/uri']); // Magic setting; don't save
331 unset($to_notes['link/name']); // Magic setting; don't save
332 unset($to_notes['hardcode categories']); // Deprecated
333 unset($to_notes['unfamiliar categories']); // Deprecated
334
335 // Collapse array settings
336 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
337 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
338 endif;
339
340 if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
341 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
342 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
347 // Collapse the author mapping rule structure back into a flat string
348 if (isset($to_notes['map authors'])) :
349 $ma = array();
350 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
351 foreach ($author_rules as $author_name => $author_action) :
352 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
353 endforeach;
354 endforeach;
355 $to_notes['map authors'] = implode("\n\n", $ma);
356 endif;
357
358 $notes = '';
359 foreach ($to_notes as $key => $value) :
360 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
361 endforeach;
362 return $notes;
363 } /* SyndicatedLink::settings_to_notes () */
364
365 function save_settings ($reload = false) {
366 global $wpdb;
367
368 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
369
370 // Update the properties of the link from the feed information
371 $result = $wpdb->query("
372 UPDATE $wpdb->links
373 SET $update_set
374 WHERE link_id='$this->id'
375 ");
376
377 if ($reload) :
378 // reload link information from DB
379 if (function_exists('clean_bookmark_cache')) :
380 clean_bookmark_cache($this->id);
381 endif;
382 endif;
383 } /* SyndicatedLink::save_settings () */
384
385 function uri () {
386 return (is_object($this->link) ? $this->link->link_rss : NULL);
387 } /* SyndicatedLink::uri () */
388
389 function homepage ($fromFeed = true) {
390 if ($fromFeed) :
391 $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
392 else :
393 $url = $this->link->link_url;
394 endif;
395 return $url;
396 } /* SyndicatedLink::homepage () */
397
398 function name ($fromFeed = true) {
399 if ($fromFeed) :
400 $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
401 else :
402 $name = $this->link->link_name;
403 endif;
404 return $name;
405 } /* SyndicatedLink::name () */
406
407 function ttl () {
408 if (is_object($this->magpie)) :
409 $channel = $this->magpie->channel;
410 else :
411 $channel = array();
412 endif;
413
414 if (isset($channel['ttl'])) :
415 // "ttl stands for time to live. It's a number of
416 // minutes that indicates how long a channel can be
417 // cached before refreshing from the source."
418 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
419 $ret = $channel['ttl'];
420 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
421 $period_minutes = array (
422 'hourly' => 60, /* minutes in an hour */
423 'daily' => 1440, /* minutes in a day */
424 'weekly' => 10080, /* minutes in a week */
425 'monthly' => 43200, /* minutes in a month */
426 'yearly' => 525600, /* minutes in a year */
427 );
428
429 // "sy:updatePeriod: Describes the period over which the
430 // channel format is updated. Acceptable values are:
431 // hourly, daily, weekly, monthly, yearly. If omitted,
432 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
433 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
434 else : $period = 'daily';
435 endif;
436
437 // "sy:updateFrequency: Used to describe the frequency
438 // of updates in relation to the update period. A
439 // positive integer indicates how many times in that
440 // period the channel is updated. ... If omitted a value
441 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
442 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
443 else : $freq = 1;
444 endif;
445
446 $ret = (int) ($period_minutes[$period] / $freq);
447 else :
448 $ret = NULL;
449 endif;
450 return $ret;
451 } /* SyndicatedLink::ttl() */
452
453 // SyndicatedLink::flatten_array (): flatten an array. Useful for
454 // hierarchical and namespaced elements.
455 //
456 // Given an array which may contain array or object elements in it,
457 // return a "flattened" array: a one-dimensional array of scalars
458 // containing each of the scalar elements contained within the array
459 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
460 // returned array for FeedWordPress::flatten_array($a) will contain a key
461 // $a['feed/b/c/d'] with value 'e'.
462 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
463 $ret = array ();
464 if (is_array($arr)) :
465 foreach ($arr as $key => $value) :
466 if (is_scalar($value)) :
467 $ret[$prefix.$key] = $value;
468 else :
469 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
470 endif;
471 endforeach;
472 endif;
473 return $ret;
474 } /* SyndicatedLink::flatten_array () */
475
476 function hardcode ($what) {
477 $default = get_option("feedwordpress_hardcode_$what");
478 if ( $default === 'yes' ) :
479 // If the default is to hardcode, then we want the
480 // negation of negative(): TRUE by default and FALSE if
481 // the setting is explicitly "no"
482 $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
483 else :
484 // If the default is NOT to hardcode, then we want
485 // affirmative(): FALSE by default and TRUE if the
486 // setting is explicitly "yes"
487 $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
488 endif;
489 return $ret;
490 } /* SyndicatedLink::hardcode () */
491
492 function syndicated_status ($what, $default, $fallback = true) {
493 global $wpdb;
494
495 // Use local setting if we have it
496 if ( isset($this->settings["$what status"]) ) :
497 $ret = $this->settings["$what status"];
498
499 // Or fall back to global default if we can
500 elseif ($fallback) :
501 $ret = FeedWordPress::syndicated_status($what, $default);
502
503 // Or use default value if we can't.
504 else :
505 $ret = $default;
506
507 endif;
508
509 return $wpdb->escape(trim(strtolower($ret)));
510 } /* SyndicatedLink:syndicated_status () */
511 } // class SyndicatedLink
512
513