diff options
Diffstat (limited to 'inc/3rdparty/makefulltextfeed.php')
-rwxr-xr-x | inc/3rdparty/makefulltextfeed.php | 912 |
1 files changed, 0 insertions, 912 deletions
diff --git a/inc/3rdparty/makefulltextfeed.php b/inc/3rdparty/makefulltextfeed.php deleted file mode 100755 index 27a62d73..00000000 --- a/inc/3rdparty/makefulltextfeed.php +++ /dev/null | |||
@@ -1,912 +0,0 @@ | |||
1 | <?php | ||
2 | // Full-Text RSS: Create Full-Text Feeds | ||
3 | // Author: Keyvan Minoukadeh | ||
4 | // Copyright (c) 2013 Keyvan Minoukadeh | ||
5 | // License: AGPLv3 | ||
6 | // Version: 3.2 | ||
7 | // Date: 2013-05-13 | ||
8 | // More info: http://fivefilters.org/content-only/ | ||
9 | // Help: http://help.fivefilters.org | ||
10 | |||
11 | /* | ||
12 | This program is free software: you can redistribute it and/or modify | ||
13 | it under the terms of the GNU Affero General Public License as published by | ||
14 | the Free Software Foundation, either version 3 of the License, or | ||
15 | (at your option) any later version. | ||
16 | |||
17 | This program is distributed in the hope that it will be useful, | ||
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | GNU Affero General Public License for more details. | ||
21 | |||
22 | You should have received a copy of the GNU Affero General Public License | ||
23 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
24 | */ | ||
25 | |||
26 | // Usage | ||
27 | // ----- | ||
28 | // Request this file passing it a web page or feed URL in the querystring: makefulltextfeed.php?url=example.org/article | ||
29 | // For more request parameters, see http://help.fivefilters.org/customer/portal/articles/226660-usage | ||
30 | |||
31 | //error_reporting(E_ALL ^ E_NOTICE); | ||
32 | ini_set("display_errors", 1); | ||
33 | @set_time_limit(120); | ||
34 | libxml_use_internal_errors(true); | ||
35 | |||
36 | |||
37 | // Deal with magic quotes | ||
38 | if (get_magic_quotes_gpc()) { | ||
39 | $process = array(&$_GET, &$_POST, &$_REQUEST); | ||
40 | while (list($key, $val) = each($process)) { | ||
41 | foreach ($val as $k => $v) { | ||
42 | unset($process[$key][$k]); | ||
43 | if (is_array($v)) { | ||
44 | $process[$key][stripslashes($k)] = $v; | ||
45 | $process[] = &$process[$key][stripslashes($k)]; | ||
46 | } else { | ||
47 | $process[$key][stripslashes($k)] = stripslashes($v); | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | unset($process); | ||
52 | } | ||
53 | |||
54 | // set include path | ||
55 | set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); | ||
56 | |||
57 | require_once dirname(__FILE__).'/makefulltextfeedHelpers.php'; | ||
58 | |||
59 | //////////////////////////////// | ||
60 | // Load config file | ||
61 | //////////////////////////////// | ||
62 | require dirname(__FILE__).'/config.php'; | ||
63 | |||
64 | //////////////////////////////// | ||
65 | // Prevent indexing/following by search engines because: | ||
66 | // 1. The content is already public and presumably indexed (why create duplicates?) | ||
67 | // 2. Not doing so might increase number of requests from search engines, thus increasing server load | ||
68 | // Note: feed readers and services such as Yahoo Pipes will not be affected by this header. | ||
69 | // Note: Using Disallow in a robots.txt file will be more effective (search engines will check | ||
70 | // that before even requesting makefulltextfeed.php). | ||
71 | //////////////////////////////// | ||
72 | header('X-Robots-Tag: noindex, nofollow'); | ||
73 | |||
74 | //////////////////////////////// | ||
75 | // Check if service is enabled | ||
76 | //////////////////////////////// | ||
77 | if (!$options->enabled) { | ||
78 | die('The full-text RSS service is currently disabled'); | ||
79 | } | ||
80 | |||
81 | //////////////////////////////// | ||
82 | // Debug mode? | ||
83 | // See the config file for debug options. | ||
84 | //////////////////////////////// | ||
85 | $debug_mode = false; | ||
86 | if (isset($_GET['debug'])) { | ||
87 | if ($options->debug === true || $options->debug == 'user') { | ||
88 | $debug_mode = true; | ||
89 | } elseif ($options->debug == 'admin') { | ||
90 | session_start(); | ||
91 | $debug_mode = (@$_SESSION['auth'] == 1); | ||
92 | } | ||
93 | if ($debug_mode) { | ||
94 | header('Content-Type: text/plain; charset=utf-8'); | ||
95 | } else { | ||
96 | if ($options->debug == 'admin') { | ||
97 | die('You must be logged in to the <a href="admin/">admin area</a> to see debug output.'); | ||
98 | } else { | ||
99 | die('Debugging is disabled.'); | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | |||
104 | //////////////////////////////// | ||
105 | // Check for APC | ||
106 | //////////////////////////////// | ||
107 | $options->apc = $options->apc && function_exists('apc_add'); | ||
108 | if ($options->apc) { | ||
109 | debug('APC is enabled and available on server'); | ||
110 | } else { | ||
111 | debug('APC is disabled or not available on server'); | ||
112 | } | ||
113 | |||
114 | //////////////////////////////// | ||
115 | // Check for smart cache | ||
116 | //////////////////////////////// | ||
117 | $options->smart_cache = $options->smart_cache && function_exists('apc_inc'); | ||
118 | |||
119 | //////////////////////////////// | ||
120 | // Check for feed URL | ||
121 | //////////////////////////////// | ||
122 | if (!isset($_GET['url'])) { | ||
123 | die('No URL supplied'); | ||
124 | } | ||
125 | $url = trim($_GET['url']); | ||
126 | if (strtolower(substr($url, 0, 7)) == 'feed://') { | ||
127 | $url = 'http://'.substr($url, 7); | ||
128 | } | ||
129 | if (!preg_match('!^https?://.+!i', $url)) { | ||
130 | $url = 'http://'.$url; | ||
131 | } | ||
132 | |||
133 | $url = filter_var($url, FILTER_SANITIZE_URL); | ||
134 | $test = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); | ||
135 | // deal with bug http://bugs.php.net/51192 (present in PHP 5.2.13 and PHP 5.3.2) | ||
136 | if ($test === false) { | ||
137 | $test = filter_var(strtr($url, '-', '_'), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED); | ||
138 | } | ||
139 | if ($test !== false && $test !== null && preg_match('!^https?://!', $url)) { | ||
140 | // all okay | ||
141 | unset($test); | ||
142 | } else { | ||
143 | die('Invalid URL supplied'); | ||
144 | } | ||
145 | debug("Supplied URL: $url"); | ||
146 | |||
147 | ///////////////////////////////// | ||
148 | // Redirect to hide API key | ||
149 | ///////////////////////////////// | ||
150 | if (isset($_GET['key']) && ($key_index = array_search($_GET['key'], $options->api_keys)) !== false) { | ||
151 | $host = $_SERVER['HTTP_HOST']; | ||
152 | $path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\'); | ||
153 | $_qs_url = (strtolower(substr($url, 0, 7)) == 'http://') ? substr($url, 7) : $url; | ||
154 | $redirect = 'http://'.htmlspecialchars($host.$path).'/makefulltextfeed.php?url='.urlencode($_qs_url); | ||
155 | $redirect .= '&key='.$key_index; | ||
156 | $redirect .= '&hash='.urlencode(sha1($_GET['key'].$url)); | ||
157 | if (isset($_GET['html'])) $redirect .= '&html='.urlencode($_GET['html']); | ||
158 | if (isset($_GET['max'])) $redirect .= '&max='.(int)$_GET['max']; | ||
159 | if (isset($_GET['links'])) $redirect .= '&links='.urlencode($_GET['links']); | ||
160 | if (isset($_GET['exc'])) $redirect .= '&exc='.urlencode($_GET['exc']); | ||
161 | if (isset($_GET['format'])) $redirect .= '&format='.urlencode($_GET['format']); | ||
162 | if (isset($_GET['callback'])) $redirect .= '&callback='.urlencode($_GET['callback']); | ||
163 | if (isset($_GET['l'])) $redirect .= '&l='.urlencode($_GET['l']); | ||
164 | if (isset($_GET['xss'])) $redirect .= '&xss'; | ||
165 | if (isset($_GET['use_extracted_title'])) $redirect .= '&use_extracted_title'; | ||
166 | if (isset($_GET['content'])) $redirect .= '&content='.urlencode($_GET['content']); | ||
167 | if (isset($_GET['summary'])) $redirect .= '&summary='.urlencode($_GET['summary']); | ||
168 | if (isset($_GET['debug'])) $redirect .= '&debug'; | ||
169 | if ($debug_mode) { | ||
170 | debug('Redirecting to hide access key, follow URL below to continue'); | ||
171 | debug("Location: $redirect"); | ||
172 | } else { | ||
173 | header("Location: $redirect"); | ||
174 | } | ||
175 | exit; | ||
176 | } | ||
177 | |||
178 | /////////////////////////////////////////////// | ||
179 | // Set timezone. | ||
180 | // Prevents warnings, but needs more testing - | ||
181 | // perhaps if timezone is set in php.ini we | ||
182 | // don't need to set it at all... | ||
183 | /////////////////////////////////////////////// | ||
184 | if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timezone'))) { | ||
185 | date_default_timezone_set('UTC'); | ||
186 | } | ||
187 | |||
188 | /////////////////////////////////////////////// | ||
189 | // Check if the request is explicitly for an HTML page | ||
190 | /////////////////////////////////////////////// | ||
191 | $html_only = (isset($_GET['html']) && ($_GET['html'] == '1' || $_GET['html'] == 'true')); | ||
192 | |||
193 | /////////////////////////////////////////////// | ||
194 | // Check if valid key supplied | ||
195 | /////////////////////////////////////////////// | ||
196 | $valid_key = false; | ||
197 | if (isset($_GET['key']) && isset($_GET['hash']) && isset($options->api_keys[(int)$_GET['key']])) { | ||
198 | $valid_key = ($_GET['hash'] == sha1($options->api_keys[(int)$_GET['key']].$url)); | ||
199 | } | ||
200 | $key_index = ($valid_key) ? (int)$_GET['key'] : 0; | ||
201 | if (!$valid_key && $options->key_required) { | ||
202 | die('A valid key must be supplied'); | ||
203 | } | ||
204 | if (!$valid_key && isset($_GET['key']) && $_GET['key'] != '') { | ||
205 | die('The entered key is invalid'); | ||
206 | } | ||
207 | |||
208 | if (file_exists('custom_init.php')) require 'custom_init.php'; | ||
209 | |||
210 | /////////////////////////////////////////////// | ||
211 | // Check URL against list of blacklisted URLs | ||
212 | /////////////////////////////////////////////// | ||
213 | if (!url_allowed($url)) die('URL blocked'); | ||
214 | |||
215 | /////////////////////////////////////////////// | ||
216 | // Max entries | ||
217 | // see config.php to find these values | ||
218 | /////////////////////////////////////////////// | ||
219 | if (isset($_GET['max'])) { | ||
220 | $max = (int)$_GET['max']; | ||
221 | if ($valid_key) { | ||
222 | $max = min($max, $options->max_entries_with_key); | ||
223 | } else { | ||
224 | $max = min($max, $options->max_entries); | ||
225 | } | ||
226 | } else { | ||
227 | if ($valid_key) { | ||
228 | $max = $options->default_entries_with_key; | ||
229 | } else { | ||
230 | $max = $options->default_entries; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | /////////////////////////////////////////////// | ||
235 | // Link handling | ||
236 | /////////////////////////////////////////////// | ||
237 | if (isset($_GET['links']) && in_array($_GET['links'], array('preserve', 'footnotes', 'remove'))) { | ||
238 | $links = $_GET['links']; | ||
239 | } else { | ||
240 | $links = 'preserve'; | ||
241 | } | ||
242 | |||
243 | /////////////////////////////////////////////// | ||
244 | // Favour item titles in feed? | ||
245 | /////////////////////////////////////////////// | ||
246 | $favour_feed_titles = true; | ||
247 | if ($options->favour_feed_titles == 'user') { | ||
248 | $favour_feed_titles = !isset($_GET['use_extracted_title']); | ||
249 | } else { | ||
250 | $favour_feed_titles = $options->favour_feed_titles; | ||
251 | } | ||
252 | |||
253 | /////////////////////////////////////////////// | ||
254 | // Include full content in output? | ||
255 | /////////////////////////////////////////////// | ||
256 | if ($options->content === 'user') { | ||
257 | if (isset($_GET['content']) && $_GET['content'] === '0') { | ||
258 | $options->content = false; | ||
259 | } else { | ||
260 | $options->content = true; | ||
261 | } | ||
262 | } | ||
263 | |||
264 | /////////////////////////////////////////////// | ||
265 | // Include summaries in output? | ||
266 | /////////////////////////////////////////////// | ||
267 | if ($options->summary === 'user') { | ||
268 | if (isset($_GET['summary']) && $_GET['summary'] === '1') { | ||
269 | $options->summary = true; | ||
270 | } else { | ||
271 | $options->summary = false; | ||
272 | } | ||
273 | } | ||
274 | |||
275 | /////////////////////////////////////////////// | ||
276 | // Exclude items if extraction fails | ||
277 | /////////////////////////////////////////////// | ||
278 | if ($options->exclude_items_on_fail === 'user') { | ||
279 | $exclude_on_fail = (isset($_GET['exc']) && ($_GET['exc'] == '1')); | ||
280 | } else { | ||
281 | $exclude_on_fail = $options->exclude_items_on_fail; | ||
282 | } | ||
283 | |||
284 | /////////////////////////////////////////////// | ||
285 | // Detect language | ||
286 | /////////////////////////////////////////////// | ||
287 | if ($options->detect_language === 'user') { | ||
288 | if (isset($_GET['l'])) { | ||
289 | $detect_language = (int)$_GET['l']; | ||
290 | } else { | ||
291 | $detect_language = 1; | ||
292 | } | ||
293 | } else { | ||
294 | $detect_language = $options->detect_language; | ||
295 | } | ||
296 | |||
297 | $use_cld = extension_loaded('cld') && (version_compare(PHP_VERSION, '5.3.0') >= 0); | ||
298 | |||
299 | ///////////////////////////////////// | ||
300 | // Check for valid format | ||
301 | // (stick to RSS (or RSS as JSON) for the time being) | ||
302 | ///////////////////////////////////// | ||
303 | if (isset($_GET['format']) && $_GET['format'] == 'json') { | ||
304 | $format = 'json'; | ||
305 | } else { | ||
306 | $format = 'rss'; | ||
307 | } | ||
308 | |||
309 | ///////////////////////////////////// | ||
310 | // Should we do XSS filtering? | ||
311 | ///////////////////////////////////// | ||
312 | if ($options->xss_filter === 'user') { | ||
313 | $xss_filter = isset($_GET['xss']); | ||
314 | } else { | ||
315 | $xss_filter = $options->xss_filter; | ||
316 | } | ||
317 | if (!$xss_filter && isset($_GET['xss'])) { | ||
318 | die('XSS filtering is disabled in config'); | ||
319 | } | ||
320 | |||
321 | ///////////////////////////////////// | ||
322 | // Check for JSONP | ||
323 | // Regex from https://gist.github.com/1217080 | ||
324 | ///////////////////////////////////// | ||
325 | $callback = null; | ||
326 | if ($format =='json' && isset($_GET['callback'])) { | ||
327 | $callback = trim($_GET['callback']); | ||
328 | foreach (explode('.', $callback) as $_identifier) { | ||
329 | if (!preg_match('/^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/', $_identifier)) { | ||
330 | die('Invalid JSONP callback'); | ||
331 | } | ||
332 | } | ||
333 | debug("JSONP callback: $callback"); | ||
334 | } | ||
335 | |||
336 | ////////////////////////////////// | ||
337 | // Enable Cross-Origin Resource Sharing (CORS) | ||
338 | ////////////////////////////////// | ||
339 | if ($options->cors) header('Access-Control-Allow-Origin: *'); | ||
340 | |||
341 | ////////////////////////////////// | ||
342 | // Check for cached copy | ||
343 | ////////////////////////////////// | ||
344 | if ($options->caching) { | ||
345 | debug('Caching is enabled...'); | ||
346 | $cache_id = md5($max.$url.(int)$valid_key.$links.(int)$favour_feed_titles.(int)$options->content.(int)$options->summary.(int)$xss_filter.(int)$exclude_on_fail.$format.$detect_language.(int)isset($_GET['pubsub'])); | ||
347 | $check_cache = true; | ||
348 | if ($options->apc && $options->smart_cache) { | ||
349 | apc_add("cache.$cache_id", 0, 10*60); | ||
350 | $apc_cache_hits = (int)apc_fetch("cache.$cache_id"); | ||
351 | $check_cache = ($apc_cache_hits >= 2); | ||
352 | apc_inc("cache.$cache_id"); | ||
353 | if ($check_cache) { | ||
354 | debug('Cache key found in APC, we\'ll try to load cache file from disk'); | ||
355 | } else { | ||
356 | debug('Cache key not found in APC'); | ||
357 | } | ||
358 | } | ||
359 | if ($check_cache) { | ||
360 | $cache = get_cache(); | ||
361 | if ($data = $cache->load($cache_id)) { | ||
362 | if ($debug_mode) { | ||
363 | debug('Loaded cached copy'); | ||
364 | exit; | ||
365 | } | ||
366 | if ($format == 'json') { | ||
367 | if ($callback === null) { | ||
368 | header('Content-type: application/json; charset=UTF-8'); | ||
369 | } else { | ||
370 | header('Content-type: application/javascript; charset=UTF-8'); | ||
371 | } | ||
372 | } else { | ||
373 | header('Content-type: text/xml; charset=UTF-8'); | ||
374 | header('X-content-type-options: nosniff'); | ||
375 | } | ||
376 | if (headers_sent()) die('Some data has already been output, can\'t send RSS file'); | ||
377 | if ($callback) { | ||
378 | echo "$callback($data);"; | ||
379 | } else { | ||
380 | echo $data; | ||
381 | } | ||
382 | exit; | ||
383 | } | ||
384 | } | ||
385 | } | ||
386 | |||
387 | ////////////////////////////////// | ||
388 | // Set Expires header | ||
389 | ////////////////////////////////// | ||
390 | if (!$debug_mode) { | ||
391 | header('Expires: ' . gmdate('D, d M Y H:i:s', time()+(60*10)) . ' GMT'); | ||
392 | } | ||
393 | |||
394 | ////////////////////////////////// | ||
395 | // Set up HTTP agent | ||
396 | ////////////////////////////////// | ||
397 | global $http; | ||
398 | $http = new HumbleHttpAgent(); | ||
399 | $http->debug = $debug_mode; | ||
400 | $http->userAgentMap = $options->user_agents; | ||
401 | $http->headerOnlyTypes = array_keys($options->content_type_exc); | ||
402 | $http->rewriteUrls = $options->rewrite_url; | ||
403 | |||
404 | ////////////////////////////////// | ||
405 | // Set up Content Extractor | ||
406 | ////////////////////////////////// | ||
407 | global $extractor; | ||
408 | $extractor = new ContentExtractor(dirname(__FILE__).'/site_config/custom', dirname(__FILE__).'/site_config/standard'); | ||
409 | $extractor->debug = $debug_mode; | ||
410 | SiteConfig::$debug = $debug_mode; | ||
411 | SiteConfig::use_apc($options->apc); | ||
412 | $extractor->fingerprints = $options->fingerprints; | ||
413 | $extractor->allowedParsers = $options->allowed_parsers; | ||
414 | |||
415 | //////////////////////////////// | ||
416 | // Get RSS/Atom feed | ||
417 | //////////////////////////////// | ||
418 | if (!$html_only) { | ||
419 | debug('--------'); | ||
420 | debug("Attempting to process URL as feed"); | ||
421 | // Send user agent header showing PHP (prevents a HTML response from feedburner) | ||
422 | $http->userAgentDefault = HumbleHttpAgent::UA_PHP; | ||
423 | // configure SimplePie HTTP extension class to use our HumbleHttpAgent instance | ||
424 | SimplePie_HumbleHttpAgent::set_agent($http); | ||
425 | $feed = new SimplePie(); | ||
426 | // some feeds use the text/html content type - force_feed tells SimplePie to process anyway | ||
427 | $feed->force_feed(true); | ||
428 | $feed->set_file_class('SimplePie_HumbleHttpAgent'); | ||
429 | //$feed->set_feed_url($url); // colons appearing in the URL's path get encoded | ||
430 | $feed->feed_url = $url; | ||
431 | $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); | ||
432 | $feed->set_timeout(20); | ||
433 | $feed->enable_cache(false); | ||
434 | $feed->set_stupidly_fast(true); | ||
435 | $feed->enable_order_by_date(false); // we don't want to do anything to the feed | ||
436 | $feed->set_url_replacements(array()); | ||
437 | // initialise the feed | ||
438 | // the @ suppresses notices which on some servers causes a 500 internal server error | ||
439 | $result = @$feed->init(); | ||
440 | //$feed->handle_content_type(); | ||
441 | //$feed->get_title(); | ||
442 | if ($result && (!is_array($feed->data) || count($feed->data) == 0)) { | ||
443 | die('Sorry, no feed items found'); | ||
444 | } | ||
445 | // from now on, we'll identify ourselves as a browser | ||
446 | $http->userAgentDefault = HumbleHttpAgent::UA_BROWSER; | ||
447 | } | ||
448 | |||
449 | //////////////////////////////////////////////////////////////////////////////// | ||
450 | // Our given URL is not a feed, so let's create our own feed with a single item: | ||
451 | // the given URL. This basically treats all non-feed URLs as if they were | ||
452 | // single-item feeds. | ||
453 | //////////////////////////////////////////////////////////////////////////////// | ||
454 | $isDummyFeed = false; | ||
455 | if ($html_only || !$result) { | ||
456 | debug('--------'); | ||
457 | debug("Constructing a single-item feed from URL"); | ||
458 | $isDummyFeed = true; | ||
459 | unset($feed, $result); | ||
460 | // create single item dummy feed object | ||
461 | $feed = new DummySingleItemFeed($url); | ||
462 | } | ||
463 | |||
464 | //////////////////////////////////////////// | ||
465 | // Create full-text feed | ||
466 | //////////////////////////////////////////// | ||
467 | $output = new FeedWriter(); | ||
468 | $output->setTitle(strip_tags($feed->get_title())); | ||
469 | $output->setDescription(strip_tags($feed->get_description())); | ||
470 | $output->setXsl('css/feed.xsl'); // Chrome uses this, most browsers ignore it | ||
471 | if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment | ||
472 | $output->addHub('http://fivefilters.superfeedr.com/'); | ||
473 | $output->addHub('http://pubsubhubbub.appspot.com/'); | ||
474 | $output->setSelf('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); | ||
475 | } | ||
476 | $output->setLink($feed->get_link()); // Google Reader uses this for pulling in favicons | ||
477 | if ($img_url = $feed->get_image_url()) { | ||
478 | $output->setImage($feed->get_title(), $feed->get_link(), $img_url); | ||
479 | } | ||
480 | |||
481 | //////////////////////////////////////////// | ||
482 | // Loop through feed items | ||
483 | //////////////////////////////////////////// | ||
484 | $items = $feed->get_items(0, $max); | ||
485 | // Request all feed items in parallel (if supported) | ||
486 | $urls_sanitized = array(); | ||
487 | $urls = array(); | ||
488 | foreach ($items as $key => $item) { | ||
489 | $permalink = htmlspecialchars_decode($item->get_permalink()); | ||
490 | // Colons in URL path segments get encoded by SimplePie, yet some sites expect them unencoded | ||
491 | $permalink = str_replace('%3A', ':', $permalink); | ||
492 | // validateUrl() strips non-ascii characters | ||
493 | // simplepie already sanitizes URLs so let's not do it again here. | ||
494 | //$permalink = $http->validateUrl($permalink); | ||
495 | if ($permalink) { | ||
496 | $urls_sanitized[] = $permalink; | ||
497 | } | ||
498 | $urls[$key] = $permalink; | ||
499 | } | ||
500 | debug('--------'); | ||
501 | debug('Fetching feed items'); | ||
502 | $http->fetchAll($urls_sanitized); | ||
503 | //$http->cacheAll(); | ||
504 | |||
505 | // count number of items added to full feed | ||
506 | $item_count = 0; | ||
507 | |||
508 | foreach ($items as $key => $item) { | ||
509 | debug('--------'); | ||
510 | debug('Processing feed item '.($item_count+1)); | ||
511 | $do_content_extraction = true; | ||
512 | $extract_result = false; | ||
513 | $text_sample = null; | ||
514 | $permalink = $urls[$key]; | ||
515 | debug("Item URL: $permalink"); | ||
516 | $extracted_title = ''; | ||
517 | $feed_item_title = $item->get_title(); | ||
518 | if ($feed_item_title !== null) { | ||
519 | $feed_item_title = strip_tags(htmlspecialchars_decode($feed_item_title)); | ||
520 | } | ||
521 | $newitem = $output->createNewItem(); | ||
522 | $newitem->setTitle($feed_item_title); | ||
523 | if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment | ||
524 | if ($permalink !== false) { | ||
525 | $newitem->setLink('http://fivefilters.org/content-only/redirect.php?url='.urlencode($permalink)); | ||
526 | } else { | ||
527 | $newitem->setLink('http://fivefilters.org/content-only/redirect.php?url='.urlencode($item->get_permalink())); | ||
528 | } | ||
529 | } else { | ||
530 | if ($permalink !== false) { | ||
531 | $newitem->setLink($permalink); | ||
532 | } else { | ||
533 | $newitem->setLink($item->get_permalink()); | ||
534 | } | ||
535 | } | ||
536 | //if ($permalink && ($response = $http->get($permalink, true)) && $response['status_code'] < 300) { | ||
537 | // Allowing error codes - some sites return correct content with error status | ||
538 | // e.g. prospectmagazine.co.uk returns 403 | ||
539 | if ($permalink && ($response = $http->get($permalink, true)) && ($response['status_code'] < 300 || $response['status_code'] > 400)) { | ||
540 | $effective_url = $response['effective_url']; | ||
541 | if (!url_allowed($effective_url)) continue; | ||
542 | // check if action defined for returned Content-Type | ||
543 | $mime_info = get_mime_action_info($response['headers']); | ||
544 | if (isset($mime_info['action'])) { | ||
545 | if ($mime_info['action'] == 'exclude') { | ||
546 | continue; // skip this feed item entry | ||
547 | } elseif ($mime_info['action'] == 'link') { | ||
548 | if ($mime_info['type'] == 'image') { | ||
549 | $html = "<a href=\"$effective_url\"><img src=\"$effective_url\" alt=\"{$mime_info['name']}\" /></a>"; | ||
550 | } else { | ||
551 | $html = "<a href=\"$effective_url\">Download {$mime_info['name']}</a>"; | ||
552 | } | ||
553 | $extracted_title = $mime_info['name']; | ||
554 | $do_content_extraction = false; | ||
555 | } | ||
556 | } | ||
557 | if ($do_content_extraction) { | ||
558 | $html = $response['body']; | ||
559 | // remove strange things | ||
560 | $html = str_replace('</[>', '', $html); | ||
561 | $html = convert_to_utf8($html, $response['headers']); | ||
562 | // check site config for single page URL - fetch it if found | ||
563 | $is_single_page = false; | ||
564 | if ($single_page_response = getSinglePage($item, $html, $effective_url)) { | ||
565 | $is_single_page = true; | ||
566 | $effective_url = $single_page_response['effective_url']; | ||
567 | // check if action defined for returned Content-Type | ||
568 | $mime_info = get_mime_action_info($single_page_response['headers']); | ||
569 | if (isset($mime_info['action'])) { | ||
570 | if ($mime_info['action'] == 'exclude') { | ||
571 | continue; // skip this feed item entry | ||
572 | } elseif ($mime_info['action'] == 'link') { | ||
573 | if ($mime_info['type'] == 'image') { | ||
574 | $html = "<a href=\"$effective_url\"><img src=\"$effective_url\" alt=\"{$mime_info['name']}\" /></a>"; | ||
575 | } else { | ||
576 | $html = "<a href=\"$effective_url\">Download {$mime_info['name']}</a>"; | ||
577 | } | ||
578 | $extracted_title = $mime_info['name']; | ||
579 | $do_content_extraction = false; | ||
580 | } | ||
581 | } | ||
582 | if ($do_content_extraction) { | ||
583 | $html = $single_page_response['body']; | ||
584 | // remove strange things | ||
585 | $html = str_replace('</[>', '', $html); | ||
586 | $html = convert_to_utf8($html, $single_page_response['headers']); | ||
587 | debug("Retrieved single-page view from $effective_url"); | ||
588 | } | ||
589 | unset($single_page_response); | ||
590 | } | ||
591 | } | ||
592 | if ($do_content_extraction) { | ||
593 | debug('--------'); | ||
594 | debug('Attempting to extract content'); | ||
595 | $extract_result = $extractor->process($html, $effective_url); | ||
596 | $readability = $extractor->readability; | ||
597 | $content_block = ($extract_result) ? $extractor->getContent() : null; | ||
598 | $extracted_title = ($extract_result) ? $extractor->getTitle() : ''; | ||
599 | // Deal with multi-page articles | ||
600 | //die('Next: '.$extractor->getNextPageUrl()); | ||
601 | $is_multi_page = (!$is_single_page && $extract_result && $extractor->getNextPageUrl()); | ||
602 | if ($options->multipage && $is_multi_page && $options->content) { | ||
603 | debug('--------'); | ||
604 | debug('Attempting to process multi-page article'); | ||
605 | $multi_page_urls = array(); | ||
606 | $multi_page_content = array(); | ||
607 | while ($next_page_url = $extractor->getNextPageUrl()) { | ||
608 | debug('--------'); | ||
609 | debug('Processing next page: '.$next_page_url); | ||
610 | // If we've got URL, resolve against $url | ||
611 | if ($next_page_url = makeAbsoluteStr($effective_url, $next_page_url)) { | ||
612 | // check it's not what we have already! | ||
613 | if (!in_array($next_page_url, $multi_page_urls)) { | ||
614 | // it's not, so let's attempt to fetch it | ||
615 | $multi_page_urls[] = $next_page_url; | ||
616 | $_prev_ref = $http->referer; | ||
617 | if (($response = $http->get($next_page_url, true)) && $response['status_code'] < 300) { | ||
618 | // make sure mime type is not something with a different action associated | ||
619 | $page_mime_info = get_mime_action_info($response['headers']); | ||
620 | if (!isset($page_mime_info['action'])) { | ||
621 | $html = $response['body']; | ||
622 | // remove strange things | ||
623 | $html = str_replace('</[>', '', $html); | ||
624 | $html = convert_to_utf8($html, $response['headers']); | ||
625 | if ($extractor->process($html, $next_page_url)) { | ||
626 | $multi_page_content[] = $extractor->getContent(); | ||
627 | continue; | ||
628 | } else { debug('Failed to extract content'); } | ||
629 | } else { debug('MIME type requires different action'); } | ||
630 | } else { debug('Failed to fetch URL'); } | ||
631 | } else { debug('URL already processed'); } | ||
632 | } else { debug('Failed to resolve against '.$effective_url); } | ||
633 | // failed to process next_page_url, so cancel further requests | ||
634 | $multi_page_content = array(); | ||
635 | break; | ||
636 | } | ||
637 | // did we successfully deal with this multi-page article? | ||
638 | if (empty($multi_page_content)) { | ||
639 | debug('Failed to extract all parts of multi-page article, so not going to include them'); | ||
640 | $_page = $readability->dom->createElement('p'); | ||
641 | $_page->innerHTML = '<em>This article appears to continue on subsequent pages which we could not extract</em>'; | ||
642 | $multi_page_content[] = $_page; | ||
643 | } | ||
644 | foreach ($multi_page_content as $_page) { | ||
645 | $_page = $content_block->ownerDocument->importNode($_page, true); | ||
646 | $content_block->appendChild($_page); | ||
647 | } | ||
648 | unset($multi_page_urls, $multi_page_content, $page_mime_info, $next_page_url, $_page); | ||
649 | } | ||
650 | } | ||
651 | // use extracted title for both feed and item title if we're using single-item dummy feed | ||
652 | if ($isDummyFeed) { | ||
653 | $output->setTitle($extracted_title); | ||
654 | $newitem->setTitle($extracted_title); | ||
655 | } else { | ||
656 | // use extracted title instead of feed item title? | ||
657 | if (!$favour_feed_titles && $extracted_title != '') { | ||
658 | debug('Using extracted title in generated feed'); | ||
659 | $newitem->setTitle($extracted_title); | ||
660 | } | ||
661 | } | ||
662 | } | ||
663 | if ($do_content_extraction) { | ||
664 | // if we failed to extract content... | ||
665 | if (!$extract_result) { | ||
666 | if ($exclude_on_fail) { | ||
667 | debug('Failed to extract, so skipping (due to exclude on fail parameter)'); | ||
668 | continue; // skip this and move to next item | ||
669 | } | ||
670 | //TODO: get text sample for language detection | ||
671 | $html = $options->error_message; | ||
672 | // keep the original item description | ||
673 | $html .= $item->get_description(); | ||
674 | } else { | ||
675 | $readability->clean($content_block, 'select'); | ||
676 | // get base URL | ||
677 | $base_url = get_base_url($readability->dom); | ||
678 | if (!$base_url) $base_url = $effective_url; | ||
679 | // rewrite URLs | ||
680 | if ($options->rewrite_relative_urls) makeAbsolute($base_url, $content_block); | ||
681 | // footnotes | ||
682 | if (($links == 'footnotes') && (strpos($effective_url, 'wikipedia.org') === false)) { | ||
683 | $readability->addFootnotes($content_block); | ||
684 | } | ||
685 | // remove nesting: <div><div><div><p>test</p></div></div></div> = <p>test</p> | ||
686 | while ($content_block->childNodes->length == 1 && $content_block->firstChild->nodeType === XML_ELEMENT_NODE) { | ||
687 | // only follow these tag names | ||
688 | if (!in_array(strtolower($content_block->tagName), array('div', 'article', 'section', 'header', 'footer'))) break; | ||
689 | //$html = $content_block->firstChild->innerHTML; // FTR 2.9.5 | ||
690 | $content_block = $content_block->firstChild; | ||
691 | } | ||
692 | // convert content block to HTML string | ||
693 | // Need to preserve things like body: //img[@id='feature'] | ||
694 | if (in_array(strtolower($content_block->tagName), array('div', 'article', 'section', 'header', 'footer'))) { | ||
695 | $html = $content_block->innerHTML; | ||
696 | } else { | ||
697 | $html = $content_block->ownerDocument->saveXML($content_block); // essentially outerHTML | ||
698 | } | ||
699 | //unset($content_block); | ||
700 | // post-processing cleanup | ||
701 | $html = preg_replace('!<p>[\s\h\v]*</p>!u', '', $html); | ||
702 | if ($links == 'remove') { | ||
703 | $html = preg_replace('!</?a[^>]*>!', '', $html); | ||
704 | } | ||
705 | // get text sample for language detection | ||
706 | $text_sample = strip_tags(substr($html, 0, 500)); | ||
707 | $html = make_substitutions($options->message_to_prepend).$html; | ||
708 | $html .= make_substitutions($options->message_to_append); | ||
709 | } | ||
710 | } | ||
711 | |||
712 | if ($valid_key && isset($_GET['pubsub'])) { // used only on fivefilters.org at the moment | ||
713 | $newitem->addElement('guid', 'http://fivefilters.org/content-only/redirect.php?url='.urlencode($item->get_permalink()), array('isPermaLink'=>'false')); | ||
714 | } else { | ||
715 | $newitem->addElement('guid', $item->get_permalink(), array('isPermaLink'=>'true')); | ||
716 | } | ||
717 | // filter xss? | ||
718 | if ($xss_filter) { | ||
719 | debug('Filtering HTML to remove XSS'); | ||
720 | $html = htmLawed::hl($html, array('safe'=>1, 'deny_attribute'=>'style', 'comment'=>1, 'cdata'=>1)); | ||
721 | } | ||
722 | |||
723 | // add content | ||
724 | if ($options->summary === true) { | ||
725 | // get summary | ||
726 | $summary = ''; | ||
727 | if (!$do_content_extraction) { | ||
728 | $summary = $html; | ||
729 | } else { | ||
730 | // Try to get first few paragraphs | ||
731 | if (isset($content_block) && ($content_block instanceof DOMElement)) { | ||
732 | $_paras = $content_block->getElementsByTagName('p'); | ||
733 | foreach ($_paras as $_para) { | ||
734 | $summary .= preg_replace("/[\n\r\t ]+/", ' ', $_para->textContent).' '; | ||
735 | if (strlen($summary) > 200) break; | ||
736 | } | ||
737 | } else { | ||
738 | $summary = $html; | ||
739 | } | ||
740 | } | ||
741 | unset($_paras, $_para); | ||
742 | $summary = get_excerpt($summary); | ||
743 | $newitem->setDescription($summary); | ||
744 | if ($options->content) $newitem->setElement('content:encoded', $html); | ||
745 | } else { | ||
746 | if ($options->content) $newitem->setDescription($html); | ||
747 | } | ||
748 | |||
749 | // set date | ||
750 | if ((int)$item->get_date('U') > 0) { | ||
751 | $newitem->setDate((int)$item->get_date('U')); | ||
752 | } elseif ($extractor->getDate()) { | ||
753 | $newitem->setDate($extractor->getDate()); | ||
754 | } | ||
755 | |||
756 | // add authors | ||
757 | if ($authors = $item->get_authors()) { | ||
758 | foreach ($authors as $author) { | ||
759 | // for some feeds, SimplePie stores author's name as email, e.g. http://feeds.feedburner.com/nymag/intel | ||
760 | if ($author->get_name() !== null) { | ||
761 | $newitem->addElement('dc:creator', $author->get_name()); | ||
762 | } elseif ($author->get_email() !== null) { | ||
763 | $newitem->addElement('dc:creator', $author->get_email()); | ||
764 | } | ||
765 | } | ||
766 | } elseif ($authors = $extractor->getAuthors()) { | ||
767 | //TODO: make sure the list size is reasonable | ||
768 | foreach ($authors as $author) { | ||
769 | // TODO: xpath often selects authors from other articles linked from the page. | ||
770 | // for now choose first item | ||
771 | $newitem->addElement('dc:creator', $author); | ||
772 | break; | ||
773 | } | ||
774 | } | ||
775 | |||
776 | // add language | ||
777 | if ($detect_language) { | ||
778 | $language = $extractor->getLanguage(); | ||
779 | if (!$language) $language = $feed->get_language(); | ||
780 | if (($detect_language == 3 || (!$language && $detect_language == 2)) && $text_sample) { | ||
781 | try { | ||
782 | if ($use_cld) { | ||
783 | // Use PHP-CLD extension | ||
784 | $php_cld = 'CLD\detect'; // in quotes to prevent PHP 5.2 parse error | ||
785 | $res = $php_cld($text_sample); | ||
786 | if (is_array($res) && count($res) > 0) { | ||
787 | $language = $res[0]['code']; | ||
788 | } | ||
789 | } else { | ||
790 | //die('what'); | ||
791 | // Use PEAR's Text_LanguageDetect | ||
792 | if (!isset($l)) { | ||
793 | $l = new Text_LanguageDetect(); | ||
794 | $l->setNameMode(2); // return ISO 639-1 codes (e.g. "en") | ||
795 | } | ||
796 | $l_result = $l->detect($text_sample, 1); | ||
797 | if (count($l_result) > 0) { | ||
798 | $language = key($l_result); | ||
799 | } | ||
800 | } | ||
801 | } catch (Exception $e) { | ||
802 | //die('error: '.$e); | ||
803 | // do nothing | ||
804 | } | ||
805 | } | ||
806 | if ($language && (strlen($language) < 7)) { | ||
807 | $newitem->addElement('dc:language', $language); | ||
808 | } | ||
809 | } | ||
810 | |||
811 | // add MIME type (if it appeared in our exclusions lists) | ||
812 | if (isset($mime_info['mime'])) $newitem->addElement('dc:format', $mime_info['mime']); | ||
813 | // add effective URL (URL after redirects) | ||
814 | if (isset($effective_url)) { | ||
815 | //TODO: ensure $effective_url is valid witout - sometimes it causes problems, e.g. | ||
816 | //http://www.siasat.pk/forum/showthread.php?108883-Pakistan-Chowk-by-Rana-Mubashir-�-25th-March-2012-Special-Program-from-Liari-(Karachi) | ||
817 | //temporary measure: use utf8_encode() | ||
818 | $newitem->addElement('dc:identifier', remove_url_cruft(utf8_encode($effective_url))); | ||
819 | } else { | ||
820 | $newitem->addElement('dc:identifier', remove_url_cruft($item->get_permalink())); | ||
821 | } | ||
822 | |||
823 | // add categories | ||
824 | if ($categories = $item->get_categories()) { | ||
825 | foreach ($categories as $category) { | ||
826 | if ($category->get_label() !== null) { | ||
827 | $newitem->addElement('category', $category->get_label()); | ||
828 | } | ||
829 | } | ||
830 | } | ||
831 | |||
832 | // check for enclosures | ||
833 | if ($options->keep_enclosures) { | ||
834 | if ($enclosures = $item->get_enclosures()) { | ||
835 | foreach ($enclosures as $enclosure) { | ||
836 | // thumbnails | ||
837 | foreach ((array)$enclosure->get_thumbnails() as $thumbnail) { | ||
838 | $newitem->addElement('media:thumbnail', '', array('url'=>$thumbnail)); | ||
839 | } | ||
840 | if (!$enclosure->get_link()) continue; | ||
841 | $enc = array(); | ||
842 | // Media RSS spec ($enc): http://search.yahoo.com/mrss | ||
843 | // SimplePie methods ($enclosure): http://simplepie.org/wiki/reference/start#methods4 | ||
844 | $enc['url'] = $enclosure->get_link(); | ||
845 | if ($enclosure->get_length()) $enc['fileSize'] = $enclosure->get_length(); | ||
846 | if ($enclosure->get_type()) $enc['type'] = $enclosure->get_type(); | ||
847 | if ($enclosure->get_medium()) $enc['medium'] = $enclosure->get_medium(); | ||
848 | if ($enclosure->get_expression()) $enc['expression'] = $enclosure->get_expression(); | ||
849 | if ($enclosure->get_bitrate()) $enc['bitrate'] = $enclosure->get_bitrate(); | ||
850 | if ($enclosure->get_framerate()) $enc['framerate'] = $enclosure->get_framerate(); | ||
851 | if ($enclosure->get_sampling_rate()) $enc['samplingrate'] = $enclosure->get_sampling_rate(); | ||
852 | if ($enclosure->get_channels()) $enc['channels'] = $enclosure->get_channels(); | ||
853 | if ($enclosure->get_duration()) $enc['duration'] = $enclosure->get_duration(); | ||
854 | if ($enclosure->get_height()) $enc['height'] = $enclosure->get_height(); | ||
855 | if ($enclosure->get_width()) $enc['width'] = $enclosure->get_width(); | ||
856 | if ($enclosure->get_language()) $enc['lang'] = $enclosure->get_language(); | ||
857 | $newitem->addElement('media:content', '', $enc); | ||
858 | } | ||
859 | } | ||
860 | } | ||
861 | $output->addItem($newitem); | ||
862 | unset($html); | ||
863 | $item_count++; | ||
864 | } | ||
865 | |||
866 | // output feed | ||
867 | debug('Done!'); | ||
868 | /* | ||
869 | if ($debug_mode) { | ||
870 | $_apc_data = apc_cache_info('user'); | ||
871 | var_dump($_apc_data); exit; | ||
872 | } | ||
873 | */ | ||
874 | if (!$debug_mode) { | ||
875 | if ($callback) echo "$callback("; // if $callback is set, $format also == 'json' | ||
876 | if ($format == 'json') $output->setFormat(($callback === null) ? JSON : JSONP); | ||
877 | $add_to_cache = $options->caching; | ||
878 | // is smart cache mode enabled? | ||
879 | if ($add_to_cache && $options->apc && $options->smart_cache) { | ||
880 | // yes, so only cache if this is the second request for this URL | ||
881 | $add_to_cache = ($apc_cache_hits >= 2); | ||
882 | // purge cache | ||
883 | if ($options->cache_cleanup > 0) { | ||
884 | if (rand(1, $options->cache_cleanup) == 1) { | ||
885 | // apc purge code adapted from from http://www.thimbleopensource.com/tutorials-snippets/php-apc-expunge-script | ||
886 | $_apc_data = apc_cache_info('user'); | ||
887 | foreach ($_apc_data['cache_list'] as $_apc_item) { | ||
888 | if ($_apc_item['ttl'] > 0 && ($_apc_item['ttl'] + $_apc_item['creation_time'] < time())) { | ||
889 | apc_delete($_apc_item['info']); | ||
890 | } | ||
891 | } | ||
892 | } | ||
893 | } | ||
894 | } | ||
895 | if ($add_to_cache) { | ||
896 | ob_start(); | ||
897 | $output->genarateFeed(false); | ||
898 | $output = ob_get_contents(); | ||
899 | ob_end_clean(); | ||
900 | if ($html_only && $item_count == 0) { | ||
901 | // do not cache - in case of temporary server glitch at source URL | ||
902 | } else { | ||
903 | $cache = get_cache(); | ||
904 | if ($add_to_cache) $cache->save($output, $cache_id); | ||
905 | } | ||
906 | echo $output; | ||
907 | } else { | ||
908 | $output->genarateFeed(false); | ||
909 | } | ||
910 | if ($callback) echo ');'; | ||
911 | } | ||
912 | |||