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");
1126 $params["labels"] = Labels
::get_all_labels($_SESSION["uid"]);
1131 function get_hotkeys_info() {
1133 __("Navigation") => array(
1134 "next_feed" => __("Open next feed"),
1135 "prev_feed" => __("Open previous feed"),
1136 "next_article" => __("Open next article"),
1137 "prev_article" => __("Open previous article"),
1138 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1139 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1140 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1141 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1142 "search_dialog" => __("Show search dialog")),
1143 __("Article") => array(
1144 "toggle_mark" => __("Toggle starred"),
1145 "toggle_publ" => __("Toggle published"),
1146 "toggle_unread" => __("Toggle unread"),
1147 "edit_tags" => __("Edit tags"),
1148 "open_in_new_window" => __("Open in new window"),
1149 "catchup_below" => __("Mark below as read"),
1150 "catchup_above" => __("Mark above as read"),
1151 "article_scroll_down" => __("Scroll down"),
1152 "article_scroll_up" => __("Scroll up"),
1153 "select_article_cursor" => __("Select article under cursor"),
1154 "email_article" => __("Email article"),
1155 "close_article" => __("Close/collapse article"),
1156 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1157 "toggle_widescreen" => __("Toggle widescreen mode"),
1158 "toggle_embed_original" => __("Toggle embed original")),
1159 __("Article selection") => array(
1160 "select_all" => __("Select all articles"),
1161 "select_unread" => __("Select unread"),
1162 "select_marked" => __("Select starred"),
1163 "select_published" => __("Select published"),
1164 "select_invert" => __("Invert selection"),
1165 "select_none" => __("Deselect everything")),
1166 __("Feed") => array(
1167 "feed_refresh" => __("Refresh current feed"),
1168 "feed_unhide_read" => __("Un/hide read feeds"),
1169 "feed_subscribe" => __("Subscribe to feed"),
1170 "feed_edit" => __("Edit feed"),
1171 "feed_catchup" => __("Mark as read"),
1172 "feed_reverse" => __("Reverse headlines"),
1173 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1174 "feed_debug_update" => __("Debug feed update"),
1175 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1176 "catchup_all" => __("Mark all feeds as read"),
1177 "cat_toggle_collapse" => __("Un/collapse current category"),
1178 "toggle_combined_mode" => __("Toggle combined mode"),
1179 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1180 __("Go to") => array(
1181 "goto_all" => __("All articles"),
1182 "goto_fresh" => __("Fresh"),
1183 "goto_marked" => __("Starred"),
1184 "goto_published" => __("Published"),
1185 "goto_tagcloud" => __("Tag cloud"),
1186 "goto_prefs" => __("Preferences")),
1187 __("Other") => array(
1188 "create_label" => __("Create label"),
1189 "create_filter" => __("Create filter"),
1190 "collapse_sidebar" => __("Un/collapse sidebar"),
1191 "help_dialog" => __("Show help dialog"))
1194 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
1195 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1201 function get_hotkeys_map() {
1203 // "navigation" => array(
1206 "n" => "next_article",
1207 "p" => "prev_article",
1208 "(38)|up" => "prev_article",
1209 "(40)|down" => "next_article",
1210 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1211 // "^(40)|Ctrl-down" => "next_article_noscroll",
1212 "(191)|/" => "search_dialog",
1213 // "article" => array(
1214 "s" => "toggle_mark",
1215 "*s" => "toggle_publ",
1216 "u" => "toggle_unread",
1217 "*t" => "edit_tags",
1218 "o" => "open_in_new_window",
1219 "c p" => "catchup_below",
1220 "c n" => "catchup_above",
1221 "*n" => "article_scroll_down",
1222 "*p" => "article_scroll_up",
1223 "*(38)|Shift+up" => "article_scroll_up",
1224 "*(40)|Shift+down" => "article_scroll_down",
1225 "a *w" => "toggle_widescreen",
1226 "a e" => "toggle_embed_original",
1227 "e" => "email_article",
1228 "a q" => "close_article",
1229 // "article_selection" => array(
1230 "a a" => "select_all",
1231 "a u" => "select_unread",
1232 "a *u" => "select_marked",
1233 "a p" => "select_published",
1234 "a i" => "select_invert",
1235 "a n" => "select_none",
1237 "f r" => "feed_refresh",
1238 "f a" => "feed_unhide_read",
1239 "f s" => "feed_subscribe",
1240 "f e" => "feed_edit",
1241 "f q" => "feed_catchup",
1242 "f x" => "feed_reverse",
1243 "f g" => "feed_toggle_vgroup",
1244 "f *d" => "feed_debug_update",
1245 "f *g" => "feed_debug_viewfeed",
1246 "f *c" => "toggle_combined_mode",
1247 "f c" => "toggle_cdm_expanded",
1248 "*q" => "catchup_all",
1249 "x" => "cat_toggle_collapse",
1251 "g a" => "goto_all",
1252 "g f" => "goto_fresh",
1253 "g s" => "goto_marked",
1254 "g p" => "goto_published",
1255 "g t" => "goto_tagcloud",
1256 "g *p" => "goto_prefs",
1257 // "other" => array(
1258 "(9)|Tab" => "select_article_cursor", // tab
1259 "c l" => "create_label",
1260 "c f" => "create_filter",
1261 "c s" => "collapse_sidebar",
1262 "^(191)|Ctrl+/" => "help_dialog",
1265 if (get_pref('COMBINED_DISPLAY_MODE')) {
1266 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1267 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1270 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
1271 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1274 $prefixes = array();
1276 foreach (array_keys($hotkeys) as $hotkey) {
1277 $pair = explode(" ", $hotkey, 2);
1279 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1280 array_push($prefixes, $pair[0]);
1284 return array($prefixes, $hotkeys);
1287 function check_for_update() {
1288 if (defined("GIT_VERSION_TIMESTAMP")) {
1289 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1292 $content = json_decode($content, true);
1294 if ($content && isset($content["changeset"])) {
1295 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
1296 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
1298 return $content["changeset"]["id"];
1307 function make_runtime_info($disable_update_check = false) {
1310 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1311 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1313 $max_feed_id = db_fetch_result($result, 0, "mid");
1314 $num_feeds = db_fetch_result($result, 0, "nf");
1316 $data["max_feed_id"] = (int) $max_feed_id;
1317 $data["num_feeds"] = (int) $num_feeds;
1319 $data['last_article_id'] = Article
::getLastArticleId();
1320 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1322 $data['dep_ts'] = calculate_dep_timestamp();
1323 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1325 $data["labels"] = Labels
::get_all_labels($_SESSION["uid"]);
1327 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
1328 $update_result = @check_for_update
();
1330 $data["update_result"] = $update_result;
1332 $_SESSION["last_version_check"] = time();
1335 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
1337 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1339 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1341 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
1344 $stamp_delta = time() - $stamp;
1346 if ($stamp_delta > 1800) {
1350 $_SESSION["daemon_stamp_check"] = time();
1353 $data['daemon_stamp_ok'] = $stamp_check;
1355 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1357 $data['daemon_stamp'] = $stamp_fmt;
1365 function search_to_sql($search, $search_language) {
1367 $keywords = str_getcsv(trim($search), " ");
1368 $query_keywords = array();
1369 $search_words = array();
1370 $search_query_leftover = array();
1372 if ($search_language)
1373 $search_language = db_escape_string(mb_strtolower($search_language));
1375 $search_language = "english";
1377 foreach ($keywords as $k) {
1378 if (strpos($k, "-") === 0) {
1385 $commandpair = explode(":", mb_strtolower($k), 2);
1387 switch ($commandpair[0]) {
1389 if ($commandpair[1]) {
1390 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1391 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1393 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1394 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1395 array_push($search_words, $k);
1399 if ($commandpair[1]) {
1400 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1401 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1403 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1404 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1405 array_push($search_words, $k);
1409 if ($commandpair[1]) {
1410 if ($commandpair[1] == "true")
1411 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1412 else if ($commandpair[1] == "false")
1413 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1415 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1416 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1418 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1419 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1420 if (!$not) array_push($search_words, $k);
1425 if ($commandpair[1]) {
1426 if ($commandpair[1] == "true")
1427 array_push($query_keywords, "($not (marked = true))");
1429 array_push($query_keywords, "($not (marked = false))");
1431 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1432 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1433 if (!$not) array_push($search_words, $k);
1437 if ($commandpair[1]) {
1438 if ($commandpair[1] == "true")
1439 array_push($query_keywords, "($not (published = true))");
1441 array_push($query_keywords, "($not (published = false))");
1444 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1445 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1446 if (!$not) array_push($search_words, $k);
1450 if ($commandpair[1]) {
1451 if ($commandpair[1] == "true")
1452 array_push($query_keywords, "($not (unread = true))");
1454 array_push($query_keywords, "($not (unread = false))");
1457 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1458 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1459 if (!$not) array_push($search_words, $k);
1463 if (strpos($k, "@") === 0) {
1465 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1466 $orig_ts = strtotime(substr($k, 1));
1467 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1469 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1471 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
1474 if (DB_TYPE
== "pgsql") {
1475 $k = mb_strtolower($k);
1476 array_push($search_query_leftover, $not ?
"!$k" : $k);
1478 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1479 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1482 if (!$not) array_push($search_words, $k);
1487 if (count($search_query_leftover) > 0) {
1488 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1490 if (DB_TYPE
== "pgsql") {
1491 array_push($query_keywords,
1492 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1497 $search_query_part = implode("AND", $query_keywords);
1499 return array($search_query_part, $search_words);
1502 function iframe_whitelisted($entry) {
1503 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1505 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
1508 foreach ($whitelist as $w) {
1509 if ($src == $w ||
$src == "www.$w")
1517 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1518 if (!$owner) $owner = $_SESSION["uid"];
1520 $res = trim($str); if (!$res) return '';
1522 $charset_hack = '<head>
1523 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1526 $res = trim($res); if (!$res) return '';
1528 libxml_use_internal_errors(true);
1530 $doc = new DOMDocument();
1531 $doc->loadHTML($charset_hack . $res);
1532 $xpath = new DOMXPath($doc);
1534 $rewrite_base_url = $site_url ?
$site_url : get_self_url_prefix();
1536 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1538 foreach ($entries as $entry) {
1540 if ($entry->hasAttribute('href')) {
1541 $entry->setAttribute('href',
1542 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1544 $entry->setAttribute('rel', 'noopener noreferrer');
1547 if ($entry->hasAttribute('src')) {
1548 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1549 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1551 if (file_exists($cached_filename)) {
1553 // this is strictly cosmetic
1554 if ($entry->tagName
== 'img') {
1556 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1558 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1564 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1566 if ($entry->hasAttribute('srcset')) {
1567 $entry->removeAttribute('srcset');
1570 if ($entry->hasAttribute('sizes')) {
1571 $entry->removeAttribute('sizes');
1575 $entry->setAttribute('src', $src);
1578 if ($entry->nodeName
== 'img') {
1580 if ($entry->hasAttribute('src')) {
1581 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1583 if (is_prefix_https() && !$is_https_url) {
1585 if ($entry->hasAttribute('srcset')) {
1586 $entry->removeAttribute('srcset');
1589 if ($entry->hasAttribute('sizes')) {
1590 $entry->removeAttribute('sizes');
1595 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1596 $force_remove_images ||
$_SESSION["bw_limit"]) {
1598 $p = $doc->createElement('p');
1600 $a = $doc->createElement('a');
1601 $a->setAttribute('href', $entry->getAttribute('src'));
1603 $a->appendChild(new DOMText($entry->getAttribute('src')));
1604 $a->setAttribute('target', '_blank');
1605 $a->setAttribute('rel', 'noopener noreferrer');
1607 $p->appendChild($a);
1609 $entry->parentNode
->replaceChild($p, $entry);
1613 if (strtolower($entry->nodeName
) == "a") {
1614 $entry->setAttribute("target", "_blank");
1615 $entry->setAttribute("rel", "noopener noreferrer");
1619 $entries = $xpath->query('//iframe');
1620 foreach ($entries as $entry) {
1621 if (!iframe_whitelisted($entry)) {
1622 $entry->setAttribute('sandbox', 'allow-scripts');
1624 if (is_prefix_https()) {
1625 $entry->setAttribute("src",
1626 str_replace("http://", "https://",
1627 $entry->getAttribute("src")));
1632 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1633 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1634 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1635 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1636 'dt', 'em', 'footer', 'figure', 'figcaption',
1637 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1638 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1639 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1640 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1641 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1642 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1644 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1646 $disallowed_attributes = array('id', 'style', 'class');
1648 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1649 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1650 if (is_array($retval)) {
1652 $allowed_elements = $retval[1];
1653 $disallowed_attributes = $retval[2];
1659 $doc->removeChild($doc->firstChild
); //remove doctype
1660 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1662 if ($highlight_words) {
1663 foreach ($highlight_words as $word) {
1665 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1667 $elements = $xpath->query("//*/text()");
1669 foreach ($elements as $child) {
1671 $fragment = $doc->createDocumentFragment();
1672 $text = $child->textContent
;
1674 while (($pos = mb_stripos($text, $word)) !== false) {
1675 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1676 $word = mb_substr($text, $pos, mb_strlen($word));
1677 $highlight = $doc->createElement('span');
1678 $highlight->appendChild(new DomText($word));
1679 $highlight->setAttribute('class', 'highlight');
1680 $fragment->appendChild($highlight);
1681 $text = mb_substr($text, $pos +
mb_strlen($word));
1684 if (!empty($text)) $fragment->appendChild(new DomText($text));
1686 $child->parentNode
->replaceChild($fragment, $child);
1691 $res = $doc->saveHTML();
1693 /* strip everything outside of <body>...</body> */
1695 $res_frag = array();
1696 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1697 return $res_frag[1];
1703 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1704 $xpath = new DOMXPath($doc);
1705 $entries = $xpath->query('//*');
1707 foreach ($entries as $entry) {
1708 if (!in_array($entry->nodeName
, $allowed_elements)) {
1709 $entry->parentNode
->removeChild($entry);
1712 if ($entry->hasAttributes()) {
1713 $attrs_to_remove = array();
1715 foreach ($entry->attributes
as $attr) {
1717 if (strpos($attr->nodeName
, 'on') === 0) {
1718 array_push($attrs_to_remove, $attr);
1721 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1722 array_push($attrs_to_remove, $attr);
1725 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1726 array_push($attrs_to_remove, $attr);
1730 foreach ($attrs_to_remove as $attr) {
1731 $entry->removeAttributeNode($attr);
1739 function trim_array($array) {
1741 array_walk($tmp, 'trim');
1745 function tag_is_valid($tag) {
1746 if ($tag == '') return false;
1747 if (is_numeric($tag)) return false;
1748 if (mb_strlen($tag) > 250) return false;
1750 if (!$tag) return false;
1755 function render_login_form() {
1756 header('Cache-Control: public');
1758 require_once "login_form.php";
1762 function T_sprintf() {
1763 $args = func_get_args();
1764 return vsprintf(__(array_shift($args)), $args);
1767 function print_checkpoint($n, $s) {
1768 $ts = microtime(true);
1769 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1773 function sanitize_tag($tag) {
1776 $tag = mb_strtolower($tag, 'utf-8');
1778 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1780 if (DB_TYPE
== "mysql") {
1781 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1787 function is_server_https() {
1788 return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) ||
$_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
1791 function is_prefix_https() {
1792 return parse_url(SELF_URL_PATH
, PHP_URL_SCHEME
) == 'https';
1795 // this returns SELF_URL_PATH sans ending slash
1796 function get_self_url_prefix() {
1797 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1798 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1800 return SELF_URL_PATH
;
1804 function encrypt_password($pass, $salt = '', $mode2 = false) {
1805 if ($salt && $mode2) {
1806 return "MODE2:" . hash('sha256', $salt . $pass);
1808 return "SHA1X:" . sha1("$salt:$pass");
1810 return "SHA1:" . sha1($pass);
1812 } // function encrypt_password
1814 function load_filters($feed_id, $owner_uid) {
1817 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1820 $null_cat_qpart = "cat_id IS NULL OR";
1822 $null_cat_qpart = "";
1824 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1825 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1827 $check_cats = array_merge(
1828 Feeds
::getParentCategories($cat_id, $owner_uid),
1831 $check_cats_str = join(",", $check_cats);
1832 $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
1834 while ($line = db_fetch_assoc($result)) {
1835 $filter_id = $line["id"];
1837 $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
1839 $result2 = db_query("SELECT
1840 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
1841 FROM ttrss_filters2_rules AS r,
1842 ttrss_filter_types AS t
1844 (match_on IS NOT NULL OR
1845 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
1846 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
1847 filter_type = t.id AND filter_id = '$filter_id'");
1852 while ($rule_line = db_fetch_assoc($result2)) {
1853 # print_r($rule_line);
1855 if ($rule_line["match_on"]) {
1856 $match_on = json_decode($rule_line["match_on"], true);
1858 if (in_array("0", $match_on) ||
in_array($feed_id, $match_on) ||
count(array_intersect($check_cats_fullids, $match_on)) > 0) {
1861 $rule["reg_exp"] = $rule_line["reg_exp"];
1862 $rule["type"] = $rule_line["type_name"];
1863 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1865 array_push($rules, $rule);
1866 } else if (!$match_any_rule) {
1867 // this filter contains a rule that doesn't match to this feed/category combination
1868 // thus filter has to be rejected
1877 $rule["reg_exp"] = $rule_line["reg_exp"];
1878 $rule["type"] = $rule_line["type_name"];
1879 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1881 array_push($rules, $rule);
1885 if (count($rules) > 0) {
1886 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1887 FROM ttrss_filters2_actions AS a,
1888 ttrss_filter_actions AS t
1890 action_id = t.id AND filter_id = '$filter_id'");
1892 while ($action_line = db_fetch_assoc($result2)) {
1893 # print_r($action_line);
1896 $action["type"] = $action_line["type_name"];
1897 $action["param"] = $action_line["action_param"];
1899 array_push($actions, $action);
1904 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1905 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1906 $filter["rules"] = $rules;
1907 $filter["actions"] = $actions;
1909 if (count($rules) > 0 && count($actions) > 0) {
1910 array_push($filters, $filter);
1917 function get_score_pic($score) {
1919 return "score_high.png";
1920 } else if ($score > 0) {
1921 return "score_half_high.png";
1922 } else if ($score < -100) {
1923 return "score_low.png";
1924 } else if ($score < 0) {
1925 return "score_half_low.png";
1927 return "score_neutral.png";
1931 function feed_has_icon($id) {
1932 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1935 function init_plugins() {
1936 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1941 function add_feed_category($feed_cat, $parent_cat_id = false) {
1943 if (!$feed_cat) return false;
1947 if ($parent_cat_id) {
1948 $parent_qpart = "parent_cat = '$parent_cat_id'";
1949 $parent_insert = "'$parent_cat_id'";
1951 $parent_qpart = "parent_cat IS NULL";
1952 $parent_insert = "NULL";
1955 $feed_cat = mb_substr($feed_cat, 0, 250);
1958 "SELECT id FROM ttrss_feed_categories
1959 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1961 if (db_num_rows($result) == 0) {
1964 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1965 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1976 * Fixes incomplete URLs by prepending "http://".
1977 * Also replaces feed:// with http://, and
1978 * prepends a trailing slash if the url is a domain name only.
1980 * @param string $url Possibly incomplete URL
1982 * @return string Fixed URL.
1984 function fix_url($url) {
1986 // support schema-less urls
1987 if (strpos($url, '//') === 0) {
1988 $url = 'https:' . $url;
1991 if (strpos($url, '://') === false) {
1992 $url = 'http://' . $url;
1993 } else if (substr($url, 0, 5) == 'feed:') {
1994 $url = 'http:' . substr($url, 5);
1997 //prepend slash if the URL has no slash in it
1998 // "http://www.example" -> "http://www.example/"
1999 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
2003 //convert IDNA hostname to punycode if possible
2004 if (function_exists("idn_to_ascii")) {
2005 $parts = parse_url($url);
2006 if (mb_detect_encoding($parts['host']) != 'ASCII')
2008 $parts['host'] = idn_to_ascii($parts['host']);
2009 $url = build_url($parts);
2013 if ($url != "http:///")
2019 function validate_feed_url($url) {
2020 $parts = parse_url($url);
2022 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2026 /* function save_email_address($email) {
2027 // FIXME: implement persistent storage of emails
2029 if (!$_SESSION['stored_emails'])
2030 $_SESSION['stored_emails'] = array();
2032 if (!in_array($email, $_SESSION['stored_emails']))
2033 array_push($_SESSION['stored_emails'], $email);
2037 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2039 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2041 $sql_is_cat = bool_to_sql_bool($is_cat);
2043 $result = db_query("SELECT access_key FROM ttrss_access_keys
2044 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2045 AND owner_uid = " . $owner_uid);
2047 if (db_num_rows($result) == 1) {
2048 return db_fetch_result($result, 0, "access_key");
2050 $key = db_escape_string(uniqid_short());
2052 $result = db_query("INSERT INTO ttrss_access_keys
2053 (access_key, feed_id, is_cat, owner_uid)
2054 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2061 function get_feeds_from_html($url, $content)
2063 $url = fix_url($url);
2064 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2066 libxml_use_internal_errors(true);
2068 $doc = new DOMDocument();
2069 $doc->loadHTML($content);
2070 $xpath = new DOMXPath($doc);
2071 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2072 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2073 $feedUrls = array();
2074 foreach ($entries as $entry) {
2075 if ($entry->hasAttribute('href')) {
2076 $title = $entry->getAttribute('title');
2078 $title = $entry->getAttribute('type');
2080 $feedUrl = rewrite_relative_url(
2081 $baseUrl, $entry->getAttribute('href')
2083 $feedUrls[$feedUrl] = $title;
2089 function is_html($content) {
2090 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2093 function url_is_html($url, $login = false, $pass = false) {
2094 return is_html(fetch_file_contents($url, false, $login, $pass));
2097 function build_url($parts) {
2098 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2101 function cleanup_url_path($path) {
2102 $path = str_replace("/./", "/", $path);
2103 $path = str_replace("//", "/", $path);
2109 * Converts a (possibly) relative URL to a absolute one.
2111 * @param string $url Base URL (i.e. from where the document is)
2112 * @param string $rel_url Possibly relative URL in the document
2114 * @return string Absolute URL
2116 function rewrite_relative_url($url, $rel_url) {
2117 if (strpos($rel_url, "://") !== false) {
2119 } else if (strpos($rel_url, "//") === 0) {
2120 # protocol-relative URL (rare but they exist)
2122 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2123 # magnet:, feed:, etc
2125 } else if (strpos($rel_url, "/") === 0) {
2126 $parts = parse_url($url);
2127 $parts['path'] = $rel_url;
2128 $parts['path'] = cleanup_url_path($parts['path']);
2130 return build_url($parts);
2133 $parts = parse_url($url);
2134 if (!isset($parts['path'])) {
2135 $parts['path'] = '/';
2137 $dir = $parts['path'];
2138 if (substr($dir, -1) !== '/') {
2139 $dir = dirname($parts['path']);
2140 $dir !== '/' && $dir .= '/';
2142 $parts['path'] = $dir . $rel_url;
2143 $parts['path'] = cleanup_url_path($parts['path']);
2145 return build_url($parts);
2149 function cleanup_tags($days = 14, $limit = 1000) {
2151 if (DB_TYPE
== "pgsql") {
2152 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2153 } else if (DB_TYPE
== "mysql") {
2154 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2159 while ($limit > 0) {
2162 $query = "SELECT ttrss_tags.id AS id
2163 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2164 WHERE post_int_id = int_id AND $interval_query AND
2165 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2167 $result = db_query($query);
2171 while ($line = db_fetch_assoc($result)) {
2172 array_push($ids, $line['id']);
2175 if (count($ids) > 0) {
2176 $ids = join(",", $ids);
2178 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2179 $tags_deleted +
= db_affected_rows($tmp_result);
2184 $limit -= $limit_part;
2187 return $tags_deleted;
2190 function print_user_stylesheet() {
2191 $value = get_pref('USER_STYLESHEET');
2194 print "<style type=\"text/css\">";
2195 print str_replace("<br/>", "\n", $value);
2201 function filter_to_sql($filter, $owner_uid) {
2204 if (DB_TYPE
== "pgsql")
2207 $reg_qpart = "REGEXP";
2209 foreach ($filter["rules"] AS $rule) {
2210 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2211 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2212 $rule['reg_exp']) !== FALSE;
2214 if ($regexp_valid) {
2216 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2218 switch ($rule["type"]) {
2220 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2221 $rule['reg_exp'] . "')";
2224 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2225 $rule['reg_exp'] . "')";
2228 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2229 $rule['reg_exp'] . "') OR LOWER(" .
2230 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2233 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2234 $rule['reg_exp'] . "')";
2237 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2238 $rule['reg_exp'] . "')";
2241 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2242 $rule['reg_exp'] . "')";
2246 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2248 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2249 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2252 if (isset($rule["cat_id"])) {
2254 if ($rule["cat_id"] > 0) {
2255 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2256 array_push($children, $rule["cat_id"]);
2258 $children = join(",", $children);
2260 $cat_qpart = "cat_id IN ($children)";
2262 $cat_qpart = "cat_id IS NULL";
2265 $qpart .= " AND $cat_qpart";
2268 $qpart .= " AND feed_id IS NOT NULL";
2270 array_push($query, "($qpart)");
2275 if (count($query) > 0) {
2276 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2278 $fullquery = "(false)";
2281 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2286 if (!function_exists('gzdecode')) {
2287 function gzdecode($string) { // no support for 2nd argument
2288 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2289 base64_encode($string));
2293 function get_random_bytes($length) {
2294 if (function_exists('openssl_random_pseudo_bytes')) {
2295 return openssl_random_pseudo_bytes($length);
2299 for ($i = 0; $i < $length; $i++
)
2300 $output .= chr(mt_rand(0, 255));
2306 function read_stdin() {
2307 $fp = fopen("php://stdin", "r");
2310 $line = trim(fgets($fp));
2318 function implements_interface($class, $interface) {
2319 return in_array($interface, class_implements($class));
2322 function get_minified_js($files) {
2323 require_once 'lib/jshrink/Minifier.php';
2327 foreach ($files as $js) {
2328 if (!isset($_GET['debug'])) {
2329 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2331 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2333 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2335 if ($header && $contents) {
2336 list($htag, $hversion) = explode(":", $header);
2338 if ($htag == "tt-rss" && $hversion == VERSION
) {
2345 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2346 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2350 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2357 function calculate_dep_timestamp() {
2358 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2362 foreach ($files as $file) {
2363 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2369 function T_js_decl($s1, $s2) {
2371 $s1 = preg_replace("/\n/", "", $s1);
2372 $s2 = preg_replace("/\n/", "", $s2);
2374 $s1 = preg_replace("/\"/", "\\\"", $s1);
2375 $s2 = preg_replace("/\"/", "\\\"", $s2);
2377 return "T_messages[\"$s1\"] = \"$s2\";\n";
2381 function init_js_translations() {
2383 print 'var T_messages = new Object();
2386 if (T_messages[msg]) {
2387 return T_messages[msg];
2393 function ngettext(msg1, msg2, n) {
2394 return __((parseInt(n) > 1) ? msg2 : msg1);
2397 $l10n = _get_reader();
2399 for ($i = 0; $i < $l10n->total
; $i++
) {
2400 $orig = $l10n->get_original_string($i);
2401 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2402 $key = explode(chr(0), $orig);
2403 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2404 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2406 $translation = __($orig);
2407 print T_js_decl($orig, $translation);
2412 function get_theme_path($theme) {
2413 $check = "themes/$theme";
2414 if (file_exists($check)) return $check;
2416 $check = "themes.local/$theme";
2417 if (file_exists($check)) return $check;
2420 function theme_valid($theme) {
2421 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2423 if (in_array($theme, $bundled_themes)) return true;
2425 $file = "themes/" . basename($theme);
2427 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2429 if (file_exists($file) && is_readable($file)) {
2430 $fh = fopen($file, "r");
2433 $header = fgets($fh);
2436 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2444 * @SuppressWarnings(unused)
2446 function error_json($code) {
2447 require_once "errors.php";
2449 @$message = $ERRORS[$code];
2451 return json_encode(array("error" =>
2452 array("code" => $code, "message" => $message)));
2456 /*function abs_to_rel_path($dir) {
2457 $tmp = str_replace(dirname(__DIR__), "", $dir);
2459 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2464 function get_upload_error_message($code) {
2467 0 => __('There is no error, the file uploaded with success'),
2468 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2469 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2470 3 => __('The uploaded file was only partially uploaded'),
2471 4 => __('No file was uploaded'),
2472 6 => __('Missing a temporary folder'),
2473 7 => __('Failed to write file to disk.'),
2474 8 => __('A PHP extension stopped the file upload.'),
2477 return $errors[$code];
2480 function base64_img($filename) {
2481 if (file_exists($filename)) {
2482 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2484 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));