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

432 lines 14.6 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 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;
73
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;
92
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;
99
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 () */
128
129 function stale () {
130 $stale = true;
131 if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
132 $stale = false; // don't update on any timed updates; pings only
133 elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
134 $stale = true; // update on the next timed update
135 elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
136 $stale = true; // initial update
137 else :
138 $after = ((int) $this->settings['update/last'])
139 +((int) $this->settings['update/ttl'] * 60);
140 $stale = (time() >= $after);
141 endif;
142 return $stale;
143 } /* SyndicatedLink::stale () */
144
145 function poll ($crash_ts = NULL) {
146 global $wpdb;
147
148 $this->magpie = fetch_rss($this->link->link_rss);
149 $new_count = NULL;
150
151 $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
152 if ($resume) :
153 // pick up where we left off
154 $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
155 else :
156 // begin at the beginning
157 $processed = array();
158 endif;
159
160 if (is_object($this->magpie)) :
161 $new_count = array('new' => 0, 'updated' => 0);
162
163 # -- Update Link metadata live from feed
164 $channel = $this->magpie->channel;
165
166 if (!isset($channel['id'])) :
167 $channel['id'] = $this->link->link_rss;
168 endif;
169
170 $update = array();
171 if (!$this->hardcode('url') and isset($channel['link'])) :
172 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
173 endif;
174
175 if (!$this->hardcode('name') and isset($channel['title'])) :
176 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
177 endif;
178
179 if (!$this->hardcode('description')) :
180 if (isset($channel['tagline'])) :
181 $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
182 elseif (isset($channel['description'])) :
183 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
184 endif;
185 endif;
186
187 $this->settings = array_merge($this->settings, $this->flatten_array($channel));
188
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;
197
198 if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
199 $this->settings['update/hold'] = 'scheduled';
200 endif;
201
202 $this->settings['update/unfinished'] = 'yes';
203
204 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
205
206 $update_set = implode(',', $update);
207
208 // Update the properties of the link from the feed information
209 $result = $wpdb->query("
210 UPDATE $wpdb->links
211 SET $update_set
212 WHERE link_id='$this->id'
213 ");
214
215 # -- Add new posts from feed and update any updated posts
216 $crashed = false;
217
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]++;
226 endif;
227
228 if (!is_null($crash_ts) and (time() > $crash_ts)) :
229 $crashed = true;
230 break;
231 endif;
232 endif;
233 endforeach;
234 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
239 $this->settings['update/processed'] = $processed;
240 if (!$crashed) :
241 $this->settings['update/unfinished'] = 'no';
242 endif;
243
244 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
245
246 // Update the properties of the link from the feed information
247 $result = $wpdb->query("
248 UPDATE $wpdb->links
249 SET $update_set
250 WHERE link_id='$this->id'
251 ");
252 endif;
253
254 return $new_count;
255 } /* SyndicatedLink::poll() */
256
257 function map_name_to_new_user ($name, $newuser_name) {
258 global $wpdb;
259
260 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);
273 if (is_numeric($newuser_id)) :
274 if (is_null($name)) : // Unfamiliar author
275 $this->settings['unfamiliar author'] = $newuser_id;
276 else :
277 $this->settings['map authors']['name'][$name] = $newuser_id;
278 endif;
279 else :
280 // TODO: Add some error detection and reporting
281 endif;
282 else :
283 // TODO: Add some error reporting
284 endif;
285 } /* SyndicatedLink::map_name_to_new_user () */
286
287 function settings_to_notes () {
288 $to_notes = $this->settings;
289
290 unset($to_notes['link/id']); // Magic setting; don't save
291 unset($to_notes['link/uri']); // Magic setting; don't save
292 unset($to_notes['link/name']); // Magic setting; don't save
293 unset($to_notes['hardcode categories']); // Deprecated
294 unset($to_notes['unfamiliar categories']); // Deprecated
295
296 // Collapse array settings
297 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
298 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
299 endif;
300
301 if (is_array($to_notes['cats'])) :
302 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
303 endif;
304 if (is_array($to_notes['tags'])) :
305 $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
306 endif;
307
308 // Collapse the author mapping rule structure back into a flat string
309 if (isset($to_notes['map authors'])) :
310 $ma = array();
311 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
312 foreach ($author_rules as $author_name => $author_action) :
313 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
314 endforeach;
315 endforeach;
316 $to_notes['map authors'] = implode("\n\n", $ma);
317 endif;
318
319 $notes = '';
320 foreach ($to_notes as $key => $value) :
321 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
322 endforeach;
323 return $notes;
324 } /* SyndicatedLink::settings_to_notes () */
325
326 function uri () {
327 return (is_object($this->link) ? $this->link->link_rss : NULL);
328 } /* SyndicatedLink::uri () */
329
330 function homepage () {
331 return (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
332 } /* SyndicatedLink::homepage () */
333
334 function ttl () {
335 if (is_object($this->magpie)) :
336 $channel = $this->magpie->channel;
337 else :
338 $channel = array();
339 endif;
340
341 if (isset($channel['ttl'])) :
342 // "ttl stands for time to live. It's a number of
343 // minutes that indicates how long a channel can be
344 // cached before refreshing from the source."
345 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
346 $ret = $channel['ttl'];
347 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
348 $period_minutes = array (
349 'hourly' => 60, /* minutes in an hour */
350 'daily' => 1440, /* minutes in a day */
351 'weekly' => 10080, /* minutes in a week */
352 'monthly' => 43200, /* minutes in a month */
353 'yearly' => 525600, /* minutes in a year */
354 );
355
356 // "sy:updatePeriod: Describes the period over which the
357 // channel format is updated. Acceptable values are:
358 // hourly, daily, weekly, monthly, yearly. If omitted,
359 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
360 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
361 else : $period = 'daily';
362 endif;
363
364 // "sy:updateFrequency: Used to describe the frequency
365 // of updates in relation to the update period. A
366 // positive integer indicates how many times in that
367 // period the channel is updated. ... If omitted a value
368 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
369 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
370 else : $freq = 1;
371 endif;
372
373 $ret = (int) ($period_minutes[$period] / $freq);
374 else :
375 $ret = NULL;
376 endif;
377 return $ret;
378 } /* SyndicatedLink::ttl() */
379
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;
395 else :
396 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
397 endif;
398 endforeach;
399 endif;
400 return $ret;
401 } /* SyndicatedLink::flatten_array () */
402
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");
415 endif;
416 return $ret;
417 } /* SyndicatedLink::hardcode () */
418
419 function syndicated_status ($what, $default) {
420 global $wpdb;
421
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;
427 endif;
428 return $wpdb->escape(trim(strtolower($ret)));
429 } /* SyndicatedLink:syndicated_status () */
430 } // class SyndicatedLink
431
432