| @@ -1,15 +1,23 @@ | ||
| 1 | 1 | <?php |
| 2 | -################################################################################ | |
| 3 | -## class FeedFinder: find likely feeds using autodetection and/or guesswork #### | |
| 4 | -################################################################################ | |
| 2 | +/** | |
| 3 | + * class FeedFinder: find likely feeds using autodetection and/or guesswork | |
| 4 | + * @version 2010.0622 | |
| 5 | + * @uses SimplePie_Misc | |
| 6 | + */ | |
| 5 | 7 | |
| 8 | +if (!class_exists('SimplePie')) : | |
| 9 | + require_once(ABSPATH . WPINC . '/class-simplepie.php'); | |
| 10 | +endif; | |
| 11 | +require_once(dirname(__FILE__).'/feedwordpresshtml.class.php'); | |
| 12 | + | |
| 6 | 13 | class FeedFinder { |
| 7 | 14 | var $uri = NULL; |
| 8 | 15 | var $_cache_uri = NULL; |
| 9 | 16 | |
| 10 | 17 | var $verify = FALSE; |
| 11 | - | |
| 18 | + var $fallbacks = 3; | |
| 19 | + | |
| 12 | 20 | var $_response = NULL; |
| 13 | 21 | var $_data = NULL; |
| 14 | 22 | var $_error = NULL; |
| 15 | 23 | var $_head = NULL; |
| @@ -23,61 +31,86 @@ | ||
| 23 | 31 | 'application/x-atom+xml' |
| 24 | 32 | ); |
| 25 | 33 | var $_feed_markers = array('\\<feed', '\\<rss', 'xmlns="http://purl.org/rss/1.0'); |
| 26 | 34 | var $_html_markers = array('\\<html'); |
| 35 | + var $_opml_markers = array('\\<opml', '\\<outline'); | |
| 27 | 36 | var $_obvious_feed_url = array('[./]rss', '[./]rdf', '[./]atom', '[./]feed', '\.xml'); |
| 28 | 37 | var $_maybe_feed_url = array ('rss', 'rdf', 'atom', 'feed', 'xml'); |
| 29 | 38 | |
| 30 | - function FeedFinder ($uri = NULL, $verify = TRUE) { | |
| 39 | + function FeedFinder ($uri = NULL, $verify = TRUE, $fallbacks = 3) { | |
| 31 | 40 | $this->uri = $uri; $this->verify = $verify; |
| 41 | + $this->fallbacks = $fallbacks; | |
| 32 | 42 | } /* FeedFinder::FeedFinder () */ |
| 33 | 43 | |
| 34 | 44 | function find ($uri = NULL) { |
| 35 | 45 | $ret = array (); |
| 36 | - if (!is_null($this->data($uri))) { | |
| 37 | - if ($this->is_feed($uri)) { | |
| 38 | - $ret = array($this->uri); | |
| 39 | - } else { | |
| 40 | - // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?) | |
| 41 | - // Autodiscovery is the preferred method | |
| 42 | - $href = $this->_link_rel_feeds(); | |
| 43 | - | |
| 44 | - // ... but we'll also take the little orange buttons | |
| 45 | - $href = array_merge($href, $this->_a_href_feeds(TRUE)); | |
| 46 | - | |
| 47 | - // If all that failed, look harder | |
| 48 | - if (count($href) == 0) $href = $this->_a_href_feeds(FALSE); | |
| 46 | + if (!is_null($this->data($uri))) : | |
| 47 | + if ($this->is_opml($uri)) : | |
| 48 | + $href = $this->_opml_rss_uris(); | |
| 49 | + else : | |
| 50 | + if ($this->is_feed($uri)) : | |
| 51 | + $href = array($this->uri); | |
| 52 | + else : | |
| 53 | + // Assume that we have HTML or XHTML (even if we don't, who's it gonna hurt?) | |
| 54 | + // Autodiscovery is the preferred method | |
| 55 | + $href = $this->_link_rel_feeds(); | |
| 56 | + | |
| 57 | + // ... but we'll also take the little orange buttons | |
| 58 | + if ($this->fallbacks > 0) : | |
| 59 | + $href = array_merge($href, $this->_a_href_feeds(TRUE)); | |
| 60 | + endif; | |
| 61 | + | |
| 62 | + // If all that failed, look harder | |
| 63 | + if ($this->fallbacks > 1) : | |
| 64 | + if (count($href) == 0) : | |
| 65 | + $href = $this->_a_href_feeds(FALSE); | |
| 66 | + endif; | |
| 67 | + endif; | |
| 68 | + | |
| 69 | + // Our search may turn up duplicate URIs. We only need to do any given URI once. | |
| 70 | + // Props to Camilo <http://projects.radgeek.com/2008/12/14/feedwordpress-20081214/#comment-20090122160414> | |
| 71 | + $href = array_unique($href); | |
| 72 | + endif; | |
| 49 | 73 | |
| 50 | - // Our search may turn up duplicate URIs. We only need to do any given URI once. | |
| 51 | - // Props to Camilo <http://projects.radgeek.com/2008/12/14/feedwordpress-20081214/#comment-20090122160414> | |
| 52 | - $href = array_unique($href); | |
| 74 | + // Try some clever URL little tricks before we go | |
| 75 | + if ($this->fallbacks > 2) : | |
| 76 | + $href = array_merge($href, $this->_url_manipulation_feeds()); | |
| 77 | + endif; | |
| 78 | + endif; | |
| 79 | + | |
| 80 | + $href = array_unique($href); | |
| 53 | 81 | |
| 54 | - // Verify feeds and resolve relative URIs | |
| 55 | - foreach ($href as $u) { | |
| 56 | - $the_uri = Relative_URI::resolve($u, $this->uri); | |
| 57 | - if ($this->verify) { | |
| 58 | - $feed =& new FeedFinder($the_uri); | |
| 59 | - if ($feed->is_feed()) $ret[] = $the_uri; | |
| 60 | - $feed = NULL; | |
| 61 | - } else { | |
| 62 | - $ret[] = $the_uri; | |
| 63 | - } | |
| 64 | - } /* foreach */ | |
| 65 | - } /* if */ | |
| 66 | - } /* if */ | |
| 67 | - return array_unique($ret); | |
| 82 | + // Verify feeds and resolve relative URIs | |
| 83 | + foreach ($href as $u) : | |
| 84 | + $the_uri = SimplePie_Misc::absolutize_url($u, $this->uri); | |
| 85 | + if ($this->verify and ($u != $this->uri and $the_uri != $this->uri)) : | |
| 86 | + $feed = new FeedFinder($the_uri); | |
| 87 | + if ($feed->is_feed()) : $ret[] = $the_uri; endif; | |
| 88 | + unset($feed); | |
| 89 | + else : | |
| 90 | + $ret[] = $the_uri; | |
| 91 | + endif; | |
| 92 | + endforeach; | |
| 93 | + endif; | |
| 94 | + | |
| 95 | + return array_values($ret); | |
| 68 | 96 | } /* FeedFinder::find () */ |
| 69 | 97 | |
| 70 | 98 | function data ($uri = NULL) { |
| 71 | 99 | $this->_get($uri); |
| 72 | 100 | return $this->_data; |
| 73 | - } | |
| 101 | + } /* FeedFinder::data () */ | |
| 74 | 102 | |
| 103 | + function upload_data ($data) { | |
| 104 | + $this->uri = 'tag:localhost'; | |
| 105 | + $this->_data = $data; | |
| 106 | + } /* FeedFinder::upload_data () */ | |
| 107 | + | |
| 75 | 108 | function status ($uri = NULL) { |
| 76 | 109 | $this->_get($uri); |
| 77 | 110 | |
| 78 | - if (isset($this->_response->status)) : | |
| 79 | - $ret = $this->_response->status; | |
| 111 | + if (!is_wp_error($this->_response) and isset($this->_response['response']['code'])) : | |
| 112 | + $ret = $this->_response['response']['code']; | |
| 80 | 113 | else : |
| 81 | 114 | $ret = NULL; |
| 82 | 115 | endif; |
| 83 | 116 | return $ret; |
| @@ -82,10 +115,18 @@ | ||
| 82 | 115 | endif; |
| 83 | 116 | return $ret; |
| 84 | 117 | } |
| 85 | 118 | |
| 86 | - function error () { | |
| 87 | - return $this->_error; | |
| 119 | + function error ($index = NULL) { | |
| 120 | + $message = NULL; | |
| 121 | + if (count($this->_error) > 0) : | |
| 122 | + if (is_scalar($index) and !is_null($index)) : | |
| 123 | + $message = $this->_error[$index]; | |
| 124 | + else : | |
| 125 | + $message = implode(" / ", $this->_error)."\n"; | |
| 126 | + endif; | |
| 127 | + endif; | |
| 128 | + return $message; | |
| 88 | 129 | } |
| 89 | 130 | |
| 90 | 131 | function is_feed ($uri = NULL) { |
| 91 | 132 | $data = $this->data($uri); |
| @@ -100,27 +141,42 @@ | ||
| 100 | 141 | ) |
| 101 | 142 | ); |
| 102 | 143 | } /* FeedFinder::is_feed () */ |
| 103 | 144 | |
| 145 | + function is_opml ($uri = NULL) { | |
| 146 | + $data = $this->data($uri); | |
| 147 | + return ( | |
| 148 | + preg_match ( | |
| 149 | + "\007(".implode('|',$this->_opml_markers).")\007i", | |
| 150 | + $data | |
| 151 | + ) | |
| 152 | + ); | |
| 153 | + } /* FeedFinder::is_opml () */ | |
| 154 | + | |
| 104 | 155 | # --- Private methods --- |
| 105 | 156 | function _get ($uri = NULL) { |
| 106 | 157 | if ($uri) $this->uri = $uri; |
| 107 | 158 | |
| 108 | 159 | // Is the result not yet cached? |
| 109 | - if ($this->_cache_uri !== $this->uri) : | |
| 160 | + if ($this->uri != 'tag:localhost' and $this->_cache_uri !== $this->uri) : | |
| 110 | 161 | $headers['Connection'] = 'close'; |
| 111 | 162 | $headers['Accept'] = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*'; |
| 112 | 163 | $headers['User-Agent'] = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress'; |
| 113 | 164 | |
| 114 | - // Use function provided by MagpieRSS package | |
| 115 | - $client = _fetch_remote_file($this->uri, $headers); | |
| 116 | - if (isset($client->error)) : | |
| 117 | - $this->_error = $client->error; | |
| 165 | + // Use WordPress API function | |
| 166 | + $client = wp_remote_request($this->uri, array( | |
| 167 | + 'headers' => $headers, | |
| 168 | + 'timeout' => FeedWordPress::fetch_timeout(), | |
| 169 | + )); | |
| 170 | + | |
| 171 | + $this->_response = $client; | |
| 172 | + if (is_wp_error($client)) : | |
| 173 | + $this->_data = NULL; | |
| 174 | + $this->_error = $client->get_error_messages(); | |
| 118 | 175 | else : |
| 176 | + $this->_data = $client['body']; | |
| 119 | 177 | $this->_error = NULL; |
| 120 | 178 | endif; |
| 121 | - $this->_response = $client; | |
| 122 | - $this->_data = $client->results; | |
| 123 | 179 | |
| 124 | 180 | // Kilroy was here |
| 125 | 181 | $this->_cache_uri = $this->uri; |
| 126 | 182 | endif; |
| @@ -125,8 +181,25 @@ | ||
| 125 | 181 | $this->_cache_uri = $this->uri; |
| 126 | 182 | endif; |
| 127 | 183 | } /* FeedFinder::_get () */ |
| 128 | 184 | |
| 185 | + function _opml_rss_uris () { | |
| 186 | + // Really we should parse the XML and use the structure to | |
| 187 | + // return something intelligent to programs that want to use it | |
| 188 | + // Oh babe! maybe some day... | |
| 189 | + | |
| 190 | + $opml = $this->data(); | |
| 191 | + | |
| 192 | + $rx = FeedWordPressHTML::attributeRegex('outline', 'xmlUrl'); | |
| 193 | + if (preg_match_all($rx, $opml, $matches, PREG_SET_ORDER)) : | |
| 194 | + foreach ($matches as $m) : | |
| 195 | + $match = FeedWordPressHTML::attributeMatch($m); | |
| 196 | + $r[] = $match['value']; | |
| 197 | + endforeach; | |
| 198 | + endif; | |
| 199 | + return $r; | |
| 200 | + } /* FeedFinder::_opml_rss_uris () */ | |
| 201 | + | |
| 129 | 202 | function _link_rel_feeds () { |
| 130 | 203 | $links = $this->_tags('link'); |
| 131 | 204 | $link_count = count($links); |
| 132 | 205 | |
| @@ -139,9 +212,9 @@ | ||
| 139 | 212 | } /* if */ |
| 140 | 213 | } /* if */ |
| 141 | 214 | } /* for */ |
| 142 | 215 | return $href; |
| 143 | - } | |
| 216 | + } /* FeedFinder::_link_rel_feeds () */ | |
| 144 | 217 | |
| 145 | 218 | function _a_href_feeds ($obvious = TRUE) { |
| 146 | 219 | $pattern = ($obvious ? $this->_obvious_feed_url : $this->_maybe_feed_url); |
| 147 | 220 | |
| @@ -155,10 +228,65 @@ | ||
| 155 | 228 | $href[] = $links[$n]['href']; |
| 156 | 229 | } /* if */ |
| 157 | 230 | } /* for */ |
| 158 | 231 | return $href; |
| 159 | - } | |
| 232 | + } /* FeedFinder::_link_a_href_feeds () */ | |
| 160 | 233 | |
| 234 | + function _url_manipulation_feeds () { | |
| 235 | + $href = array(); | |
| 236 | + | |
| 237 | + // check for HTTP GET parameters that look feed-like. | |
| 238 | + $bits = parse_url($this->uri); | |
| 239 | + foreach (array('rss', 'rss2', 'atom', 'rdf') as $format) : | |
| 240 | + if (isset($bits['query']) and (strlen($bits['query']) > 0)) : | |
| 241 | + $newQuery = preg_replace('/([?&=])(rss2?|atom|rdf)/i', '$1'.$format, $bits['query']); | |
| 242 | + else : | |
| 243 | + $newQuery = NULL; | |
| 244 | + endif; | |
| 245 | + | |
| 246 | + if (isset($bits['path']) and (strlen($bits['path']) > 0)) : | |
| 247 | + $newPath = preg_replace('!([/.])(rss2?|atom|rdf)!i', '$1'.$format, $bits['path']); | |
| 248 | + else : | |
| 249 | + $newPath = NULL; | |
| 250 | + endif; | |
| 251 | + | |
| 252 | + // Reassemble, check and return | |
| 253 | + $credentials = ''; | |
| 254 | + if (isset($bits['user'])) : | |
| 255 | + $credentials = $bits['user']; | |
| 256 | + if (isset($bits['pass'])) : | |
| 257 | + $credentials .= ':'.$bits['pass']; | |
| 258 | + endif; | |
| 259 | + $credentials .= '@'; | |
| 260 | + endif; | |
| 261 | + | |
| 262 | + // Variations on a theme | |
| 263 | + $newUrl[0] = ('' | |
| 264 | + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') | |
| 265 | + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') | |
| 266 | + .(!is_null($newPath) ? $newPath : '') | |
| 267 | + .(!is_null($newQuery) ? '?'.$newQuery : '') | |
| 268 | + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') | |
| 269 | + ); | |
| 270 | + $newUrl[1] = ('' | |
| 271 | + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') | |
| 272 | + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') | |
| 273 | + .(!is_null($newPath) ? $newPath : '') | |
| 274 | + .(isset($bits['query']) ? '?'.$bits['query'] : '') | |
| 275 | + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') | |
| 276 | + ); | |
| 277 | + $newUrl[2] = ('' | |
| 278 | + .(isset($bits['scheme']) ? $bits['scheme'].':' : '') | |
| 279 | + .(isset($bits['host']) ? '//'.$credentials.$bits['host'] : '') | |
| 280 | + .(isset($bits['path']) ? $bits['path'] : '') | |
| 281 | + .(!is_null($newQuery) ? '?'.$newQuery : '') | |
| 282 | + .(isset($bits['fragment']) ? '#'.$bits['fragment'] : '') | |
| 283 | + ); | |
| 284 | + $href = array_merge($href, $newUrl); | |
| 285 | + endforeach; | |
| 286 | + return array_unique($href); | |
| 287 | + } /* FeedFinder::_url_manipulation_feeds () */ | |
| 288 | + | |
| 161 | 289 | function _tags ($tag) { |
| 162 | 290 | $html = $this->data(); |
| 163 | 291 | |
| 164 | 292 | // search through the HTML, save all <link> tags |
| @@ -178,7 +306,7 @@ | ||
| 178 | 306 | } /* foreach */ |
| 179 | 307 | $ret[$n] = $final_link; |
| 180 | 308 | } /* for */ |
| 181 | 309 | return $ret; |
| 182 | - } | |
| 310 | + } /* FeedFinder::_tags () */ | |
| 183 | 311 | } /* class FeedFinder */ |
| 184 | 312 | |