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