PluginProbe
FeedWordPress / 2014.0805
FeedWordPress v2014.0805
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 2014.0805, at syndicatedlink.class.php

1,146 lines 34.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 # class SyndicatedLink: represents a syndication feed stored within the
3 # WordPress database
4 #
5 # To keep things compact and editable from within WordPress, we use all the
6 # links under a particular category in the WordPress "Blogroll" for the list of
7 # feeds to syndicate. "Contributors" is the category used by default; you can
8 # configure that under Options --> Syndication.
9 #
10 # Fields used are:
11 #
12 # * link_rss: the URI of the Atom/RSS feed to syndicate
13 #
14 # * link_notes: user-configurable options, with keys and values
15 # like so:
16 #
17 # key: value
18 # cats: computers\nweb
19 # feed/key: value
20 #
21 # Keys that start with "feed/" are gleaned from the data supplied
22 # by the feed itself, and will be overwritten with each update.
23 #
24 # Values have linebreak characters escaped with C-style
25 # backslashes (so, for example, a newline becomes "\n").
26 #
27 # The value of `cats` is used as a newline-separated list of
28 # default categories for any post coming from a particular feed.
29 # (In the example above, any posts from this feed will be placed
30 # in the "computers" and "web" categories--*in addition to* any
31 # categories that may already be applied to the posts.)
32 #
33 # Values of keys in link_notes are accessible from templates using
34 # the function `get_feed_meta($key)` if this plugin is activated.
35
36 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 // 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 = '".esc_sql($channel['link'])."'";
196 endif;
197
198 if (!$this->hardcode('name') and isset($channel['title'])) :
199 $update[] = "link_name = '".esc_sql($channel['title'])."'";
200 endif;
201
202 if (!$this->hardcode('description')) :
203 if (isset($channel['tagline'])) :
204 $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
205 elseif (isset($channel['description'])) :
206 $update[] = "link_description = '".esc_sql($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 = '".esc_sql($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_non_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 = '".esc_sql($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
381 return $delta;
382 }
383
384 /**
385 * Updates the URL for the feed syndicated by this link.
386 *
387 * @param string $url The new feed URL to use for this source.
388 * @return bool TRUE on success, FALSE on failure.
389 */
390 function set_uri ($url) {
391 global $wpdb;
392
393 if ($this->found()) :
394 // Update link_rss
395 $result = $wpdb->query("
396 UPDATE $wpdb->links
397 SET
398 link_rss = '".esc_sql($url)."'
399 WHERE link_id = '".esc_sql($this->id)."'
400 ");
401
402 $ret = ($result ? true : false);
403 else :
404 $ret = false;
405 endif;
406 return $ret;
407 } /* SyndicatedLink::set_uri () */
408
409 function deactivate () {
410 global $wpdb;
411
412 $wpdb->query($wpdb->prepare("
413 UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
414 ", (int) $this->id));
415 } /* SyndicatedLink::deactivate () */
416
417 function delete () {
418 global $wpdb;
419
420 $wpdb->query($wpdb->prepare("
421 DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
422 AND meta_value = '%s'
423 ", $this->id));
424
425 $wpdb->query($wpdb->prepare("
426 DELETE FROM $wpdb->links WHERE link_id = %d
427 ", (int) $this->id));
428
429 $this->id = NULL;
430 } /* SyndicatedLink::delete () */
431
432 function nuke () {
433 global $wpdb;
434
435 // Make a list of the items syndicated from this feed...
436 $post_ids = $wpdb->get_col($wpdb->prepare("
437 SELECT post_id FROM $wpdb->postmeta
438 WHERE meta_key = 'syndication_feed_id'
439 AND meta_value = '%s'
440 ", $this->id));
441
442 // ... and kill them all
443 if (count($post_ids) > 0) :
444 foreach ($post_ids as $post_id) :
445 // Force scrubbing of deleted post
446 // rather than sending to Trashcan
447 wp_delete_post(
448 /*postid=*/ $post_id,
449 /*force_delete=*/ true
450 );
451 endforeach;
452 endif;
453
454 $this->delete();
455 } /* SyndicatedLink::nuke () */
456
457 function map_name_to_new_user ($name, $newuser_name) {
458 global $wpdb;
459
460 if (strlen($newuser_name) > 0) :
461 $newuser_id = fwp_insert_new_user($newuser_name);
462 if (is_numeric($newuser_id)) :
463 if (is_null($name)) : // Unfamiliar author
464 $this->update_setting('unfamiliar author', $newuser_id);
465 else :
466 $map = $this->setting('map authors');
467 $map['name'][$name] = $newuser_id;
468 $this->update_setting('map authors', $map);
469 endif;
470 else :
471 // TODO: Add some error detection and reporting
472 endif;
473 else :
474 // TODO: Add some error reporting
475 endif;
476 } /* SyndicatedLink::map_name_to_new_user () */
477
478 function imploded_settings () {
479 return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
480 }
481
482 function get_settings_from_notes () {
483 // Read off feed settings from link_notes
484 $notes = explode("\n", $this->link->link_notes);
485 foreach ($notes as $note):
486 $pair = explode(": ", $note, 2);
487 $key = (isset($pair[0]) ? $pair[0] : null);
488 $value = (isset($pair[1]) ? $pair[1] : null);
489 if (!is_null($key) and !is_null($value)) :
490 // Unescape and trim() off the whitespace.
491 // Thanks to Ray Lischner for pointing out the
492 // need to trim off whitespace.
493 $this->settings[$key] = stripcslashes (trim($value));
494 endif;
495 endforeach;
496
497 // "Magic" feed settings
498 $this->settings['link/uri'] = $this->link->link_rss;
499 $this->settings['link/name'] = $this->link->link_name;
500 $this->settings['link/id'] = $this->link->link_id;
501
502 // `hardcode categories` and `unfamiliar categories` are
503 // deprecated in favor of `unfamiliar category`
504 if (
505 isset($this->settings['unfamiliar categories'])
506 and !isset($this->settings['unfamiliar category'])
507 ) :
508 $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
509 endif;
510 if (
511 FeedWordPress::affirmative($this->settings, 'hardcode categories')
512 and !isset($this->settings['unfamiliar category'])
513 ) :
514 $this->settings['unfamiliar category'] = 'default';
515 endif;
516
517 // Set this up automagically for del.icio.us
518 $bits = parse_url($this->link->link_rss);
519 $tagspacers = array('del.icio.us', 'feeds.delicious.com');
520 if (!isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
521 $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
522 endif;
523
524 // Simple lists
525 foreach ($this->imploded_settings() as $what) :
526 if (isset($this->settings[$what])):
527 $this->settings[$what] = explode(
528 FEEDWORDPRESS_CAT_SEPARATOR,
529 $this->settings[$what]
530 );
531 endif;
532 endforeach;
533
534 if (isset($this->settings['terms'])) :
535 // Look for new format
536 $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
537
538 if (!is_array($this->settings['terms'])) :
539 // Deal with old format instead. Ugh.
540
541 // Split on two *or more* consecutive breaks
542 // because in the old format, a taxonomy
543 // without any associated terms would
544 // produce tax_name#1\n\n\ntax_name#2\nterm,
545 // and the naive split on the first \n\n
546 // would screw up the tax_name#2 list.
547 //
548 // Props to David Morris for pointing this
549 // out.
550
551 $this->settings['terms'] = preg_split(
552 "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
553 $this->settings['terms']
554 );
555 $terms = array();
556 foreach ($this->settings['terms'] as $line) :
557 $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
558 $tax = array_shift($line);
559 $terms[$tax] = $line;
560 endforeach;
561 $this->settings['terms'] = $terms;
562 endif;
563 endif;
564
565 if (isset($this->settings['map authors'])) :
566 $author_rules = explode("\n\n", $this->settings['map authors']);
567 $ma = array();
568 foreach ($author_rules as $rule) :
569 list($rule_type, $author_name, $author_action) = explode("\n", $rule);
570
571 // Normalize for case and whitespace
572 $rule_type = strtolower(trim($rule_type));
573 $author_name = strtolower(trim($author_name));
574 $author_action = strtolower(trim($author_action));
575
576 $ma[$rule_type][$author_name] = $author_action;
577 endforeach;
578 $this->settings['map authors'] = $ma;
579 endif;
580
581 } /* SyndicatedLink::get_settings_from_notes () */
582
583 function settings_to_notes () {
584 $to_notes = $this->settings;
585
586 unset($to_notes['link/id']); // Magic setting; don't save
587 unset($to_notes['link/uri']); // Magic setting; don't save
588 unset($to_notes['link/name']); // Magic setting; don't save
589 unset($to_notes['hardcode categories']); // Deprecated
590 unset($to_notes['unfamiliar categories']); // Deprecated
591
592 // Collapse array settings
593 if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
594 $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
595 endif;
596
597 foreach ($this->imploded_settings() as $what) :
598 if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
599 $to_notes[$what] = implode(
600 FEEDWORDPRESS_CAT_SEPARATOR,
601 $to_notes[$what]
602 );
603 endif;
604 endforeach;
605
606 if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
607 // Serialize it.
608 $to_notes['terms'] = serialize($to_notes['terms']);
609 endif;
610
611 // Collapse the author mapping rule structure back into a flat string
612 if (isset($to_notes['map authors'])) :
613 $ma = array();
614 foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
615 foreach ($author_rules as $author_name => $author_action) :
616 $ma[] = $rule_type."\n".$author_name."\n".$author_action;
617 endforeach;
618 endforeach;
619 $to_notes['map authors'] = implode("\n\n", $ma);
620 endif;
621
622 $notes = '';
623 foreach ($to_notes as $key => $value) :
624 $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
625 endforeach;
626 return $notes;
627 } /* SyndicatedLink::settings_to_notes () */
628
629 function save_settings ($reload = false) {
630 global $wpdb;
631
632 // Save channel-level meta-data
633 foreach (array('link_name', 'link_description', 'link_url') as $what) :
634 $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
635 endforeach;
636
637 // Save settings to the notes field
638 $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
639
640 // Update the properties of the link from settings changes, etc.
641 $update_set = implode(", ", $alter);
642
643 $result = $wpdb->query("
644 UPDATE $wpdb->links
645 SET $update_set
646 WHERE link_id='$this->id'
647 ");
648
649 if ($reload) :
650 // force reload of link information from DB
651 if (function_exists('clean_bookmark_cache')) :
652 clean_bookmark_cache($this->id);
653 endif;
654 endif;
655 } /* SyndicatedLink::save_settings () */
656
657 /**
658 * Retrieves the value of a setting, allowing for a global setting to be
659 * used as a fallback, or a constant value, or both.
660 *
661 * @param string $name The link setting key
662 * @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.
663 * @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.
664 * @return bool TRUE on success, FALSE on failure.
665 */
666 function setting ($name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default') {
667 $ret = NULL;
668 if (isset($this->settings[$name])) :
669 $ret = $this->settings[$name];
670 endif;
671
672 $no_value = (
673 is_null($ret)
674 or (is_string($ret) and strtolower($ret)==$default)
675 );
676
677 if ($no_value and !is_null($fallback_global)) :
678 // Avoid duplication of this correction
679 $fallback_global = preg_replace('/^feedwordpress_/', '', $fallback_global);
680
681 // Occasionally we'll get an array back. Convert it to a string
682 if ( is_array($fallback_global) && sizeof($fallback_global) )
683 $fallback_global = reset($fallback_global);
684
685 if ( !empty($fallback_global) )
686 $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
687 endif;
688
689 $no_value = (
690 is_null($ret)
691 or (is_string($ret) and strtolower($ret)==$default)
692 );
693
694 if ($no_value and !is_null($fallback_value)) :
695 $ret = $fallback_value;
696 endif;
697 return $ret;
698 } /* SyndicatedLink::setting () */
699
700 function merge_settings ($data, $prefix, $separator = '/') {
701 $dd = $this->flatten_array($data, $prefix, $separator);
702 $this->settings = array_merge($this->settings, $dd);
703 } /* SyndicatedLink::merge_settings () */
704
705 function update_setting ($name, $value, $default = 'default') {
706 if (!is_null($value) and $value != $default) :
707 $this->settings[$name] = $value;
708 else : // Zap it.
709 unset($this->settings[$name]);
710 endif;
711 } /* SyndicatedLink::update_setting () */
712
713 function is_non_incremental () {
714 return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
715 } /* SyndicatedLink::is_non_incremental () */
716
717 function uri ($params = array()) {
718 $params = wp_parse_args($params, array(
719 'add_params' => false,
720 'fetch' => false,
721 ));
722
723 $link_rss = (is_object($this->link) ? $this->link->link_rss : NULL);
724
725 $uri = $link_rss;
726 if (!is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
727 $qp = maybe_unserialize($this->setting('query parameters', array()));
728
729 // For high-tech HTTP feed request kung fu
730 $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
731
732 $q = array();
733 if (is_array($qp) and count($qp) > 0) :
734 foreach ($qp as $pair) :
735 $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
736 endforeach;
737
738 // Are we appending to a URI that already has params?
739 $sep = ((strpos($uri, "?")===false) ? '?' : '&');
740
741 // Tack it on
742 $uri .= $sep . implode("&", $q);
743 endif;
744 endif;
745
746 // Do we have any filters that apply here?
747 $uri = apply_filters('syndicated_link_uri', $uri, $link_rss, $qp, $params, $this);
748
749 // Return the filtered link URI.
750 return $uri;
751 } /* SyndicatedLink::uri () */
752
753 function username () {
754 return $this->setting('http username', 'http_username', NULL);
755 } /* SyndicatedLink::username () */
756
757 function password () {
758 return $this->setting('http password', 'http_password', NULL);
759 } /* SyndicatedLink::password () */
760
761 function authentication_method () {
762 $auth = $this->setting('http auth method', NULL);
763 if (('-' == $auth) or (strlen($auth)==0)) :
764 $auth = NULL;
765 endif;
766 return $auth;
767 } /* SyndicatedLink::authentication_method () */
768
769 var $postmeta = array();
770 function postmeta ($params = array()) {
771 $params = wp_parse_args($params, /*defaults=*/ array(
772 "field" => NULL,
773 "parsed" => false,
774 "force" => false,
775 ));
776
777 if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
778 // First, get the global settings.
779 $default_custom_settings = get_option('feedwordpress_custom_settings');
780 if ($default_custom_settings and !is_array($default_custom_settings)) :
781 $default_custom_settings = unserialize($default_custom_settings);
782 endif;
783 if (!is_array($default_custom_settings)) :
784 $default_custom_settings = array();
785 endif;
786
787 // Next, get the settings for this particular feed.
788 $custom_settings = $this->setting('postmeta', NULL, NULL);
789 if ($custom_settings and !is_array($custom_settings)) :
790 $custom_settings = unserialize($custom_settings);
791 endif;
792 if (!is_array($custom_settings)) :
793 $custom_settings = array();
794 endif;
795
796 $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
797 $this->postmeta[/*parsed=*/ true] = array();
798
799 // Now, run through and parse them all.
800 foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
801 $meta = apply_filters("syndicated_link_post_meta_${key}_pre", $meta, $this);
802 $this->postmeta[/*parsed=*/ false][$key] = $meta;
803 $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
804 endforeach;
805 endif;
806
807 $ret = $this->postmeta[!!$params['parsed']];
808 if (is_string($params['field'])) :
809 $ret = $ret[$params['field']];
810 endif;
811 return $ret;
812 } /* SyndicatedLink::postmeta () */
813
814 function property_cascade ($fromFeed, $link_field, $setting, $method) {
815 $value = NULL;
816 if ($fromFeed) :
817 $value = $this->setting($setting, NULL, NULL, NULL);
818
819 $s = $this->simplepie;
820 $callable = (is_object($s) and method_exists($s, $method));
821 if (is_null($value) and $callable) :
822 $fallback = $s->{$method}();
823 endif;
824 else :
825 $value = $this->link->{$link_field};
826 endif;
827 return $value;
828 } /* SyndicatedLink::property_cascade () */
829
830 function homepage ($fromFeed = true) {
831 return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
832 } /* SyndicatedLink::homepage () */
833
834 function name ($fromFeed = true) {
835 return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
836 } /* SyndicatedLink::name () */
837
838 function guid () {
839 $ret = $this->setting('feed/id', NULL, $this->uri());
840
841 // If we can get it live from the feed, do so.
842 if (is_object($this->simplepie)) :
843 $search = array(
844 array('', 'id'),
845 array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
846 array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
847 array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
848 array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
849 array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
850 );
851
852 foreach ($search as $pair) :
853 if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
854 $ret = $id_tags[0]['data'];
855 break;
856 elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
857 $ret = $id_tags[0]['data'];
858 break;
859 endif;
860 endforeach;
861 endif;
862 return $ret;
863 }
864
865 function links ($params = array()) {
866 $params = wp_parse_args($params, array(
867 "rel" => NULL,
868 ));
869
870 $fLinks = array();
871 $search = array(
872 array('', 'link'),
873 array(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'),
874 array(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'),
875 );
876
877 foreach ($search as $pair) :
878 if ($link_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
879 $fLinks = array_merge($fLinks, $link_tags);
880 endif;
881 if ($link_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
882 $fLinks = array_merge($fLinks, $link_tags);
883 endif;
884 endforeach;
885
886 $ret = array();
887 foreach ($fLinks as $link) :
888 $filter = false;
889 if (!is_null($params['rel'])) :
890 $filter = true;
891
892 if (isset($link['attribs'])) :
893 // Get a list of NSes from the search
894 foreach ($search as $pair) :
895 $ns = $pair[0];
896
897 if (isset($link['attribs'][$ns])
898 and isset($link['attribs'][$ns]['rel'])
899 ) :
900 $rel = strtolower(trim($link['attribs'][$ns]['rel']));
901 $fRel = strtolower(trim($params['rel']));
902
903 if ($rel == $fRel) :
904 $filter = false;
905 endif;
906 endif;
907 endforeach;
908 endif;
909 endif;
910
911 if (!$filter) :
912 $ret[] = $link;
913 endif;
914 endforeach;
915
916 return $ret;
917 }
918
919 function ttl ($return_element = false) {
920 if (is_object($this->magpie)) :
921 $channel = $this->magpie->channel;
922 else :
923 $channel = array();
924 endif;
925
926 if (isset($channel['ttl'])) :
927 // "ttl stands for time to live. It's a number of
928 // minutes that indicates how long a channel can be
929 // cached before refreshing from the source."
930 // <http://blogs.law.harvard.edu/tech/rss#ltttlgtSubelementOfLtchannelgt>
931 $xml = 'rss:ttl';
932 $ret = $channel['ttl'];
933 elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
934 $period_minutes = array (
935 'hourly' => 60, /* minutes in an hour */
936 'daily' => 1440, /* minutes in a day */
937 'weekly' => 10080, /* minutes in a week */
938 'monthly' => 43200, /* minutes in a month */
939 'yearly' => 525600, /* minutes in a year */
940 );
941
942 // "sy:updatePeriod: Describes the period over which the
943 // channel format is updated. Acceptable values are:
944 // hourly, daily, weekly, monthly, yearly. If omitted,
945 // daily is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
946 if (isset($channel['sy']['updateperiod'])) : $period = $channel['sy']['updateperiod'];
947 else : $period = 'daily';
948 endif;
949
950 // "sy:updateFrequency: Used to describe the frequency
951 // of updates in relation to the update period. A
952 // positive integer indicates how many times in that
953 // period the channel is updated. ... If omitted a value
954 // of 1 is assumed." <http://web.resource.org/rss/1.0/modules/syndication/>
955 if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
956 else : $freq = 1;
957 endif;
958
959 $xml = 'sy:updateFrequency';
960 $ret = (int) ($period_minutes[$period] / $freq);
961 else :
962 $xml = NULL;
963 $ret = NULL;
964 endif;
965
966 if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
967 $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
968
969 if ($min > $ret) :
970 $ret = NULL;
971 endif;
972 endif;
973 return ($return_element ? array($ret, $xml) : $ret);
974 } /* SyndicatedLink::ttl() */
975
976 function automatic_ttl () {
977 // spread out over a time interval for staggered updates
978 $updateWindow = $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
979 if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
980 $updateWindow = DEFAULT_UPDATE_PERIOD;
981 endif;
982
983 // We get a fudge of 1/3 of window from elsewhere. We'll do some more
984 // fudging here.
985 $fudgedInterval = $updateWindow+rand(-($updateWindow/6), 5*($updateWindow/12));
986 return apply_filters('syndicated_feed_automatic_ttl', $fudgedInterval, $this);
987 } /* SyndicatedLink::automatic_ttl () */
988
989 // SyndicatedLink::flatten_array (): flatten an array. Useful for
990 // hierarchical and namespaced elements.
991 //
992 // Given an array which may contain array or object elements in it,
993 // return a "flattened" array: a one-dimensional array of scalars
994 // containing each of the scalar elements contained within the array
995 // structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
996 // returned array for FeedWordPress::flatten_array($a) will contain a key
997 // $a['feed/b/c/d'] with value 'e'.
998 function flatten_array ($arr, $prefix = 'feed/', $separator = '/') {
999 $ret = array ();
1000 if (is_array($arr)) :
1001 foreach ($arr as $key => $value) :
1002 if (is_scalar($value)) :
1003 $ret[$prefix.$key] = $value;
1004 else :
1005 $ret = array_merge($ret, $this->flatten_array($value, $prefix.$key.$separator, $separator));
1006 endif;
1007 endforeach;
1008 endif;
1009 return $ret;
1010 } /* SyndicatedLink::flatten_array () */
1011
1012 function hardcode ($what) {
1013
1014 $ret = $this->setting('hardcode '.$what, 'hardcode_'.$what, NULL);
1015
1016 if ('yes' == $ret) :
1017 $ret = true;
1018 else :
1019 $ret = false;
1020 endif;
1021 return $ret;
1022 } /* SyndicatedLink::hardcode () */
1023
1024 function syndicated_status ($what, $default, $fallback = true) {
1025 global $wpdb;
1026
1027 $g_set = ($fallback ? 'syndicated_' . $what . '_status' : NULL);
1028 $ret = $this->setting($what.' status', $g_set, $default);
1029
1030 return esc_sql(trim(strtolower($ret)));
1031 } /* SyndicatedLink:syndicated_status () */
1032
1033 function taxonomies () {
1034 $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
1035 return get_object_taxonomies(array('object_type' => $post_type), 'names');
1036 } /* SyndicatedLink::taxonomies () */
1037
1038 /**
1039 * category_ids: look up (and create) category ids from a list of
1040 * categories
1041 *
1042 * @param array $cats
1043 * @param string $unfamiliar_category
1044 * @param array|null $taxonomies
1045 * @return array
1046 */
1047 function category_ids ($post, $cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
1048 $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
1049 $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
1050
1051 $catTax = 'category';
1052
1053 if (is_null($taxonomies)) :
1054 $taxonomies = array('category');
1055 endif;
1056
1057 // We need to normalize whitespace because (1) trailing
1058 // whitespace can cause PHP and MySQL not to see eye to eye on
1059 // VARCHAR comparisons for some versions of MySQL (cf.
1060 // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1061 // because I doubt most people want to make a semantic
1062 // distinction between 'Computers' and 'Computers '
1063 $cats = array_map('trim', $cats);
1064
1065 $terms = array();
1066 foreach ($taxonomies as $tax) :
1067 $terms[$tax] = array();
1068 endforeach;
1069
1070 foreach ($cats as $cat_name) :
1071 if (strlen(trim($cat_name)) < 1) :
1072 continue;
1073 endif;
1074
1075 $oTerm = new SyndicatedPostTerm($cat_name, $taxonomies, $post);
1076
1077 if ($oTerm->is_familiar()) :
1078
1079 $tax = $oTerm->taxonomy();
1080 if (!isset($terms[$tax])) :
1081 $terms[$tax] = array();
1082 endif;
1083 $terms[$tax][] = $oTerm->id();
1084
1085 else :
1086
1087 if ('tag'==$unfamiliar_category) :
1088 $unfamiliar_category = 'create:post_tag';
1089 endif;
1090
1091 if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
1092 $tax = $catTax; // Default
1093
1094 if (isset($ref[2])
1095 and strlen($ref[2]) > 2) :
1096 $tax = $ref[2];
1097 endif;
1098
1099 $inserted = $oTerm->insert($tax);
1100 if (!is_null($inserted)) :
1101 if (!isset($terms[$tax])) :
1102 $terms[$tax] = array();
1103 endif;
1104 $terms[$tax][] = $inserted;
1105 else :
1106
1107 endif; // !is_null($inserted)
1108 endif; // preg_match(...)
1109
1110 endif; /* ($oTerm->is_familiar()) */
1111 endforeach;
1112
1113 $filtersOn = $allowFilters;
1114 if ($allowFilters) :
1115 $filters = array_filter(
1116 $this->setting('match/filter', 'match_filter', array()),
1117 'remove_dummy_zero'
1118 );
1119 $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
1120 endif;
1121
1122 // Check for filter conditions
1123 foreach ($terms as $tax => $term_ids) :
1124 if ($filtersOn
1125 and (count($term_ids)==0)
1126 and in_array($tax, $filters)) :
1127 $terms = NULL; // Drop the post
1128 break;
1129 else :
1130 $terms[$tax] = array_unique($term_ids);
1131 endif;
1132 endforeach;
1133
1134 if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
1135 $terms = end($terms);
1136 endif;
1137
1138 FeedWordPress::diagnostic(
1139 'syndicated_posts:categories',
1140 'Category: MAPPED term names '.json_encode($cats).' to IDs: '.json_encode($terms)
1141 );
1142 return $terms;
1143 } /* SyndicatedLink::category_ids () */
1144 } // class SyndicatedLink
1145
1146