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

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