PluginProbe
FeedWordPress / 2010.0127
FeedWordPress v2010.0127
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 2010.0127, at syndicatedlink.class.php

538 lines 17.9 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 /**
386 * Retrieves the value of a setting, allowing for a global setting to be
387 * used as a fallback, or a constant value, or both.
388 *
389 * @param string $name The link setting key
390 * @param mixed $fallback_global If the link setting is nonexistent or marked as a use-default value, fall back to the value of this global setting.
391 * @param mixed $fallback_value If the link setting and the global setting are nonexistent or marked as a use-default value, fall back to this constant value.
392 * @return bool TRUE on success, FALSE on failure.
393 */
394 function setting ($name, $fallback_global = NULL, $fallback_value = NULL) {
395 $ret = NULL;
396 if (isset($this->settings[$name])) :
397 $ret = $this->settings[$name];
398 endif;
399
400 if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_global)) :
401 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
402 endif;
403
404 if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_value)) :
405 $ret = $fallback_value;
406 endif;
407 return $ret;
408 } /* SyndicatedLink::setting () */
409
410 function uri () {
411 return (is_object($this->link) ? $this->link->link_rss : NULL);
412 } /* SyndicatedLink::uri () */
413
414 function homepage ($fromFeed = true) {
415 if ($fromFeed) :
416 $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
417 else :
418 $url = $this->link->link_url;
419 endif;
420 return $url;
421 } /* SyndicatedLink::homepage () */
422
423 function name ($fromFeed = true) {
424 if ($fromFeed) :
425 $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
426 else :
427 $name = $this->link->link_name;
428 endif;
429 return $name;
430 } /* SyndicatedLink::name () */
431
432 function ttl () {
433 if (is_object($this->magpie)) :
434 $channel = $this->magpie->channel;
435 else :
436 $channel = array();
437 endif;
438
439 if (isset($channel['ttl'])) :
440 // "ttl stands for time to live. It's a number of
441 // minutes that indicates how long a channel can be
442 // cached before refreshing from the source."
443 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
444 $ret = $channel['ttl'];
445 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
446 $period_minutes = array (
447 'hourly' => 60, /* minutes in an hour */
448 'daily' => 1440, /* minutes in a day */
449 'weekly' => 10080, /* minutes in a week */
450 'monthly' => 43200, /* minutes in a month */
451 'yearly' => 525600, /* minutes in a year */
452 );
453
454 // "sy:updatePeriod: Describes the period over which the
455 // channel format is updated. Acceptable values are:
456 // hourly, daily, weekly, monthly, yearly. If omitted,
457 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
458 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
459 else : $period = 'daily';
460 endif;
461
462 // "sy:updateFrequency: Used to describe the frequency
463 // of updates in relation to the update period. A
464 // positive integer indicates how many times in that
465 // period the channel is updated. ... If omitted a value
466 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
467 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
468 else : $freq = 1;
469 endif;
470
471 $ret = (int) ($period_minutes[$period] / $freq);
472 else :
473 $ret = NULL;
474 endif;
475 return $ret;
476 } /* SyndicatedLink::ttl() */
477
478 // SyndicatedLink::flatten_array (): flatten an array. Useful for
479 // hierarchical and namespaced elements.
480 //
481 // Given an array which may contain array or object elements in it,
482 // return a "flattened" array: a one-dimensional array of scalars
483 // containing each of the scalar elements contained within the array
484 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
485 // returned array for FeedWordPress::flatten_array($a) will contain a key
486 // $a['feed/b/c/d'] with value 'e'.
487 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
488 $ret = array ();
489 if (is_array($arr)) :
490 foreach ($arr as $key => $value) :
491 if (is_scalar($value)) :
492 $ret[$prefix.$key] = $value;
493 else :
494 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
495 endif;
496 endforeach;
497 endif;
498 return $ret;
499 } /* SyndicatedLink::flatten_array () */
500
501 function hardcode ($what) {
502 $default = get_option("feedwordpress_hardcode_$what");
503 if ( $default === 'yes' ) :
504 // If the default is to hardcode, then we want the
505 // negation of negative(): TRUE by default and FALSE if
506 // the setting is explicitly "no"
507 $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
508 else :
509 // If the default is NOT to hardcode, then we want
510 // affirmative(): FALSE by default and TRUE if the
511 // setting is explicitly "yes"
512 $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
513 endif;
514 return $ret;
515 } /* SyndicatedLink::hardcode () */
516
517 function syndicated_status ($what, $default, $fallback = true) {
518 global $wpdb;
519
520 // Use local setting if we have it
521 if ( isset($this->settings["$what status"]) ) :
522 $ret = $this->settings["$what status"];
523
524 // Or fall back to global default if we can
525 elseif ($fallback) :
526 $ret = FeedWordPress::syndicated_status($what, $default);
527
528 // Or use default value if we can't.
529 else :
530 $ret = $default;
531
532 endif;
533
534 return $wpdb->escape(trim(strtolower($ret)));
535 } /* SyndicatedLink:syndicated_status () */
536 } // class SyndicatedLink
537
538