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 : SELF_URL_PATH
;
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 function get_self_url_prefix() {
1786 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1787 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1789 return SELF_URL_PATH
;
1793 function encrypt_password($pass, $salt = '', $mode2 = false) {
1794 if ($salt && $mode2) {
1795 return "MODE2:" . hash('sha256', $salt . $pass);
1797 return "SHA1X:" . sha1("$salt:$pass");
1799 return "SHA1:" . sha1($pass);
1801 } // function encrypt_password
1803 function load_filters($feed_id, $owner_uid) {
1806 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1809 $null_cat_qpart = "cat_id IS NULL OR";
1811 $null_cat_qpart = "";
1813 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1814 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1816 $check_cats = array_merge(
1817 Feeds
::getParentCategories($cat_id, $owner_uid),
1820 $check_cats_str = join(",", $check_cats);
1821 $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
1823 while ($line = db_fetch_assoc($result)) {
1824 $filter_id = $line["id"];
1826 $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
1828 $result2 = db_query("SELECT
1829 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
1830 FROM ttrss_filters2_rules AS r,
1831 ttrss_filter_types AS t
1833 (match_on IS NOT NULL OR
1834 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
1835 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
1836 filter_type = t.id AND filter_id = '$filter_id'");
1841 while ($rule_line = db_fetch_assoc($result2)) {
1842 # print_r($rule_line);
1844 if ($rule_line["match_on"]) {
1845 $match_on = json_decode($rule_line["match_on"], true);
1847 if (in_array("0", $match_on) ||
in_array($feed_id, $match_on) ||
count(array_intersect($check_cats_fullids, $match_on)) > 0) {
1850 $rule["reg_exp"] = $rule_line["reg_exp"];
1851 $rule["type"] = $rule_line["type_name"];
1852 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1854 array_push($rules, $rule);
1855 } else if (!$match_any_rule) {
1856 // this filter contains a rule that doesn't match to this feed/category combination
1857 // thus filter has to be rejected
1866 $rule["reg_exp"] = $rule_line["reg_exp"];
1867 $rule["type"] = $rule_line["type_name"];
1868 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1870 array_push($rules, $rule);
1874 if (count($rules) > 0) {
1875 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1876 FROM ttrss_filters2_actions AS a,
1877 ttrss_filter_actions AS t
1879 action_id = t.id AND filter_id = '$filter_id'");
1881 while ($action_line = db_fetch_assoc($result2)) {
1882 # print_r($action_line);
1885 $action["type"] = $action_line["type_name"];
1886 $action["param"] = $action_line["action_param"];
1888 array_push($actions, $action);
1893 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1894 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1895 $filter["rules"] = $rules;
1896 $filter["actions"] = $actions;
1898 if (count($rules) > 0 && count($actions) > 0) {
1899 array_push($filters, $filter);
1906 function get_score_pic($score) {
1908 return "score_high.png";
1909 } else if ($score > 0) {
1910 return "score_half_high.png";
1911 } else if ($score < -100) {
1912 return "score_low.png";
1913 } else if ($score < 0) {
1914 return "score_half_low.png";
1916 return "score_neutral.png";
1920 function feed_has_icon($id) {
1921 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1924 function init_plugins() {
1925 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1930 function add_feed_category($feed_cat, $parent_cat_id = false) {
1932 if (!$feed_cat) return false;
1936 if ($parent_cat_id) {
1937 $parent_qpart = "parent_cat = '$parent_cat_id'";
1938 $parent_insert = "'$parent_cat_id'";
1940 $parent_qpart = "parent_cat IS NULL";
1941 $parent_insert = "NULL";
1944 $feed_cat = mb_substr($feed_cat, 0, 250);
1947 "SELECT id FROM ttrss_feed_categories
1948 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1950 if (db_num_rows($result) == 0) {
1953 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1954 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1965 * Fixes incomplete URLs by prepending "http://".
1966 * Also replaces feed:// with http://, and
1967 * prepends a trailing slash if the url is a domain name only.
1969 * @param string $url Possibly incomplete URL
1971 * @return string Fixed URL.
1973 function fix_url($url) {
1975 // support schema-less urls
1976 if (strpos($url, '//') === 0) {
1977 $url = 'https:' . $url;
1980 if (strpos($url, '://') === false) {
1981 $url = 'http://' . $url;
1982 } else if (substr($url, 0, 5) == 'feed:') {
1983 $url = 'http:' . substr($url, 5);
1986 //prepend slash if the URL has no slash in it
1987 // "http://www.example" -> "http://www.example/"
1988 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1992 //convert IDNA hostname to punycode if possible
1993 if (function_exists("idn_to_ascii")) {
1994 $parts = parse_url($url);
1995 if (mb_detect_encoding($parts['host']) != 'ASCII')
1997 $parts['host'] = idn_to_ascii($parts['host']);
1998 $url = build_url($parts);
2002 if ($url != "http:///")
2008 function validate_feed_url($url) {
2009 $parts = parse_url($url);
2011 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2015 /* function save_email_address($email) {
2016 // FIXME: implement persistent storage of emails
2018 if (!$_SESSION['stored_emails'])
2019 $_SESSION['stored_emails'] = array();
2021 if (!in_array($email, $_SESSION['stored_emails']))
2022 array_push($_SESSION['stored_emails'], $email);
2026 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2028 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2030 $sql_is_cat = bool_to_sql_bool($is_cat);
2032 $result = db_query("SELECT access_key FROM ttrss_access_keys
2033 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2034 AND owner_uid = " . $owner_uid);
2036 if (db_num_rows($result) == 1) {
2037 return db_fetch_result($result, 0, "access_key");
2039 $key = db_escape_string(uniqid_short());
2041 $result = db_query("INSERT INTO ttrss_access_keys
2042 (access_key, feed_id, is_cat, owner_uid)
2043 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2050 function get_feeds_from_html($url, $content)
2052 $url = fix_url($url);
2053 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2055 libxml_use_internal_errors(true);
2057 $doc = new DOMDocument();
2058 $doc->loadHTML($content);
2059 $xpath = new DOMXPath($doc);
2060 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2061 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2062 $feedUrls = array();
2063 foreach ($entries as $entry) {
2064 if ($entry->hasAttribute('href')) {
2065 $title = $entry->getAttribute('title');
2067 $title = $entry->getAttribute('type');
2069 $feedUrl = rewrite_relative_url(
2070 $baseUrl, $entry->getAttribute('href')
2072 $feedUrls[$feedUrl] = $title;
2078 function is_html($content) {
2079 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2082 function url_is_html($url, $login = false, $pass = false) {
2083 return is_html(fetch_file_contents($url, false, $login, $pass));
2086 function build_url($parts) {
2087 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2090 function cleanup_url_path($path) {
2091 $path = str_replace("/./", "/", $path);
2092 $path = str_replace("//", "/", $path);
2098 * Converts a (possibly) relative URL to a absolute one.
2100 * @param string $url Base URL (i.e. from where the document is)
2101 * @param string $rel_url Possibly relative URL in the document
2103 * @return string Absolute URL
2105 function rewrite_relative_url($url, $rel_url) {
2106 if (strpos($rel_url, "://") !== false) {
2108 } else if (strpos($rel_url, "//") === 0) {
2109 # protocol-relative URL (rare but they exist)
2111 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2112 # magnet:, feed:, etc
2114 } else if (strpos($rel_url, "/") === 0) {
2115 $parts = parse_url($url);
2116 $parts['path'] = $rel_url;
2117 $parts['path'] = cleanup_url_path($parts['path']);
2119 return build_url($parts);
2122 $parts = parse_url($url);
2123 if (!isset($parts['path'])) {
2124 $parts['path'] = '/';
2126 $dir = $parts['path'];
2127 if (substr($dir, -1) !== '/') {
2128 $dir = dirname($parts['path']);
2129 $dir !== '/' && $dir .= '/';
2131 $parts['path'] = $dir . $rel_url;
2132 $parts['path'] = cleanup_url_path($parts['path']);
2134 return build_url($parts);
2138 function cleanup_tags($days = 14, $limit = 1000) {
2140 if (DB_TYPE
== "pgsql") {
2141 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2142 } else if (DB_TYPE
== "mysql") {
2143 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2148 while ($limit > 0) {
2151 $query = "SELECT ttrss_tags.id AS id
2152 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2153 WHERE post_int_id = int_id AND $interval_query AND
2154 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2156 $result = db_query($query);
2160 while ($line = db_fetch_assoc($result)) {
2161 array_push($ids, $line['id']);
2164 if (count($ids) > 0) {
2165 $ids = join(",", $ids);
2167 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2168 $tags_deleted +
= db_affected_rows($tmp_result);
2173 $limit -= $limit_part;
2176 return $tags_deleted;
2179 function print_user_stylesheet() {
2180 $value = get_pref('USER_STYLESHEET');
2183 print "<style type=\"text/css\">";
2184 print str_replace("<br/>", "\n", $value);
2190 function filter_to_sql($filter, $owner_uid) {
2193 if (DB_TYPE
== "pgsql")
2196 $reg_qpart = "REGEXP";
2198 foreach ($filter["rules"] AS $rule) {
2199 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2200 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2201 $rule['reg_exp']) !== FALSE;
2203 if ($regexp_valid) {
2205 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2207 switch ($rule["type"]) {
2209 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2210 $rule['reg_exp'] . "')";
2213 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2214 $rule['reg_exp'] . "')";
2217 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2218 $rule['reg_exp'] . "') OR LOWER(" .
2219 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2222 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2223 $rule['reg_exp'] . "')";
2226 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2227 $rule['reg_exp'] . "')";
2230 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2231 $rule['reg_exp'] . "')";
2235 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2237 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2238 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2241 if (isset($rule["cat_id"])) {
2243 if ($rule["cat_id"] > 0) {
2244 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2245 array_push($children, $rule["cat_id"]);
2247 $children = join(",", $children);
2249 $cat_qpart = "cat_id IN ($children)";
2251 $cat_qpart = "cat_id IS NULL";
2254 $qpart .= " AND $cat_qpart";
2257 $qpart .= " AND feed_id IS NOT NULL";
2259 array_push($query, "($qpart)");
2264 if (count($query) > 0) {
2265 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2267 $fullquery = "(false)";
2270 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2275 if (!function_exists('gzdecode')) {
2276 function gzdecode($string) { // no support for 2nd argument
2277 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2278 base64_encode($string));
2282 function get_random_bytes($length) {
2283 if (function_exists('openssl_random_pseudo_bytes')) {
2284 return openssl_random_pseudo_bytes($length);
2288 for ($i = 0; $i < $length; $i++
)
2289 $output .= chr(mt_rand(0, 255));
2295 function read_stdin() {
2296 $fp = fopen("php://stdin", "r");
2299 $line = trim(fgets($fp));
2307 function implements_interface($class, $interface) {
2308 return in_array($interface, class_implements($class));
2311 function get_minified_js($files) {
2312 require_once 'lib/jshrink/Minifier.php';
2316 foreach ($files as $js) {
2317 if (!isset($_GET['debug'])) {
2318 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2320 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2322 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2324 if ($header && $contents) {
2325 list($htag, $hversion) = explode(":", $header);
2327 if ($htag == "tt-rss" && $hversion == VERSION
) {
2334 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2335 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2339 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2346 function calculate_dep_timestamp() {
2347 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2351 foreach ($files as $file) {
2352 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2358 function T_js_decl($s1, $s2) {
2360 $s1 = preg_replace("/\n/", "", $s1);
2361 $s2 = preg_replace("/\n/", "", $s2);
2363 $s1 = preg_replace("/\"/", "\\\"", $s1);
2364 $s2 = preg_replace("/\"/", "\\\"", $s2);
2366 return "T_messages[\"$s1\"] = \"$s2\";\n";
2370 function init_js_translations() {
2372 print 'var T_messages = new Object();
2375 if (T_messages[msg]) {
2376 return T_messages[msg];
2382 function ngettext(msg1, msg2, n) {
2383 return __((parseInt(n) > 1) ? msg2 : msg1);
2386 $l10n = _get_reader();
2388 for ($i = 0; $i < $l10n->total
; $i++
) {
2389 $orig = $l10n->get_original_string($i);
2390 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2391 $key = explode(chr(0), $orig);
2392 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2393 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2395 $translation = __($orig);
2396 print T_js_decl($orig, $translation);
2401 function get_theme_path($theme) {
2402 $check = "themes/$theme";
2403 if (file_exists($check)) return $check;
2405 $check = "themes.local/$theme";
2406 if (file_exists($check)) return $check;
2409 function theme_valid($theme) {
2410 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2412 if (in_array($theme, $bundled_themes)) return true;
2414 $file = "themes/" . basename($theme);
2416 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2418 if (file_exists($file) && is_readable($file)) {
2419 $fh = fopen($file, "r");
2422 $header = fgets($fh);
2425 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2433 * @SuppressWarnings(unused)
2435 function error_json($code) {
2436 require_once "errors.php";
2438 @$message = $ERRORS[$code];
2440 return json_encode(array("error" =>
2441 array("code" => $code, "message" => $message)));
2445 /*function abs_to_rel_path($dir) {
2446 $tmp = str_replace(dirname(__DIR__), "", $dir);
2448 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2453 function get_upload_error_message($code) {
2456 0 => __('There is no error, the file uploaded with success'),
2457 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2458 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2459 3 => __('The uploaded file was only partially uploaded'),
2460 4 => __('No file was uploaded'),
2461 6 => __('Missing a temporary folder'),
2462 7 => __('Failed to write file to disk.'),
2463 8 => __('A PHP extension stopped the file upload.'),
2466 return $errors[$code];
2469 function base64_img($filename) {
2470 if (file_exists($filename)) {
2471 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2473 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));