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 $rewrite_base_url = $site_url ?
$site_url : get_self_url_prefix();
1533 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1535 foreach ($entries as $entry) {
1537 if ($entry->hasAttribute('href')) {
1538 $entry->setAttribute('href',
1539 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1541 $entry->setAttribute('rel', 'noopener noreferrer');
1544 if ($entry->hasAttribute('src')) {
1545 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1546 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1548 if (file_exists($cached_filename)) {
1550 // this is strictly cosmetic
1551 if ($entry->tagName
== 'img') {
1553 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1555 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1561 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1563 if ($entry->hasAttribute('srcset')) {
1564 $entry->removeAttribute('srcset');
1567 if ($entry->hasAttribute('sizes')) {
1568 $entry->removeAttribute('sizes');
1572 $entry->setAttribute('src', $src);
1575 if ($entry->nodeName
== 'img') {
1577 if ($entry->hasAttribute('src')) {
1578 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1580 if (is_prefix_https() && !$is_https_url) {
1582 if ($entry->hasAttribute('srcset')) {
1583 $entry->removeAttribute('srcset');
1586 if ($entry->hasAttribute('sizes')) {
1587 $entry->removeAttribute('sizes');
1592 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1593 $force_remove_images ||
$_SESSION["bw_limit"]) {
1595 $p = $doc->createElement('p');
1597 $a = $doc->createElement('a');
1598 $a->setAttribute('href', $entry->getAttribute('src'));
1600 $a->appendChild(new DOMText($entry->getAttribute('src')));
1601 $a->setAttribute('target', '_blank');
1602 $a->setAttribute('rel', 'noopener noreferrer');
1604 $p->appendChild($a);
1606 $entry->parentNode
->replaceChild($p, $entry);
1610 if (strtolower($entry->nodeName
) == "a") {
1611 $entry->setAttribute("target", "_blank");
1612 $entry->setAttribute("rel", "noopener noreferrer");
1616 $entries = $xpath->query('//iframe');
1617 foreach ($entries as $entry) {
1618 if (!iframe_whitelisted($entry)) {
1619 $entry->setAttribute('sandbox', 'allow-scripts');
1621 if (is_prefix_https()) {
1622 $entry->setAttribute("src",
1623 str_replace("http://", "https://",
1624 $entry->getAttribute("src")));
1629 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1630 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1631 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1632 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1633 'dt', 'em', 'footer', 'figure', 'figcaption',
1634 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1635 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1636 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1637 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1638 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1639 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1641 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1643 $disallowed_attributes = array('id', 'style', 'class');
1645 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1646 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1647 if (is_array($retval)) {
1649 $allowed_elements = $retval[1];
1650 $disallowed_attributes = $retval[2];
1656 $doc->removeChild($doc->firstChild
); //remove doctype
1657 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1659 if ($highlight_words) {
1660 foreach ($highlight_words as $word) {
1662 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1664 $elements = $xpath->query("//*/text()");
1666 foreach ($elements as $child) {
1668 $fragment = $doc->createDocumentFragment();
1669 $text = $child->textContent
;
1671 while (($pos = mb_stripos($text, $word)) !== false) {
1672 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1673 $word = mb_substr($text, $pos, mb_strlen($word));
1674 $highlight = $doc->createElement('span');
1675 $highlight->appendChild(new DomText($word));
1676 $highlight->setAttribute('class', 'highlight');
1677 $fragment->appendChild($highlight);
1678 $text = mb_substr($text, $pos +
mb_strlen($word));
1681 if (!empty($text)) $fragment->appendChild(new DomText($text));
1683 $child->parentNode
->replaceChild($fragment, $child);
1688 $res = $doc->saveHTML();
1690 /* strip everything outside of <body>...</body> */
1692 $res_frag = array();
1693 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1694 return $res_frag[1];
1700 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1701 $xpath = new DOMXPath($doc);
1702 $entries = $xpath->query('//*');
1704 foreach ($entries as $entry) {
1705 if (!in_array($entry->nodeName
, $allowed_elements)) {
1706 $entry->parentNode
->removeChild($entry);
1709 if ($entry->hasAttributes()) {
1710 $attrs_to_remove = array();
1712 foreach ($entry->attributes
as $attr) {
1714 if (strpos($attr->nodeName
, 'on') === 0) {
1715 array_push($attrs_to_remove, $attr);
1718 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1719 array_push($attrs_to_remove, $attr);
1722 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1723 array_push($attrs_to_remove, $attr);
1727 foreach ($attrs_to_remove as $attr) {
1728 $entry->removeAttributeNode($attr);
1736 function trim_array($array) {
1738 array_walk($tmp, 'trim');
1742 function tag_is_valid($tag) {
1743 if ($tag == '') return false;
1744 if (is_numeric($tag)) return false;
1745 if (mb_strlen($tag) > 250) return false;
1747 if (!$tag) return false;
1752 function render_login_form() {
1753 header('Cache-Control: public');
1755 require_once "login_form.php";
1759 function T_sprintf() {
1760 $args = func_get_args();
1761 return vsprintf(__(array_shift($args)), $args);
1764 function print_checkpoint($n, $s) {
1765 $ts = microtime(true);
1766 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1770 function sanitize_tag($tag) {
1773 $tag = mb_strtolower($tag, 'utf-8');
1775 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1777 if (DB_TYPE
== "mysql") {
1778 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1784 function is_server_https() {
1785 return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) ||
$_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
1788 function is_prefix_https() {
1789 return parse_url(SELF_URL_PATH
, PHP_URL_SCHEME
) == 'https';
1792 // this returns SELF_URL_PATH sans ending slash
1793 function get_self_url_prefix() {
1794 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1795 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1797 return SELF_URL_PATH
;
1801 function encrypt_password($pass, $salt = '', $mode2 = false) {
1802 if ($salt && $mode2) {
1803 return "MODE2:" . hash('sha256', $salt . $pass);
1805 return "SHA1X:" . sha1("$salt:$pass");
1807 return "SHA1:" . sha1($pass);
1809 } // function encrypt_password
1811 function load_filters($feed_id, $owner_uid) {
1814 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1817 $null_cat_qpart = "cat_id IS NULL OR";
1819 $null_cat_qpart = "";
1821 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1822 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1824 $check_cats = array_merge(
1825 Feeds
::getParentCategories($cat_id, $owner_uid),
1828 $check_cats_str = join(",", $check_cats);
1829 $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
1831 while ($line = db_fetch_assoc($result)) {
1832 $filter_id = $line["id"];
1834 $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
1836 $result2 = db_query("SELECT
1837 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
1838 FROM ttrss_filters2_rules AS r,
1839 ttrss_filter_types AS t
1841 (match_on IS NOT NULL OR
1842 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
1843 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
1844 filter_type = t.id AND filter_id = '$filter_id'");
1849 while ($rule_line = db_fetch_assoc($result2)) {
1850 # print_r($rule_line);
1852 if ($rule_line["match_on"]) {
1853 $match_on = json_decode($rule_line["match_on"], true);
1855 if (in_array("0", $match_on) ||
in_array($feed_id, $match_on) ||
count(array_intersect($check_cats_fullids, $match_on)) > 0) {
1858 $rule["reg_exp"] = $rule_line["reg_exp"];
1859 $rule["type"] = $rule_line["type_name"];
1860 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1862 array_push($rules, $rule);
1863 } else if (!$match_any_rule) {
1864 // this filter contains a rule that doesn't match to this feed/category combination
1865 // thus filter has to be rejected
1874 $rule["reg_exp"] = $rule_line["reg_exp"];
1875 $rule["type"] = $rule_line["type_name"];
1876 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1878 array_push($rules, $rule);
1882 if (count($rules) > 0) {
1883 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1884 FROM ttrss_filters2_actions AS a,
1885 ttrss_filter_actions AS t
1887 action_id = t.id AND filter_id = '$filter_id'");
1889 while ($action_line = db_fetch_assoc($result2)) {
1890 # print_r($action_line);
1893 $action["type"] = $action_line["type_name"];
1894 $action["param"] = $action_line["action_param"];
1896 array_push($actions, $action);
1901 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1902 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1903 $filter["rules"] = $rules;
1904 $filter["actions"] = $actions;
1906 if (count($rules) > 0 && count($actions) > 0) {
1907 array_push($filters, $filter);
1914 function get_score_pic($score) {
1916 return "score_high.png";
1917 } else if ($score > 0) {
1918 return "score_half_high.png";
1919 } else if ($score < -100) {
1920 return "score_low.png";
1921 } else if ($score < 0) {
1922 return "score_half_low.png";
1924 return "score_neutral.png";
1928 function feed_has_icon($id) {
1929 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1932 function init_plugins() {
1933 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1938 function add_feed_category($feed_cat, $parent_cat_id = false) {
1940 if (!$feed_cat) return false;
1944 if ($parent_cat_id) {
1945 $parent_qpart = "parent_cat = '$parent_cat_id'";
1946 $parent_insert = "'$parent_cat_id'";
1948 $parent_qpart = "parent_cat IS NULL";
1949 $parent_insert = "NULL";
1952 $feed_cat = mb_substr($feed_cat, 0, 250);
1955 "SELECT id FROM ttrss_feed_categories
1956 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1958 if (db_num_rows($result) == 0) {
1961 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1962 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1973 * Fixes incomplete URLs by prepending "http://".
1974 * Also replaces feed:// with http://, and
1975 * prepends a trailing slash if the url is a domain name only.
1977 * @param string $url Possibly incomplete URL
1979 * @return string Fixed URL.
1981 function fix_url($url) {
1983 // support schema-less urls
1984 if (strpos($url, '//') === 0) {
1985 $url = 'https:' . $url;
1988 if (strpos($url, '://') === false) {
1989 $url = 'http://' . $url;
1990 } else if (substr($url, 0, 5) == 'feed:') {
1991 $url = 'http:' . substr($url, 5);
1994 //prepend slash if the URL has no slash in it
1995 // "http://www.example" -> "http://www.example/"
1996 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
2000 //convert IDNA hostname to punycode if possible
2001 if (function_exists("idn_to_ascii")) {
2002 $parts = parse_url($url);
2003 if (mb_detect_encoding($parts['host']) != 'ASCII')
2005 $parts['host'] = idn_to_ascii($parts['host']);
2006 $url = build_url($parts);
2010 if ($url != "http:///")
2016 function validate_feed_url($url) {
2017 $parts = parse_url($url);
2019 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2023 /* function save_email_address($email) {
2024 // FIXME: implement persistent storage of emails
2026 if (!$_SESSION['stored_emails'])
2027 $_SESSION['stored_emails'] = array();
2029 if (!in_array($email, $_SESSION['stored_emails']))
2030 array_push($_SESSION['stored_emails'], $email);
2034 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2036 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2038 $sql_is_cat = bool_to_sql_bool($is_cat);
2040 $result = db_query("SELECT access_key FROM ttrss_access_keys
2041 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2042 AND owner_uid = " . $owner_uid);
2044 if (db_num_rows($result) == 1) {
2045 return db_fetch_result($result, 0, "access_key");
2047 $key = db_escape_string(uniqid_short());
2049 $result = db_query("INSERT INTO ttrss_access_keys
2050 (access_key, feed_id, is_cat, owner_uid)
2051 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2058 function get_feeds_from_html($url, $content)
2060 $url = fix_url($url);
2061 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2063 libxml_use_internal_errors(true);
2065 $doc = new DOMDocument();
2066 $doc->loadHTML($content);
2067 $xpath = new DOMXPath($doc);
2068 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2069 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2070 $feedUrls = array();
2071 foreach ($entries as $entry) {
2072 if ($entry->hasAttribute('href')) {
2073 $title = $entry->getAttribute('title');
2075 $title = $entry->getAttribute('type');
2077 $feedUrl = rewrite_relative_url(
2078 $baseUrl, $entry->getAttribute('href')
2080 $feedUrls[$feedUrl] = $title;
2086 function is_html($content) {
2087 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2090 function url_is_html($url, $login = false, $pass = false) {
2091 return is_html(fetch_file_contents($url, false, $login, $pass));
2094 function build_url($parts) {
2095 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2098 function cleanup_url_path($path) {
2099 $path = str_replace("/./", "/", $path);
2100 $path = str_replace("//", "/", $path);
2106 * Converts a (possibly) relative URL to a absolute one.
2108 * @param string $url Base URL (i.e. from where the document is)
2109 * @param string $rel_url Possibly relative URL in the document
2111 * @return string Absolute URL
2113 function rewrite_relative_url($url, $rel_url) {
2114 if (strpos($rel_url, "://") !== false) {
2116 } else if (strpos($rel_url, "//") === 0) {
2117 # protocol-relative URL (rare but they exist)
2119 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2120 # magnet:, feed:, etc
2122 } else if (strpos($rel_url, "/") === 0) {
2123 $parts = parse_url($url);
2124 $parts['path'] = $rel_url;
2125 $parts['path'] = cleanup_url_path($parts['path']);
2127 return build_url($parts);
2130 $parts = parse_url($url);
2131 if (!isset($parts['path'])) {
2132 $parts['path'] = '/';
2134 $dir = $parts['path'];
2135 if (substr($dir, -1) !== '/') {
2136 $dir = dirname($parts['path']);
2137 $dir !== '/' && $dir .= '/';
2139 $parts['path'] = $dir . $rel_url;
2140 $parts['path'] = cleanup_url_path($parts['path']);
2142 return build_url($parts);
2146 function cleanup_tags($days = 14, $limit = 1000) {
2148 if (DB_TYPE
== "pgsql") {
2149 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2150 } else if (DB_TYPE
== "mysql") {
2151 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2156 while ($limit > 0) {
2159 $query = "SELECT ttrss_tags.id AS id
2160 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2161 WHERE post_int_id = int_id AND $interval_query AND
2162 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2164 $result = db_query($query);
2168 while ($line = db_fetch_assoc($result)) {
2169 array_push($ids, $line['id']);
2172 if (count($ids) > 0) {
2173 $ids = join(",", $ids);
2175 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2176 $tags_deleted +
= db_affected_rows($tmp_result);
2181 $limit -= $limit_part;
2184 return $tags_deleted;
2187 function print_user_stylesheet() {
2188 $value = get_pref('USER_STYLESHEET');
2191 print "<style type=\"text/css\">";
2192 print str_replace("<br/>", "\n", $value);
2198 function filter_to_sql($filter, $owner_uid) {
2201 if (DB_TYPE
== "pgsql")
2204 $reg_qpart = "REGEXP";
2206 foreach ($filter["rules"] AS $rule) {
2207 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2208 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2209 $rule['reg_exp']) !== FALSE;
2211 if ($regexp_valid) {
2213 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2215 switch ($rule["type"]) {
2217 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2218 $rule['reg_exp'] . "')";
2221 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2222 $rule['reg_exp'] . "')";
2225 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2226 $rule['reg_exp'] . "') OR LOWER(" .
2227 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2230 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2231 $rule['reg_exp'] . "')";
2234 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2235 $rule['reg_exp'] . "')";
2238 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2239 $rule['reg_exp'] . "')";
2243 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2245 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2246 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2249 if (isset($rule["cat_id"])) {
2251 if ($rule["cat_id"] > 0) {
2252 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2253 array_push($children, $rule["cat_id"]);
2255 $children = join(",", $children);
2257 $cat_qpart = "cat_id IN ($children)";
2259 $cat_qpart = "cat_id IS NULL";
2262 $qpart .= " AND $cat_qpart";
2265 $qpart .= " AND feed_id IS NOT NULL";
2267 array_push($query, "($qpart)");
2272 if (count($query) > 0) {
2273 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2275 $fullquery = "(false)";
2278 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2283 if (!function_exists('gzdecode')) {
2284 function gzdecode($string) { // no support for 2nd argument
2285 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2286 base64_encode($string));
2290 function get_random_bytes($length) {
2291 if (function_exists('openssl_random_pseudo_bytes')) {
2292 return openssl_random_pseudo_bytes($length);
2296 for ($i = 0; $i < $length; $i++
)
2297 $output .= chr(mt_rand(0, 255));
2303 function read_stdin() {
2304 $fp = fopen("php://stdin", "r");
2307 $line = trim(fgets($fp));
2315 function implements_interface($class, $interface) {
2316 return in_array($interface, class_implements($class));
2319 function get_minified_js($files) {
2320 require_once 'lib/jshrink/Minifier.php';
2324 foreach ($files as $js) {
2325 if (!isset($_GET['debug'])) {
2326 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2328 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2330 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2332 if ($header && $contents) {
2333 list($htag, $hversion) = explode(":", $header);
2335 if ($htag == "tt-rss" && $hversion == VERSION
) {
2342 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2343 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2347 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2354 function calculate_dep_timestamp() {
2355 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2359 foreach ($files as $file) {
2360 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2366 function T_js_decl($s1, $s2) {
2368 $s1 = preg_replace("/\n/", "", $s1);
2369 $s2 = preg_replace("/\n/", "", $s2);
2371 $s1 = preg_replace("/\"/", "\\\"", $s1);
2372 $s2 = preg_replace("/\"/", "\\\"", $s2);
2374 return "T_messages[\"$s1\"] = \"$s2\";\n";
2378 function init_js_translations() {
2380 print 'var T_messages = new Object();
2383 if (T_messages[msg]) {
2384 return T_messages[msg];
2390 function ngettext(msg1, msg2, n) {
2391 return __((parseInt(n) > 1) ? msg2 : msg1);
2394 $l10n = _get_reader();
2396 for ($i = 0; $i < $l10n->total
; $i++
) {
2397 $orig = $l10n->get_original_string($i);
2398 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2399 $key = explode(chr(0), $orig);
2400 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2401 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2403 $translation = __($orig);
2404 print T_js_decl($orig, $translation);
2409 function get_theme_path($theme) {
2410 $check = "themes/$theme";
2411 if (file_exists($check)) return $check;
2413 $check = "themes.local/$theme";
2414 if (file_exists($check)) return $check;
2417 function theme_valid($theme) {
2418 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2420 if (in_array($theme, $bundled_themes)) return true;
2422 $file = "themes/" . basename($theme);
2424 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2426 if (file_exists($file) && is_readable($file)) {
2427 $fh = fopen($file, "r");
2430 $header = fgets($fh);
2433 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2441 * @SuppressWarnings(unused)
2443 function error_json($code) {
2444 require_once "errors.php";
2446 @$message = $ERRORS[$code];
2448 return json_encode(array("error" =>
2449 array("code" => $code, "message" => $message)));
2453 /*function abs_to_rel_path($dir) {
2454 $tmp = str_replace(dirname(__DIR__), "", $dir);
2456 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2461 function get_upload_error_message($code) {
2464 0 => __('There is no error, the file uploaded with success'),
2465 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2466 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2467 3 => __('The uploaded file was only partially uploaded'),
2468 4 => __('No file was uploaded'),
2469 6 => __('Missing a temporary folder'),
2470 7 => __('Failed to write file to disk.'),
2471 8 => __('A PHP extension stopped the file upload.'),
2474 return $errors[$code];
2477 function base64_img($filename) {
2478 if (file_exists($filename)) {
2479 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2481 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));