PluginProbe
FeedWordPress / 2016.1211
FeedWordPress v2016.1211
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 2016.1211, at syndicatedlink.class.php

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