]> git.wh0rd.org - tt-rss.git/blame - include/functions.php
fetch_file_contents: improve error handling
[tt-rss.git] / include / functions.php
CommitLineData
1d3a17c7 1<?php
6e658547 2 define('EXPECTED_CONFIG_VERSION', 26);
153cb6d3 3 define('SCHEMA_VERSION', 132);
545ca067 4
f822a8e5 5 define('LABEL_BASE_INDEX', -1024);
a413f53e 6 define('PLUGIN_FEED_BASE_INDEX', -128);
f822a8e5 7
e57a1507
AD
8 define('COOKIE_LIFETIME_LONG', 86400*365);
9
23d2471c 10 $fetch_last_error = false;
7a01dc77 11 $fetch_last_error_code = false;
ef39be2b 12 $fetch_last_content_type = false;
cf0231f9 13 $fetch_last_error_content = false; // curl only for the time being
3f6f0857 14 $fetch_curl_used = false;
4f71d743 15 $suppress_debugging = false;
23d2471c 16
584411fe
AD
17 libxml_disable_entity_loader(true);
18
e6905f7f
AD
19 // separate test because this is included before sanity checks
20 if (function_exists("mb_internal_encoding")) mb_internal_encoding("UTF-8");
21
324944f3 22 date_default_timezone_set('UTC');
8a7f5767
CW
23 if (defined('E_DEPRECATED')) {
24 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
25 } else {
26 error_reporting(E_ALL & ~E_NOTICE);
27 }
cce28758 28
40d13c28 29 require_once 'config.php';
cc17c205 30
046ec657
BK
31 /**
32 * Define a constant if not already defined
046ec657
BK
33 */
34 function define_default($name, $value) {
046ec657
BK
35 defined($name) or define($name, $value);
36 }
37
6fd03996 38 /* Some tunables you can override in config.php using define(): */
9ef8798b
BK
39
40 define_default('FEED_FETCH_TIMEOUT', 45);
41 // How may seconds to wait for response when requesting feed from a site
42 define_default('FEED_FETCH_NO_CACHE_TIMEOUT', 15);
43 // How may seconds to wait for response when requesting feed from a
44 // site when that feed wasn't cached before
45 define_default('FILE_FETCH_TIMEOUT', 45);
46 // Default timeout when fetching files from remote sites
47 define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
48 // How many seconds to wait for initial response from website when
49 // fetching files from remote sites
4fd07908 50 define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
6fd03996 51 // stop updating feeds if users haven't logged in for X days
4fd07908 52 define_default('DAEMON_FEED_LIMIT', 500);
6fd03996 53 // feed limit for one update batch
4fd07908 54 define_default('DAEMON_SLEEP_INTERVAL', 120);
6fd03996
AD
55 // default sleep interval between feed updates (sec)
56 define_default('MIN_CACHE_FILE_SIZE', 1024);
57 // do not cache files smaller than that (bytes)
58 define_default('CACHE_MAX_DAYS', 7);
59 // max age in days for various automatically cached (temporary) files
60
61 /* tunables end here */
4fd07908 62
fc2b26a6
AD
63 if (DB_TYPE == "pgsql") {
64 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
65 } else {
66 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
67 }
68
9632f884
AD
69 /**
70 * Return available translations names.
8d505d78 71 *
9632f884
AD
72 * @access public
73 * @return array A array of available translations.
74 */
f8c612d4 75 function get_translations() {
6a214f92 76 $tr = array(
8d505d78 77 "auto" => "Detect automatically",
ca6ef932 78 "ar_SA" => "العربيّة (Arabic)",
51faa115 79 "bg_BG" => "Bulgarian",
c2aa0593 80 "da_DA" => "Dansk",
a3162add 81 "ca_CA" => "Català",
a06b79c4 82 "cs_CZ" => "Česky",
6a214f92 83 "en_US" => "English",
c2aa0593 84 "el_GR" => "Ελληνικά",
1f16ede0 85 "es_ES" => "Español (España)",
c2aa0593 86 "es_LA" => "Español",
a927fe7b 87 "de_DE" => "Deutsch",
6a214f92 88 "fr_FR" => "Français",
e78fd196 89 "hu_HU" => "Magyar (Hungarian)",
bb5d3960 90 "it_IT" => "Italiano",
1d004f12 91 "ja_JP" => "日本語 (Japanese)",
7b6c1ca7 92 "lv_LV" => "Latviešu",
592535d7 93 "nb_NO" => "Norwegian bokmål",
9e7f1f12 94 "nl_NL" => "Dutch",
ea45791a 95 "pl_PL" => "Polski",
d3b923c9 96 "ru_RU" => "Русский",
9a063469 97 "pt_BR" => "Portuguese/Brazil",
1c776ade 98 "pt_PT" => "Portuguese/Portugal",
5d608138 99 "zh_CN" => "Simplified Chinese",
8dc5e7f0 100 "zh_TW" => "Traditional Chinese",
2324f153 101 "sv_SE" => "Svenska",
76d78eb2 102 "fi_FI" => "Suomi",
42a5abdc 103 "tr_TR" => "Türkçe");
f8c612d4
AD
104
105 return $tr;
106 }
107
7b26a148
AD
108 require_once "lib/accept-to-gettext.php";
109 require_once "lib/gettext/gettext.inc";
aba609e0 110
7b26a148 111 function startup_gettext() {
8d505d78 112
7b26a148
AD
113 # Get locale from Accept-Language header
114 $lang = al2gt(array_keys(get_translations()), "text/html");
89cb787e 115
7b26a148
AD
116 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
117 $lang = _TRANSLATION_OVERRIDE_DEFAULT;
118 }
89cb787e 119
b18d109f
AD
120 if ($_SESSION["uid"] && get_schema_version() >= 120) {
121 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
1ee4900a 122
c4db796f 123 if ($pref_lang && $pref_lang != 'auto') {
7b149552
AD
124 $lang = $pref_lang;
125 }
7b26a148 126 }
7c33dbd4 127
7b26a148
AD
128 if ($lang) {
129 if (defined('LC_MESSAGES')) {
130 _setlocale(LC_MESSAGES, $lang);
131 } else if (defined('LC_ALL')) {
132 _setlocale(LC_ALL, $lang);
8d039718 133 }
aba609e0 134
d98e76d9 135 _bindtextdomain("messages", "locale");
865220a4 136
7b26a148
AD
137 _textdomain("messages");
138 _bind_textdomain_codeset("messages", "UTF-8");
865220a4 139 }
7b26a148
AD
140 }
141
b619ff15 142 require_once 'db-prefs.php';
8911ac8b 143 require_once 'version.php';
9549e33c 144 require_once 'controls.php';
40d13c28 145
fb850eec 146 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION . ' (http://tt-rss.org/)');
500943a4
AD
147 ini_set('user_agent', SELF_USER_AGENT);
148
7d96bfcd
AD
149 $schema_version = false;
150
4f71d743
AD
151 function _debug_suppress($suppress) {
152 global $suppress_debugging;
153
154 $suppress_debugging = $suppress;
155 }
156
45004d43
AD
157 /**
158 * Print a timestamped debug message.
8d505d78 159 *
45004d43
AD
160 * @param string $msg The debug message.
161 * @return void
162 */
68cccafc 163 function _debug($msg, $show = true) {
4f71d743
AD
164 global $suppress_debugging;
165
166 //echo "[$suppress_debugging] $msg $show\n";
167
168 if ($suppress_debugging) return false;
9ec10352 169
6f9e33e4 170 $ts = strftime("%H:%M:%S", time());
2a6a9395
AD
171 if (function_exists('posix_getpid')) {
172 $ts = "$ts/" . posix_getpid();
173 }
2191eb7a 174
68cccafc 175 if ($show && !(defined('QUIET') && QUIET)) {
2191eb7a
AD
176 print "[$ts] $msg\n";
177 }
178
179 if (defined('LOGFILE')) {
180 $fp = fopen(LOGFILE, 'a+');
181
182 if ($fp) {
a33558a6
AD
183 $locked = false;
184
185 if (function_exists("flock")) {
186 $tries = 0;
187
188 // try to lock logfile for writing
189 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
190 sleep(1);
191 ++$tries;
192 }
193
194 if (!$locked) {
195 fclose($fp);
196 return;
197 }
198 }
199
2191eb7a 200 fputs($fp, "[$ts] $msg\n");
a33558a6
AD
201
202 if (function_exists("flock")) {
203 flock($fp, LOCK_UN);
204 }
205
2191eb7a
AD
206 fclose($fp);
207 }
208 }
209
45004d43 210 } // function _debug
6f9e33e4 211
9632f884
AD
212 /**
213 * Purge a feed old posts.
8d505d78 214 *
9632f884
AD
215 * @param mixed $link A database connection.
216 * @param mixed $feed_id The id of the purged feed.
217 * @param mixed $purge_interval Olderness of purged posts.
218 * @param boolean $debug Set to True to enable the debug. False by default.
219 * @access public
220 * @return void
221 */
a42c55f0 222 function purge_feed($feed_id, $purge_interval, $debug = false) {
ad507f85 223
a42c55f0 224 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
8d505d78 225
ad507f85 226 $rows = -1;
4c193675 227
6322ac79 228 $result = db_query(
07d0efe9
AD
229 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
230
231 $owner_uid = false;
232
233 if (db_num_rows($result) == 1) {
234 $owner_uid = db_fetch_result($result, 0, "owner_uid");
235 }
236
ab954dff
AD
237 if ($purge_interval == -1 || !$purge_interval) {
238 if ($owner_uid) {
2ed0d6c4 239 CCache::update($feed_id, $owner_uid);
ab954dff
AD
240 }
241 return;
242 }
243
07d0efe9
AD
244 if (!$owner_uid) return;
245
3907ef71 246 if (FORCE_ARTICLE_PURGE == 0) {
a42c55f0 247 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
3907ef71
AD
248 $owner_uid, false);
249 } else {
250 $purge_unread = true;
251 $purge_interval = FORCE_ARTICLE_PURGE;
252 }
07d0efe9
AD
253
254 if (!$purge_unread) $query_limit = " unread = false AND ";
255
fefa6ca3 256 if (DB_TYPE == "pgsql") {
6b3160cf
AD
257 $result = db_query("DELETE FROM ttrss_user_entries
258 USING ttrss_entries
259 WHERE ttrss_entries.id = ref_id AND
260 marked = false AND
261 feed_id = '$feed_id' AND
262 $query_limit
263 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
ad507f85 264
fefa6ca3 265 } else {
8d505d78 266
a42c55f0 267/* $result = db_query("DELETE FROM ttrss_user_entries WHERE
fefa6ca3 268 marked = false AND feed_id = '$feed_id' AND
8d505d78 269 (SELECT date_updated FROM ttrss_entries WHERE
30f1746f
AD
270 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
271
a42c55f0 272 $result = db_query("DELETE FROM ttrss_user_entries
8d505d78
AD
273 USING ttrss_user_entries, ttrss_entries
274 WHERE ttrss_entries.id = ref_id AND
275 marked = false AND
276 feed_id = '$feed_id' AND
07d0efe9 277 $query_limit
25ea2805 278 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
ad507f85
AD
279 }
280
a08f94bd 281 $rows = db_affected_rows($result);
3f6f0857 282
2ed0d6c4 283 CCache::update($feed_id, $owner_uid);
ced46404 284
ad507f85 285 if ($debug) {
6f9e33e4 286 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
fefa6ca3 287 }
2ea09bde
AD
288
289 return $rows;
9632f884 290 } // function purge_feed
fefa6ca3 291
a42c55f0 292 function feed_purge_interval($feed_id) {
07d0efe9 293
a42c55f0 294 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
07d0efe9
AD
295 WHERE id = '$feed_id'");
296
297 if (db_num_rows($result) == 1) {
298 $purge_interval = db_fetch_result($result, 0, "purge_interval");
299 $owner_uid = db_fetch_result($result, 0, "owner_uid");
300
6322ac79 301 if ($purge_interval == 0) $purge_interval = get_pref(
863be6ca 302 'PURGE_OLD_DAYS', $owner_uid);
07d0efe9
AD
303
304 return $purge_interval;
305
306 } else {
307 return -1;
308 }
309 }
310
a230bf88 311 /*function get_feed_update_interval($feed_id) {
a42c55f0 312 $result = db_query("SELECT owner_uid, update_interval FROM
c7d57b66
AD
313 ttrss_feeds WHERE id = '$feed_id'");
314
315 if (db_num_rows($result) == 1) {
316 $update_interval = db_fetch_result($result, 0, "update_interval");
317 $owner_uid = db_fetch_result($result, 0, "owner_uid");
318
319 if ($update_interval != 0) {
320 return $update_interval;
321 } else {
a42c55f0 322 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
c7d57b66
AD
323 }
324
325 } else {
326 return -1;
327 }
a230bf88 328 }*/
c7d57b66 329
465fb16d
AD
330 // TODO: multiple-argument way is deprecated, first parameter is a hash now
331 function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
332 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
8d505d78 333
23d2471c 334 global $fetch_last_error;
7a01dc77 335 global $fetch_last_error_code;
cf0231f9 336 global $fetch_last_error_content;
ef39be2b 337 global $fetch_last_content_type;
153cb6d3 338 global $fetch_last_modified;
3f6f0857 339 global $fetch_curl_used;
33373eca 340
24c7e413
AD
341 $fetch_last_error = false;
342 $fetch_last_error_code = -1;
343 $fetch_last_error_content = "";
344 $fetch_last_content_type = "";
345 $fetch_curl_used = false;
153cb6d3 346 $fetch_last_modified = "";
24c7e413 347
c71add38 348 if (!is_array($options)) {
465fb16d
AD
349
350 // falling back on compatibility shim
153cb6d3 351 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ];
e934d63e
AD
352 $tmp = [];
353
354 for ($i = 0; $i < func_num_args(); $i++) {
355 $tmp[$option_names[$i]] = func_get_arg($i);
356 }
357
358 $options = $tmp;
359
360 /*$options = array(
465fb16d
AD
361 "url" => func_get_arg(0),
362 "type" => @func_get_arg(1),
363 "login" => @func_get_arg(2),
364 "pass" => @func_get_arg(3),
365 "post_query" => @func_get_arg(4),
366 "timeout" => @func_get_arg(5),
367 "timestamp" => @func_get_arg(6),
368 "useragent" => @func_get_arg(7)
e934d63e 369 ); */
465fb16d
AD
370 }
371
e3bc4591
AD
372 $url = $options["url"];
373 $type = isset($options["type"]) ? $options["type"] : false;
374 $login = isset($options["login"]) ? $options["login"] : false;
465fb16d 375 $pass = isset($options["pass"]) ? $options["pass"] : false;
e3bc4591
AD
376 $post_query = isset($options["post_query"]) ? $options["post_query"] : false;
377 $timeout = isset($options["timeout"]) ? $options["timeout"] : false;
153cb6d3 378 $last_modified = isset($options["last_modified"]) ? $options["last_modified"] : "";
e3bc4591 379 $useragent = isset($options["useragent"]) ? $options["useragent"] : false;
fabfb9fc 380 $followlocation = isset($options["followlocation"]) ? $options["followlocation"] : true;
465fb16d 381
32703cc6 382 $url = ltrim($url, ' ');
2375b6b6 383 $url = str_replace(' ', '%20', $url);
23d2471c 384
9fd58133
AD
385 if (strpos($url, "//") === 0)
386 $url = 'http:' . $url;
387
312742db 388 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
3f6f0857
AD
389
390 $fetch_curl_used = true;
b799dc8b 391
4c467026 392 $ch = curl_init($url);
a1af1574 393
153cb6d3 394 if ($last_modified && !$post_query) {
7a01dc77 395 curl_setopt($ch, CURLOPT_HTTPHEADER,
153cb6d3 396 array("If-Modified-Since: $last_modified"));
7a01dc77
AD
397 }
398
8401101d
BK
399 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
400 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : FILE_FETCH_TIMEOUT);
fabfb9fc 401 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir") && $followlocation);
a1af1574
AD
402 curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
403 curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
404 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
153cb6d3 405 curl_setopt($ch, CURLOPT_HEADER, true);
5f6804bc 406 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
f826070c
AD
407 curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
408 SELF_USER_AGENT);
3f6f0857 409 curl_setopt($ch, CURLOPT_ENCODING, "");
1fd733c8 410 //curl_setopt($ch, CURLOPT_REFERER, $url);
0f6b9263 411
4c467026 412 if (!ini_get("open_basedir")) {
0f6b9263
AD
413 curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
414 }
8d505d78 415
05f14a7d
AD
416 if (defined('_CURL_HTTP_PROXY')) {
417 curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY);
418 }
419
ae5f7bb1
AD
420 if ($post_query) {
421 curl_setopt($ch, CURLOPT_POST, true);
422 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
423 }
424
8d505d78
AD
425 if ($login && $pass)
426 curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
a1af1574 427
153cb6d3
AD
428 $ret = @curl_exec($ch);
429
430 $headers_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
431 $headers = explode("\r\n", substr($ret, 0, $headers_length));
432 $contents = substr($ret, $headers_length);
433
434 foreach ($headers as $header) {
9d930af9
AD
435 if (strstr($header, ": ") !== FALSE) {
436 list ($key, $value) = explode(": ", $header);
437
438 if (strtolower($key) == "last-modified") {
439 $fetch_last_modified = $value;
440 }
441 }
442
443 if (substr(strtolower($header), 0, 7) == 'http/1.') {
444 $fetch_last_error_code = (int) substr($header, 9, 3);
445 $fetch_last_error = $header;
446 }
153cb6d3 447 }
268a06dc 448
48b657fc
AD
449 if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
450 curl_setopt($ch, CURLOPT_ENCODING, 'none');
451 $contents = @curl_exec($ch);
fb850eec
AD
452 }
453
8d505d78 454 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
ef39be2b 455 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
4065b60b 456
7a01dc77
AD
457 $fetch_last_error_code = $http_code;
458
ef39be2b 459 if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) {
9d930af9 460
fb850eec 461 if (curl_errno($ch) != 0) {
9d930af9 462 $fetch_last_error .= "; " . curl_errno($ch) . " " . curl_error($ch);
fb850eec 463 }
9d930af9 464
cf0231f9 465 $fetch_last_error_content = $contents;
fb850eec 466 curl_close($ch);
a1af1574
AD
467 return false;
468 }
4065b60b 469
153cb6d3
AD
470 if (!$contents) {
471 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
472 curl_close($ch);
473 return false;
474 }
475
fb850eec
AD
476 curl_close($ch);
477
a1af1574 478 return $contents;
4065b60b 479 } else {
3f6f0857
AD
480
481 $fetch_curl_used = false;
482
d3911f80 483 if ($login && $pass){
8d505d78
AD
484 $url_parts = array();
485
486 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
487
d3911f80
AD
488 $pass = urlencode($pass);
489
8d505d78
AD
490 if ($url_parts[1] && $url_parts[2]) {
491 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
492 }
493 }
494
3bba9c39
AD
495 // TODO: should this support POST requests or not? idk
496
153cb6d3 497 if (!$post_query && $last_modified) {
45913edd
AD
498 $context = stream_context_create(array(
499 'http' => array(
500 'method' => 'GET',
24c7e413 501 'ignore_errors' => true,
3bba9c39 502 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
45913edd 503 'protocol_version'=> 1.1,
153cb6d3
AD
504 'header' => "If-Modified-Since: $last_modified\r\n")
505 ));
01311d86 506 } else {
45913edd
AD
507 $context = stream_context_create(array(
508 'http' => array(
509 'method' => 'GET',
24c7e413 510 'ignore_errors' => true,
3bba9c39 511 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
45913edd
AD
512 'protocol_version'=> 1.1
513 )));
7475580b 514 }
01311d86 515
6122c449
AD
516 $old_error = error_get_last();
517
01311d86 518 $data = @file_get_contents($url, false, $context);
23d2471c 519
37ddf5b7 520 if (isset($http_response_header) && is_array($http_response_header)) {
9d930af9
AD
521 foreach ($http_response_header as $header) {
522 if (strstr($header, ": ") !== FALSE) {
523 list ($key, $value) = explode(": ", $header);
524
525 $key = strtolower($key);
526
527 if ($key == 'content-type') {
528 $fetch_last_content_type = $value;
529 // don't abort here b/c there might be more than one
530 // e.g. if we were being redirected -- last one is the right one
531 } else if ($key == 'last-modified') {
532 $fetch_last_modified = $value;
533 }
534 }
535
536 if (substr(strtolower($header), 0, 7) == 'http/1.') {
537 $fetch_last_error_code = (int) substr($header, 9, 3);
538 $fetch_last_error = $header;
01311d86 539 }
ef39be2b
MB
540 }
541 }
542
24c7e413 543 if ($fetch_last_error_code != 200) {
23d2471c 544 $error = error_get_last();
6122c449
AD
545
546 if ($error['message'] != $old_error['message']) {
9d930af9 547 $fetch_last_error .= "; " . $error["message"];
6122c449 548 }
24c7e413
AD
549
550 $fetch_last_error_content = $data;
551
552 return false;
23d2471c
AD
553 }
554 return $data;
4065b60b
AD
555 }
556
557 }
78800912 558
9632f884
AD
559 /**
560 * Try to determine the favicon URL for a feed.
561 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
562 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
8d505d78 563 *
9632f884
AD
564 * @param string $url A feed or page URL
565 * @access public
566 * @return mixed The favicon URL, or false if none was found.
567 */
1bd11fdf 568 function get_favicon_url($url) {
99331724 569
1bd11fdf 570 $favicon_url = false;
ed214298 571
4065b60b 572 if ($html = @fetch_file_contents($url)) {
78800912 573
ed214298 574 libxml_use_internal_errors(true);
c798704b 575
ed214298
AD
576 $doc = new DOMDocument();
577 $doc->loadHTML($html);
578 $xpath = new DOMXPath($doc);
717f5e64 579
701c5a7e 580 $base = $xpath->query('/html/head/base[@href]');
a712429e 581 foreach ($base as $b) {
241f69e4 582 $url = rewrite_relative_url($url, $b->getAttribute("href"));
a712429e
AD
583 break;
584 }
585
1bd11fdf 586 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
ed214298
AD
587 if (count($entries) > 0) {
588 foreach ($entries as $entry) {
1bd11fdf
AD
589 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
590 break;
ed214298 591 }
8d505d78 592 }
4065b60b 593 }
c798704b 594
1bd11fdf
AD
595 if (!$favicon_url)
596 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
597
598 return $favicon_url;
599 } // function get_favicon_url
600
a42c55f0 601 function initialize_user_prefs($uid, $profile = false) {
ff485f1d 602
a42c55f0 603 $uid = db_escape_string($uid);
ff485f1d 604
d9084cf2
AD
605 if (!$profile) {
606 $profile = "NULL";
f9aa6a89 607 $profile_qpart = "AND profile IS NULL";
d9084cf2 608 } else {
f9aa6a89 609 $profile_qpart = "AND profile = '$profile'";
d9084cf2
AD
610 }
611
6322ac79 612 if (get_schema_version() < 63) $profile_qpart = "";
f9aa6a89 613
a42c55f0 614 db_query("BEGIN");
ff485f1d 615
a42c55f0 616 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
8d505d78 617
a42c55f0 618 $u_result = db_query("SELECT pref_name
f9aa6a89 619 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
ff485f1d
AD
620
621 $active_prefs = array();
622
623 while ($line = db_fetch_assoc($u_result)) {
8d505d78 624 array_push($active_prefs, $line["pref_name"]);
ff485f1d
AD
625 }
626
627 while ($line = db_fetch_assoc($result)) {
628 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
629// print "adding " . $line["pref_name"] . "<br>";
630
a42c55f0
AD
631 $line["def_value"] = db_escape_string($line["def_value"]);
632 $line["pref_name"] = db_escape_string($line["pref_name"]);
d296ba50 633
6322ac79 634 if (get_schema_version() < 63) {
a42c55f0 635 db_query("INSERT INTO ttrss_user_prefs
8d505d78 636 (owner_uid,pref_name,value) VALUES
f9aa6a89
AD
637 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
638
639 } else {
a42c55f0 640 db_query("INSERT INTO ttrss_user_prefs
8d505d78 641 (owner_uid,pref_name,value, profile) VALUES
f9aa6a89
AD
642 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
643 }
ff485f1d
AD
644
645 }
646 }
647
a42c55f0 648 db_query("COMMIT");
ff485f1d
AD
649
650 }
956c7629 651
8de8bfb8
AD
652 function get_ssl_certificate_id() {
653 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
654 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
655 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
656 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
657 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
658 }
ac617ebc
GG
659 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
660 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
661 $_SERVER["SSL_CLIENT_V_START"] .
662 $_SERVER["SSL_CLIENT_V_END"] .
663 $_SERVER["SSL_CLIENT_S_DN"]);
664 }
8de8bfb8
AD
665 return "";
666 }
667
a42c55f0 668 function authenticate_user($login, $password, $check_only = false) {
c8437f35 669
131b01b3 670 if (!SINGLE_USER_MODE) {
0d421af8 671 $user_id = false;
0f28f81f 672
1ffe3391 673 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
0f28f81f
AD
674
675 $user_id = (int) $plugin->authenticate($login, $password);
676
677 if ($user_id) {
678 $_SESSION["auth_module"] = strtolower(get_class($plugin));
679 break;
680 }
461766f3
AD
681 }
682
0d421af8 683 if ($user_id && !$check_only) {
70400171
AD
684 @session_start();
685
0d421af8 686 $_SESSION["uid"] = $user_id;
3472c4c5 687 $_SESSION["version"] = VERSION_STATIC;
0d421af8 688
a42c55f0 689 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
0d421af8 690 WHERE id = '$user_id'");
8d505d78 691
131b01b3
AD
692 $_SESSION["name"] = db_fetch_result($result, 0, "login");
693 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
3ceb893f 694 $_SESSION["csrf_token"] = uniqid_short();
8d505d78 695
a42c55f0 696 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
131b01b3 697 $_SESSION["uid"]);
8d505d78 698
131b01b3 699 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
837ec70e 700 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
1a9f4d3c 701 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
91c5f229
AD
702
703 $_SESSION["last_version_check"] = time();
8d505d78 704
a42c55f0 705 initialize_user_prefs($_SESSION["uid"]);
8d505d78 706
131b01b3
AD
707 return true;
708 }
8d505d78 709
131b01b3 710 return false;
503eb349 711
131b01b3 712 } else {
503eb349 713
131b01b3
AD
714 $_SESSION["uid"] = 1;
715 $_SESSION["name"] = "admin";
787e5ebc 716 $_SESSION["access_level"] = 10;
21e42e5f 717
0d421af8
AD
718 $_SESSION["hide_hello"] = true;
719 $_SESSION["hide_logout"] = true;
720
d5fd183d
AD
721 $_SESSION["auth_module"] = false;
722
21e42e5f 723 if (!$_SESSION["csrf_token"]) {
3ceb893f 724 $_SESSION["csrf_token"] = uniqid_short();
21e42e5f 725 }
f557cd78 726
0bbba72d 727 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
8d505d78 728
a42c55f0 729 initialize_user_prefs($_SESSION["uid"]);
8d505d78 730
c8437f35
AD
731 return true;
732 }
c8437f35
AD
733 }
734
e6cb77a0
AD
735 function make_password($length = 8) {
736
85db6213
AD
737 $password = "";
738 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
739
740 $i = 0;
741
742 while ($i < $length) {
743 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
744
745 if (!strstr($password, $char)) {
746 $password .= $char;
747 $i++;
748 }
749 }
750 return $password;
e6cb77a0
AD
751 }
752
753 // this is called after user is created to initialize default feeds, labels
754 // or whatever else
8d505d78 755
e6cb77a0
AD
756 // user preferences are checked on every login, not here
757
a42c55f0 758 function initialize_user($uid) {
e6cb77a0 759
a42c55f0 760 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
cd2cd415 761 values ('$uid', 'Tiny Tiny RSS: Forum',
f0855b88 762 'http://tt-rss.org/forum/rss.php')");
3b0feb9b 763 }
e6cb77a0 764
b8aa49bc 765 function logout_user() {
5ccc1cf5
AD
766 session_destroy();
767 if (isset($_COOKIE[session_name()])) {
768 setcookie(session_name(), '', time()-42000, '/');
769 }
b8aa49bc
AD
770 }
771
8484ce22
AD
772 function validate_csrf($csrf_token) {
773 return $csrf_token == $_SESSION['csrf_token'];
774 }
775
5cbd1fe8
AD
776 function load_user_plugins($owner_uid, $pluginhost = false) {
777
778 if (!$pluginhost) $pluginhost = PluginHost::getInstance();
779
6d45a152 780 if ($owner_uid && SCHEMA_VERSION >= 100) {
a42c55f0 781 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
de612e7a 782
5cbd1fe8 783 $pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
e9c04fd4 784
6322ac79 785 if (get_schema_version() > 100) {
5cbd1fe8 786 $pluginhost->load_data();
e9c04fd4 787 }
de612e7a
AD
788 }
789 }
790
6322ac79 791 function login_sequence() {
97acbaf1 792 if (SINGLE_USER_MODE) {
25db6c51 793 @session_start();
a42c55f0 794 authenticate_user("admin", null);
68349f55 795 startup_gettext();
a42c55f0 796 load_user_plugins($_SESSION["uid"]);
97acbaf1 797 } else {
6322ac79 798 if (!validate_session()) $_SESSION["uid"] = false;
d0eef2a3
AD
799
800 if (!$_SESSION["uid"]) {
97acbaf1 801
a42c55f0
AD
802 if (AUTH_AUTO_LOGIN && authenticate_user(null, null)) {
803 $_SESSION["ref_schema_version"] = get_schema_version(true);
97acbaf1 804 } else {
a42c55f0 805 authenticate_user(null, null, true);
97acbaf1
AD
806 }
807
d0eef2a3 808 if (!$_SESSION["uid"]) {
d0eef2a3
AD
809 @session_destroy();
810 setcookie(session_name(), '', time()-42000, '/');
9ce7a554 811
6322ac79 812 render_login_form();
d0eef2a3
AD
813 exit;
814 }
4ad99f23 815
97acbaf1
AD
816 } else {
817 /* bump login timestamp */
a42c55f0 818 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
97acbaf1 819 $_SESSION["uid"]);
06b0777f 820 $_SESSION["last_login_update"] = time();
01a87dff
AD
821 }
822
de612e7a 823 if ($_SESSION["uid"]) {
7b149552 824 startup_gettext();
a42c55f0 825 load_user_plugins($_SESSION["uid"]);
b1b1d25f
AD
826
827 /* cleanup ccache */
828
a42c55f0 829 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
b1b1d25f
AD
830 $_SESSION["uid"] . " AND
831 (SELECT COUNT(id) FROM ttrss_feeds WHERE
832 ttrss_feeds.id = feed_id) = 0");
833
a42c55f0 834 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
b1b1d25f
AD
835 $_SESSION["uid"] . " AND
836 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
837 ttrss_feed_categories.id = feed_id) = 0");
838
de612e7a 839 }
b1b1d25f 840
b8aa49bc 841 }
afc3cf55 842 }
3547842a 843
411fe209 844 function truncate_string($str, $max_len, $suffix = '&hellip;') {
878a0083 845 if (mb_strlen($str, "utf-8") > $max_len) {
411fe209 846 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
3547842a
AD
847 } else {
848 return $str;
849 }
850 }
54a60e1a 851
cc43e19b
AD
852 // is not utf8 clean
853 function truncate_middle($str, $max_len, $suffix = '&hellip;') {
854 if (strlen($str) > $max_len) {
855 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
856 } else {
857 return $str;
858 }
859 }
860
ab4b768f
AD
861 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
862
863 try {
864 $source_tz = new DateTimeZone($source_tz);
865 } catch (Exception $e) {
866 $source_tz = new DateTimeZone('UTC');
867 }
868
869 try {
870 $dest_tz = new DateTimeZone($dest_tz);
871 } catch (Exception $e) {
872 $dest_tz = new DateTimeZone('UTC');
873 }
874
875 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
876 return $dt->format('U') + $dest_tz->getOffset($dt);
877 }
878
a42c55f0 879 function make_local_datetime($timestamp, $long, $owner_uid = false,
b6714c77 880 $no_smart_dt = false, $eta_min = false) {
324944f3
AD
881
882 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
883 if (!$timestamp) $timestamp = '1970-01-01 0:00';
884
7d96bfcd 885 global $utc_tz;
5ddef5ba
AD
886 global $user_tz;
887
888 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
324944f3 889
90e5f4f1
AD
890 $timestamp = substr($timestamp, 0, 19);
891
7d96bfcd
AD
892 # We store date in UTC internally
893 $dt = new DateTime($timestamp, $utc_tz);
894
5ddef5ba 895 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
7d96bfcd 896
6bfc97da 897 if ($user_tz_string != 'Automatic') {
324944f3 898
6bfc97da
AD
899 try {
900 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
901 } catch (Exception $e) {
902 $user_tz = $utc_tz;
903 }
904
905 $tz_offset = $user_tz->getOffset($dt);
906 } else {
96f0cbe3 907 $tz_offset = (int) -$_SESSION["clientTzOffset"];
6bfc97da 908 }
5ddef5ba 909
7d96bfcd 910 $user_timestamp = $dt->format('U') + $tz_offset;
324944f3 911
1dc52ae7 912 if (!$no_smart_dt) {
a42c55f0 913 return smart_date_time($user_timestamp,
b6714c77 914 $tz_offset, $owner_uid, $eta_min);
324944f3
AD
915 } else {
916 if ($long)
a42c55f0 917 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
324944f3 918 else
a42c55f0 919 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
324944f3
AD
920
921 return date($format, $user_timestamp);
922 }
923 }
924
b6714c77 925 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
2a5c136e
AD
926 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
927
97aa917c 928 if ($eta_min && time() + $tz_offset - $timestamp < 3600) {
46973af5
AD
929 return T_sprintf("%d min", date("i", time() + $tz_offset - $timestamp));
930 } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() + $tz_offset)) {
be773442 931 return date("G:i", $timestamp);
2a5c136e 932 } else if (date("Y", $timestamp) == date("Y", time() + $tz_offset)) {
a42c55f0 933 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
2a5c136e 934 return date($format, $timestamp);
be773442 935 } else {
a42c55f0 936 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
2a5c136e 937 return date($format, $timestamp);
be773442
AD
938 }
939 }
940
e3c99f3b 941 function sql_bool_to_bool($s) {
9955a134 942 if ($s == "t" || $s == "1" || strtolower($s) == "true") {
e3c99f3b
AD
943 return true;
944 } else {
945 return false;
946 }
947 }
8d505d78 948
badac687
AD
949 function bool_to_sql_bool($s) {
950 if ($s) {
951 return "true";
952 } else {
953 return "false";
954 }
955 }
e3c99f3b 956
fcfa9ef1
AD
957 // Session caching removed due to causing wrong redirects to upgrade
958 // script when get_schema_version() is called on an obsolete session
959 // created on a previous schema version.
a42c55f0 960 function get_schema_version($nocache = false) {
7d96bfcd
AD
961 global $schema_version;
962
e2cf81e2 963 if (!$schema_version && !$nocache) {
a42c55f0 964 $result = db_query("SELECT schema_version FROM ttrss_version");
199db684 965 $version = db_fetch_result($result, 0, "schema_version");
7d96bfcd 966 $schema_version = $version;
199db684 967 return $version;
7d96bfcd
AD
968 } else {
969 return $schema_version;
970 }
e4c51a6c
AD
971 }
972
6322ac79 973 function sanity_check() {
31303c6b 974 require_once 'errors.php';
cacc1877 975 global $ERRORS;
ebb948c2 976
6043fb7e 977 $error_code = 0;
a42c55f0 978 $schema_version = get_schema_version(true);
6043fb7e
AD
979
980 if ($schema_version != SCHEMA_VERSION) {
981 $error_code = 5;
982 }
983
aec3ce39 984 if (DB_TYPE == "mysql") {
a42c55f0 985 $result = db_query("SELECT true", false);
aec3ce39
AD
986 if (db_num_rows($result) != 1) {
987 $error_code = 10;
988 }
989 }
990
a42c55f0 991 if (db_escape_string("testTEST") != "testTEST") {
f29ba148
AD
992 $error_code = 12;
993 }
994
ebb948c2 995 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
6043fb7e
AD
996 }
997
27981ca3 998 function file_is_locked($filename) {
8ff2a86c
AD
999 if (file_exists(LOCK_DIRECTORY . "/$filename")) {
1000 if (function_exists('flock')) {
1001 $fp = @fopen(LOCK_DIRECTORY . "/$filename", "r");
1002 if ($fp) {
1003 if (flock($fp, LOCK_EX | LOCK_NB)) {
1004 flock($fp, LOCK_UN);
1005 fclose($fp);
1006 return false;
1007 }
31a6d42d 1008 fclose($fp);
8ff2a86c
AD
1009 return true;
1010 } else {
31a6d42d
AD
1011 return false;
1012 }
27981ca3 1013 }
8ff2a86c
AD
1014 return true; // consider the file always locked and skip the test
1015 } else {
1016 return false;
27981ca3 1017 }
27981ca3
AD
1018 }
1019
8ff2a86c 1020
fcb4c0c9 1021 function make_lockfile($filename) {
cfa43e02 1022 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
fcb4c0c9 1023
a44bfcfd 1024 if ($fp && flock($fp, LOCK_EX | LOCK_NB)) {
58fc7095
AD
1025 $stat_h = fstat($fp);
1026 $stat_f = stat(LOCK_DIRECTORY . "/$filename");
1027
1fcebfb3
AD
1028 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1029 if ($stat_h["ino"] != $stat_f["ino"] ||
1030 $stat_h["dev"] != $stat_f["dev"]) {
1031
1032 return false;
1033 }
58fc7095
AD
1034 }
1035
4c59adb1
AD
1036 if (function_exists('posix_getpid')) {
1037 fwrite($fp, posix_getpid() . "\n");
1038 }
fcb4c0c9
AD
1039 return $fp;
1040 } else {
1041 return false;
1042 }
1043 }
1044
bf7fcde8 1045 function make_stampfile($filename) {
cfa43e02 1046 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
bf7fcde8 1047
8e00ae9b 1048 if (flock($fp, LOCK_EX | LOCK_NB)) {
bf7fcde8 1049 fwrite($fp, time() . "\n");
8e00ae9b 1050 flock($fp, LOCK_UN);
bf7fcde8
AD
1051 fclose($fp);
1052 return true;
1053 } else {
1054 return false;
1055 }
1056 }
1057
894ebcf5 1058 function sql_random_function() {
8c0496f7 1059 if (DB_TYPE == "mysql") {
894ebcf5
AD
1060 return "RAND()";
1061 } else {
1062 return "RANDOM()";
1063 }
1064 }
1065
ff5cc7d7 1066 function getFeedUnread($feed, $is_cat = false) {
86a8351c 1067 return Feeds::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
ff5cc7d7
AD
1068 }
1069
ff5cc7d7 1070
86a8351c 1071 /*function get_pgsql_version() {
ff5cc7d7
AD
1072 $result = db_query("SELECT version() AS version");
1073 $version = explode(" ", db_fetch_result($result, 0, "version"));
1074 return $version[1];
86a8351c 1075 }*/
ff5cc7d7 1076
ff5cc7d7
AD
1077 function checkbox_to_sql_bool($val) {
1078 return ($val == "on") ? "true" : "false";
1079 }
1080
86a8351c 1081 /*function getFeedCatTitle($id) {
ff5cc7d7
AD
1082 if ($id == -1) {
1083 return __("Special");
1084 } else if ($id < LABEL_BASE_INDEX) {
1085 return __("Labels");
1086 } else if ($id > 0) {
1087 $result = db_query("SELECT ttrss_feed_categories.title
1088 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1089 cat_id = ttrss_feed_categories.id");
1090 if (db_num_rows($result) == 1) {
1091 return db_fetch_result($result, 0, "title");
1092 } else {
1093 return __("Uncategorized");
1094 }
1095 } else {
1096 return "getFeedCatTitle($id) failed";
1097 }
1098
86a8351c 1099 }*/
ff5cc7d7 1100
3ceb893f
AD
1101 function uniqid_short() {
1102 return uniqid(base_convert(rand(), 10, 36));
1103 }
1104
aeb1abed
AD
1105 function make_init_params() {
1106 $params = array();
1107
1108 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1109 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1110 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1111 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1112
1113 $params[strtolower($param)] = (int) get_pref($param);
1114 }
1115
1116 $params["icons_url"] = ICONS_URL;
1117 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
1118 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1119 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1120 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1121 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1122 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
1123
1124 $theme = get_pref( "USER_CSS_THEME", false, false);
1125 $params["theme"] = theme_valid("$theme") ? $theme : "";
1126
1127 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
1128
1129 $params["php_platform"] = PHP_OS;
1130 $params["php_version"] = PHP_VERSION;
1131
1132 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1133
1134 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1135 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1136
1137 $max_feed_id = db_fetch_result($result, 0, "mid");
1138 $num_feeds = db_fetch_result($result, 0, "nf");
1139
1140 $params["max_feed_id"] = (int) $max_feed_id;
1141 $params["num_feeds"] = (int) $num_feeds;
1142
1143 $params["hotkeys"] = get_hotkeys_map();
1144
1145 $params["csrf_token"] = $_SESSION["csrf_token"];
1146 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1147
1148 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
1149
1150 $params["icon_alert"] = base64_img("images/alert.png");
1151 $params["icon_information"] = base64_img("images/information.png");
1152 $params["icon_cross"] = base64_img("images/cross.png");
1153 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
1154
5e78b0c2
AD
1155 $params["labels"] = Labels::get_all_labels($_SESSION["uid"]);
1156
aeb1abed
AD
1157 return $params;
1158 }
1159
1160 function get_hotkeys_info() {
1161 $hotkeys = array(
1162 __("Navigation") => array(
1163 "next_feed" => __("Open next feed"),
1164 "prev_feed" => __("Open previous feed"),
1165 "next_article" => __("Open next article"),
1166 "prev_article" => __("Open previous article"),
1167 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1168 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1169 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1170 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1171 "search_dialog" => __("Show search dialog")),
1172 __("Article") => array(
1173 "toggle_mark" => __("Toggle starred"),
1174 "toggle_publ" => __("Toggle published"),
1175 "toggle_unread" => __("Toggle unread"),
1176 "edit_tags" => __("Edit tags"),
1177 "open_in_new_window" => __("Open in new window"),
1178 "catchup_below" => __("Mark below as read"),
1179 "catchup_above" => __("Mark above as read"),
1180 "article_scroll_down" => __("Scroll down"),
1181 "article_scroll_up" => __("Scroll up"),
1182 "select_article_cursor" => __("Select article under cursor"),
1183 "email_article" => __("Email article"),
1184 "close_article" => __("Close/collapse article"),
1185 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1186 "toggle_widescreen" => __("Toggle widescreen mode"),
1187 "toggle_embed_original" => __("Toggle embed original")),
1188 __("Article selection") => array(
1189 "select_all" => __("Select all articles"),
1190 "select_unread" => __("Select unread"),
1191 "select_marked" => __("Select starred"),
1192 "select_published" => __("Select published"),
1193 "select_invert" => __("Invert selection"),
1194 "select_none" => __("Deselect everything")),
1195 __("Feed") => array(
1196 "feed_refresh" => __("Refresh current feed"),
1197 "feed_unhide_read" => __("Un/hide read feeds"),
1198 "feed_subscribe" => __("Subscribe to feed"),
1199 "feed_edit" => __("Edit feed"),
1200 "feed_catchup" => __("Mark as read"),
1201 "feed_reverse" => __("Reverse headlines"),
1202 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1203 "feed_debug_update" => __("Debug feed update"),
1204 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1205 "catchup_all" => __("Mark all feeds as read"),
1206 "cat_toggle_collapse" => __("Un/collapse current category"),
1207 "toggle_combined_mode" => __("Toggle combined mode"),
1208 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1209 __("Go to") => array(
1210 "goto_all" => __("All articles"),
1211 "goto_fresh" => __("Fresh"),
1212 "goto_marked" => __("Starred"),
1213 "goto_published" => __("Published"),
1214 "goto_tagcloud" => __("Tag cloud"),
1215 "goto_prefs" => __("Preferences")),
1216 __("Other") => array(
1217 "create_label" => __("Create label"),
1218 "create_filter" => __("Create filter"),
1219 "collapse_sidebar" => __("Un/collapse sidebar"),
1220 "help_dialog" => __("Show help dialog"))
1221 );
1222
1223 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
1224 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1225 }
1226
1227 return $hotkeys;
1228 }
1229
1230 function get_hotkeys_map() {
1231 $hotkeys = array(
1232 // "navigation" => array(
1233 "k" => "next_feed",
1234 "j" => "prev_feed",
1235 "n" => "next_article",
1236 "p" => "prev_article",
1237 "(38)|up" => "prev_article",
1238 "(40)|down" => "next_article",
1239 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1240 // "^(40)|Ctrl-down" => "next_article_noscroll",
1241 "(191)|/" => "search_dialog",
1242 // "article" => array(
1243 "s" => "toggle_mark",
1244 "*s" => "toggle_publ",
1245 "u" => "toggle_unread",
1246 "*t" => "edit_tags",
1247 "o" => "open_in_new_window",
1248 "c p" => "catchup_below",
1249 "c n" => "catchup_above",
1250 "*n" => "article_scroll_down",
1251 "*p" => "article_scroll_up",
1252 "*(38)|Shift+up" => "article_scroll_up",
1253 "*(40)|Shift+down" => "article_scroll_down",
1254 "a *w" => "toggle_widescreen",
1255 "a e" => "toggle_embed_original",
1256 "e" => "email_article",
1257 "a q" => "close_article",
1258 // "article_selection" => array(
1259 "a a" => "select_all",
1260 "a u" => "select_unread",
1261 "a *u" => "select_marked",
1262 "a p" => "select_published",
1263 "a i" => "select_invert",
1264 "a n" => "select_none",
1265 // "feed" => array(
1266 "f r" => "feed_refresh",
1267 "f a" => "feed_unhide_read",
1268 "f s" => "feed_subscribe",
1269 "f e" => "feed_edit",
1270 "f q" => "feed_catchup",
1271 "f x" => "feed_reverse",
1272 "f g" => "feed_toggle_vgroup",
1273 "f *d" => "feed_debug_update",
1274 "f *g" => "feed_debug_viewfeed",
1275 "f *c" => "toggle_combined_mode",
1276 "f c" => "toggle_cdm_expanded",
1277 "*q" => "catchup_all",
1278 "x" => "cat_toggle_collapse",
1279 // "goto" => array(
1280 "g a" => "goto_all",
1281 "g f" => "goto_fresh",
1282 "g s" => "goto_marked",
1283 "g p" => "goto_published",
1284 "g t" => "goto_tagcloud",
1285 "g *p" => "goto_prefs",
1286 // "other" => array(
1287 "(9)|Tab" => "select_article_cursor", // tab
1288 "c l" => "create_label",
1289 "c f" => "create_filter",
1290 "c s" => "collapse_sidebar",
1291 "^(191)|Ctrl+/" => "help_dialog",
1292 );
1293
1294 if (get_pref('COMBINED_DISPLAY_MODE')) {
1295 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1296 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1297 }
1298
1299 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
1300 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1301 }
1302
1303 $prefixes = array();
1304
1305 foreach (array_keys($hotkeys) as $hotkey) {
1306 $pair = explode(" ", $hotkey, 2);
1307
1308 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1309 array_push($prefixes, $pair[0]);
1310 }
1311 }
1312
1313 return array($prefixes, $hotkeys);
1314 }
1315
1316 function check_for_update() {
1317 if (defined("GIT_VERSION_TIMESTAMP")) {
1318 $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1319
1320 if ($content) {
1321 $content = json_decode($content, true);
1322
1323 if ($content && isset($content["changeset"])) {
1324 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
1325 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
1326
1327 return $content["changeset"]["id"];
1328 }
1329 }
1330 }
1331 }
1332
1333 return "";
1334 }
1335
1336 function make_runtime_info($disable_update_check = false) {
1337 $data = array();
1338
1339 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1340 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1341
1342 $max_feed_id = db_fetch_result($result, 0, "mid");
1343 $num_feeds = db_fetch_result($result, 0, "nf");
1344
1345 $data["max_feed_id"] = (int) $max_feed_id;
1346 $data["num_feeds"] = (int) $num_feeds;
1347
1348 $data['last_article_id'] = Article::getLastArticleId();
1349 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1350
1351 $data['dep_ts'] = calculate_dep_timestamp();
1352 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1353
5e78b0c2 1354 $data["labels"] = Labels::get_all_labels($_SESSION["uid"]);
aeb1abed
AD
1355
1356 if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
1357 $update_result = @check_for_update();
1358
1359 $data["update_result"] = $update_result;
1360
1361 $_SESSION["last_version_check"] = time();
1362 }
1363
1364 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
1365
1366 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1367
1368 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1369
1370 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
1371
1372 if ($stamp) {
1373 $stamp_delta = time() - $stamp;
1374
1375 if ($stamp_delta > 1800) {
1376 $stamp_check = 0;
1377 } else {
1378 $stamp_check = 1;
1379 $_SESSION["daemon_stamp_check"] = time();
1380 }
1381
1382 $data['daemon_stamp_ok'] = $stamp_check;
1383
1384 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1385
1386 $data['daemon_stamp'] = $stamp_fmt;
1387 }
1388 }
1389 }
1390
1391 return $data;
1392 }
1393
1394 function search_to_sql($search, $search_language) {
1395
1396 $keywords = str_getcsv(trim($search), " ");
1397 $query_keywords = array();
1398 $search_words = array();
1399 $search_query_leftover = array();
1400
1401 if ($search_language)
1402 $search_language = db_escape_string(mb_strtolower($search_language));
1403 else
1404 $search_language = "english";
1405
1406 foreach ($keywords as $k) {
1407 if (strpos($k, "-") === 0) {
1408 $k = substr($k, 1);
1409 $not = "NOT";
1410 } else {
1411 $not = "";
1412 }
1413
1414 $commandpair = explode(":", mb_strtolower($k), 2);
1415
1416 switch ($commandpair[0]) {
1417 case "title":
1418 if ($commandpair[1]) {
1419 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1420 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1421 } else {
1422 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1423 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1424 array_push($search_words, $k);
1425 }
1426 break;
1427 case "author":
1428 if ($commandpair[1]) {
1429 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1430 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1431 } else {
1432 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1433 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1434 array_push($search_words, $k);
1435 }
1436 break;
1437 case "note":
1438 if ($commandpair[1]) {
1439 if ($commandpair[1] == "true")
1440 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1441 else if ($commandpair[1] == "false")
1442 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1443 else
1444 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1445 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1446 } else {
1447 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1448 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1449 if (!$not) array_push($search_words, $k);
1450 }
1451 break;
1452 case "star":
1453
1454 if ($commandpair[1]) {
1455 if ($commandpair[1] == "true")
1456 array_push($query_keywords, "($not (marked = true))");
1457 else
1458 array_push($query_keywords, "($not (marked = false))");
1459 } else {
1460 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1461 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1462 if (!$not) array_push($search_words, $k);
1463 }
1464 break;
1465 case "pub":
1466 if ($commandpair[1]) {
1467 if ($commandpair[1] == "true")
1468 array_push($query_keywords, "($not (published = true))");
1469 else
1470 array_push($query_keywords, "($not (published = false))");
1471
1472 } else {
1473 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1474 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1475 if (!$not) array_push($search_words, $k);
1476 }
1477 break;
1478 case "unread":
1479 if ($commandpair[1]) {
1480 if ($commandpair[1] == "true")
1481 array_push($query_keywords, "($not (unread = true))");
1482 else
1483 array_push($query_keywords, "($not (unread = false))");
1484
1485 } else {
1486 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1487 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1488 if (!$not) array_push($search_words, $k);
1489 }
1490 break;
1491 default:
1492 if (strpos($k, "@") === 0) {
1493
1494 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1495 $orig_ts = strtotime(substr($k, 1));
1496 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1497
1498 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1499
1500 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
1501 } else {
1502
1503 if (DB_TYPE == "pgsql") {
1504 $k = mb_strtolower($k);
1505 array_push($search_query_leftover, $not ? "!$k" : $k);
1506 } else {
1507 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1508 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1509 }
1510
1511 if (!$not) array_push($search_words, $k);
1512 }
1513 }
1514 }
1515
1516 if (count($search_query_leftover) > 0) {
1517 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1518
1519 if (DB_TYPE == "pgsql") {
1520 array_push($query_keywords,
1521 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1522 }
1523
1524 }
1525
1526 $search_query_part = implode("AND", $query_keywords);
1527
1528 return array($search_query_part, $search_words);
1529 }
1530
1531 function iframe_whitelisted($entry) {
1532 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1533
1534 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
1535
1536 if ($src) {
1537 foreach ($whitelist as $w) {
1538 if ($src == $w || $src == "www.$w")
1539 return true;
1540 }
1541 }
1542
1543 return false;
1544 }
1545
1546 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1547 if (!$owner) $owner = $_SESSION["uid"];
1548
1549 $res = trim($str); if (!$res) return '';
1550
1551 $charset_hack = '<head>
1552 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1553 </head>';
1554
1555 $res = trim($res); if (!$res) return '';
1556
1557 libxml_use_internal_errors(true);
1558
1559 $doc = new DOMDocument();
1560 $doc->loadHTML($charset_hack . $res);
1561 $xpath = new DOMXPath($doc);
1562
b2d42e96 1563 $rewrite_base_url = $site_url ? $site_url : get_self_url_prefix();
aeb1abed
AD
1564
1565 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1566
1567 foreach ($entries as $entry) {
1568
1569 if ($entry->hasAttribute('href')) {
1570 $entry->setAttribute('href',
1571 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1572
1573 $entry->setAttribute('rel', 'noopener noreferrer');
1574 }
1575
1576 if ($entry->hasAttribute('src')) {
1577 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1578 $cached_filename = CACHE_DIR . '/images/' . sha1($src);
1579
1580 if (file_exists($cached_filename)) {
1581
1582 // this is strictly cosmetic
1583 if ($entry->tagName == 'img') {
1584 $suffix = ".png";
1585 } else if ($entry->parentNode && $entry->parentNode->tagName == "video") {
1586 $suffix = ".mp4";
1587 } else if ($entry->parentNode && $entry->parentNode->tagName == "audio") {
1588 $suffix = ".ogg";
1589 } else {
1590 $suffix = "";
1591 }
1592
1593 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1594
1595 if ($entry->hasAttribute('srcset')) {
1596 $entry->removeAttribute('srcset');
1597 }
1598
1599 if ($entry->hasAttribute('sizes')) {
1600 $entry->removeAttribute('sizes');
1601 }
1602 }
1603
1604 $entry->setAttribute('src', $src);
1605 }
1606
1607 if ($entry->nodeName == 'img') {
1608
1609 if ($entry->hasAttribute('src')) {
1610 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
1611
9f7bd151 1612 if (is_prefix_https() && !$is_https_url) {
aeb1abed
AD
1613
1614 if ($entry->hasAttribute('srcset')) {
1615 $entry->removeAttribute('srcset');
1616 }
1617
1618 if ($entry->hasAttribute('sizes')) {
1619 $entry->removeAttribute('sizes');
1620 }
1621 }
1622 }
1623
1624 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1625 $force_remove_images || $_SESSION["bw_limit"]) {
1626
1627 $p = $doc->createElement('p');
1628
1629 $a = $doc->createElement('a');
1630 $a->setAttribute('href', $entry->getAttribute('src'));
1631
1632 $a->appendChild(new DOMText($entry->getAttribute('src')));
1633 $a->setAttribute('target', '_blank');
1634 $a->setAttribute('rel', 'noopener noreferrer');
1635
1636 $p->appendChild($a);
1637
1638 $entry->parentNode->replaceChild($p, $entry);
1639 }
1640 }
1641
1642 if (strtolower($entry->nodeName) == "a") {
1643 $entry->setAttribute("target", "_blank");
1644 $entry->setAttribute("rel", "noopener noreferrer");
1645 }
1646 }
1647
1648 $entries = $xpath->query('//iframe');
1649 foreach ($entries as $entry) {
1650 if (!iframe_whitelisted($entry)) {
1651 $entry->setAttribute('sandbox', 'allow-scripts');
1652 } else {
9f7bd151 1653 if (is_prefix_https()) {
aeb1abed
AD
1654 $entry->setAttribute("src",
1655 str_replace("http://", "https://",
1656 $entry->getAttribute("src")));
1657 }
1658 }
1659 }
1660
1661 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1662 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1663 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1664 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1665 'dt', 'em', 'footer', 'figure', 'figcaption',
1666 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1667 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1668 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1669 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1670 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1671 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1672
1673 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1674
1675 $disallowed_attributes = array('id', 'style', 'class');
1676
1677 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
1678 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1679 if (is_array($retval)) {
1680 $doc = $retval[0];
1681 $allowed_elements = $retval[1];
1682 $disallowed_attributes = $retval[2];
1683 } else {
1684 $doc = $retval;
1685 }
1686 }
1687
1688 $doc->removeChild($doc->firstChild); //remove doctype
1689 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1690
1691 if ($highlight_words) {
1692 foreach ($highlight_words as $word) {
1693
1694 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1695
1696 $elements = $xpath->query("//*/text()");
1697
1698 foreach ($elements as $child) {
1699
1700 $fragment = $doc->createDocumentFragment();
1701 $text = $child->textContent;
1702
1703 while (($pos = mb_stripos($text, $word)) !== false) {
1704 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1705 $word = mb_substr($text, $pos, mb_strlen($word));
1706 $highlight = $doc->createElement('span');
1707 $highlight->appendChild(new DomText($word));
1708 $highlight->setAttribute('class', 'highlight');
1709 $fragment->appendChild($highlight);
1710 $text = mb_substr($text, $pos + mb_strlen($word));
1711 }
1712
1713 if (!empty($text)) $fragment->appendChild(new DomText($text));
1714
1715 $child->parentNode->replaceChild($fragment, $child);
1716 }
1717 }
1718 }
1719
1720 $res = $doc->saveHTML();
1721
1722 /* strip everything outside of <body>...</body> */
1723
1724 $res_frag = array();
1725 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1726 return $res_frag[1];
1727 } else {
1728 return $res;
1729 }
1730 }
1731
1732 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1733 $xpath = new DOMXPath($doc);
1734 $entries = $xpath->query('//*');
1735
1736 foreach ($entries as $entry) {
1737 if (!in_array($entry->nodeName, $allowed_elements)) {
1738 $entry->parentNode->removeChild($entry);
1739 }
1740
1741 if ($entry->hasAttributes()) {
1742 $attrs_to_remove = array();
1743
1744 foreach ($entry->attributes as $attr) {
1745
1746 if (strpos($attr->nodeName, 'on') === 0) {
1747 array_push($attrs_to_remove, $attr);
1748 }
1749
1750 if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1751 array_push($attrs_to_remove, $attr);
1752 }
1753
1754 if (in_array($attr->nodeName, $disallowed_attributes)) {
1755 array_push($attrs_to_remove, $attr);
1756 }
1757 }
1758
1759 foreach ($attrs_to_remove as $attr) {
1760 $entry->removeAttributeNode($attr);
1761 }
1762 }
1763 }
1764
1765 return $doc;
1766 }
1767
1768 function trim_array($array) {
1769 $tmp = $array;
1770 array_walk($tmp, 'trim');
1771 return $tmp;
1772 }
1773
1774 function tag_is_valid($tag) {
1775 if ($tag == '') return false;
1776 if (is_numeric($tag)) return false;
1777 if (mb_strlen($tag) > 250) return false;
1778
1779 if (!$tag) return false;
1780
1781 return true;
1782 }
1783
1784 function render_login_form() {
1785 header('Cache-Control: public');
1786
1787 require_once "login_form.php";
1788 exit;
1789 }
1790
1791 function T_sprintf() {
1792 $args = func_get_args();
1793 return vsprintf(__(array_shift($args)), $args);
1794 }
1795
1796 function print_checkpoint($n, $s) {
1797 $ts = microtime(true);
1798 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1799 return $ts;
1800 }
1801
1802 function sanitize_tag($tag) {
1803 $tag = trim($tag);
1804
1805 $tag = mb_strtolower($tag, 'utf-8');
1806
1807 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1808
1809 if (DB_TYPE == "mysql") {
1810 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1811 }
1812
1813 return $tag;
1814 }
1815
9f7bd151 1816 function is_server_https() {
e234ac8d 1817 return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) || $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
9f7bd151
AD
1818 }
1819
1820 function is_prefix_https() {
1821 return parse_url(SELF_URL_PATH, PHP_URL_SCHEME) == 'https';
1822 }
1823
b2d42e96 1824 // this returns SELF_URL_PATH sans ending slash
aeb1abed
AD
1825 function get_self_url_prefix() {
1826 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1827 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1828 } else {
1829 return SELF_URL_PATH;
1830 }
1831 }
1832
aeb1abed
AD
1833 function encrypt_password($pass, $salt = '', $mode2 = false) {
1834 if ($salt && $mode2) {
1835 return "MODE2:" . hash('sha256', $salt . $pass);
1836 } else if ($salt) {
1837 return "SHA1X:" . sha1("$salt:$pass");
1838 } else {
1839 return "SHA1:" . sha1($pass);
1840 }
1841 } // function encrypt_password
1842
1843 function load_filters($feed_id, $owner_uid) {
1844 $filters = array();
1845
0086a897 1846 $cat_id = (int)Feeds::getFeedCategory($feed_id);
aeb1abed
AD
1847
1848 if ($cat_id == 0)
1849 $null_cat_qpart = "cat_id IS NULL OR";
1850 else
1851 $null_cat_qpart = "";
1852
1853 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1854 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1855
02f3992a 1856 $check_cats = array_merge(
aeb1abed 1857 Feeds::getParentCategories($cat_id, $owner_uid),
02f3992a
AD
1858 [$cat_id]);
1859
1860 $check_cats_str = join(",", $check_cats);
1861 $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
aeb1abed
AD
1862
1863 while ($line = db_fetch_assoc($result)) {
1864 $filter_id = $line["id"];
1865
02f3992a
AD
1866 $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
1867
aeb1abed 1868 $result2 = db_query("SELECT
02f3992a 1869 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
aeb1abed
AD
1870 FROM ttrss_filters2_rules AS r,
1871 ttrss_filter_types AS t
1872 WHERE
b2d42e96 1873 (match_on IS NOT NULL OR
02f3992a
AD
1874 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
1875 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
aeb1abed
AD
1876 filter_type = t.id AND filter_id = '$filter_id'");
1877
1878 $rules = array();
1879 $actions = array();
1880
1881 while ($rule_line = db_fetch_assoc($result2)) {
1882 # print_r($rule_line);
1883
02f3992a
AD
1884 if ($rule_line["match_on"]) {
1885 $match_on = json_decode($rule_line["match_on"], true);
aeb1abed 1886
02f3992a 1887 if (in_array("0", $match_on) || in_array($feed_id, $match_on) || count(array_intersect($check_cats_fullids, $match_on)) > 0) {
aeb1abed 1888
02f3992a
AD
1889 $rule = array();
1890 $rule["reg_exp"] = $rule_line["reg_exp"];
1891 $rule["type"] = $rule_line["type_name"];
1892 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1893
1894 array_push($rules, $rule);
1895 } else if (!$match_any_rule) {
1896 // this filter contains a rule that doesn't match to this feed/category combination
1897 // thus filter has to be rejected
aeb1abed 1898
02f3992a
AD
1899 $rules = [];
1900 break;
1901 }
aeb1abed 1902
02f3992a 1903 } else {
0bf7e007 1904
02f3992a
AD
1905 $rule = array();
1906 $rule["reg_exp"] = $rule_line["reg_exp"];
1907 $rule["type"] = $rule_line["type_name"];
1908 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1909
1910 array_push($rules, $rule);
1911 }
aeb1abed
AD
1912 }
1913
02f3992a
AD
1914 if (count($rules) > 0) {
1915 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1916 FROM ttrss_filters2_actions AS a,
1917 ttrss_filter_actions AS t
1918 WHERE
1919 action_id = t.id AND filter_id = '$filter_id'");
1920
1921 while ($action_line = db_fetch_assoc($result2)) {
1922 # print_r($action_line);
1923
1924 $action = array();
1925 $action["type"] = $action_line["type_name"];
1926 $action["param"] = $action_line["action_param"];
1927
1928 array_push($actions, $action);
1929 }
1930 }
aeb1abed
AD
1931
1932 $filter = array();
1933 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1934 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1935 $filter["rules"] = $rules;
1936 $filter["actions"] = $actions;
1937
1938 if (count($rules) > 0 && count($actions) > 0) {
1939 array_push($filters, $filter);
1940 }
1941 }
1942
1943 return $filters;
1944 }
1945
1946 function get_score_pic($score) {
1947 if ($score > 100) {
1948 return "score_high.png";
1949 } else if ($score > 0) {
1950 return "score_half_high.png";
1951 } else if ($score < -100) {
1952 return "score_low.png";
1953 } else if ($score < 0) {
1954 return "score_half_low.png";
1955 } else {
1956 return "score_neutral.png";
1957 }
1958 }
1959
1960 function feed_has_icon($id) {
1961 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1962 }
1963
1964 function init_plugins() {
1965 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1966
1967 return true;
1968 }
1969
1970 function add_feed_category($feed_cat, $parent_cat_id = false) {
1971
1972 if (!$feed_cat) return false;
1973
1974 db_query("BEGIN");
1975
1976 if ($parent_cat_id) {
1977 $parent_qpart = "parent_cat = '$parent_cat_id'";
1978 $parent_insert = "'$parent_cat_id'";
1979 } else {
1980 $parent_qpart = "parent_cat IS NULL";
1981 $parent_insert = "NULL";
1982 }
1983
1984 $feed_cat = mb_substr($feed_cat, 0, 250);
1985
1986 $result = db_query(
1987 "SELECT id FROM ttrss_feed_categories
1988 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1989
1990 if (db_num_rows($result) == 0) {
1991
1992 $result = db_query(
1993 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1994 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1995
1996 db_query("COMMIT");
1997
1998 return true;
1999 }
2000
2001 return false;
2002 }
2003
2004 /**
2005 * Fixes incomplete URLs by prepending "http://".
2006 * Also replaces feed:// with http://, and
2007 * prepends a trailing slash if the url is a domain name only.
2008 *
2009 * @param string $url Possibly incomplete URL
2010 *
2011 * @return string Fixed URL.
2012 */
2013 function fix_url($url) {
2014
2015 // support schema-less urls
2016 if (strpos($url, '//') === 0) {
2017 $url = 'https:' . $url;
2018 }
2019
2020 if (strpos($url, '://') === false) {
2021 $url = 'http://' . $url;
2022 } else if (substr($url, 0, 5) == 'feed:') {
2023 $url = 'http:' . substr($url, 5);
2024 }
2025
2026 //prepend slash if the URL has no slash in it
2027 // "http://www.example" -> "http://www.example/"
2028 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
2029 $url .= '/';
2030 }
2031
2032 //convert IDNA hostname to punycode if possible
2033 if (function_exists("idn_to_ascii")) {
2034 $parts = parse_url($url);
2035 if (mb_detect_encoding($parts['host']) != 'ASCII')
2036 {
2037 $parts['host'] = idn_to_ascii($parts['host']);
2038 $url = build_url($parts);
2039 }
2040 }
2041
2042 if ($url != "http:///")
2043 return $url;
2044 else
2045 return '';
2046 }
2047
2048 function validate_feed_url($url) {
2049 $parts = parse_url($url);
2050
2051 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
2052
2053 }
2054
2055 /* function save_email_address($email) {
2056 // FIXME: implement persistent storage of emails
2057
2058 if (!$_SESSION['stored_emails'])
2059 $_SESSION['stored_emails'] = array();
2060
2061 if (!in_array($email, $_SESSION['stored_emails']))
2062 array_push($_SESSION['stored_emails'], $email);
2063 } */
2064
2065
2066 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2067
2068 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2069
2070 $sql_is_cat = bool_to_sql_bool($is_cat);
2071
2072 $result = db_query("SELECT access_key FROM ttrss_access_keys
2073 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2074 AND owner_uid = " . $owner_uid);
2075
2076 if (db_num_rows($result) == 1) {
2077 return db_fetch_result($result, 0, "access_key");
2078 } else {
2079 $key = db_escape_string(uniqid_short());
2080
2081 $result = db_query("INSERT INTO ttrss_access_keys
2082 (access_key, feed_id, is_cat, owner_uid)
2083 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2084
2085 return $key;
2086 }
2087 return false;
2088 }
2089
2090 function get_feeds_from_html($url, $content)
2091 {
2092 $url = fix_url($url);
2093 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
2094
2095 libxml_use_internal_errors(true);
2096
2097 $doc = new DOMDocument();
2098 $doc->loadHTML($content);
2099 $xpath = new DOMXPath($doc);
2100 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2101 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2102 $feedUrls = array();
2103 foreach ($entries as $entry) {
2104 if ($entry->hasAttribute('href')) {
2105 $title = $entry->getAttribute('title');
2106 if ($title == '') {
2107 $title = $entry->getAttribute('type');
2108 }
2109 $feedUrl = rewrite_relative_url(
2110 $baseUrl, $entry->getAttribute('href')
2111 );
2112 $feedUrls[$feedUrl] = $title;
2113 }
2114 }
2115 return $feedUrls;
2116 }
2117
2118 function is_html($content) {
2119 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2120 }
2121
2122 function url_is_html($url, $login = false, $pass = false) {
2123 return is_html(fetch_file_contents($url, false, $login, $pass));
2124 }
2125
2126 function build_url($parts) {
2127 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2128 }
2129
2130 function cleanup_url_path($path) {
2131 $path = str_replace("/./", "/", $path);
2132 $path = str_replace("//", "/", $path);
2133
2134 return $path;
2135 }
2136
2137 /**
2138 * Converts a (possibly) relative URL to a absolute one.
2139 *
2140 * @param string $url Base URL (i.e. from where the document is)
2141 * @param string $rel_url Possibly relative URL in the document
2142 *
2143 * @return string Absolute URL
2144 */
2145 function rewrite_relative_url($url, $rel_url) {
2146 if (strpos($rel_url, "://") !== false) {
2147 return $rel_url;
2148 } else if (strpos($rel_url, "//") === 0) {
2149 # protocol-relative URL (rare but they exist)
2150 return $rel_url;
2151 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2152 # magnet:, feed:, etc
2153 return $rel_url;
2154 } else if (strpos($rel_url, "/") === 0) {
2155 $parts = parse_url($url);
2156 $parts['path'] = $rel_url;
2157 $parts['path'] = cleanup_url_path($parts['path']);
2158
2159 return build_url($parts);
2160
2161 } else {
2162 $parts = parse_url($url);
2163 if (!isset($parts['path'])) {
2164 $parts['path'] = '/';
2165 }
2166 $dir = $parts['path'];
2167 if (substr($dir, -1) !== '/') {
2168 $dir = dirname($parts['path']);
2169 $dir !== '/' && $dir .= '/';
2170 }
2171 $parts['path'] = $dir . $rel_url;
2172 $parts['path'] = cleanup_url_path($parts['path']);
2173
2174 return build_url($parts);
2175 }
2176 }
2177
2178 function cleanup_tags($days = 14, $limit = 1000) {
2179
2180 if (DB_TYPE == "pgsql") {
2181 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2182 } else if (DB_TYPE == "mysql") {
2183 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2184 }
2185
2186 $tags_deleted = 0;
2187
2188 while ($limit > 0) {
2189 $limit_part = 500;
2190
2191 $query = "SELECT ttrss_tags.id AS id
2192 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2193 WHERE post_int_id = int_id AND $interval_query AND
2194 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2195
2196 $result = db_query($query);
2197
2198 $ids = array();
2199
2200 while ($line = db_fetch_assoc($result)) {
2201 array_push($ids, $line['id']);
2202 }
2203
2204 if (count($ids) > 0) {
2205 $ids = join(",", $ids);
2206
2207 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2208 $tags_deleted += db_affected_rows($tmp_result);
2209 } else {
2210 break;
2211 }
2212
2213 $limit -= $limit_part;
2214 }
2215
2216 return $tags_deleted;
2217 }
2218
2219 function print_user_stylesheet() {
2220 $value = get_pref('USER_STYLESHEET');
2221
2222 if ($value) {
2223 print "<style type=\"text/css\">";
2224 print str_replace("<br/>", "\n", $value);
2225 print "</style>";
2226 }
2227
2228 }
2229
2230 function filter_to_sql($filter, $owner_uid) {
2231 $query = array();
2232
2233 if (DB_TYPE == "pgsql")
2234 $reg_qpart = "~";
2235 else
2236 $reg_qpart = "REGEXP";
2237
2238 foreach ($filter["rules"] AS $rule) {
2239 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2240 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2241 $rule['reg_exp']) !== FALSE;
2242
2243 if ($regexp_valid) {
2244
2245 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2246
2247 switch ($rule["type"]) {
2248 case "title":
2249 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2250 $rule['reg_exp'] . "')";
2251 break;
2252 case "content":
2253 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2254 $rule['reg_exp'] . "')";
2255 break;
2256 case "both":
2257 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2258 $rule['reg_exp'] . "') OR LOWER(" .
2259 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2260 break;
2261 case "tag":
2262 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2263 $rule['reg_exp'] . "')";
2264 break;
2265 case "link":
2266 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2267 $rule['reg_exp'] . "')";
2268 break;
2269 case "author":
2270 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2271 $rule['reg_exp'] . "')";
2272 break;
2273 }
2274
2275 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2276
2277 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2278 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2279 }
2280
2281 if (isset($rule["cat_id"])) {
2282
2283 if ($rule["cat_id"] > 0) {
2284 $children = Feeds::getChildCategories($rule["cat_id"], $owner_uid);
2285 array_push($children, $rule["cat_id"]);
2286
2287 $children = join(",", $children);
2288
2289 $cat_qpart = "cat_id IN ($children)";
2290 } else {
2291 $cat_qpart = "cat_id IS NULL";
2292 }
2293
2294 $qpart .= " AND $cat_qpart";
2295 }
2296
2297 $qpart .= " AND feed_id IS NOT NULL";
2298
2299 array_push($query, "($qpart)");
2300
2301 }
2302 }
2303
2304 if (count($query) > 0) {
2305 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2306 } else {
2307 $fullquery = "(false)";
2308 }
2309
2310 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2311
2312 return $fullquery;
2313 }
2314
2315 if (!function_exists('gzdecode')) {
2316 function gzdecode($string) { // no support for 2nd argument
2317 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2318 base64_encode($string));
2319 }
2320 }
2321
2322 function get_random_bytes($length) {
2323 if (function_exists('openssl_random_pseudo_bytes')) {
2324 return openssl_random_pseudo_bytes($length);
2325 } else {
2326 $output = "";
2327
2328 for ($i = 0; $i < $length; $i++)
2329 $output .= chr(mt_rand(0, 255));
2330
2331 return $output;
2332 }
2333 }
2334
2335 function read_stdin() {
2336 $fp = fopen("php://stdin", "r");
2337
2338 if ($fp) {
2339 $line = trim(fgets($fp));
2340 fclose($fp);
2341 return $line;
2342 }
2343
2344 return null;
2345 }
2346
aeb1abed
AD
2347 function implements_interface($class, $interface) {
2348 return in_array($interface, class_implements($class));
2349 }
2350
2351 function get_minified_js($files) {
2352 require_once 'lib/jshrink/Minifier.php';
2353
2354 $rv = '';
2355
2356 foreach ($files as $js) {
2357 if (!isset($_GET['debug'])) {
2358 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2359
2360 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2361
2362 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2363
2364 if ($header && $contents) {
2365 list($htag, $hversion) = explode(":", $header);
2366
2367 if ($htag == "tt-rss" && $hversion == VERSION) {
2368 $rv .= $contents;
2369 continue;
2370 }
2371 }
2372 }
2373
2374 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2375 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2376 $rv .= $minified;
2377
2378 } else {
2379 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2380 }
2381 }
2382
2383 return $rv;
2384 }
2385
2386 function calculate_dep_timestamp() {
2387 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2388
2389 $max_ts = -1;
2390
2391 foreach ($files as $file) {
2392 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2393 }
2394
2395 return $max_ts;
2396 }
2397
2398 function T_js_decl($s1, $s2) {
2399 if ($s1 && $s2) {
2400 $s1 = preg_replace("/\n/", "", $s1);
2401 $s2 = preg_replace("/\n/", "", $s2);
2402
2403 $s1 = preg_replace("/\"/", "\\\"", $s1);
2404 $s2 = preg_replace("/\"/", "\\\"", $s2);
2405
2406 return "T_messages[\"$s1\"] = \"$s2\";\n";
2407 }
2408 }
2409
2410 function init_js_translations() {
2411
2412 print 'var T_messages = new Object();
b2d42e96 2413
aeb1abed
AD
2414 function __(msg) {
2415 if (T_messages[msg]) {
2416 return T_messages[msg];
2417 } else {
2418 return msg;
2419 }
2420 }
b2d42e96 2421
aeb1abed
AD
2422 function ngettext(msg1, msg2, n) {
2423 return __((parseInt(n) > 1) ? msg2 : msg1);
2424 }';
2425
2426 $l10n = _get_reader();
2427
2428 for ($i = 0; $i < $l10n->total; $i++) {
2429 $orig = $l10n->get_original_string($i);
2430 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2431 $key = explode(chr(0), $orig);
2432 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2433 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2434 } else {
2435 $translation = __($orig);
2436 print T_js_decl($orig, $translation);
2437 }
2438 }
2439 }
2440
aeb1abed
AD
2441 function get_theme_path($theme) {
2442 $check = "themes/$theme";
2443 if (file_exists($check)) return $check;
2444
2445 $check = "themes.local/$theme";
2446 if (file_exists($check)) return $check;
2447 }
2448
2449 function theme_valid($theme) {
2450 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2451
2452 if (in_array($theme, $bundled_themes)) return true;
2453
2454 $file = "themes/" . basename($theme);
2455
2456 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2457
2458 if (file_exists($file) && is_readable($file)) {
2459 $fh = fopen($file, "r");
2460
2461 if ($fh) {
2462 $header = fgets($fh);
2463 fclose($fh);
2464
2465 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2466 }
2467 }
2468
2469 return false;
2470 }
2471
2472 /**
2473 * @SuppressWarnings(unused)
2474 */
2475 function error_json($code) {
2476 require_once "errors.php";
2477
2478 @$message = $ERRORS[$code];
2479
2480 return json_encode(array("error" =>
2481 array("code" => $code, "message" => $message)));
2482
2483 }
2484
904aff76 2485 /*function abs_to_rel_path($dir) {
aeb1abed
AD
2486 $tmp = str_replace(dirname(__DIR__), "", $dir);
2487
2488 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2489
2490 return $tmp;
904aff76 2491 }*/
aeb1abed
AD
2492
2493 function get_upload_error_message($code) {
2494
2495 $errors = array(
2496 0 => __('There is no error, the file uploaded with success'),
2497 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2498 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2499 3 => __('The uploaded file was only partially uploaded'),
2500 4 => __('No file was uploaded'),
2501 6 => __('Missing a temporary folder'),
2502 7 => __('Failed to write file to disk.'),
2503 8 => __('A PHP extension stopped the file upload.'),
2504 );
2505
2506 return $errors[$code];
2507 }
2508
2509 function base64_img($filename) {
2510 if (file_exists($filename)) {
2511 $ext = pathinfo($filename, PATHINFO_EXTENSION);
2512
2513 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));
2514 } else {
2515 return "";
2516 }
2517 }
2518
8b73bd28
AD
2519 /* this is essentially a wrapper for readfile() which allows plugins to hook
2520 output with httpd-specific "fast" implementation i.e. X-Sendfile or whatever else
2521
2522 hook function should return true if request was handled (or at least attempted to)
2523
2524 note that this can be called without user context so the plugin to handle this
2525 should be loaded systemwide in config.php */
2526 function send_local_file($filename) {
2527 if (file_exists($filename)) {
2528 $tmppluginhost = new PluginHost();
2529
2530 $tmppluginhost->load(PLUGINS, PluginHost::KIND_SYSTEM);
2531 $tmppluginhost->load_data();
2532
2533 foreach ($tmppluginhost->get_hooks(PluginHost::HOOK_SEND_LOCAL_FILE) as $plugin) {
2534 if ($plugin->hook_send_local_file($filename)) return true;
2535 }
2536
2537 $mimetype = mime_content_type($filename);
2538 header("Content-type: $mimetype");
2539
2540 $stamp = gmdate("D, d M Y H:i:s", filemtime($filename)) . " GMT";
2541 header("Last-Modified: $stamp", true);
2542
2543 return readfile($filename);
2544 } else {
2545 return false;
2546 }
2547 }
2548