PluginProbe
FeedWordPress / 2013.0503
FeedWordPress v2013.0503
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 2013.0503, at syndicatedlink.class.php

972 lines 30.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 require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php');
37 require_once(dirname(__FILE__).'/feedwordpressparsedpostmeta.class.php');
38
39 class SyndicatedLink {
40 var $id = null;
41 var $link = null;
42 var $settings = array ();
43 var $simplepie = null;
44 var $magpie = null;
45
46 function SyndicatedLink ($link) {
47 global $wpdb;
48
49 if (is_object($link)) :
50 $this->link = $link;
51 $this->id = $link->link_id;
52 else :
53 $this->id = $link;
54 $this->link = get_bookmark($link);
55 endif;
56
57 if (strlen($this->link->link_rss) > 0) :
58 $this->get_settings_from_notes();
59 endif;
60
61 add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
62 } /* SyndicatedLink::SyndicatedLink () */
63
64 function found () {
65 return is_object($this->link) and !is_wp_error($this->link);
66 } /* SyndicatedLink::found () */
67
68 function id () {
69 return (is_object($this->link) ? $this->link->link_id : NULL);
70 }
71
72 function stale () {
73 global $feedwordpress;
74
75 $stale = true;
76 if ($this->setting('update/hold')=='ping') :
77 $stale = false; // don't update on any timed updates; pings only
78 elseif ($this->setting('update/hold')=='next') :
79 $stale = true; // update on the next timed update
80 elseif ( !$this->setting('update/last') ) :
81 $stale = true; // initial update
82 elseif ($feedwordpress->force_update_all()) :
83 $stale = true; // forced general updating
84 else :
85 $after = (
86 (int) $this->setting('update/last')
87 + (int) $this->setting('update/fudge')
88 + ((int) $this->setting('update/ttl') * 60)
89 );
90 $stale = (time() >= $after);
91 endif;
92 return $stale;
93 } /* SyndicatedLink::stale () */
94
95 function fetch () {
96 $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
97
98 $this->simplepie = apply_filters(
99 'syndicated_feed',
100 FeedWordPress::fetch($this, array('timeout' => $timeout)),
101 $this
102 );
103
104 // Filter compatibility mode
105 if (is_wp_error($this->simplepie)) :
106 $this->magpie = $this->simplepie;
107 else :
108 $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
109 endif;
110 }
111 function live_posts () {
112 if (!is_object($this->simplepie)) :
113 $this->fetch();
114 endif;
115
116 if (is_object($this->simplepie) and method_exists($this->simplepie, 'get_items')) :
117 $ret = apply_filters(
118 'syndicated_feed_items',
119 $this->simplepie->get_items(),
120 $this
121 );
122 else :
123 $ret = $this->simplepie;
124 endif;
125 return $ret;
126 }
127
128 function poll ($crash_ts = NULL) {
129 global $wpdb;
130
131 $url = $this->uri(array('add_params' => true));
132 FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
133
134 $this->fetch();
135
136 $new_count = NULL;
137
138 $resume = ('yes'==$this->setting('update/unfinished'));
139 if ($resume) :
140 // pick up where we left off
141 $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
142 else :
143 // begin at the beginning
144 $processed = array();
145 endif;
146
147 if (is_wp_error($this->simplepie)) :
148 $new_count = $this->simplepie;
149 // Error; establish an error setting.
150 $theError = array();
151 $theError['ts'] = time();
152 $theError['since'] = time();
153 $theError['object'] = $this->simplepie;
154
155 $oldError = $this->setting('update/error', NULL, NULL);
156 if (is_string($oldError)) :
157 $oldError = unserialize($oldError);
158 endif;
159
160 if (!is_null($oldError)) :
161 // Copy over the in-error-since timestamp
162 $theError['since'] = $oldError['since'];
163
164 // If this is a repeat error, then we should
165 // take a step back before we try to fetch it
166 // again.
167 $this->update_setting('update/last', time(), NULL);
168 $ttl = $this->automatic_ttl();
169 $ttl = apply_filters('syndicated_feed_ttl', $ttl, $this);
170 $ttl = apply_filters('syndicated_feed_ttl_from_error', $ttl, $this);
171 $this->update_setting('update/ttl', $ttl, $this);
172 $this->update_setting('update/timed', 'automatically');
173 endif;
174
175 do_action('syndicated_feed_error', $theError, $oldError, $this);
176
177 $this->update_setting('update/error', serialize($theError));
178 $this->save_settings(/*reload=*/ true);
179
180 elseif (is_object($this->simplepie)) :
181 // Success; clear out error setting, if any.
182 $this->update_setting('update/error', NULL);
183
184 $new_count = array('new' => 0, 'updated' => 0);
185
186 # -- Update Link metadata live from feed
187 $channel = $this->magpie->channel;
188
189 if (!isset($channel['id'])) :
190 $channel['id'] = $this->link->link_rss;
191 endif;
192
193 $update = array();
194 if (!$this->hardcode('url') and isset($channel['link'])) :
195 $update[] = "link_url = '".$wpdb->escape($channel['link'])."'";
196 endif;
197
198 if (!$this->hardcode('name') and isset($channel['title'])) :
199 $update[] = "link_name = '".$wpdb->escape($channel['title'])."'";
200 endif;
201
202 if (!$this->hardcode('description')) :
203 if (isset($channel['tagline'])) :
204 $update[] = "link_description = '".$wpdb->escape($channel['tagline'])."'";
205 elseif (isset($channel['description'])) :
206 $update[] = "link_description = '".$wpdb->escape($channel['description'])."'";
207 endif;
208 endif;
209
210 $this->merge_settings($channel, 'feed/');
211
212 $this->update_setting('update/last', time());
213 list($ttl, $xml) = $this->ttl(/*return element=*/ true);
214
215 if (!is_null($ttl)) :
216 $this->update_setting('update/ttl', $ttl);
217 $this->update_setting('update/xml', $xml);
218 $this->update_setting('update/timed', 'feed');
219 else :
220 $ttl = $this->automatic_ttl();
221 $this->update_setting('update/ttl', $ttl);
222 $this->update_setting('update/xml', NULL);
223 $this->update_setting('update/timed', 'automatically');
224 endif;
225 $this->update_setting('update/fudge', rand(0, ($ttl/3))*60);
226 $this->update_setting('update/ttl', apply_filters(
227 'syndicated_feed_ttl',
228 $this->setting('update/ttl'),
229 $this
230 ));
231
232 if (!$this->setting('update/hold') != 'ping') :
233 $this->update_setting('update/hold', 'scheduled');
234 endif;
235
236 $this->update_setting('update/unfinished', 'yes');
237
238 $update[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
239
240 $update_set = implode(',', $update);
241
242 // Update the properties of the link from the feed information
243 $result = $wpdb->query("
244 UPDATE $wpdb->links
245 SET $update_set
246 WHERE link_id='$this->id'
247 ");
248 do_action('update_syndicated_feed', $this->id, $this);
249
250 # -- Add new posts from feed and update any updated posts
251 $crashed = false;
252
253 $posts = $this->live_posts();
254
255 $this->magpie->originals = $posts;
256
257 // If this is a complete feed, rather than an incremental feed, we
258 // need to prepare to mark everything for presumptive retirement.
259 if ($this->is_incremental()) :
260 $q = new WP_Query(array(
261 'fields' => '_synfrom',
262 'post_status__not' => 'fwpretired',
263 'ignore_sticky_posts' => true,
264 'meta_key' => 'syndication_feed_id',
265 'meta_value' => $this->id,
266 ));
267 foreach ($q->posts as $p) :
268 update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
269 endforeach;
270 endif;
271
272 if (is_array($posts)) :
273 foreach ($posts as $key => $item) :
274 $post = new SyndicatedPost($item, $this);
275
276 if (!$resume or !in_array(trim($post->guid()), $processed)) :
277 $processed[] = $post->guid();
278 if (!$post->filtered()) :
279 $new = $post->store();
280 if ( $new !== false ) $new_count[$new]++;
281 endif;
282
283 if (!is_null($crash_ts) and (time() > $crash_ts)) :
284 $crashed = true;
285 break;
286 endif;
287 endif;
288 unset($post);
289 endforeach;
290 endif;
291
292 if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
293 // Check for use of Atom tombstones. Spec:
294 // <http://tools.ietf.org/html/draft-snell-atompub-tombstones-18>
295 $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
296 if (count($tombstones) > 0) :
297 foreach ($tombstones as $tombstone) :
298 $ref = NULL;
299 foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
300 if (isset($tombstone['attribs'][$ns])
301 and isset($tombstone['attribs'][$ns]['ref'])) :
302 $ref = $tombstone['attribs'][$ns]['ref'];
303 endif;
304 endforeach;
305
306 $q = new WP_Query(array(
307 'ignore_sticky_posts' => true,
308 'guid' => $ref,
309 'meta_key' => 'syndication_feed_id',
310 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
311 ));
312
313 foreach ($q->posts as $p) :
314 $old_status = $p->post_status;
315 FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
316 set_post_field('post_status', 'fwpretired', $p->ID);
317 wp_transition_post_status('fwpretired', $old_status, $p);
318 endforeach;
319
320 endforeach;
321 endif;
322 endif;
323
324 $suffix = ($crashed ? 'crashed' : 'completed');
325 do_action('update_syndicated_feed_items', $this->id, $this);
326 do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
327
328 $this->update_setting('update/processed', $processed);
329 if (!$crashed) :
330 $this->update_setting('update/unfinished', 'no');
331 endif;
332 $this->update_setting('link/item count', count($posts));
333
334 // Copy back any changes to feed settings made in the
335 // course of updating (e.g. new author rules)
336 $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
337
338 // Update the properties of the link from the feed information
339 $result = $wpdb->query("
340 UPDATE $wpdb->links
341 SET $update_set
342 WHERE link_id='$this->id'
343 ");
344
345 do_action("update_syndicated_feed_completed", $this->id, $this);
346 endif;
347
348 // All done; let's clean up.
349 $this->magpie = NULL;
350
351 // Avoid circular-reference memory leak in PHP < 5.3.
352 // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks>
353 if (method_exists($this->simplepie, '__destruct')) :
354 $this->simplepie->__destruct();
355 endif;
356 $this->simplepie = NULL;
357
358 return $new_count;
359 } /* SyndicatedLink::poll() */
360
361 function process_retirements ($delta) {
362 global $post;
363
364 $q = new WP_Query(array(
365 'fields' => '_synfrom',
366 'post_status__not' => 'fwpretired',
367 'ignore_sticky_posts' => true,
368 'meta_key' => '_feedwordpress_retire_me_'.$this->id,
369 'meta_value' => '1',
370 ));
371 if ($q->have_posts()) :
372 foreach ($q->posts as $p) :
373 $old_status = $p->post_status;
374 FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to absence from a non-incremental feed.');
375 set_post_field('post_status', 'fwpretired', $p->ID);
376 wp_transition_post_status('fwpretired', $old_status, $p);
377 delete_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id);
378 endforeach;
379 endif;
380 return $delta;
381 }
382
383 /**
384 * Updates the URL for the feed syndicated by this link.
385 *
386 * @param string $url The new feed URL to use for this source.
387 * @return bool TRUE on success, FALSE on failure.
388 */
389 function set_uri ($url) {
390 global $wpdb;
391
392 if ($this->found()) :
393 // Update link_rss
394 $result = $wpdb->query("
395 UPDATE $wpdb->links
396 SET
397 link_rss = '".$wpdb->escape($url)."'
398 WHERE link_id = '".$wpdb->escape($this->id)."'
399 ");
400
401 $ret = ($result ? true : false);
402 else :
403 $ret = false;
404 endif;
405 return $ret;
406 } /* SyndicatedLink::set_uri () */
407
408 function deactivate () {
409 global $wpdb;
410
411 $wpdb->query($wpdb->prepare("
412 UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
413 ", (int) $this->id));
414 } /* SyndicatedLink::deactivate () */
415
416 function delete () {
417 global $wpdb;
418
419 $wpdb->query($wpdb->prepare("
420 DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
421 AND meta_value = '%s'
422 ", $this->id));
423
424 $wpdb->query($wpdb->prepare("
425 DELETE FROM $wpdb->links WHERE link_id = %d
426 ", (int) $this->id));
427
428 $this->id = NULL;
429 } /* SyndicatedLink::delete () */
430
431 function nuke () {
432 global $wpdb;
433
434 // Make a list of the items syndicated from this feed...
435 $post_ids = $wpdb->get_col($wpdb->prepare("
436 SELECT post_id FROM $wpdb->postmeta
437 WHERE meta_key = 'syndication_feed_id'
438 AND meta_value = '%s'
439 ", $this->id));
440
441 // ... and kill them all
442 if (count($post_ids) > 0) :
443 foreach ($post_ids as $post_id) :
444 // Force scrubbing of deleted post
445 // rather than sending to Trashcan
446 wp_delete_post(
447 /*postid=*/ $post_id,
448 /*force_delete=*/ true
449 );
450 endforeach;
451 endif;
452
453 $this->delete();
454 } /* SyndicatedLink::nuke () */
455
456 function map_name_to_new_user ($name, $newuser_name) {
457 global $wpdb;
458
459 if (strlen($newuser_name) > 0) :
460 $newuser_id = fwp_insert_new_user($newuser_name);
461 if (is_numeric($newuser_id)) :
462 if (is_null($name)) : // Unfamiliar author
463 $this->update_setting('unfamiliar author', $newuser_id);
464 else :
465 $map = $this->setting('map authors');
466 $map['name'][$name] = $newuser_id;
467 $this->update_setting('map authors', $map);
468 endif;
469 else :
470 // TODO: Add some error detection and reporting
471 endif;
472 else :
473 // TODO: Add some error reporting
474 endif;
475 } /* SyndicatedLink::map_name_to_new_user () */
476
477 function imploded_settings () {
478 return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
479 }
480
481 function get_settings_from_notes () {
482 // Read off feed settings from link_notes
483 $notes = explode("\n", $this->link->link_notes);
484 foreach ($notes as $note):
485 $pair = explode(": ", $note, 2);
486 $key = (isset($pair[0]) ? $pair[0] : null);
487 $value = (isset($pair[1]) ? $pair[1] : null);
488 if (!is_null($key) and !is_null($value)) :
489 // Unescape and trim() off the whitespace.
490 // Thanks to Ray Lischner for pointing out the
491 // need to trim off whitespace.
492 $this->settings[$key] = stripcslashes (trim($value));
493 endif;
494 endforeach;
495
496 // "Magic" feed settings
497 $this->settings['link/uri'] = $this->link->link_rss;
498 $this->settings['link/name'] = $this->link->link_name;
499 $this->settings['link/id'] = $this->link->link_id;
500
501 // `hardcode categories` and `unfamiliar categories` are
502 // deprecated in favor of `unfamiliar category`
503 if (
504 isset($this->settings['unfamiliar categories'])
505 and !isset($this->settings['unfamiliar category'])
506 ) :
507 $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
508 endif;
509 if (
510 FeedWordPress::affirmative($this->settings, 'hardcode categories')
511 and !isset($this->settings['unfamiliar category'])
512 ) :
513 $this->settings['unfamiliar category'] = 'default';
514 endif;
515
516 // Set this up automagically for del.icio.us
517 $bits = parse_url($this->link->link_rss);
518 $tagspacers = array('del.icio.us', 'feeds.delicious.com');
519 if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
520 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
521 endif;
522
523 // Simple lists
524 foreach ($this->imploded_settings() as $what) :
525 if (isset($this->settings[$what])):
526 $this->settings[$what] = explode(
527 FEEDWORDPRESS_CAT_SEPARATOR,
528 $this->settings[$what]
529 );
530 endif;
531 endforeach;
532
533 if (isset($this->settings['terms'])) :
534 // Look for new format
535 $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
536
537 if (!is_array($this->settings['terms'])) :
538 // Deal with old format instead. Ugh.
539
540 // Split on two *or more* consecutive breaks
541 // because in the old format, a taxonomy
542 // without any associated terms would
543 // produce tax_name#1\n\n\ntax_name#2\nterm,
544 // and the naive split on the first \n\n
545 // would screw up the tax_name#2 list.
546 //
547 // Props to David Morris for pointing this
548 // out.
549
550 $this->settings['terms'] = preg_split(
551 "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
552 $this->settings['terms']
553 );
554 $terms = array();
555 foreach ($this->settings['terms'] as $line) :
556 $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
557 $tax = array_shift($line);
558 $terms[$tax] = $line;
559 endforeach;
560 $this->settings['terms'] = $terms;
561 endif;
562 endif;
563
564 if (isset($this->settings['map authors'])) :
565 $author_rules = explode("\n\n", $this->settings['map authors']);
566 $ma = array();
567 foreach ($author_rules as $rule) :
568 list($rule_type, $author_name, $author_action) = explode("\n", $rule);
569
570 // Normalize for case and whitespace
571 $rule_type = strtolower(trim($rule_type));
572 $author_name = strtolower(trim($author_name));
573 $author_action = strtolower(trim($author_action));
574
575 $ma[$rule_type][$author_name] = $author_action;
576 endforeach;
577 $this->settings['map authors'] = $ma;
578 endif;
579
580 } /* SyndicatedLink::get_settings_from_notes () */
581
582 function settings_to_notes () {
583 $to_notes = $this->settings;
584
585 unset($to_notes['link/id']); // Magic setting; don't save
586 unset($to_notes['link/uri']); // Magic setting; don't save
587 unset($to_notes['link/name']); // Magic setting; don't save
588 unset($to_notes['hardcode categories']); // Deprecated
589 unset($to_notes['unfamiliar categories']); // Deprecated
590
591 // Collapse array settings
592 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
593 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
594 endif;
595
596 foreach ($this->imploded_settings() as $what) :
597 if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
598 $to_notes[$what] = implode(
599 FEEDWORDPRESS_CAT_SEPARATOR,
600 $to_notes[$what]
601 );
602 endif;
603 endforeach;
604
605 if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
606 // Serialize it.
607 $to_notes['terms'] = serialize($to_notes['terms']);
608 endif;
609
610 // Collapse the author mapping rule structure back into a flat string
611 if (isset($to_notes['map authors'])) :
612 $ma = array();
613 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
614 foreach ($author_rules as $author_name => $author_action) :
615 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
616 endforeach;
617 endforeach;
618 $to_notes['map authors'] = implode("\n\n", $ma);
619 endif;
620
621 $notes = '';
622 foreach ($to_notes as $key => $value) :
623 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
624 endforeach;
625 return $notes;
626 } /* SyndicatedLink::settings_to_notes () */
627
628 function save_settings ($reload = false) {
629 global $wpdb;
630
631 // Save channel-level meta-data
632 foreach (array('link_name', 'link_description', 'link_url') as $what) :
633 $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
634 endforeach;
635
636 // Save settings to the notes field
637 $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
638
639 // Update the properties of the link from settings changes, etc.
640 $update_set = implode(", ", $alter);
641
642 $result = $wpdb->query("
643 UPDATE $wpdb->links
644 SET $update_set
645 WHERE link_id='$this->id'
646 ");
647
648 if ($reload) :
649 // force reload of link information from DB
650 if (function_exists('clean_bookmark_cache')) :
651 clean_bookmark_cache($this->id);
652 endif;
653 endif;
654 } /* SyndicatedLink::save_settings () */
655
656 /**
657 * Retrieves the value of a setting, allowing for a global setting to be
658 * used as a fallback, or a constant value, or both.
659 *
660 * @param string $name The link setting key
661 * @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.
662 * @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.
663 * @return bool TRUE on success, FALSE on failure.
664 */
665 function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
666 $ret = NULL;
667 if (isset($this->settings[$name])) :
668 $ret = $this->settings[$name];
669 endif;
670
671 $no_value = (
672 is_null($ret)
673 or (is_string($ret) and strtolower($ret)==$default)
674 );
675
676 if ($no_value and !is_null($fallback_global)) :
677 // Avoid duplication of this correction
678 $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
679
680 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
681 endif;
682
683 $no_value = (
684 is_null($ret)
685 or (is_string($ret) and strtolower($ret)==$default)
686 );
687
688 if ($no_value and !is_null($fallback_value)) :
689 $ret = $fallback_value;
690 endif;
691 return $ret;
692 } /* SyndicatedLink::setting () */
693
694 function merge_settings ($data, $prefix, $separator = '/') {
695 $dd = $this->flatten_array($data, $prefix, $separator);
696 $this->settings = array_merge($this->settings, $dd);
697 } /* SyndicatedLink::merge_settings () */
698
699 function update_setting ($name, $value, $default = 'default') {
700 if (!is_null($value) and $value != $default) :
701 $this->settings[$name] = $value;
702 else : // Zap it.
703 unset($this->settings[$name]);
704 endif;
705 } /* SyndicatedLink::update_setting () */
706
707 function is_incremental () {
708 return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
709 } /* SyndicatedLink::is_incremental () */
710
711 function uri ($params = array()) {
712 $params = wp_parse_args($params, array(
713 'add_params' => false,
714 ));
715
716 $uri = (is_object($this->link) ? $this->link->link_rss : NULL);
717 if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
718 $qp = maybe_unserialize($this->setting('query parameters', array()));
719
720 // For high-tech HTTP feed request kung fu
721 $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
722
723 $q = array();
724 if (is_array($qp) and count($qp) > 0) :
725 foreach ($qp as $pair) :
726 $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
727 endforeach;
728
729 // Are we appending to a URI that already has params?
730 $sep = ((strpos($uri, "?")===false) ? '?' : '&');
731
732 // Tack it on
733 $uri .= $sep . implode("&", $q);
734 endif;
735 endif;
736
737 return $uri;
738 } /* SyndicatedLink::uri () */
739
740 function username () {
741 return $this->setting('http username', 'http_username', NULL);
742 } /* SyndicatedLink::username () */
743
744 function password () {
745 return $this->setting('http password', 'http_password', NULL);
746 } /* SyndicatedLink::password () */
747
748 function authentication_method () {
749 $auth = $this->setting('http auth method', NULL);
750 if (('-' == $auth) or (strlen($auth)==0)) :
751 $auth = NULL;
752 endif;
753 return $auth;
754 } /* SyndicatedLink::authentication_method () */
755
756 var $postmeta = array();
757 function postmeta ($params = array()) {
758 $params = wp_parse_args($params, /*defaults=*/ array(
759 "field" => NULL,
760 "parsed" => false,
761 "force" => false,
762 ));
763
764 if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
765 // First, get the global settings.
766 $default_custom_settings = get_option('feedwordpress_custom_settings');
767 if ($default_custom_settings and !is_array($default_custom_settings)) :
768 $default_custom_settings = unserialize($default_custom_settings);
769 endif;
770 if (!is_array($default_custom_settings)) :
771 $default_custom_settings = array();
772 endif;
773
774 // Next, get the settings for this particular feed.
775 $custom_settings = $this->setting('postmeta', NULL, NULL);
776 if ($custom_settings and !is_array($custom_settings)) :
777 $custom_settings = unserialize($custom_settings);
778 endif;
779 if (!is_array($custom_settings)) :
780 $custom_settings = array();
781 endif;
782
783 $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
784 $this->postmeta[/*parsed=*/ true] = array();
785
786 // Now, run through and parse them all.
787 foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
788 $meta = apply_filters("syndicated_link_post_meta_${key}_pre", $meta, $this);
789 $this->postmeta[/*parsed=*/ false][$key] = $meta;
790 $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
791 endforeach;
792 endif;
793
794 $ret = $this->postmeta[!!$params['parsed']];
795 if (is_string($params['field'])) :
796 $ret = $ret[$params['field']];
797 endif;
798 return $ret;
799 } /* SyndicatedLink::postmeta () */
800
801 function property_cascade ($fromFeed, $link_field, $setting, $method) {
802 $value = NULL;
803 if ($fromFeed) :
804 $value = $this->setting($setting, NULL, NULL, NULL);
805
806 $s = $this->simplepie;
807 $callable = (is_object($s) and method_exists($s, $method));
808 if (is_null($value) and $callable) :
809 $fallback = $s->{$method}();
810 endif;
811 else :
812 $value = $this->link->{$link_field};
813 endif;
814 return $value;
815 } /* SyndicatedLink::property_cascade () */
816
817 function homepage ($fromFeed = true) {
818 return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
819 } /* SyndicatedLink::homepage () */
820
821 function name ($fromFeed = true) {
822 return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
823 } /* SyndicatedLink::name () */
824
825 function guid () {
826 $ret = $this->setting('feed/id', NULL, $this->uri());
827
828 // If we can get it live from the feed, do so.
829 if (is_object($this->simplepie)) :
830 $search = array(
831 array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
832 array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
833 array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
834 array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
835 array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
836 );
837
838 foreach ($search as $pair) :
839 if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
840 $ret = $id_tags[0]['data'];
841 break;
842 elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
843 $ret = $id_tags[0]['data'];
844 break;
845 endif;
846 endforeach;
847 endif;
848 return $ret;
849 }
850
851 function ttl ($return_element = false) {
852 if (is_object($this->magpie)) :
853 $channel = $this->magpie->channel;
854 else :
855 $channel = array();
856 endif;
857
858 if (isset($channel['ttl'])) :
859 // "ttl stands for time to live. It's a number of
860 // minutes that indicates how long a channel can be
861 // cached before refreshing from the source."
862 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
863 $xml = 'rss:ttl';
864 $ret = $channel['ttl'];
865 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
866 $period_minutes = array (
867 'hourly' => 60, /* minutes in an hour */
868 'daily' => 1440, /* minutes in a day */
869 'weekly' => 10080, /* minutes in a week */
870 'monthly' => 43200, /* minutes in a month */
871 'yearly' => 525600, /* minutes in a year */
872 );
873
874 // "sy:updatePeriod: Describes the period over which the
875 // channel format is updated. Acceptable values are:
876 // hourly, daily, weekly, monthly, yearly. If omitted,
877 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
878 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
879 else : $period = 'daily';
880 endif;
881
882 // "sy:updateFrequency: Used to describe the frequency
883 // of updates in relation to the update period. A
884 // positive integer indicates how many times in that
885 // period the channel is updated. ... If omitted a value
886 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
887 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
888 else : $freq = 1;
889 endif;
890
891 $xml = 'sy:updateFrequency';
892 $ret = (int) ($period_minutes[$period] / $freq);
893 else :
894 $xml = NULL;
895 $ret = NULL;
896 endif;
897
898 if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
899 $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
900
901 if ($min > $ret) :
902 $ret = NULL;
903 endif;
904 endif;
905 return ($return_element ? array($ret, $xml) : $ret);
906 } /* SyndicatedLink::ttl() */
907
908 function automatic_ttl () {
909 // spread out over a time interval for staggered updates
910 $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
911 if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
912 $updateWindow = DEFAULT_UPDATE_PERIOD;
913 endif;
914
915 // We get a fudge of 1/3 of window from elsewhere. We'll do some more
916 // fudging here.
917 $fudgedInterval = $updateWindow+rand(-($updateWindow/6), 5*($updateWindow/12));
918 return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
919 } /* SyndicatedLink::automatic_ttl () */
920
921 // SyndicatedLink::flatten_array (): flatten an array. Useful for
922 // hierarchical and namespaced elements.
923 //
924 // Given an array which may contain array or object elements in it,
925 // return a "flattened" array: a one-dimensional array of scalars
926 // containing each of the scalar elements contained within the array
927 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
928 // returned array for FeedWordPress::flatten_array($a) will contain a key
929 // $a['feed/b/c/d'] with value 'e'.
930 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
931 $ret = array ();
932 if (is_array($arr)) :
933 foreach ($arr as $key => $value) :
934 if (is_scalar($value)) :
935 $ret[$prefix.$key] = $value;
936 else :
937 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
938 endif;
939 endforeach;
940 endif;
941 return $ret;
942 } /* SyndicatedLink::flatten_array () */
943
944 function hardcode ($what) {
945
946 $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
947
948 if ('yes' == $ret) :
949 $ret = true;
950 else :
951 $ret = false;
952 endif;
953 return $ret;
954 } /* SyndicatedLink::hardcode () */
955
956 function syndicated_status ($what, $default, $fallback = true) {
957 global $wpdb;
958
959 $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
960 $ret = $this->setting($what.' status', $g_set, $default);
961
962 return $wpdb->escape(trim(strtolower($ret)));
963 } /* SyndicatedLink:syndicated_status () */
964
965 function taxonomies () {
966 $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
967 return get_object_taxonomies(array('object_type' => $post_type), 'names');
968 } /* SyndicatedLink::taxonomies () */
969
970 } // class SyndicatedLink
971
972