PluginProbe
FeedWordPress / 2015.0514
FeedWordPress v2015.0514
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 2015.0514, at syndicatedlink.class.php

1,155 lines 35.3 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 public $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, 'fetch' => 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
182 // Success; clear out error setting, if any.
183 $this->update_setting('update/error', NULL);
184
185 $new_count = array('new' => 0, 'updated' => 0);
186
187 # -- Update Link metadata live from feed
188 $channel = $this->magpie->channel;
189
190 if (!isset($channel['id'])) :
191 $channel['id'] = $this->link->link_rss;
192 endif;
193
194 $update = array();
195 if (!$this->hardcode('url') and isset($channel['link'])) :
196 $update[] = "link_url = '".esc_sql($channel['link'])."'";
197 endif;
198
199 if (!$this->hardcode('name') and isset($channel['title'])) :
200 $update[] = "link_name = '".esc_sql($channel['title'])."'";
201 endif;
202
203 if (!$this->hardcode('description')) :
204 if (isset($channel['tagline'])) :
205 $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
206 elseif (isset($channel['description'])) :
207 $update[] = "link_description = '".esc_sql($channel['description'])."'";
208 endif;
209 endif;
210
211 $this->merge_settings($channel, 'feed/');
212
213 $this->update_setting('update/last', time());
214 list($ttl, $xml) = $this->ttl(/*return element=*/ true);
215
216 if (!is_null($ttl)) :
217 $this->update_setting('update/ttl', $ttl);
218 $this->update_setting('update/xml', $xml);
219 $this->update_setting('update/timed', 'feed');
220 else :
221 $ttl = $this->automatic_ttl();
222 $this->update_setting('update/ttl', $ttl);
223 $this->update_setting('update/xml', NULL);
224 $this->update_setting('update/timed', 'automatically');
225 endif;
226 $this->update_setting('update/fudge', rand(0, ($ttl/3))*60);
227 $this->update_setting('update/ttl', apply_filters(
228 'syndicated_feed_ttl',
229 $this->setting('update/ttl'),
230 $this
231 ));
232
233 if (!$this->setting('update/hold') != 'ping') :
234 $this->update_setting('update/hold', 'scheduled');
235 endif;
236
237 $this->update_setting('update/unfinished', 'yes');
238
239 $update[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
240
241 $update_set = implode(',', $update);
242
243 // Update the properties of the link from the feed information
244 $result = $wpdb->query("
245 UPDATE $wpdb->links
246 SET $update_set
247 WHERE link_id='$this->id'
248 ");
249 do_action('update_syndicated_feed', $this->id, $this);
250
251 # -- Add new posts from feed and update any updated posts
252 $crashed = false;
253
254 $posts = $this->live_posts();
255
256 $this->magpie->originals = $posts;
257
258 // If this is a complete feed, rather than an incremental feed, we
259 // need to prepare to mark everything for presumptive retirement.
260 if ($this->is_non_incremental()) :
261 $q = new WP_Query(array(
262 'fields' => '_synfrom',
263 'post_status__not' => 'fwpretired',
264 'ignore_sticky_posts' => true,
265 'meta_key' => 'syndication_feed_id',
266 'meta_value' => $this->id,
267 ));
268 foreach ($q->posts as $p) :
269 update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
270 endforeach;
271 endif;
272
273 if (is_array($posts)) :
274 foreach ($posts as $key => $item) :
275 $post = new SyndicatedPost($item, $this);
276
277 if (!$resume or !in_array(trim($post->guid()), $processed)) :
278 $processed[] = $post->guid();
279 if (!$post->filtered()) :
280 $new = $post->store();
281 if ( $new !== false ) $new_count[$new]++;
282 endif;
283
284 if (!is_null($crash_ts) and (time() > $crash_ts)) :
285 $crashed = true;
286 break;
287 endif;
288 endif;
289
290 unset($post);
291
292 endforeach;
293 endif;
294
295 if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
296 // Check for use of Atom tombstones. Spec:
297 // <http://tools.ietf.org/html/draft-snell-atompub-tombstones-18>
298 $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
299 if (count($tombstones) > 0) :
300 foreach ($tombstones as $tombstone) :
301 $ref = NULL;
302 foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
303 if (isset($tombstone['attribs'][$ns])
304 and isset($tombstone['attribs'][$ns]['ref'])) :
305 $ref = $tombstone['attribs'][$ns]['ref'];
306 endif;
307 endforeach;
308
309 $q = new WP_Query(array(
310 'ignore_sticky_posts' => true,
311 'guid' => $ref,
312 'meta_key' => 'syndication_feed_id',
313 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
314 ));
315
316 foreach ($q->posts as $p) :
317 $old_status = $p->post_status;
318 FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
319 set_post_field('post_status', 'fwpretired', $p->ID);
320 wp_transition_post_status('fwpretired', $old_status, $p);
321 endforeach;
322
323 endforeach;
324 endif;
325 endif;
326
327 $suffix = ($crashed ? 'crashed' : 'completed');
328 do_action('update_syndicated_feed_items', $this->id, $this);
329 do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
330
331 $this->update_setting('update/processed', $processed);
332 if (!$crashed) :
333 $this->update_setting('update/unfinished', 'no');
334 endif;
335 $this->update_setting('link/item count', count($posts));
336
337 // Copy back any changes to feed settings made in the
338 // course of updating (e.g. new author rules)
339 $update_set = "link_notes = '".esc_sql($this->settings_to_notes())."'";
340
341 // Update the properties of the link from the feed information
342 $result = $wpdb->query("
343 UPDATE $wpdb->links
344 SET $update_set
345 WHERE link_id='$this->id'
346 ");
347
348 do_action("update_syndicated_feed_completed", $this->id, $this);
349 endif;
350
351 // All done; let's clean up.
352 $this->magpie = NULL;
353
354 // Avoid circular-reference memory leak in PHP < 5.3.
355 // Cf. <http://simplepie.org/wiki/faq/i_m_getting_memory_leaks>
356 if (method_exists($this->simplepie, '__destruct')) :
357 $this->simplepie->__destruct();
358 endif;
359 $this->simplepie = NULL;
360
361 return $new_count;
362 } /* SyndicatedLink::poll() */
363
364 function process_retirements ($delta) {
365 global $post;
366
367 $q = new WP_Query(array(
368 'fields' => '_synfrom',
369 'post_status__not' => 'fwpretired',
370 'ignore_sticky_posts' => true,
371 'meta_key' => '_feedwordpress_retire_me_'.$this->id,
372 'meta_value' => '1',
373 ));
374 if ($q->have_posts()) :
375 foreach ($q->posts as $p) :
376 $old_status = $p->post_status;
377 FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to absence from a non-incremental feed.');
378 set_post_field('post_status', 'fwpretired', $p->ID);
379 wp_transition_post_status('fwpretired', $old_status, $p);
380 delete_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id);
381 endforeach;
382 endif;
383
384 return $delta;
385 }
386
387 /**
388 * Updates the URL for the feed syndicated by this link.
389 *
390 * @param string $url The new feed URL to use for this source.
391 * @return bool TRUE on success, FALSE on failure.
392 */
393 function set_uri ($url) {
394 global $wpdb;
395
396 if ($this->found()) :
397 // Update link_rss
398 $result = $wpdb->query("
399 UPDATE $wpdb->links
400 SET
401 link_rss = '".esc_sql($url)."'
402 WHERE link_id = '".esc_sql($this->id)."'
403 ");
404
405 $ret = ($result ? true : false);
406 else :
407 $ret = false;
408 endif;
409 return $ret;
410 } /* SyndicatedLink::set_uri () */
411
412 function deactivate () {
413 global $wpdb;
414
415 $wpdb->query($wpdb->prepare("
416 UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
417 ", (int) $this->id));
418 } /* SyndicatedLink::deactivate () */
419
420 function delete () {
421 global $wpdb;
422
423 $wpdb->query($wpdb->prepare("
424 DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
425 AND meta_value = '%s'
426 ", $this->id));
427
428 $wpdb->query($wpdb->prepare("
429 DELETE FROM $wpdb->links WHERE link_id = %d
430 ", (int) $this->id));
431
432 $this->id = NULL;
433 } /* SyndicatedLink::delete () */
434
435 function nuke () {
436 global $wpdb;
437
438 // Make a list of the items syndicated from this feed...
439 $post_ids = $wpdb->get_col($wpdb->prepare("
440 SELECT post_id FROM $wpdb->postmeta
441 WHERE meta_key = 'syndication_feed_id'
442 AND meta_value = '%s'
443 ", $this->id));
444
445 // ... and kill them all
446 if (count($post_ids) > 0) :
447 foreach ($post_ids as $post_id) :
448 // Force scrubbing of deleted post
449 // rather than sending to Trashcan
450 wp_delete_post(
451 /*postid=*/ $post_id,
452 /*force_delete=*/ true
453 );
454 endforeach;
455 endif;
456
457 $this->delete();
458 } /* SyndicatedLink::nuke () */
459
460 function map_name_to_new_user ($name, $newuser_name) {
461 global $wpdb;
462
463 if (strlen($newuser_name) > 0) :
464 $newuser_id = fwp_insert_new_user($newuser_name);
465 if (is_numeric($newuser_id)) :
466 if (is_null($name)) : // Unfamiliar author
467 $this->update_setting('unfamiliar author', $newuser_id);
468 else :
469 $map = $this->setting('map authors');
470 $map['name'][$name] = $newuser_id;
471 $this->update_setting('map authors', $map);
472 endif;
473 else :
474 // TODO: Add some error detection and reporting
475 endif;
476 else :
477 // TODO: Add some error reporting
478 endif;
479 } /* SyndicatedLink::map_name_to_new_user () */
480
481 function imploded_settings () {
482 return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
483 }
484
485 function get_settings_from_notes () {
486 // Read off feed settings from link_notes
487 $notes = explode("\n", $this->link->link_notes);
488 foreach ($notes as $note):
489 $pair = explode(": ", $note, 2);
490 $key = (isset($pair[0]) ? $pair[0] : null);
491 $value = (isset($pair[1]) ? $pair[1] : null);
492 if (!is_null($key) and !is_null($value)) :
493 // Unescape and trim() off the whitespace.
494 // Thanks to Ray Lischner for pointing out the
495 // need to trim off whitespace.
496 $this->settings[$key] = stripcslashes (trim($value));
497 endif;
498 endforeach;
499
500 // "Magic" feed settings
501 $this->settings['link/uri'] = $this->link->link_rss;
502 $this->settings['link/name'] = $this->link->link_name;
503 $this->settings['link/id'] = $this->link->link_id;
504
505 // `hardcode categories` and `unfamiliar categories` are
506 // deprecated in favor of `unfamiliar category`
507 if (
508 isset($this->settings['unfamiliar categories'])
509 and !isset($this->settings['unfamiliar category'])
510 ) :
511 $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
512 endif;
513 if (
514 FeedWordPress::affirmative($this->settings, 'hardcode categories')
515 and !isset($this->settings['unfamiliar category'])
516 ) :
517 $this->settings['unfamiliar category'] = 'default';
518 endif;
519
520 // Set this up automagically for del.icio.us
521 $bits = parse_url($this->link->link_rss);
522 $tagspacers = array('del.icio.us', 'feeds.delicious.com');
523 if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
524 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
525 endif;
526
527 // Simple lists
528 foreach ($this->imploded_settings() as $what) :
529 if (isset($this->settings[$what])):
530 $this->settings[$what] = explode(
531 FEEDWORDPRESS_CAT_SEPARATOR,
532 $this->settings[$what]
533 );
534 endif;
535 endforeach;
536
537 if (isset($this->settings['terms'])) :
538 // Look for new format
539 $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
540
541 if (!is_array($this->settings['terms'])) :
542 // Deal with old format instead. Ugh.
543
544 // Split on two *or more* consecutive breaks
545 // because in the old format, a taxonomy
546 // without any associated terms would
547 // produce tax_name#1\n\n\ntax_name#2\nterm,
548 // and the naive split on the first \n\n
549 // would screw up the tax_name#2 list.
550 //
551 // Props to David Morris for pointing this
552 // out.
553
554 $this->settings['terms'] = preg_split(
555 "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
556 $this->settings['terms']
557 );
558 $terms = array();
559 foreach ($this->settings['terms'] as $line) :
560 $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
561 $tax = array_shift($line);
562 $terms[$tax] = $line;
563 endforeach;
564 $this->settings['terms'] = $terms;
565 endif;
566 endif;
567
568 if (isset($this->settings['map authors'])) :
569 $author_rules = explode("\n\n", $this->settings['map authors']);
570 $ma = array();
571 foreach ($author_rules as $rule) :
572 list($rule_type, $author_name, $author_action) = explode("\n", $rule);
573
574 // Normalize for case and whitespace
575 $rule_type = strtolower(trim($rule_type));
576 $author_name = strtolower(trim($author_name));
577 $author_action = strtolower(trim($author_action));
578
579 $ma[$rule_type][$author_name] = $author_action;
580 endforeach;
581 $this->settings['map authors'] = $ma;
582 endif;
583
584 } /* SyndicatedLink::get_settings_from_notes () */
585
586 function settings_to_notes () {
587 $to_notes = $this->settings;
588
589 unset($to_notes['link/id']); // Magic setting; don't save
590 unset($to_notes['link/uri']); // Magic setting; don't save
591 unset($to_notes['link/name']); // Magic setting; don't save
592 unset($to_notes['hardcode categories']); // Deprecated
593 unset($to_notes['unfamiliar categories']); // Deprecated
594
595 // Collapse array settings
596 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
597 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
598 endif;
599
600 foreach ($this->imploded_settings() as $what) :
601 if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
602 $to_notes[$what] = implode(
603 FEEDWORDPRESS_CAT_SEPARATOR,
604 $to_notes[$what]
605 );
606 endif;
607 endforeach;
608
609 if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
610 // Serialize it.
611 $to_notes['terms'] = serialize($to_notes['terms']);
612 endif;
613
614 // Collapse the author mapping rule structure back into a flat string
615 if (isset($to_notes['map authors'])) :
616 $ma = array();
617 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
618 foreach ($author_rules as $author_name => $author_action) :
619 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
620 endforeach;
621 endforeach;
622 $to_notes['map authors'] = implode("\n\n", $ma);
623 endif;
624
625 $notes = '';
626 foreach ($to_notes as $key => $value) :
627 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
628 endforeach;
629 return $notes;
630 } /* SyndicatedLink::settings_to_notes () */
631
632 function save_settings ($reload = false) {
633 global $wpdb;
634
635 // Save channel-level meta-data
636 foreach (array('link_name', 'link_description', 'link_url') as $what) :
637 $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
638 endforeach;
639
640 // Save settings to the notes field
641 $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
642
643 // Update the properties of the link from settings changes, etc.
644 $update_set = implode(", ", $alter);
645
646 $result = $wpdb->query("
647 UPDATE $wpdb->links
648 SET $update_set
649 WHERE link_id='$this->id'
650 ");
651
652 if ($reload) :
653 // force reload of link information from DB
654 if (function_exists('clean_bookmark_cache')) :
655 clean_bookmark_cache($this->id);
656 endif;
657 endif;
658 } /* SyndicatedLink::save_settings () */
659
660 /**
661 * Retrieves the value of a setting, allowing for a global setting to be
662 * used as a fallback, or a constant value, or both.
663 *
664 * @param string $name The link setting key
665 * @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.
666 * @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.
667 * @return bool TRUE on success, FALSE on failure.
668 */
669 function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
670 $ret = NULL;
671 if (isset($this->settings[$name])) :
672 $ret = $this->settings[$name];
673 endif;
674
675 $no_value = (
676 is_null($ret)
677 or (is_string($ret) and strtolower($ret)==$default)
678 );
679
680 if ($no_value and !is_null($fallback_global)) :
681 // Avoid duplication of this correction
682 $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
683
684 // Occasionally we'll get an array back. Convert it to a string
685 if ( is_array($fallback_global) && sizeof($fallback_global) )
686 $fallback_global = reset($fallback_global);
687
688 if ( !empty($fallback_global) )
689 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
690 endif;
691
692 $no_value = (
693 is_null($ret)
694 or (is_string($ret) and strtolower($ret)==$default)
695 );
696
697 if ($no_value and !is_null($fallback_value)) :
698 $ret = $fallback_value;
699 endif;
700 return $ret;
701 } /* SyndicatedLink::setting () */
702
703 function merge_settings ($data, $prefix, $separator = '/') {
704 $dd = $this->flatten_array($data, $prefix, $separator);
705 $this->settings = array_merge($this->settings, $dd);
706 } /* SyndicatedLink::merge_settings () */
707
708 function update_setting ($name, $value, $default = 'default') {
709 if (!is_null($value) and $value != $default) :
710 $this->settings[$name] = $value;
711 else : // Zap it.
712 unset($this->settings[$name]);
713 endif;
714 } /* SyndicatedLink::update_setting () */
715
716 function is_non_incremental () {
717 return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
718 } /* SyndicatedLink::is_non_incremental () */
719
720 function uri ($params = array()) {
721 $params = wp_parse_args($params, array(
722 'add_params' => false,
723 'fetch' => false,
724 ));
725
726 // Initialize $qp (= array for added query parameters, if any)
727 $qp = array();
728
729 $link_rss = (is_object($this->link) ? $this->link->link_rss : NULL);
730
731 // $link_rss stores the URI for the subscription as stored in the feed's record.
732 // $uri stores the effective URI of the request including any/all added query parameters
733 $uri = $link_rss;
734 if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
735 $qp = maybe_unserialize($this->setting('query parameters', array()));
736
737 // For high-tech HTTP feed request kung fu
738 $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
739
740 // $qp is an array of key-value pairs stored as arrays of format [$key, $value]
741 $q = array();
742 if (is_array($qp) and count($qp) > 0) :
743 foreach ($qp as $pair) :
744 $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
745 endforeach;
746
747 // Are we appending to a URI that already has params?
748 $sep = ((strpos($uri, "?")===false) ? '?' : '&');
749
750 // Tack it on
751 $uri .= $sep . implode("&", $q);
752 endif;
753 endif;
754
755 // Do we have any filters that apply here?
756 $uri = apply_filters('syndicated_link_uri', $uri, $link_rss, $qp, $params, $this);
757
758 // Return the filtered link URI.
759 return $uri;
760 } /* SyndicatedLink::uri () */
761
762 function username () {
763 return $this->setting('http username', 'http_username', NULL);
764 } /* SyndicatedLink::username () */
765
766 function password () {
767 return $this->setting('http password', 'http_password', NULL);
768 } /* SyndicatedLink::password () */
769
770 function authentication_method () {
771 $auth = $this->setting('http auth method', NULL);
772 if (('-' == $auth) or (strlen($auth)==0)) :
773 $auth = NULL;
774 endif;
775 return $auth;
776 } /* SyndicatedLink::authentication_method () */
777
778 var $postmeta = array();
779 function postmeta ($params = array()) {
780 $params = wp_parse_args($params, /*defaults=*/ array(
781 "field" => NULL,
782 "parsed" => false,
783 "force" => false,
784 ));
785
786 if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
787 // First, get the global settings.
788 $default_custom_settings = get_option('feedwordpress_custom_settings');
789 if ($default_custom_settings and !is_array($default_custom_settings)) :
790 $default_custom_settings = unserialize($default_custom_settings);
791 endif;
792 if (!is_array($default_custom_settings)) :
793 $default_custom_settings = array();
794 endif;
795
796 // Next, get the settings for this particular feed.
797 $custom_settings = $this->setting('postmeta', NULL, NULL);
798 if ($custom_settings and !is_array($custom_settings)) :
799 $custom_settings = unserialize($custom_settings);
800 endif;
801 if (!is_array($custom_settings)) :
802 $custom_settings = array();
803 endif;
804
805 $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
806 $this->postmeta[/*parsed=*/ true] = array();
807
808 // Now, run through and parse them all.
809 foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
810 $meta = apply_filters("syndicated_link_post_meta_${key}_pre", $meta, $this);
811 $this->postmeta[/*parsed=*/ false][$key] = $meta;
812 $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
813 endforeach;
814 endif;
815
816 $ret = $this->postmeta[!!$params['parsed']];
817 if (is_string($params['field'])) :
818 $ret = $ret[$params['field']];
819 endif;
820 return $ret;
821 } /* SyndicatedLink::postmeta () */
822
823 function property_cascade ($fromFeed, $link_field, $setting, $method) {
824 $value = NULL;
825 if ($fromFeed) :
826 $value = $this->setting($setting, NULL, NULL, NULL);
827
828 $s = $this->simplepie;
829 $callable = (is_object($s) and method_exists($s, $method));
830 if (is_null($value) and $callable) :
831 $fallback = $s->{$method}();
832 endif;
833 else :
834 $value = $this->link->{$link_field};
835 endif;
836 return $value;
837 } /* SyndicatedLink::property_cascade () */
838
839 function homepage ($fromFeed = true) {
840 return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
841 } /* SyndicatedLink::homepage () */
842
843 function name ($fromFeed = true) {
844 return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
845 } /* SyndicatedLink::name () */
846
847 function guid () {
848 $ret = $this->setting('feed/id', NULL, $this->uri());
849
850 // If we can get it live from the feed, do so.
851 if (is_object($this->simplepie)) :
852 $search = array(
853 array('', 'id'),
854 array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
855 array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
856 array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
857 array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
858 array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
859 );
860
861 foreach ($search as $pair) :
862 if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
863 $ret = $id_tags[0]['data'];
864 break;
865 elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
866 $ret = $id_tags[0]['data'];
867 break;
868 endif;
869 endforeach;
870 endif;
871 return $ret;
872 }
873
874 function links ($params = array()) {
875 $params = wp_parse_args($params, array(
876 "rel" => NULL,
877 ));
878
879 $fLinks = array();
880 $search = array(
881 array('', 'link'),
882 array(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'),
883 array(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'),
884 );
885
886 foreach ($search as $pair) :
887 if ($link_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
888 $fLinks = array_merge($fLinks, $link_tags);
889 endif;
890 if ($link_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
891 $fLinks = array_merge($fLinks, $link_tags);
892 endif;
893 endforeach;
894
895 $ret = array();
896 foreach ($fLinks as $link) :
897 $filter = false;
898 if (!is_null($params['rel'])) :
899 $filter = true;
900
901 if (isset($link['attribs'])) :
902 // Get a list of NSes from the search
903 foreach ($search as $pair) :
904 $ns = $pair[0];
905
906 if (isset($link['attribs'][$ns])
907 and isset($link['attribs'][$ns]['rel'])
908 ) :
909 $rel = strtolower(trim($link['attribs'][$ns]['rel']));
910 $fRel = strtolower(trim($params['rel']));
911
912 if ($rel == $fRel) :
913 $filter = false;
914 endif;
915 endif;
916 endforeach;
917 endif;
918 endif;
919
920 if (!$filter) :
921 $ret[] = $link;
922 endif;
923 endforeach;
924
925 return $ret;
926 }
927
928 function ttl ($return_element = false) {
929 if (is_object($this->magpie)) :
930 $channel = $this->magpie->channel;
931 else :
932 $channel = array();
933 endif;
934
935 if (isset($channel['ttl'])) :
936 // "ttl stands for time to live. It's a number of
937 // minutes that indicates how long a channel can be
938 // cached before refreshing from the source."
939 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
940 $xml = 'rss:ttl';
941 $ret = $channel['ttl'];
942 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
943 $period_minutes = array (
944 'hourly' => 60, /* minutes in an hour */
945 'daily' => 1440, /* minutes in a day */
946 'weekly' => 10080, /* minutes in a week */
947 'monthly' => 43200, /* minutes in a month */
948 'yearly' => 525600, /* minutes in a year */
949 );
950
951 // "sy:updatePeriod: Describes the period over which the
952 // channel format is updated. Acceptable values are:
953 // hourly, daily, weekly, monthly, yearly. If omitted,
954 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
955 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
956 else : $period = 'daily';
957 endif;
958
959 // "sy:updateFrequency: Used to describe the frequency
960 // of updates in relation to the update period. A
961 // positive integer indicates how many times in that
962 // period the channel is updated. ... If omitted a value
963 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
964 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
965 else : $freq = 1;
966 endif;
967
968 $xml = 'sy:updateFrequency';
969 $ret = (int) ($period_minutes[$period] / $freq);
970 else :
971 $xml = NULL;
972 $ret = NULL;
973 endif;
974
975 if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
976 $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
977
978 if ($min > $ret) :
979 $ret = NULL;
980 endif;
981 endif;
982 return ($return_element ? array($ret, $xml) : $ret);
983 } /* SyndicatedLink::ttl() */
984
985 function automatic_ttl () {
986 // spread out over a time interval for staggered updates
987 $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
988 if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
989 $updateWindow = DEFAULT_UPDATE_PERIOD;
990 endif;
991
992 // We get a fudge of 1/3 of window from elsewhere. We'll do some more
993 // fudging here.
994 $fudgedInterval = $updateWindow+rand(-($updateWindow/6), 5*($updateWindow/12));
995 return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
996 } /* SyndicatedLink::automatic_ttl () */
997
998 // SyndicatedLink::flatten_array (): flatten an array. Useful for
999 // hierarchical and namespaced elements.
1000 //
1001 // Given an array which may contain array or object elements in it,
1002 // return a "flattened" array: a one-dimensional array of scalars
1003 // containing each of the scalar elements contained within the array
1004 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
1005 // returned array for FeedWordPress::flatten_array($a) will contain a key
1006 // $a['feed/b/c/d'] with value 'e'.
1007 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
1008 $ret = array ();
1009 if (is_array($arr)) :
1010 foreach ($arr as $key => $value) :
1011 if (is_scalar($value)) :
1012 $ret[$prefix.$key] = $value;
1013 else :
1014 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
1015 endif;
1016 endforeach;
1017 endif;
1018 return $ret;
1019 } /* SyndicatedLink::flatten_array () */
1020
1021 function hardcode ($what) {
1022
1023 $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
1024
1025 if ('yes' == $ret) :
1026 $ret = true;
1027 else :
1028 $ret = false;
1029 endif;
1030 return $ret;
1031 } /* SyndicatedLink::hardcode () */
1032
1033 function syndicated_status ($what, $default, $fallback = true) {
1034 global $wpdb;
1035
1036 $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
1037 $ret = $this->setting($what.' status', $g_set, $default);
1038
1039 return esc_sql(trim(strtolower($ret)));
1040 } /* SyndicatedLink:syndicated_status () */
1041
1042 function taxonomies () {
1043 $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
1044 return get_object_taxonomies(array('object_type' => $post_type), 'names');
1045 } /* SyndicatedLink::taxonomies () */
1046
1047 /**
1048 * category_ids: look up (and create) category ids from a list of
1049 * categories
1050 *
1051 * @param array $cats
1052 * @param string $unfamiliar_category
1053 * @param array|null $taxonomies
1054 * @return array
1055 */
1056 function category_ids ($post, $cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
1057 $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
1058 $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
1059
1060 $catTax = 'category';
1061
1062 if (is_null($taxonomies)) :
1063 $taxonomies = array('category');
1064 endif;
1065
1066 // We need to normalize whitespace because (1) trailing
1067 // whitespace can cause PHP and MySQL not to see eye to eye on
1068 // VARCHAR comparisons for some versions of MySQL (cf.
1069 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1070 // because I doubt most people want to make a semantic
1071 // distinction between 'Computers' and 'Computers '
1072 $cats = array_map('trim', $cats);
1073
1074 $terms = array();
1075 foreach ($taxonomies as $tax) :
1076 $terms[$tax] = array();
1077 endforeach;
1078
1079 foreach ($cats as $cat_name) :
1080 if (strlen(trim($cat_name)) < 1) :
1081 continue;
1082 endif;
1083
1084 $oTerm = new SyndicatedPostTerm($cat_name, $taxonomies, $post);
1085
1086 if ($oTerm->is_familiar()) :
1087
1088 $tax = $oTerm->taxonomy();
1089 if (!isset($terms[$tax])) :
1090 $terms[$tax] = array();
1091 endif;
1092 $terms[$tax][] = $oTerm->id();
1093
1094 else :
1095
1096 if ('tag'==$unfamiliar_category) :
1097 $unfamiliar_category = 'create:post_tag';
1098 endif;
1099
1100 if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
1101 $tax = $catTax; // Default
1102
1103 if (isset($ref[2])
1104 and strlen($ref[2]) > 2) :
1105 $tax = $ref[2];
1106 endif;
1107
1108 $inserted = $oTerm->insert($tax);
1109 if (!is_null($inserted)) :
1110 if (!isset($terms[$tax])) :
1111 $terms[$tax] = array();
1112 endif;
1113 $terms[$tax][] = $inserted;
1114 else :
1115
1116 endif; // !is_null($inserted)
1117 endif; // preg_match(...)
1118
1119 endif; /* ($oTerm->is_familiar()) */
1120 endforeach;
1121
1122 $filtersOn = $allowFilters;
1123 if ($allowFilters) :
1124 $filters = array_filter(
1125 $this->setting('match/filter', 'match_filter', array()),
1126 'remove_dummy_zero'
1127 );
1128 $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
1129 endif;
1130
1131 // Check for filter conditions
1132 foreach ($terms as $tax => $term_ids) :
1133 if ($filtersOn
1134 and (count($term_ids)==0)
1135 and in_array($tax, $filters)) :
1136 $terms = NULL; // Drop the post
1137 break;
1138 else :
1139 $terms[$tax] = array_unique($term_ids);
1140 endif;
1141 endforeach;
1142
1143 if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
1144 $terms = end($terms);
1145 endif;
1146
1147 FeedWordPress::diagnostic(
1148 'syndicated_posts:categories',
1149 'Category: MAPPED term names '.json_encode($cats).' to IDs: '.json_encode($terms)
1150 );
1151 return $terms;
1152 } /* SyndicatedLink::category_ids () */
1153 } // class SyndicatedLink
1154
1155