2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 131);
5 define('LABEL_BASE_INDEX', -1024);
6 define('PLUGIN_FEED_BASE_INDEX', -128);
8 define('COOKIE_LIFETIME_LONG', 86400*365);
10 $fetch_last_error = false;
11 $fetch_last_error_code = false;
12 $fetch_last_content_type = false;
13 $fetch_last_error_content = false; // curl only for the time being
14 $fetch_curl_used = false;
15 $suppress_debugging = false;
17 libxml_disable_entity_loader(true);
19 // separate test because this is included before sanity checks
20 if (function_exists("mb_internal_encoding")) mb_internal_encoding("UTF-8");
22 date_default_timezone_set('UTC');
23 if (defined('E_DEPRECATED')) {
24 error_reporting(E_ALL
& ~E_NOTICE
& ~E_DEPRECATED
);
26 error_reporting(E_ALL
& ~E_NOTICE
);
29 require_once 'config.php';
32 * Define a constant if not already defined
34 function define_default($name, $value) {
35 defined($name) or define($name, $value);
38 /* Some tunables you can override in config.php using define(): */
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
50 define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
51 // stop updating feeds if users haven't logged in for X days
52 define_default('DAEMON_FEED_LIMIT', 500);
53 // feed limit for one update batch
54 define_default('DAEMON_SLEEP_INTERVAL', 120);
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
61 /* tunables end here */
63 if (DB_TYPE
== "pgsql") {
64 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
66 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
70 * Return available translations names.
73 * @return array A array of available translations.
75 function get_translations() {
77 "auto" => "Detect automatically",
78 "ar_SA" => "العربيّة (Arabic)",
79 "bg_BG" => "Bulgarian",
84 "el_GR" => "Ελληνικά",
85 "es_ES" => "Español (España)",
88 "fr_FR" => "Français",
89 "hu_HU" => "Magyar (Hungarian)",
90 "it_IT" => "Italiano",
91 "ja_JP" => "日本語 (Japanese)",
92 "lv_LV" => "Latviešu",
93 "nb_NO" => "Norwegian bokmål",
97 "pt_BR" => "Portuguese/Brazil",
98 "pt_PT" => "Portuguese/Portugal",
99 "zh_CN" => "Simplified Chinese",
100 "zh_TW" => "Traditional Chinese",
101 "sv_SE" => "Svenska",
103 "tr_TR" => "Türkçe");
108 require_once "lib/accept-to-gettext.php";
109 require_once "lib/gettext/gettext.inc";
111 function startup_gettext() {
113 # Get locale from Accept-Language header
114 $lang = al2gt(array_keys(get_translations()), "text/html");
116 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
117 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
120 if ($_SESSION["uid"] && get_schema_version() >= 120) {
121 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
123 if ($pref_lang && $pref_lang != 'auto') {
129 if (defined('LC_MESSAGES')) {
130 _setlocale(LC_MESSAGES
, $lang);
131 } else if (defined('LC_ALL')) {
132 _setlocale(LC_ALL
, $lang);
135 _bindtextdomain("messages", "locale");
137 _textdomain("messages");
138 _bind_textdomain_codeset("messages", "UTF-8");
142 require_once 'db-prefs.php';
143 require_once 'version.php';
144 require_once 'controls.php';
146 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
147 ini_set('user_agent', SELF_USER_AGENT
);
149 $schema_version = false;
151 function _debug_suppress($suppress) {
152 global $suppress_debugging;
154 $suppress_debugging = $suppress;
158 * Print a timestamped debug message.
160 * @param string $msg The debug message.
163 function _debug($msg, $show = true) {
164 global $suppress_debugging;
166 //echo "[$suppress_debugging] $msg $show\n";
168 if ($suppress_debugging) return false;
170 $ts = strftime("%H:%M:%S", time());
171 if (function_exists('posix_getpid')) {
172 $ts = "$ts/" . posix_getpid();
175 if ($show && !(defined('QUIET') && QUIET
)) {
176 print "[$ts] $msg\n";
179 if (defined('LOGFILE')) {
180 $fp = fopen(LOGFILE
, 'a+');
185 if (function_exists("flock")) {
188 // try to lock logfile for writing
189 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
200 fputs($fp, "[$ts] $msg\n");
202 if (function_exists("flock")) {
213 * Purge a feed old posts.
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.
222 function purge_feed($feed_id, $purge_interval, $debug = false) {
224 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
229 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
233 if (db_num_rows($result) == 1) {
234 $owner_uid = db_fetch_result($result, 0, "owner_uid");
237 if ($purge_interval == -1 ||
!$purge_interval) {
239 CCache
::update($feed_id, $owner_uid);
244 if (!$owner_uid) return;
246 if (FORCE_ARTICLE_PURGE
== 0) {
247 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
250 $purge_unread = true;
251 $purge_interval = FORCE_ARTICLE_PURGE
;
254 if (!$purge_unread) $query_limit = " unread = false AND ";
256 if (DB_TYPE
== "pgsql") {
257 $result = db_query("DELETE FROM ttrss_user_entries
259 WHERE ttrss_entries.id = ref_id AND
261 feed_id = '$feed_id' AND
263 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
267 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
268 marked = false AND feed_id = '$feed_id' AND
269 (SELECT date_updated FROM ttrss_entries WHERE
270 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
272 $result = db_query("DELETE FROM ttrss_user_entries
273 USING ttrss_user_entries, ttrss_entries
274 WHERE ttrss_entries.id = ref_id AND
276 feed_id = '$feed_id' AND
278 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
281 $rows = db_affected_rows($result);
283 CCache
::update($feed_id, $owner_uid);
286 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
290 } // function purge_feed
292 function feed_purge_interval($feed_id) {
294 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
295 WHERE id = '$feed_id'");
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");
301 if ($purge_interval == 0) $purge_interval = get_pref(
302 'PURGE_OLD_DAYS', $owner_uid);
304 return $purge_interval;
311 /*function get_feed_update_interval($feed_id) {
312 $result = db_query("SELECT owner_uid, update_interval FROM
313 ttrss_feeds WHERE id = '$feed_id'");
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");
319 if ($update_interval != 0) {
320 return $update_interval;
322 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
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*/) {
334 global $fetch_last_error;
335 global $fetch_last_error_code;
336 global $fetch_last_error_content;
337 global $fetch_last_content_type;
338 global $fetch_curl_used;
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;
346 if (!is_array($options)) {
348 // falling back on compatibility shim
349 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
352 for ($i = 0; $i < func_num_args(); $i++
) {
353 $tmp[$option_names[$i]] = func_get_arg($i);
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)
370 $url = $options["url"];
371 $type = isset($options["type"]) ?
$options["type"] : false;
372 $login = isset($options["login"]) ?
$options["login"] : false;
373 $pass = isset($options["pass"]) ?
$options["pass"] : false;
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;
378 $followlocation = isset($options["followlocation"]) ?
$options["followlocation"] : true;
380 $url = ltrim($url, ' ');
381 $url = str_replace(' ', '%20', $url);
383 if (strpos($url, "//") === 0)
384 $url = 'http:' . $url;
386 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
388 $fetch_curl_used = true;
390 $ch = curl_init($url);
392 if ($timestamp && !$post_query) {
393 curl_setopt($ch, CURLOPT_HTTPHEADER
,
394 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
397 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
398 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
399 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("open_basedir") && $followlocation);
400 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
401 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
402 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
403 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
404 curl_setopt($ch, CURLOPT_USERAGENT
, $useragent ?
$useragent :
406 curl_setopt($ch, CURLOPT_ENCODING
, "");
407 //curl_setopt($ch, CURLOPT_REFERER, $url);
409 if (!ini_get("open_basedir")) {
410 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
413 if (defined('_CURL_HTTP_PROXY')) {
414 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
418 curl_setopt($ch, CURLOPT_POST
, true);
419 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
423 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
425 $contents = @curl_exec
($ch);
427 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
428 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
429 $contents = @curl_exec
($ch);
432 if ($contents === false) {
433 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
438 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
439 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
441 $fetch_last_error_code = $http_code;
443 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
444 if (curl_errno($ch) != 0) {
445 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
447 $fetch_last_error = "HTTP Code: $http_code";
449 $fetch_last_error_content = $contents;
459 $fetch_curl_used = false;
461 if ($login && $pass){
462 $url_parts = array();
464 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
466 $pass = urlencode($pass);
468 if ($url_parts[1] && $url_parts[2]) {
469 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
473 // TODO: should this support POST requests or not? idk
475 if (!$post_query && $timestamp) {
476 $context = stream_context_create(array(
479 'ignore_errors' => true,
480 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
481 'protocol_version'=> 1.1,
482 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
485 $context = stream_context_create(array(
488 'ignore_errors' => true,
489 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
490 'protocol_version'=> 1.1
494 $old_error = error_get_last();
496 $data = @file_get_contents
($url, false, $context);
498 if (isset($http_response_header) && is_array($http_response_header)) {
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
506 if (substr(strtolower($h), 0, 7) == 'http/1.') {
507 $fetch_last_error_code = (int) substr($h, 9, 3);
512 if ($fetch_last_error_code != 200) {
513 $error = error_get_last();
515 if ($error['message'] != $old_error['message']) {
516 $fetch_last_error = $error["message"];
518 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
521 $fetch_last_error_content = $data;
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
535 * @param string $url A feed or page URL
537 * @return mixed The favicon URL, or false if none was found.
539 function get_favicon_url($url) {
541 $favicon_url = false;
543 if ($html = @fetch_file_contents
($url)) {
545 libxml_use_internal_errors(true);
547 $doc = new DOMDocument();
548 $doc->loadHTML($html);
549 $xpath = new DOMXPath($doc);
551 $base = $xpath->query('/html/head/base');
552 foreach ($base as $b) {
553 $url = $b->getAttribute("href");
557 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
558 if (count($entries) > 0) {
559 foreach ($entries as $entry) {
560 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
567 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
570 } // function get_favicon_url
572 function initialize_user_prefs($uid, $profile = false) {
574 $uid = db_escape_string($uid);
578 $profile_qpart = "AND profile IS NULL";
580 $profile_qpart = "AND profile = '$profile'";
583 if (get_schema_version() < 63) $profile_qpart = "";
587 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
589 $u_result = db_query("SELECT pref_name
590 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
592 $active_prefs = array();
594 while ($line = db_fetch_assoc($u_result)) {
595 array_push($active_prefs, $line["pref_name"]);
598 while ($line = db_fetch_assoc($result)) {
599 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
600 // print "adding " . $line["pref_name"] . "<br>";
602 $line["def_value"] = db_escape_string($line["def_value"]);
603 $line["pref_name"] = db_escape_string($line["pref_name"]);
605 if (get_schema_version() < 63) {
606 db_query("INSERT INTO ttrss_user_prefs
607 (owner_uid,pref_name,value) VALUES
608 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
611 db_query("INSERT INTO ttrss_user_prefs
612 (owner_uid,pref_name,value, profile) VALUES
613 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
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"]);
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"]);
639 function authenticate_user($login, $password, $check_only = false) {
641 if (!SINGLE_USER_MODE
) {
644 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
646 $user_id = (int) $plugin->authenticate($login, $password);
649 $_SESSION["auth_module"] = strtolower(get_class($plugin));
654 if ($user_id && !$check_only) {
657 $_SESSION["uid"] = $user_id;
658 $_SESSION["version"] = VERSION_STATIC
;
660 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
661 WHERE id = '$user_id'");
663 $_SESSION["name"] = db_fetch_result($result, 0, "login");
664 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
665 $_SESSION["csrf_token"] = uniqid_short();
667 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
670 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
671 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
672 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
674 $_SESSION["last_version_check"] = time();
676 initialize_user_prefs($_SESSION["uid"]);
685 $_SESSION["uid"] = 1;
686 $_SESSION["name"] = "admin";
687 $_SESSION["access_level"] = 10;
689 $_SESSION["hide_hello"] = true;
690 $_SESSION["hide_logout"] = true;
692 $_SESSION["auth_module"] = false;
694 if (!$_SESSION["csrf_token"]) {
695 $_SESSION["csrf_token"] = uniqid_short();
698 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
700 initialize_user_prefs($_SESSION["uid"]);
706 function make_password($length = 8) {
709 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
713 while ($i < $length) {
714 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
716 if (!strstr($password, $char)) {
724 // this is called after user is created to initialize default feeds, labels
727 // user preferences are checked on every login, not here
729 function initialize_user($uid) {
731 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
732 values ('$uid', 'Tiny Tiny RSS: Forum',
733 'http://tt-rss.org/forum/rss.php')");
736 function logout_user() {
738 if (isset($_COOKIE[session_name()])) {
739 setcookie(session_name(), '', time()-42000, '/');
743 function validate_csrf($csrf_token) {
744 return $csrf_token == $_SESSION['csrf_token'];
747 function load_user_plugins($owner_uid, $pluginhost = false) {
749 if (!$pluginhost) $pluginhost = PluginHost
::getInstance();
751 if ($owner_uid && SCHEMA_VERSION
>= 100) {
752 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
754 $pluginhost->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
756 if (get_schema_version() > 100) {
757 $pluginhost->load_data();
762 function login_sequence() {
763 if (SINGLE_USER_MODE
) {
765 authenticate_user("admin", null);
767 load_user_plugins($_SESSION["uid"]);
769 if (!validate_session()) $_SESSION["uid"] = false;
771 if (!$_SESSION["uid"]) {
773 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
774 $_SESSION["ref_schema_version"] = get_schema_version(true);
776 authenticate_user(null, null, true);
779 if (!$_SESSION["uid"]) {
781 setcookie(session_name(), '', time()-42000, '/');
788 /* bump login timestamp */
789 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
791 $_SESSION["last_login_update"] = time();
794 if ($_SESSION["uid"]) {
796 load_user_plugins($_SESSION["uid"]);
800 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
801 $_SESSION["uid"] . " AND
802 (SELECT COUNT(id) FROM ttrss_feeds WHERE
803 ttrss_feeds.id = feed_id) = 0");
805 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
806 $_SESSION["uid"] . " AND
807 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
808 ttrss_feed_categories.id = feed_id) = 0");
815 function truncate_string($str, $max_len, $suffix = '…') {
816 if (mb_strlen($str, "utf-8") > $max_len) {
817 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
824 function truncate_middle($str, $max_len, $suffix = '…') {
825 if (strlen($str) > $max_len) {
826 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
832 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
835 $source_tz = new DateTimeZone($source_tz);
836 } catch (Exception
$e) {
837 $source_tz = new DateTimeZone('UTC');
841 $dest_tz = new DateTimeZone($dest_tz);
842 } catch (Exception
$e) {
843 $dest_tz = new DateTimeZone('UTC');
846 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
847 return $dt->format('U') +
$dest_tz->getOffset($dt);
850 function make_local_datetime($timestamp, $long, $owner_uid = false,
851 $no_smart_dt = false, $eta_min = false) {
853 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
854 if (!$timestamp) $timestamp = '1970-01-01 0:00';
859 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
861 $timestamp = substr($timestamp, 0, 19);
863 # We store date in UTC internally
864 $dt = new DateTime($timestamp, $utc_tz);
866 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
868 if ($user_tz_string != 'Automatic') {
871 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
872 } catch (Exception
$e) {
876 $tz_offset = $user_tz->getOffset($dt);
878 $tz_offset = (int) -$_SESSION["clientTzOffset"];
881 $user_timestamp = $dt->format('U') +
$tz_offset;
884 return smart_date_time($user_timestamp,
885 $tz_offset, $owner_uid, $eta_min);
888 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
890 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
892 return date($format, $user_timestamp);
896 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
897 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
899 if ($eta_min && time() +
$tz_offset - $timestamp < 3600) {
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)) {
902 return date("G:i", $timestamp);
903 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
904 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
905 return date($format, $timestamp);
907 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
908 return date($format, $timestamp);
912 function sql_bool_to_bool($s) {
913 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
920 function bool_to_sql_bool($s) {
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.
931 function get_schema_version($nocache = false) {
932 global $schema_version;
934 if (!$schema_version && !$nocache) {
935 $result = db_query("SELECT schema_version FROM ttrss_version");
936 $version = db_fetch_result($result, 0, "schema_version");
937 $schema_version = $version;
940 return $schema_version;
944 function sanity_check() {
945 require_once 'errors.php';
949 $schema_version = get_schema_version(true);
951 if ($schema_version != SCHEMA_VERSION
) {
955 if (DB_TYPE
== "mysql") {
956 $result = db_query("SELECT true", false);
957 if (db_num_rows($result) != 1) {
962 if (db_escape_string("testTEST") != "testTEST") {
966 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
969 function file_is_locked($filename) {
970 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
971 if (function_exists('flock')) {
972 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
974 if (flock($fp, LOCK_EX | LOCK_NB
)) {
985 return true; // consider the file always locked and skip the test
992 function make_lockfile($filename) {
993 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
995 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
996 $stat_h = fstat($fp);
997 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
999 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
1000 if ($stat_h["ino"] != $stat_f["ino"] ||
1001 $stat_h["dev"] != $stat_f["dev"]) {
1007 if (function_exists('posix_getpid')) {
1008 fwrite($fp, posix_getpid() . "\n");
1016 function make_stampfile($filename) {
1017 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1019 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1020 fwrite($fp, time() . "\n");
1021 flock($fp, LOCK_UN
);
1029 function sql_random_function() {
1030 if (DB_TYPE
== "mysql") {
1037 function getFeedUnread($feed, $is_cat = false) {
1038 return Feeds
::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1042 /*function get_pgsql_version() {
1043 $result = db_query("SELECT version() AS version");
1044 $version = explode(" ", db_fetch_result($result, 0, "version"));
1048 function checkbox_to_sql_bool($val) {
1049 return ($val == "on") ?
"true" : "false";
1052 /*function getFeedCatTitle($id) {
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");
1064 return __("Uncategorized");
1067 return "getFeedCatTitle($id) failed";
1072 function uniqid_short() {
1073 return uniqid(base_convert(rand(), 10, 36));
1076 function make_init_params() {
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) {
1084 $params[strtolower($param)] = (int) get_pref($param);
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
;
1095 $theme = get_pref( "USER_CSS_THEME", false, false);
1096 $params["theme"] = theme_valid("$theme") ?
$theme : "";
1098 $params["plugins"] = implode(", ", PluginHost
::getInstance()->get_plugin_names());
1100 $params["php_platform"] = PHP_OS
;
1101 $params["php_version"] = PHP_VERSION
;
1103 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1105 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1106 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1108 $max_feed_id = db_fetch_result($result, 0, "mid");
1109 $num_feeds = db_fetch_result($result, 0, "nf");
1111 $params["max_feed_id"] = (int) $max_feed_id;
1112 $params["num_feeds"] = (int) $num_feeds;
1114 $params["hotkeys"] = get_hotkeys_map();
1116 $params["csrf_token"] = $_SESSION["csrf_token"];
1117 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1119 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
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");
1129 function get_hotkeys_info() {
1131 __("Navigation") => array(
1132 "next_feed" => __("Open next feed"),
1133 "prev_feed" => __("Open previous feed"),
1134 "next_article" => __("Open next article"),
1135 "prev_article" => __("Open previous article"),
1136 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1137 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1138 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1139 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1140 "search_dialog" => __("Show search dialog")),
1141 __("Article") => array(
1142 "toggle_mark" => __("Toggle starred"),
1143 "toggle_publ" => __("Toggle published"),
1144 "toggle_unread" => __("Toggle unread"),
1145 "edit_tags" => __("Edit tags"),
1146 "open_in_new_window" => __("Open in new window"),
1147 "catchup_below" => __("Mark below as read"),
1148 "catchup_above" => __("Mark above as read"),
1149 "article_scroll_down" => __("Scroll down"),
1150 "article_scroll_up" => __("Scroll up"),
1151 "select_article_cursor" => __("Select article under cursor"),
1152 "email_article" => __("Email article"),
1153 "close_article" => __("Close/collapse article"),
1154 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1155 "toggle_widescreen" => __("Toggle widescreen mode"),
1156 "toggle_embed_original" => __("Toggle embed original")),
1157 __("Article selection") => array(
1158 "select_all" => __("Select all articles"),
1159 "select_unread" => __("Select unread"),
1160 "select_marked" => __("Select starred"),
1161 "select_published" => __("Select published"),
1162 "select_invert" => __("Invert selection"),
1163 "select_none" => __("Deselect everything")),
1164 __("Feed") => array(
1165 "feed_refresh" => __("Refresh current feed"),
1166 "feed_unhide_read" => __("Un/hide read feeds"),
1167 "feed_subscribe" => __("Subscribe to feed"),
1168 "feed_edit" => __("Edit feed"),
1169 "feed_catchup" => __("Mark as read"),
1170 "feed_reverse" => __("Reverse headlines"),
1171 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1172 "feed_debug_update" => __("Debug feed update"),
1173 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1174 "catchup_all" => __("Mark all feeds as read"),
1175 "cat_toggle_collapse" => __("Un/collapse current category"),
1176 "toggle_combined_mode" => __("Toggle combined mode"),
1177 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1178 __("Go to") => array(
1179 "goto_all" => __("All articles"),
1180 "goto_fresh" => __("Fresh"),
1181 "goto_marked" => __("Starred"),
1182 "goto_published" => __("Published"),
1183 "goto_tagcloud" => __("Tag cloud"),
1184 "goto_prefs" => __("Preferences")),
1185 __("Other") => array(
1186 "create_label" => __("Create label"),
1187 "create_filter" => __("Create filter"),
1188 "collapse_sidebar" => __("Un/collapse sidebar"),
1189 "help_dialog" => __("Show help dialog"))
1192 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
1193 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1199 function get_hotkeys_map() {
1201 // "navigation" => array(
1204 "n" => "next_article",
1205 "p" => "prev_article",
1206 "(38)|up" => "prev_article",
1207 "(40)|down" => "next_article",
1208 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1209 // "^(40)|Ctrl-down" => "next_article_noscroll",
1210 "(191)|/" => "search_dialog",
1211 // "article" => array(
1212 "s" => "toggle_mark",
1213 "*s" => "toggle_publ",
1214 "u" => "toggle_unread",
1215 "*t" => "edit_tags",
1216 "o" => "open_in_new_window",
1217 "c p" => "catchup_below",
1218 "c n" => "catchup_above",
1219 "*n" => "article_scroll_down",
1220 "*p" => "article_scroll_up",
1221 "*(38)|Shift+up" => "article_scroll_up",
1222 "*(40)|Shift+down" => "article_scroll_down",
1223 "a *w" => "toggle_widescreen",
1224 "a e" => "toggle_embed_original",
1225 "e" => "email_article",
1226 "a q" => "close_article",
1227 // "article_selection" => array(
1228 "a a" => "select_all",
1229 "a u" => "select_unread",
1230 "a *u" => "select_marked",
1231 "a p" => "select_published",
1232 "a i" => "select_invert",
1233 "a n" => "select_none",
1235 "f r" => "feed_refresh",
1236 "f a" => "feed_unhide_read",
1237 "f s" => "feed_subscribe",
1238 "f e" => "feed_edit",
1239 "f q" => "feed_catchup",
1240 "f x" => "feed_reverse",
1241 "f g" => "feed_toggle_vgroup",
1242 "f *d" => "feed_debug_update",
1243 "f *g" => "feed_debug_viewfeed",
1244 "f *c" => "toggle_combined_mode",
1245 "f c" => "toggle_cdm_expanded",
1246 "*q" => "catchup_all",
1247 "x" => "cat_toggle_collapse",
1249 "g a" => "goto_all",
1250 "g f" => "goto_fresh",
1251 "g s" => "goto_marked",
1252 "g p" => "goto_published",
1253 "g t" => "goto_tagcloud",
1254 "g *p" => "goto_prefs",
1255 // "other" => array(
1256 "(9)|Tab" => "select_article_cursor", // tab
1257 "c l" => "create_label",
1258 "c f" => "create_filter",
1259 "c s" => "collapse_sidebar",
1260 "^(191)|Ctrl+/" => "help_dialog",
1263 if (get_pref('COMBINED_DISPLAY_MODE')) {
1264 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1265 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1268 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
1269 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1272 $prefixes = array();
1274 foreach (array_keys($hotkeys) as $hotkey) {
1275 $pair = explode(" ", $hotkey, 2);
1277 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1278 array_push($prefixes, $pair[0]);
1282 return array($prefixes, $hotkeys);
1285 function check_for_update() {
1286 if (defined("GIT_VERSION_TIMESTAMP")) {
1287 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1290 $content = json_decode($content, true);
1292 if ($content && isset($content["changeset"])) {
1293 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
1294 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
1296 return $content["changeset"]["id"];
1305 function make_runtime_info($disable_update_check = false) {
1308 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1309 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1311 $max_feed_id = db_fetch_result($result, 0, "mid");
1312 $num_feeds = db_fetch_result($result, 0, "nf");
1314 $data["max_feed_id"] = (int) $max_feed_id;
1315 $data["num_feeds"] = (int) $num_feeds;
1317 $data['last_article_id'] = Article
::getLastArticleId();
1318 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1320 $data['dep_ts'] = calculate_dep_timestamp();
1321 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1324 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
1325 $update_result = @check_for_update
();
1327 $data["update_result"] = $update_result;
1329 $_SESSION["last_version_check"] = time();
1332 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
1334 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1336 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1338 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
1341 $stamp_delta = time() - $stamp;
1343 if ($stamp_delta > 1800) {
1347 $_SESSION["daemon_stamp_check"] = time();
1350 $data['daemon_stamp_ok'] = $stamp_check;
1352 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1354 $data['daemon_stamp'] = $stamp_fmt;
1362 function search_to_sql($search, $search_language) {
1364 $keywords = str_getcsv(trim($search), " ");
1365 $query_keywords = array();
1366 $search_words = array();
1367 $search_query_leftover = array();
1369 if ($search_language)
1370 $search_language = db_escape_string(mb_strtolower($search_language));
1372 $search_language = "english";
1374 foreach ($keywords as $k) {
1375 if (strpos($k, "-") === 0) {
1382 $commandpair = explode(":", mb_strtolower($k), 2);
1384 switch ($commandpair[0]) {
1386 if ($commandpair[1]) {
1387 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1388 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1390 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1391 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1392 array_push($search_words, $k);
1396 if ($commandpair[1]) {
1397 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1398 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1400 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1401 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1402 array_push($search_words, $k);
1406 if ($commandpair[1]) {
1407 if ($commandpair[1] == "true")
1408 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1409 else if ($commandpair[1] == "false")
1410 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1412 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1413 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1415 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1416 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1417 if (!$not) array_push($search_words, $k);
1422 if ($commandpair[1]) {
1423 if ($commandpair[1] == "true")
1424 array_push($query_keywords, "($not (marked = true))");
1426 array_push($query_keywords, "($not (marked = false))");
1428 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1429 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1430 if (!$not) array_push($search_words, $k);
1434 if ($commandpair[1]) {
1435 if ($commandpair[1] == "true")
1436 array_push($query_keywords, "($not (published = true))");
1438 array_push($query_keywords, "($not (published = false))");
1441 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1442 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1443 if (!$not) array_push($search_words, $k);
1447 if ($commandpair[1]) {
1448 if ($commandpair[1] == "true")
1449 array_push($query_keywords, "($not (unread = true))");
1451 array_push($query_keywords, "($not (unread = false))");
1454 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1455 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1456 if (!$not) array_push($search_words, $k);
1460 if (strpos($k, "@") === 0) {
1462 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1463 $orig_ts = strtotime(substr($k, 1));
1464 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1466 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1468 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
1471 if (DB_TYPE
== "pgsql") {
1472 $k = mb_strtolower($k);
1473 array_push($search_query_leftover, $not ?
"!$k" : $k);
1475 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1476 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1479 if (!$not) array_push($search_words, $k);
1484 if (count($search_query_leftover) > 0) {
1485 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1487 if (DB_TYPE
== "pgsql") {
1488 array_push($query_keywords,
1489 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1494 $search_query_part = implode("AND", $query_keywords);
1496 return array($search_query_part, $search_words);
1499 function iframe_whitelisted($entry) {
1500 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1502 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
1505 foreach ($whitelist as $w) {
1506 if ($src == $w ||
$src == "www.$w")
1514 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1515 if (!$owner) $owner = $_SESSION["uid"];
1517 $res = trim($str); if (!$res) return '';
1519 $charset_hack = '<head>
1520 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1523 $res = trim($res); if (!$res) return '';
1525 libxml_use_internal_errors(true);
1527 $doc = new DOMDocument();
1528 $doc->loadHTML($charset_hack . $res);
1529 $xpath = new DOMXPath($doc);
1531 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
1532 $rewrite_base_url = $site_url ?
$site_url : get_self_url_prefix();
1534 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1536 foreach ($entries as $entry) {
1538 if ($entry->hasAttribute('href')) {
1539 $entry->setAttribute('href',
1540 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1542 $entry->setAttribute('rel', 'noopener noreferrer');
1545 if ($entry->hasAttribute('src')) {
1546 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1547 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1549 if (file_exists($cached_filename)) {
1551 // this is strictly cosmetic
1552 if ($entry->tagName
== 'img') {
1554 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1556 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1562 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1564 if ($entry->hasAttribute('srcset')) {
1565 $entry->removeAttribute('srcset');
1568 if ($entry->hasAttribute('sizes')) {
1569 $entry->removeAttribute('sizes');
1573 $entry->setAttribute('src', $src);
1576 if ($entry->nodeName
== 'img') {
1578 if ($entry->hasAttribute('src')) {
1579 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1581 if ($ttrss_uses_https && !$is_https_url) {
1583 if ($entry->hasAttribute('srcset')) {
1584 $entry->removeAttribute('srcset');
1587 if ($entry->hasAttribute('sizes')) {
1588 $entry->removeAttribute('sizes');
1593 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1594 $force_remove_images ||
$_SESSION["bw_limit"]) {
1596 $p = $doc->createElement('p');
1598 $a = $doc->createElement('a');
1599 $a->setAttribute('href', $entry->getAttribute('src'));
1601 $a->appendChild(new DOMText($entry->getAttribute('src')));
1602 $a->setAttribute('target', '_blank');
1603 $a->setAttribute('rel', 'noopener noreferrer');
1605 $p->appendChild($a);
1607 $entry->parentNode
->replaceChild($p, $entry);
1611 if (strtolower($entry->nodeName
) == "a") {
1612 $entry->setAttribute("target", "_blank");
1613 $entry->setAttribute("rel", "noopener noreferrer");
1617 $entries = $xpath->query('//iframe');
1618 foreach ($entries as $entry) {
1619 if (!iframe_whitelisted($entry)) {
1620 $entry->setAttribute('sandbox', 'allow-scripts');
1622 if ($_SERVER['HTTPS'] == "on") {
1623 $entry->setAttribute("src",
1624 str_replace("http://", "https://",
1625 $entry->getAttribute("src")));
1630 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1631 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1632 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1633 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1634 'dt', 'em', 'footer', 'figure', 'figcaption',
1635 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1636 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1637 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1638 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1639 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1640 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1642 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1644 $disallowed_attributes = array('id', 'style', 'class');
1646 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1647 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1648 if (is_array($retval)) {
1650 $allowed_elements = $retval[1];
1651 $disallowed_attributes = $retval[2];
1657 $doc->removeChild($doc->firstChild
); //remove doctype
1658 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1660 if ($highlight_words) {
1661 foreach ($highlight_words as $word) {
1663 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1665 $elements = $xpath->query("//*/text()");
1667 foreach ($elements as $child) {
1669 $fragment = $doc->createDocumentFragment();
1670 $text = $child->textContent
;
1672 while (($pos = mb_stripos($text, $word)) !== false) {
1673 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1674 $word = mb_substr($text, $pos, mb_strlen($word));
1675 $highlight = $doc->createElement('span');
1676 $highlight->appendChild(new DomText($word));
1677 $highlight->setAttribute('class', 'highlight');
1678 $fragment->appendChild($highlight);
1679 $text = mb_substr($text, $pos +
mb_strlen($word));
1682 if (!empty($text)) $fragment->appendChild(new DomText($text));
1684 $child->parentNode
->replaceChild($fragment, $child);
1689 $res = $doc->saveHTML();
1691 /* strip everything outside of <body>...</body> */
1693 $res_frag = array();
1694 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1695 return $res_frag[1];
1701 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1702 $xpath = new DOMXPath($doc);
1703 $entries = $xpath->query('//*');
1705 foreach ($entries as $entry) {
1706 if (!in_array($entry->nodeName
, $allowed_elements)) {
1707 $entry->parentNode
->removeChild($entry);
1710 if ($entry->hasAttributes()) {
1711 $attrs_to_remove = array();
1713 foreach ($entry->attributes
as $attr) {
1715 if (strpos($attr->nodeName
, 'on') === 0) {
1716 array_push($attrs_to_remove, $attr);
1719 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1720 array_push($attrs_to_remove, $attr);
1723 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1724 array_push($attrs_to_remove, $attr);
1728 foreach ($attrs_to_remove as $attr) {
1729 $entry->removeAttributeNode($attr);
1737 function trim_array($array) {
1739 array_walk($tmp, 'trim');
1743 function tag_is_valid($tag) {
1744 if ($tag == '') return false;
1745 if (is_numeric($tag)) return false;
1746 if (mb_strlen($tag) > 250) return false;
1748 if (!$tag) return false;
1753 function render_login_form() {
1754 header('Cache-Control: public');
1756 require_once "login_form.php";
1760 function T_sprintf() {
1761 $args = func_get_args();
1762 return vsprintf(__(array_shift($args)), $args);
1765 function print_checkpoint($n, $s) {
1766 $ts = microtime(true);
1767 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1771 function sanitize_tag($tag) {
1774 $tag = mb_strtolower($tag, 'utf-8');
1776 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1778 if (DB_TYPE
== "mysql") {
1779 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1785 // this returns SELF_URL_PATH sans ending slash
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);
1790 return SELF_URL_PATH
;
1794 function encrypt_password($pass, $salt = '', $mode2 = false) {
1795 if ($salt && $mode2) {
1796 return "MODE2:" . hash('sha256', $salt . $pass);
1798 return "SHA1X:" . sha1("$salt:$pass");
1800 return "SHA1:" . sha1($pass);
1802 } // function encrypt_password
1804 function load_filters($feed_id, $owner_uid) {
1807 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1810 $null_cat_qpart = "cat_id IS NULL OR";
1812 $null_cat_qpart = "";
1814 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1815 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1817 $check_cats = array_merge(
1818 Feeds
::getParentCategories($cat_id, $owner_uid),
1821 $check_cats_str = join(",", $check_cats);
1822 $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
1824 while ($line = db_fetch_assoc($result)) {
1825 $filter_id = $line["id"];
1827 $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
1829 $result2 = db_query("SELECT
1830 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
1831 FROM ttrss_filters2_rules AS r,
1832 ttrss_filter_types AS t
1834 (match_on IS NOT NULL OR
1835 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
1836 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
1837 filter_type = t.id AND filter_id = '$filter_id'");
1842 while ($rule_line = db_fetch_assoc($result2)) {
1843 # print_r($rule_line);
1845 if ($rule_line["match_on"]) {
1846 $match_on = json_decode($rule_line["match_on"], true);
1848 if (in_array("0", $match_on) ||
in_array($feed_id, $match_on) ||
count(array_intersect($check_cats_fullids, $match_on)) > 0) {
1851 $rule["reg_exp"] = $rule_line["reg_exp"];
1852 $rule["type"] = $rule_line["type_name"];
1853 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1855 array_push($rules, $rule);
1856 } else if (!$match_any_rule) {
1857 // this filter contains a rule that doesn't match to this feed/category combination
1858 // thus filter has to be rejected
1867 $rule["reg_exp"] = $rule_line["reg_exp"];
1868 $rule["type"] = $rule_line["type_name"];
1869 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1871 array_push($rules, $rule);
1875 if (count($rules) > 0) {
1876 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1877 FROM ttrss_filters2_actions AS a,
1878 ttrss_filter_actions AS t
1880 action_id = t.id AND filter_id = '$filter_id'");
1882 while ($action_line = db_fetch_assoc($result2)) {
1883 # print_r($action_line);
1886 $action["type"] = $action_line["type_name"];
1887 $action["param"] = $action_line["action_param"];
1889 array_push($actions, $action);
1894 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1895 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1896 $filter["rules"] = $rules;
1897 $filter["actions"] = $actions;
1899 if (count($rules) > 0 && count($actions) > 0) {
1900 array_push($filters, $filter);
1907 function get_score_pic($score) {
1909 return "score_high.png";
1910 } else if ($score > 0) {
1911 return "score_half_high.png";
1912 } else if ($score < -100) {
1913 return "score_low.png";
1914 } else if ($score < 0) {
1915 return "score_half_low.png";
1917 return "score_neutral.png";
1921 function feed_has_icon($id) {
1922 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1925 function init_plugins() {
1926 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1931 function add_feed_category($feed_cat, $parent_cat_id = false) {
1933 if (!$feed_cat) return false;
1937 if ($parent_cat_id) {
1938 $parent_qpart = "parent_cat = '$parent_cat_id'";
1939 $parent_insert = "'$parent_cat_id'";
1941 $parent_qpart = "parent_cat IS NULL";
1942 $parent_insert = "NULL";
1945 $feed_cat = mb_substr($feed_cat, 0, 250);
1948 "SELECT id FROM ttrss_feed_categories
1949 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1951 if (db_num_rows($result) == 0) {
1954 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1955 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1966 * Fixes incomplete URLs by prepending "http://".
1967 * Also replaces feed:// with http://, and
1968 * prepends a trailing slash if the url is a domain name only.
1970 * @param string $url Possibly incomplete URL
1972 * @return string Fixed URL.
1974 function fix_url($url) {
1976 // support schema-less urls
1977 if (strpos($url, '//') === 0) {
1978 $url = 'https:' . $url;
1981 if (strpos($url, '://') === false) {
1982 $url = 'http://' . $url;
1983 } else if (substr($url, 0, 5) == 'feed:') {
1984 $url = 'http:' . substr($url, 5);
1987 //prepend slash if the URL has no slash in it
1988 // "http://www.example" -> "http://www.example/"
1989 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1993 //convert IDNA hostname to punycode if possible
1994 if (function_exists("idn_to_ascii")) {
1995 $parts = parse_url($url);
1996 if (mb_detect_encoding($parts['host']) != 'ASCII')
1998 $parts['host'] = idn_to_ascii($parts['host']);
1999 $url = build_url($parts);
2003 if ($url != "http:///")
2009 function validate_feed_url($url) {
2010 $parts = parse_url($url);
2012 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2016 /* function save_email_address($email) {
2017 // FIXME: implement persistent storage of emails
2019 if (!$_SESSION['stored_emails'])
2020 $_SESSION['stored_emails'] = array();
2022 if (!in_array($email, $_SESSION['stored_emails']))
2023 array_push($_SESSION['stored_emails'], $email);
2027 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2029 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2031 $sql_is_cat = bool_to_sql_bool($is_cat);
2033 $result = db_query("SELECT access_key FROM ttrss_access_keys
2034 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2035 AND owner_uid = " . $owner_uid);
2037 if (db_num_rows($result) == 1) {
2038 return db_fetch_result($result, 0, "access_key");
2040 $key = db_escape_string(uniqid_short());
2042 $result = db_query("INSERT INTO ttrss_access_keys
2043 (access_key, feed_id, is_cat, owner_uid)
2044 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2051 function get_feeds_from_html($url, $content)
2053 $url = fix_url($url);
2054 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2056 libxml_use_internal_errors(true);
2058 $doc = new DOMDocument();
2059 $doc->loadHTML($content);
2060 $xpath = new DOMXPath($doc);
2061 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2062 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2063 $feedUrls = array();
2064 foreach ($entries as $entry) {
2065 if ($entry->hasAttribute('href')) {
2066 $title = $entry->getAttribute('title');
2068 $title = $entry->getAttribute('type');
2070 $feedUrl = rewrite_relative_url(
2071 $baseUrl, $entry->getAttribute('href')
2073 $feedUrls[$feedUrl] = $title;
2079 function is_html($content) {
2080 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2083 function url_is_html($url, $login = false, $pass = false) {
2084 return is_html(fetch_file_contents($url, false, $login, $pass));
2087 function build_url($parts) {
2088 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2091 function cleanup_url_path($path) {
2092 $path = str_replace("/./", "/", $path);
2093 $path = str_replace("//", "/", $path);
2099 * Converts a (possibly) relative URL to a absolute one.
2101 * @param string $url Base URL (i.e. from where the document is)
2102 * @param string $rel_url Possibly relative URL in the document
2104 * @return string Absolute URL
2106 function rewrite_relative_url($url, $rel_url) {
2107 if (strpos($rel_url, "://") !== false) {
2109 } else if (strpos($rel_url, "//") === 0) {
2110 # protocol-relative URL (rare but they exist)
2112 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2113 # magnet:, feed:, etc
2115 } else if (strpos($rel_url, "/") === 0) {
2116 $parts = parse_url($url);
2117 $parts['path'] = $rel_url;
2118 $parts['path'] = cleanup_url_path($parts['path']);
2120 return build_url($parts);
2123 $parts = parse_url($url);
2124 if (!isset($parts['path'])) {
2125 $parts['path'] = '/';
2127 $dir = $parts['path'];
2128 if (substr($dir, -1) !== '/') {
2129 $dir = dirname($parts['path']);
2130 $dir !== '/' && $dir .= '/';
2132 $parts['path'] = $dir . $rel_url;
2133 $parts['path'] = cleanup_url_path($parts['path']);
2135 return build_url($parts);
2139 function cleanup_tags($days = 14, $limit = 1000) {
2141 if (DB_TYPE
== "pgsql") {
2142 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2143 } else if (DB_TYPE
== "mysql") {
2144 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2149 while ($limit > 0) {
2152 $query = "SELECT ttrss_tags.id AS id
2153 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2154 WHERE post_int_id = int_id AND $interval_query AND
2155 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2157 $result = db_query($query);
2161 while ($line = db_fetch_assoc($result)) {
2162 array_push($ids, $line['id']);
2165 if (count($ids) > 0) {
2166 $ids = join(",", $ids);
2168 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2169 $tags_deleted +
= db_affected_rows($tmp_result);
2174 $limit -= $limit_part;
2177 return $tags_deleted;
2180 function print_user_stylesheet() {
2181 $value = get_pref('USER_STYLESHEET');
2184 print "<style type=\"text/css\">";
2185 print str_replace("<br/>", "\n", $value);
2191 function filter_to_sql($filter, $owner_uid) {
2194 if (DB_TYPE
== "pgsql")
2197 $reg_qpart = "REGEXP";
2199 foreach ($filter["rules"] AS $rule) {
2200 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2201 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2202 $rule['reg_exp']) !== FALSE;
2204 if ($regexp_valid) {
2206 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2208 switch ($rule["type"]) {
2210 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2211 $rule['reg_exp'] . "')";
2214 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2215 $rule['reg_exp'] . "')";
2218 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2219 $rule['reg_exp'] . "') OR LOWER(" .
2220 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2223 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2224 $rule['reg_exp'] . "')";
2227 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2228 $rule['reg_exp'] . "')";
2231 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2232 $rule['reg_exp'] . "')";
2236 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2238 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2239 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2242 if (isset($rule["cat_id"])) {
2244 if ($rule["cat_id"] > 0) {
2245 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2246 array_push($children, $rule["cat_id"]);
2248 $children = join(",", $children);
2250 $cat_qpart = "cat_id IN ($children)";
2252 $cat_qpart = "cat_id IS NULL";
2255 $qpart .= " AND $cat_qpart";
2258 $qpart .= " AND feed_id IS NOT NULL";
2260 array_push($query, "($qpart)");
2265 if (count($query) > 0) {
2266 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2268 $fullquery = "(false)";
2271 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2276 if (!function_exists('gzdecode')) {
2277 function gzdecode($string) { // no support for 2nd argument
2278 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2279 base64_encode($string));
2283 function get_random_bytes($length) {
2284 if (function_exists('openssl_random_pseudo_bytes')) {
2285 return openssl_random_pseudo_bytes($length);
2289 for ($i = 0; $i < $length; $i++
)
2290 $output .= chr(mt_rand(0, 255));
2296 function read_stdin() {
2297 $fp = fopen("php://stdin", "r");
2300 $line = trim(fgets($fp));
2308 function implements_interface($class, $interface) {
2309 return in_array($interface, class_implements($class));
2312 function get_minified_js($files) {
2313 require_once 'lib/jshrink/Minifier.php';
2317 foreach ($files as $js) {
2318 if (!isset($_GET['debug'])) {
2319 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2321 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2323 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2325 if ($header && $contents) {
2326 list($htag, $hversion) = explode(":", $header);
2328 if ($htag == "tt-rss" && $hversion == VERSION
) {
2335 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2336 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2340 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2347 function calculate_dep_timestamp() {
2348 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2352 foreach ($files as $file) {
2353 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2359 function T_js_decl($s1, $s2) {
2361 $s1 = preg_replace("/\n/", "", $s1);
2362 $s2 = preg_replace("/\n/", "", $s2);
2364 $s1 = preg_replace("/\"/", "\\\"", $s1);
2365 $s2 = preg_replace("/\"/", "\\\"", $s2);
2367 return "T_messages[\"$s1\"] = \"$s2\";\n";
2371 function init_js_translations() {
2373 print 'var T_messages = new Object();
2376 if (T_messages[msg]) {
2377 return T_messages[msg];
2383 function ngettext(msg1, msg2, n) {
2384 return __((parseInt(n) > 1) ? msg2 : msg1);
2387 $l10n = _get_reader();
2389 for ($i = 0; $i < $l10n->total
; $i++
) {
2390 $orig = $l10n->get_original_string($i);
2391 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2392 $key = explode(chr(0), $orig);
2393 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2394 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2396 $translation = __($orig);
2397 print T_js_decl($orig, $translation);
2402 function get_theme_path($theme) {
2403 $check = "themes/$theme";
2404 if (file_exists($check)) return $check;
2406 $check = "themes.local/$theme";
2407 if (file_exists($check)) return $check;
2410 function theme_valid($theme) {
2411 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2413 if (in_array($theme, $bundled_themes)) return true;
2415 $file = "themes/" . basename($theme);
2417 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2419 if (file_exists($file) && is_readable($file)) {
2420 $fh = fopen($file, "r");
2423 $header = fgets($fh);
2426 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2434 * @SuppressWarnings(unused)
2436 function error_json($code) {
2437 require_once "errors.php";
2439 @$message = $ERRORS[$code];
2441 return json_encode(array("error" =>
2442 array("code" => $code, "message" => $message)));
2446 /*function abs_to_rel_path($dir) {
2447 $tmp = str_replace(dirname(__DIR__), "", $dir);
2449 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2454 function get_upload_error_message($code) {
2457 0 => __('There is no error, the file uploaded with success'),
2458 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2459 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2460 3 => __('The uploaded file was only partially uploaded'),
2461 4 => __('No file was uploaded'),
2462 6 => __('Missing a temporary folder'),
2463 7 => __('Failed to write file to disk.'),
2464 8 => __('A PHP extension stopped the file upload.'),
2467 return $errors[$code];
2470 function base64_img($filename) {
2471 if (file_exists($filename)) {
2472 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2474 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));