source: trunk/www.guidonia.net/wp/wp-content/plugins/feedwordpress/syndicatedlink.class.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 14.7 KB
Line 
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
36class 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 $this->settings['update/ttl'] = rand(30, 120); // spread over time interval for staggered updates
196 $this->settings['update/timed'] = 'automatically';
197 endif;
198
199 if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
200 $this->settings['update/hold'] = 'scheduled';
201 endif;
202
203 $this->settings['update/unfinished'] = 'yes';
204
205 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
206
207 $update_set = implode(',', $update);
208
209 // Update the properties of the link from the feed information
210 $result = $wpdb->query("
211 UPDATE $wpdb->links
212 SET $update_set
213 WHERE link_id='$this->id'
214 ");
215
216 # -- Add new posts from feed and update any updated posts
217 $crashed = false;
218
219 if (is_array($this->magpie->items)) :
220 foreach ($this->magpie->items as $item) :
221 $post =& new SyndicatedPost($item, $this);
222 if (!$resume or !in_array(trim($post->guid()), $processed)) :
223 $processed[] = $post->guid();
224 if (!$post->filtered()) :
225 $new = $post->store();
226 if ( $new !== false ) $new_count[$new]++;
227 endif;
228
229 if (!is_null($crash_ts) and (time() > $crash_ts)) :
230 $crashed = true;
231 break;
232 endif;
233 endif;
234 endforeach;
235 endif;
236
237 // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
238 $to_notes = $this->settings;
239
240 $this->settings['update/processed'] = $processed;
241 if (!$crashed) :
242 $this->settings['update/unfinished'] = 'no';
243 endif;
244
245 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
246
247 // Update the properties of the link from the feed information
248 $result = $wpdb->query("
249 UPDATE $wpdb->links
250 SET $update_set
251 WHERE link_id='$this->id'
252 ");
253 endif;
254
255 return $new_count;
256 } /* SyndicatedLink::poll() */
257
258 function map_name_to_new_user ($name, $newuser_name) {
259 global $wpdb;
260
261 if (strlen($newuser_name) > 0) :
262 $userdata = array();
263 $userdata['ID'] = NULL;
264
265 $userdata['user_login'] = sanitize_user($newuser_name);
266 $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
267
268 $userdata['user_nicename'] = sanitize_title($newuser_name);
269 $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
270
271 $userdata['display_name'] = $wpdb->escape($newuser_name);
272
273 $newuser_id = wp_insert_user($userdata);
274 if (is_numeric($newuser_id)) :
275 if (is_null($name)) : // Unfamiliar author
276 $this->settings['unfamiliar author'] = $newuser_id;
277 else :
278 $this->settings['map authors']['name'][$name] = $newuser_id;
279 endif;
280 else :
281 // TODO: Add some error detection and reporting
282 endif;
283 else :
284 // TODO: Add some error reporting
285 endif;
286 } /* SyndicatedLink::map_name_to_new_user () */
287
288 function settings_to_notes () {
289 $to_notes = $this->settings;
290
291 unset($to_notes['link/id']); // Magic setting; don't save
292 unset($to_notes['link/uri']); // Magic setting; don't save
293 unset($to_notes['link/name']); // Magic setting; don't save
294 unset($to_notes['hardcode categories']); // Deprecated
295 unset($to_notes['unfamiliar categories']); // Deprecated
296
297 // Collapse array settings
298 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
299 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
300 endif;
301
302 if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
303 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
304 endif;
305 if (isset($to_notes['tags']) and is_array($to_notes['tags'])) :
306 $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
307 endif;
308
309 // Collapse the author mapping rule structure back into a flat string
310 if (isset($to_notes['map authors'])) :
311 $ma = array();
312 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
313 foreach ($author_rules as $author_name => $author_action) :
314 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
315 endforeach;
316 endforeach;
317 $to_notes['map authors'] = implode("\n\n", $ma);
318 endif;
319
320 $notes = '';
321 foreach ($to_notes as $key => $value) :
322 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
323 endforeach;
324 return $notes;
325 } /* SyndicatedLink::settings_to_notes () */
326
327 function uri () {
328 return (is_object($this->link) ? $this->link->link_rss : NULL);
329 } /* SyndicatedLink::uri () */
330
331 function homepage () {
332 return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
333 } /* SyndicatedLink::homepage () */
334
335 function ttl () {
336 if (is_object($this->magpie)) :
337 $channel = $this->magpie->channel;
338 else :
339 $channel = array();
340 endif;
341
342 if (isset($channel['ttl'])) :
343 // "ttl stands for time to live. It's a number of
344 // minutes that indicates how long a channel can be
345 // cached before refreshing from the source."
346 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
347 $ret = $channel['ttl'];
348 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
349 $period_minutes = array (
350 'hourly' => 60, /* minutes in an hour */
351 'daily' => 1440, /* minutes in a day */
352 'weekly' => 10080, /* minutes in a week */
353 'monthly' => 43200, /* minutes in a month */
354 'yearly' => 525600, /* minutes in a year */
355 );
356
357 // "sy:updatePeriod: Describes the period over which the
358 // channel format is updated. Acceptable values are:
359 // hourly, daily, weekly, monthly, yearly. If omitted,
360 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
361 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
362 else : $period = 'daily';
363 endif;
364
365 // "sy:updateFrequency: Used to describe the frequency
366 // of updates in relation to the update period. A
367 // positive integer indicates how many times in that
368 // period the channel is updated. ... If omitted a value
369 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
370 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
371 else : $freq = 1;
372 endif;
373
374 $ret = (int) ($period_minutes[$period] / $freq);
375 else :
376 $ret = NULL;
377 endif;
378 return $ret;
379 } /* SyndicatedLink::ttl() */
380
381 // SyndicatedLink::flatten_array (): flatten an array. Useful for
382 // hierarchical and namespaced elements.
383 //
384 // Given an array which may contain array or object elements in it,
385 // return a "flattened" array: a one-dimensional array of scalars
386 // containing each of the scalar elements contained within the array
387 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
388 // returned array for FeedWordPress::flatten_array($a) will contain a key
389 // $a['feed/b/c/d'] with value 'e'.
390 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
391 $ret = array ();
392 if (is_array($arr)) :
393 foreach ($arr as $key => $value) :
394 if (is_scalar($value)) :
395 $ret[$prefix.$key] = $value;
396 else :
397 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
398 endif;
399 endforeach;
400 endif;
401 return $ret;
402 } /* SyndicatedLink::flatten_array () */
403
404 function hardcode ($what) {
405 $default = get_option("feedwordpress_hardcode_$what");
406 if ( $default === 'yes' ) :
407 // If the default is to hardcode, then we want the
408 // negation of negative(): TRUE by default and FALSE if
409 // the setting is explicitly "no"
410 $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
411 else :
412 // If the default is NOT to hardcode, then we want
413 // affirmative(): FALSE by default and TRUE if the
414 // setting is explicitly "yes"
415 $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
416 endif;
417 return $ret;
418 } /* SyndicatedLink::hardcode () */
419
420 function syndicated_status ($what, $default) {
421 global $wpdb;
422
423 $ret = get_option("feedwordpress_syndicated_{$what}_status");
424 if ( isset($this->settings["$what status"]) ) :
425 $ret = $this->settings["$what status"];
426 elseif (!$ret) :
427 $ret = $default;
428 endif;
429 return $wpdb->escape(trim(strtolower($ret)));
430 } /* SyndicatedLink:syndicated_status () */
431} // class SyndicatedLink
432
Note: See TracBrowser for help on using the repository browser.