PluginProbe
FeedWordPress / 2020.0118
FeedWordPress v2020.0118
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 2020.0118, at syndicatedlink.class.php

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