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