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