PluginProbe
FeedWordPress / 2009.0618
FeedWordPress v2009.0618
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.0618, at syndicatedlink.class.php

433 lines 14.7 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 $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
433