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