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

591 lines 19.2 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 require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php');
37
38 class SyndicatedLink {
39 var $id = null;
40 var $link = null;
41 var $settings = array ();
42 var $simplepie = null;
43 var $magpie = null;
44
45 function SyndicatedLink ($link) {
46 global $wpdb;
47
48 if (is_object($link)) :
49 $this->link = $link;
50 $this->id = $link->link_id;
51 else :
52 $this->id = $link;
53 if (function_exists('get_bookmark')) : // WP 2.1+
54 $this->link = get_bookmark($link);
55 else :
56 $this->link = $wpdb->get_row("
57 SELECT * FROM $wpdb->links
58 WHERE (link_id = '".$wpdb->escape($link)."')"
59 );
60 endif;
61 endif;
62
63 if (strlen($this->link->link_rss) > 0) :
64 // Read off feed settings from link_notes
65 $notes = explode("\n", $this->link->link_notes);
66 foreach ($notes as $note):
67 $pair = explode(": ", $note, 2);
68 $key = (isset($pair[0]) ? $pair[0] : null);
69 $value = (isset($pair[1]) ? $pair[1] : null);
70 if (!is_null($key) and !is_null($value)) :
71 // Unescape and trim() off the whitespace.
72 // Thanks to Ray Lischner for pointing out the
73 // need to trim off whitespace.
74 $this->settings[$key] = stripcslashes (trim($value));
75 endif;
76 endforeach;
77
78 // "Magic" feed settings
79 $this->settings['link/uri'] = $this->link->link_rss;
80 $this->settings['link/name'] = $this->link->link_name;
81 $this->settings['link/id'] = $this->link->link_id;
82
83 // `hardcode categories` and `unfamiliar categories` are deprecated in favor of `unfamiliar category`
84 if (
85 isset($this->settings['unfamiliar categories'])
86 and !isset($this->settings['unfamiliar category'])
87 ) :
88 $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
89 endif;
90 if (
91 FeedWordPress::affirmative($this->settings, 'hardcode categories')
92 and !isset($this->settings['unfamiliar category'])
93 ) :
94 $this->settings['unfamiliar category'] = 'default';
95 endif;
96
97 // Set this up automagically for del.icio.us
98 $bits = parse_url($this->link->link_rss);
99 $tagspacers = array('del.icio.us', 'feeds.delicious.com');
100 if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
101 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
102 endif;
103
104 if (isset($this->settings['cats'])):
105 $this->settings['cats'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['cats']);
106 endif;
107 if (isset($this->settings['tags'])):
108 $this->settings['tags'] = preg_split(FEEDWORDPRESS_CAT_SEPARATOR_PATTERN, $this->settings['tags']);
109 endif;
110
111 if (isset($this->settings['map authors'])) :
112 $author_rules = explode("\n\n", $this->settings['map authors']);
113 $ma = array();
114 foreach ($author_rules as $rule) :
115 list($rule_type, $author_name, $author_action) = explode("\n", $rule);
116
117 // Normalize for case and whitespace
118 $rule_type = strtolower(trim($rule_type));
119 $author_name = strtolower(trim($author_name));
120 $author_action = strtolower(trim($author_action));
121
122 $ma[$rule_type][$author_name] = $author_action;
123 endforeach;
124 $this->settings['map authors'] = $ma;
125 endif;
126 endif;
127 } /* SyndicatedLink::SyndicatedLink () */
128
129 function found () {
130 return is_object($this->link) and !is_wp_error($this->link);
131 } /* SyndicatedLink::found () */
132
133 function stale () {
134 $stale = true;
135 if (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='ping')) :
136 $stale = false; // don't update on any timed updates; pings only
137 elseif (isset($this->settings['update/hold']) and ($this->settings['update/hold']=='next')) :
138 $stale = true; // update on the next timed update
139 elseif (!isset($this->settings['update/ttl']) or !isset($this->settings['update/last'])) :
140 $stale = true; // initial update
141 else :
142 $after = ((int) $this->settings['update/last'])
143 +((int) $this->settings['update/ttl'] * 60);
144 $stale = (time() >= $after);
145 endif;
146 return $stale;
147 } /* SyndicatedLink::stale () */
148
149 function poll ($crash_ts = NULL) {
150 global $wpdb;
151
152 FeedWordPress::diagnostic('updated_feeds', 'Polling feed <'.$this->link->link_rss.'>');
153
154 $this->simplepie = apply_filters(
155 'syndicated_feed',
156 FeedWordPress::fetch($this->link->link_rss),
157 $this
158 );
159
160 // Filter compatibility mode
161 if (is_wp_error($this->simplepie)) :
162 $this->magpie = $this->simplepie;
163 else :
164 $this->magpie = new MagpieFromSimplePie($this->simplepie);
165 endif;
166
167 $new_count = NULL;
168
169 $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
170 if ($resume) :
171 // pick up where we left off
172 $processed = array_map('trim', explode("\n", $this->settings['update/processed']));
173 else :
174 // begin at the beginning
175 $processed = array();
176 endif;
177
178 if (is_wp_error($this->simplepie)) :
179 $new_count = $this->simplepie;
180 elseif (is_object($this->simplepie)) :
181 $new_count = array('new' => 0, 'updated' => 0);
182
183 # -- Update Link metadata live from feed
184 $channel = $this->magpie->channel;
185
186 if (!isset($channel['id'])) :
187 $channel['id'] = $this->link->link_rss;
188 endif;
189
190 $update = array();
191 if (!$this->hardcode('url') and isset($channel['link'])) :
192 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
193 endif;
194
195 if (!$this->hardcode('name') and isset($channel['title'])) :
196 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
197 endif;
198
199 if (!$this->hardcode('description')) :
200 if (isset($channel['tagline'])) :
201 $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
202 elseif (isset($channel['description'])) :
203 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
204 endif;
205 endif;
206
207 $this->settings = array_merge($this->settings, $this->flatten_array($channel));
208
209 $this->settings['update/last'] = time(); $ttl = $this->ttl();
210 if (!is_null($ttl)) :
211 $this->settings['update/ttl'] = $ttl;
212 $this->settings['update/timed'] = 'feed';
213 else :
214 // spread out over a time interval for staggered updates
215 $updateWindow = $this->setting(
216 'update/window',
217 'update_window',
218 DEFAULT_UPDATE_PERIOD
219 );
220 if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
221 $updateWindow = DEFAULT_UPDATE_PERIOD;
222 endif;
223
224 $fudgedInterval = $updateWindow+rand(0, 2*($updateWindow/3));
225 $this->settings['update/ttl'] = apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
226 $this->settings['update/timed'] = 'automatically';
227 endif;
228 $this->settings['update/ttl'] = apply_filters('syndicated_feed_ttl', $this->settings['update/ttl'], $this);
229
230 if (!isset($this->settings['update/hold']) or $this->settings['update/hold']!='ping') :
231 $this->settings['update/hold'] = 'scheduled';
232 endif;
233
234 $this->settings['update/unfinished'] = 'yes';
235
236 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
237
238 $update_set = implode(',', $update);
239
240 // Update the properties of the link from the feed information
241 $result = $wpdb->query("
242 UPDATE $wpdb->links
243 SET $update_set
244 WHERE link_id='$this->id'
245 ");
246 do_action('update_syndicated_feed', $this->id, $this);
247
248 # -- Add new posts from feed and update any updated posts
249 $crashed = false;
250
251 $posts = apply_filters(
252 'syndicated_feed_items',
253 $this->magpie->originals,
254 $this
255 );
256 if (is_array($posts)) :
257 foreach ($posts as $key => $original) :
258 $item = $this->magpie->items[$key];
259 $post = new SyndicatedPost(array(
260 'simplepie' => $original,
261 'magpie' => $item,
262 ), $this);
263
264 if (!$resume or !in_array(trim($post->guid()), $processed)) :
265 $processed[] = $post->guid();
266 if (!$post->filtered()) :
267 $new = $post->store();
268 if ( $new !== false ) $new_count[$new]++;
269 endif;
270
271 if (!is_null($crash_ts) and (time() > $crash_ts)) :
272 $crashed = true;
273 break;
274 endif;
275 endif;
276 unset($post);
277 endforeach;
278 endif;
279 $suffix = ($crashed ? 'crashed' : 'completed');
280 do_action('update_syndicated_feed_items', $this->id, $this);
281 do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
282
283 // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
284 $to_notes = $this->settings;
285
286 $this->settings['update/processed'] = $processed;
287 if (!$crashed) :
288 $this->settings['update/unfinished'] = 'no';
289 endif;
290
291 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
292
293 // Update the properties of the link from the feed information
294 $result = $wpdb->query("
295 UPDATE $wpdb->links
296 SET $update_set
297 WHERE link_id='$this->id'
298 ");
299
300 do_action("update_syndicated_feed_completed", $this->id, $this);
301 endif;
302
303 return $new_count;
304 } /* SyndicatedLink::poll() */
305
306 /**
307 * Updates the URL for the feed syndicated by this link.
308 *
309 * @param string $url The new feed URL to use for this source.
310 * @return bool TRUE on success, FALSE on failure.
311 */
312 function set_uri ($url) {
313 global $wpdb;
314
315 if ($this->found()) :
316 // Update link_rss
317 $result = $wpdb->query("
318 UPDATE $wpdb->links
319 SET
320 link_rss = '".$wpdb->escape($url)."'
321 WHERE link_id = '".$wpdb->escape($this->id)."'
322 ");
323
324 $ret = ($result ? true : false);
325 else :
326 $ret = false;
327 endif;
328 return $ret;
329 } /* SyndicatedLink::set_uri () */
330
331 function map_name_to_new_user ($name, $newuser_name) {
332 global $wpdb;
333
334 if (strlen($newuser_name) > 0) :
335 $userdata = array();
336 $userdata['ID'] = NULL;
337
338 $userdata['user_login'] = sanitize_user($newuser_name);
339 $userdata['user_login'] = apply_filters('pre_user_login', $userdata['user_login']);
340
341 $userdata['user_nicename'] = sanitize_title($newuser_name);
342 $userdata['user_nicename'] = apply_filters('pre_user_nicename', $userdata['user_nicename']);
343
344 $userdata['display_name'] = $wpdb->escape($newuser_name);
345
346 $newuser_id = wp_insert_user($userdata);
347 if (is_numeric($newuser_id)) :
348 if (is_null($name)) : // Unfamiliar author
349 $this->settings['unfamiliar author'] = $newuser_id;
350 else :
351 $this->settings['map authors']['name'][$name] = $newuser_id;
352 endif;
353 else :
354 // TODO: Add some error detection and reporting
355 endif;
356 else :
357 // TODO: Add some error reporting
358 endif;
359 } /* SyndicatedLink::map_name_to_new_user () */
360
361 function settings_to_notes () {
362 $to_notes = $this->settings;
363
364 unset($to_notes['link/id']); // Magic setting; don't save
365 unset($to_notes['link/uri']); // Magic setting; don't save
366 unset($to_notes['link/name']); // Magic setting; don't save
367 unset($to_notes['hardcode categories']); // Deprecated
368 unset($to_notes['unfamiliar categories']); // Deprecated
369
370 // Collapse array settings
371 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
372 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
373 endif;
374
375 if (isset($to_notes['cats']) and is_array($to_notes['cats'])) :
376 $to_notes['cats'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['cats']);
377 endif;
378 if (isset($to_notes['tags']) and is_array($to_notes['tags'])) :
379 $to_notes['tags'] = implode(FEEDWORDPRESS_CAT_SEPARATOR, $to_notes['tags']);
380 endif;
381
382 // Collapse the author mapping rule structure back into a flat string
383 if (isset($to_notes['map authors'])) :
384 $ma = array();
385 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
386 foreach ($author_rules as $author_name => $author_action) :
387 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
388 endforeach;
389 endforeach;
390 $to_notes['map authors'] = implode("\n\n", $ma);
391 endif;
392
393 $notes = '';
394 foreach ($to_notes as $key => $value) :
395 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
396 endforeach;
397 return $notes;
398 } /* SyndicatedLink::settings_to_notes () */
399
400 function save_settings ($reload = false) {
401 global $wpdb;
402
403 // Save channel-level meta-data
404 foreach (array('link_name', 'link_description', 'link_url') as $what) :
405 $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
406 endforeach;
407
408 // Save settings to the notes field
409 $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
410
411 // Update the properties of the link from settings changes, etc.
412 $update_set = implode(", ", $alter);
413
414 $result = $wpdb->query("
415 UPDATE $wpdb->links
416 SET $update_set
417 WHERE link_id='$this->id'
418 ");
419
420 if ($reload) :
421 // force reload of link information from DB
422 if (function_exists('clean_bookmark_cache')) :
423 clean_bookmark_cache($this->id);
424 endif;
425 endif;
426 } /* SyndicatedLink::save_settings () */
427
428 /**
429 * Retrieves the value of a setting, allowing for a global setting to be
430 * used as a fallback, or a constant value, or both.
431 *
432 * @param string $name The link setting key
433 * @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.
434 * @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.
435 * @return bool TRUE on success, FALSE on failure.
436 */
437 function setting ($name, $fallback_global = NULL, $fallback_value = NULL) {
438 $ret = NULL;
439 if (isset($this->settings[$name])) :
440 $ret = $this->settings[$name];
441 endif;
442
443 $no_value = (
444 is_null($ret)
445 or (is_string($ret) and strtolower($ret)=='default')
446 );
447
448 if ($no_value and !is_null($fallback_global)) :
449 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
450 endif;
451
452 $no_value = (
453 is_null($ret)
454 or (is_string($ret) and strtolower($ret)=='default')
455 );
456
457 if ($no_value and !is_null($fallback_value)) :
458 $ret = $fallback_value;
459 endif;
460 return $ret;
461 } /* SyndicatedLink::setting () */
462
463 function uri () {
464 return (is_object($this->link) ? $this->link->link_rss : NULL);
465 } /* SyndicatedLink::uri () */
466
467 function homepage ($fromFeed = true) {
468 if ($fromFeed) :
469 $url = (isset($this->settings['feed/link']) ? $this->settings['feed/link'] : NULL);
470 else :
471 $url = $this->link->link_url;
472 endif;
473 return $url;
474 } /* SyndicatedLink::homepage () */
475
476 function name ($fromFeed = true) {
477 if ($fromFeed) :
478 $name = (isset($this->settings['feed/title']) ? $this->settings['feed/title'] : NULL);
479 else :
480 $name = $this->link->link_name;
481 endif;
482 return $name;
483 } /* SyndicatedLink::name () */
484
485 function ttl () {
486 if (is_object($this->magpie)) :
487 $channel = $this->magpie->channel;
488 else :
489 $channel = array();
490 endif;
491
492 if (isset($channel['ttl'])) :
493 // "ttl stands for time to live. It's a number of
494 // minutes that indicates how long a channel can be
495 // cached before refreshing from the source."
496 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
497 $ret = $channel['ttl'];
498 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
499 $period_minutes = array (
500 'hourly' => 60, /* minutes in an hour */
501 'daily' => 1440, /* minutes in a day */
502 'weekly' => 10080, /* minutes in a week */
503 'monthly' => 43200, /* minutes in a month */
504 'yearly' => 525600, /* minutes in a year */
505 );
506
507 // "sy:updatePeriod: Describes the period over which the
508 // channel format is updated. Acceptable values are:
509 // hourly, daily, weekly, monthly, yearly. If omitted,
510 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
511 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
512 else : $period = 'daily';
513 endif;
514
515 // "sy:updateFrequency: Used to describe the frequency
516 // of updates in relation to the update period. A
517 // positive integer indicates how many times in that
518 // period the channel is updated. ... If omitted a value
519 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
520 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
521 else : $freq = 1;
522 endif;
523
524 $ret = (int) ($period_minutes[$period] / $freq);
525 else :
526 $ret = NULL;
527 endif;
528 return $ret;
529 } /* SyndicatedLink::ttl() */
530
531 // SyndicatedLink::flatten_array (): flatten an array. Useful for
532 // hierarchical and namespaced elements.
533 //
534 // Given an array which may contain array or object elements in it,
535 // return a "flattened" array: a one-dimensional array of scalars
536 // containing each of the scalar elements contained within the array
537 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
538 // returned array for FeedWordPress::flatten_array($a) will contain a key
539 // $a['feed/b/c/d'] with value 'e'.
540 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
541 $ret = array ();
542 if (is_array($arr)) :
543 foreach ($arr as $key => $value) :
544 if (is_scalar($value)) :
545 $ret[$prefix.$key] = $value;
546 else :
547 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
548 endif;
549 endforeach;
550 endif;
551 return $ret;
552 } /* SyndicatedLink::flatten_array () */
553
554 function hardcode ($what) {
555 $default = get_option("feedwordpress_hardcode_$what");
556 if ( $default === 'yes' ) :
557 // If the default is to hardcode, then we want the
558 // negation of negative(): TRUE by default and FALSE if
559 // the setting is explicitly "no"
560 $ret = !FeedWordPress::negative($this->settings, "hardcode $what");
561 else :
562 // If the default is NOT to hardcode, then we want
563 // affirmative(): FALSE by default and TRUE if the
564 // setting is explicitly "yes"
565 $ret = FeedWordPress::affirmative($this->settings, "hardcode $what");
566 endif;
567 return $ret;
568 } /* SyndicatedLink::hardcode () */
569
570 function syndicated_status ($what, $default, $fallback = true) {
571 global $wpdb;
572
573 // Use local setting if we have it
574 if ( isset($this->settings["$what status"]) ) :
575 $ret = $this->settings["$what status"];
576
577 // Or fall back to global default if we can
578 elseif ($fallback) :
579 $ret = FeedWordPress::syndicated_status($what, $default);
580
581 // Or use default value if we can't.
582 else :
583 $ret = $default;
584
585 endif;
586
587 return $wpdb->escape(trim(strtolower($ret)));
588 } /* SyndicatedLink:syndicated_status () */
589 } // class SyndicatedLink
590
591