2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 130);
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 * @param string $name The constant name.
35 * @param mixed $value The constant value.
37 * @return boolean True if defined successfully or not.
39 function define_default($name, $value) {
40 defined($name) or define($name, $value);
43 ///// Some defaults that you can override in config.php //////
45 define_default('FEED_FETCH_TIMEOUT', 45);
46 // How may seconds to wait for response when requesting feed from a site
47 define_default('FEED_FETCH_NO_CACHE_TIMEOUT', 15);
48 // How may seconds to wait for response when requesting feed from a
49 // site when that feed wasn't cached before
50 define_default('FILE_FETCH_TIMEOUT', 45);
51 // Default timeout when fetching files from remote sites
52 define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
53 // How many seconds to wait for initial response from website when
54 // fetching files from remote sites
56 if (DB_TYPE
== "pgsql") {
57 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
59 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
63 * Return available translations names.
66 * @return array A array of available translations.
68 function get_translations() {
70 "auto" => "Detect automatically",
71 "ar_SA" => "العربيّة (Arabic)",
72 "bg_BG" => "Bulgarian",
77 "el_GR" => "Ελληνικά",
78 "es_ES" => "Español (España)",
81 "fr_FR" => "Français",
82 "hu_HU" => "Magyar (Hungarian)",
83 "it_IT" => "Italiano",
84 "ja_JP" => "日本語 (Japanese)",
85 "lv_LV" => "Latviešu",
86 "nb_NO" => "Norwegian bokmål",
90 "pt_BR" => "Portuguese/Brazil",
91 "pt_PT" => "Portuguese/Portugal",
92 "zh_CN" => "Simplified Chinese",
93 "zh_TW" => "Traditional Chinese",
101 require_once "lib/accept-to-gettext.php";
102 require_once "lib/gettext/gettext.inc";
104 function startup_gettext() {
106 # Get locale from Accept-Language header
107 $lang = al2gt(array_keys(get_translations()), "text/html");
109 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
110 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
113 if ($_SESSION["uid"] && get_schema_version() >= 120) {
114 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
116 if ($pref_lang && $pref_lang != 'auto') {
122 if (defined('LC_MESSAGES')) {
123 _setlocale(LC_MESSAGES
, $lang);
124 } else if (defined('LC_ALL')) {
125 _setlocale(LC_ALL
, $lang);
128 _bindtextdomain("messages", "locale");
130 _textdomain("messages");
131 _bind_textdomain_codeset("messages", "UTF-8");
135 require_once 'db-prefs.php';
136 require_once 'version.php';
137 require_once 'controls.php';
139 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
140 ini_set('user_agent', SELF_USER_AGENT
);
142 require_once 'lib/pubsubhubbub/Publisher.php';
144 $schema_version = false;
146 function _debug_suppress($suppress) {
147 global $suppress_debugging;
149 $suppress_debugging = $suppress;
153 * Print a timestamped debug message.
155 * @param string $msg The debug message.
158 function _debug($msg, $show = true) {
159 global $suppress_debugging;
161 //echo "[$suppress_debugging] $msg $show\n";
163 if ($suppress_debugging) return false;
165 $ts = strftime("%H:%M:%S", time());
166 if (function_exists('posix_getpid')) {
167 $ts = "$ts/" . posix_getpid();
170 if ($show && !(defined('QUIET') && QUIET
)) {
171 print "[$ts] $msg\n";
174 if (defined('LOGFILE')) {
175 $fp = fopen(LOGFILE
, 'a+');
180 if (function_exists("flock")) {
183 // try to lock logfile for writing
184 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
195 fputs($fp, "[$ts] $msg\n");
197 if (function_exists("flock")) {
208 * Purge a feed old posts.
210 * @param mixed $link A database connection.
211 * @param mixed $feed_id The id of the purged feed.
212 * @param mixed $purge_interval Olderness of purged posts.
213 * @param boolean $debug Set to True to enable the debug. False by default.
217 function purge_feed($feed_id, $purge_interval, $debug = false) {
219 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
224 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
228 if (db_num_rows($result) == 1) {
229 $owner_uid = db_fetch_result($result, 0, "owner_uid");
232 if ($purge_interval == -1 ||
!$purge_interval) {
234 CCache
::update($feed_id, $owner_uid);
239 if (!$owner_uid) return;
241 if (FORCE_ARTICLE_PURGE
== 0) {
242 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
245 $purge_unread = true;
246 $purge_interval = FORCE_ARTICLE_PURGE
;
249 if (!$purge_unread) $query_limit = " unread = false AND ";
251 if (DB_TYPE
== "pgsql") {
252 $result = db_query("DELETE FROM ttrss_user_entries
254 WHERE ttrss_entries.id = ref_id AND
256 feed_id = '$feed_id' AND
258 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
262 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
263 marked = false AND feed_id = '$feed_id' AND
264 (SELECT date_updated FROM ttrss_entries WHERE
265 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
267 $result = db_query("DELETE FROM ttrss_user_entries
268 USING ttrss_user_entries, ttrss_entries
269 WHERE ttrss_entries.id = ref_id AND
271 feed_id = '$feed_id' AND
273 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
276 $rows = db_affected_rows($result);
278 CCache
::update($feed_id, $owner_uid);
281 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
285 } // function purge_feed
287 function feed_purge_interval($feed_id) {
289 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
290 WHERE id = '$feed_id'");
292 if (db_num_rows($result) == 1) {
293 $purge_interval = db_fetch_result($result, 0, "purge_interval");
294 $owner_uid = db_fetch_result($result, 0, "owner_uid");
296 if ($purge_interval == 0) $purge_interval = get_pref(
297 'PURGE_OLD_DAYS', $owner_uid);
299 return $purge_interval;
306 /*function get_feed_update_interval($feed_id) {
307 $result = db_query("SELECT owner_uid, update_interval FROM
308 ttrss_feeds WHERE id = '$feed_id'");
310 if (db_num_rows($result) == 1) {
311 $update_interval = db_fetch_result($result, 0, "update_interval");
312 $owner_uid = db_fetch_result($result, 0, "owner_uid");
314 if ($update_interval != 0) {
315 return $update_interval;
317 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
325 // TODO: multiple-argument way is deprecated, first parameter is a hash now
326 function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
327 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
329 global $fetch_last_error;
330 global $fetch_last_error_code;
331 global $fetch_last_error_content;
332 global $fetch_last_content_type;
333 global $fetch_curl_used;
335 $fetch_last_error = false;
336 $fetch_last_error_code = -1;
337 $fetch_last_error_content = "";
338 $fetch_last_content_type = "";
339 $fetch_curl_used = false;
341 if (!is_array($options)) {
343 // falling back on compatibility shim
344 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
347 for ($i = 0; $i < func_num_args(); $i++
) {
348 $tmp[$option_names[$i]] = func_get_arg($i);
354 "url" => func_get_arg(0),
355 "type" => @func_get_arg(1),
356 "login" => @func_get_arg(2),
357 "pass" => @func_get_arg(3),
358 "post_query" => @func_get_arg(4),
359 "timeout" => @func_get_arg(5),
360 "timestamp" => @func_get_arg(6),
361 "useragent" => @func_get_arg(7)
365 $url = $options["url"];
366 $type = isset($options["type"]) ?
$options["type"] : false;
367 $login = isset($options["login"]) ?
$options["login"] : false;
368 $pass = isset($options["pass"]) ?
$options["pass"] : false;
369 $post_query = isset($options["post_query"]) ?
$options["post_query"] : false;
370 $timeout = isset($options["timeout"]) ?
$options["timeout"] : false;
371 $timestamp = isset($options["timestamp"]) ?
$options["timestamp"] : 0;
372 $useragent = isset($options["useragent"]) ?
$options["useragent"] : false;
373 $followlocation = isset($options["followlocation"]) ?
$options["followlocation"] : true;
375 $url = ltrim($url, ' ');
376 $url = str_replace(' ', '%20', $url);
378 if (strpos($url, "//") === 0)
379 $url = 'http:' . $url;
381 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
383 $fetch_curl_used = true;
385 $ch = curl_init($url);
387 if ($timestamp && !$post_query) {
388 curl_setopt($ch, CURLOPT_HTTPHEADER
,
389 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
392 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
393 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
394 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("open_basedir") && $followlocation);
395 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
396 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
397 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
398 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
399 curl_setopt($ch, CURLOPT_USERAGENT
, $useragent ?
$useragent :
401 curl_setopt($ch, CURLOPT_ENCODING
, "");
402 //curl_setopt($ch, CURLOPT_REFERER, $url);
404 if (!ini_get("open_basedir")) {
405 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
408 if (defined('_CURL_HTTP_PROXY')) {
409 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
413 curl_setopt($ch, CURLOPT_POST
, true);
414 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
418 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
420 $contents = @curl_exec
($ch);
422 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
423 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
424 $contents = @curl_exec
($ch);
427 if ($contents === false) {
428 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
433 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
434 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
436 $fetch_last_error_code = $http_code;
438 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
439 if (curl_errno($ch) != 0) {
440 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
442 $fetch_last_error = "HTTP Code: $http_code";
444 $fetch_last_error_content = $contents;
454 $fetch_curl_used = false;
456 if ($login && $pass){
457 $url_parts = array();
459 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
461 $pass = urlencode($pass);
463 if ($url_parts[1] && $url_parts[2]) {
464 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
468 // TODO: should this support POST requests or not? idk
470 if (!$post_query && $timestamp) {
471 $context = stream_context_create(array(
474 'ignore_errors' => true,
475 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
476 'protocol_version'=> 1.1,
477 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
480 $context = stream_context_create(array(
483 'ignore_errors' => true,
484 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
485 'protocol_version'=> 1.1
489 $old_error = error_get_last();
491 $data = @file_get_contents
($url, false, $context);
493 if (isset($http_response_header) && is_array($http_response_header)) {
494 foreach ($http_response_header as $h) {
495 if (substr(strtolower($h), 0, 13) == 'content-type:') {
496 $fetch_last_content_type = substr($h, 14);
497 // don't abort here b/c there might be more than one
498 // e.g. if we were being redirected -- last one is the right one
501 if (substr(strtolower($h), 0, 7) == 'http/1.') {
502 $fetch_last_error_code = (int) substr($h, 9, 3);
507 if ($fetch_last_error_code != 200) {
508 $error = error_get_last();
510 if ($error['message'] != $old_error['message']) {
511 $fetch_last_error = $error["message"];
513 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
516 $fetch_last_error_content = $data;
526 * Try to determine the favicon URL for a feed.
527 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
528 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
530 * @param string $url A feed or page URL
532 * @return mixed The favicon URL, or false if none was found.
534 function get_favicon_url($url) {
536 $favicon_url = false;
538 if ($html = @fetch_file_contents
($url)) {
540 libxml_use_internal_errors(true);
542 $doc = new DOMDocument();
543 $doc->loadHTML($html);
544 $xpath = new DOMXPath($doc);
546 $base = $xpath->query('/html/head/base');
547 foreach ($base as $b) {
548 $url = $b->getAttribute("href");
552 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
553 if (count($entries) > 0) {
554 foreach ($entries as $entry) {
555 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
562 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
565 } // function get_favicon_url
567 function initialize_user_prefs($uid, $profile = false) {
569 $uid = db_escape_string($uid);
573 $profile_qpart = "AND profile IS NULL";
575 $profile_qpart = "AND profile = '$profile'";
578 if (get_schema_version() < 63) $profile_qpart = "";
582 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
584 $u_result = db_query("SELECT pref_name
585 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
587 $active_prefs = array();
589 while ($line = db_fetch_assoc($u_result)) {
590 array_push($active_prefs, $line["pref_name"]);
593 while ($line = db_fetch_assoc($result)) {
594 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
595 // print "adding " . $line["pref_name"] . "<br>";
597 $line["def_value"] = db_escape_string($line["def_value"]);
598 $line["pref_name"] = db_escape_string($line["pref_name"]);
600 if (get_schema_version() < 63) {
601 db_query("INSERT INTO ttrss_user_prefs
602 (owner_uid,pref_name,value) VALUES
603 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
606 db_query("INSERT INTO ttrss_user_prefs
607 (owner_uid,pref_name,value, profile) VALUES
608 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
618 function get_ssl_certificate_id() {
619 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
620 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
621 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
622 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
623 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
625 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
626 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
627 $_SERVER["SSL_CLIENT_V_START"] .
628 $_SERVER["SSL_CLIENT_V_END"] .
629 $_SERVER["SSL_CLIENT_S_DN"]);
634 function authenticate_user($login, $password, $check_only = false) {
636 if (!SINGLE_USER_MODE
) {
639 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
641 $user_id = (int) $plugin->authenticate($login, $password);
644 $_SESSION["auth_module"] = strtolower(get_class($plugin));
649 if ($user_id && !$check_only) {
652 $_SESSION["uid"] = $user_id;
653 $_SESSION["version"] = VERSION_STATIC
;
655 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
656 WHERE id = '$user_id'");
658 $_SESSION["name"] = db_fetch_result($result, 0, "login");
659 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
660 $_SESSION["csrf_token"] = uniqid_short();
662 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
665 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
666 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
667 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
669 $_SESSION["last_version_check"] = time();
671 initialize_user_prefs($_SESSION["uid"]);
680 $_SESSION["uid"] = 1;
681 $_SESSION["name"] = "admin";
682 $_SESSION["access_level"] = 10;
684 $_SESSION["hide_hello"] = true;
685 $_SESSION["hide_logout"] = true;
687 $_SESSION["auth_module"] = false;
689 if (!$_SESSION["csrf_token"]) {
690 $_SESSION["csrf_token"] = uniqid_short();
693 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
695 initialize_user_prefs($_SESSION["uid"]);
701 function make_password($length = 8) {
704 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
708 while ($i < $length) {
709 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
711 if (!strstr($password, $char)) {
719 // this is called after user is created to initialize default feeds, labels
722 // user preferences are checked on every login, not here
724 function initialize_user($uid) {
726 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
727 values ('$uid', 'Tiny Tiny RSS: Forum',
728 'http://tt-rss.org/forum/rss.php')");
731 function logout_user() {
733 if (isset($_COOKIE[session_name()])) {
734 setcookie(session_name(), '', time()-42000, '/');
738 function validate_csrf($csrf_token) {
739 return $csrf_token == $_SESSION['csrf_token'];
742 function load_user_plugins($owner_uid, $pluginhost = false) {
744 if (!$pluginhost) $pluginhost = PluginHost
::getInstance();
746 if ($owner_uid && SCHEMA_VERSION
>= 100) {
747 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
749 $pluginhost->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
751 if (get_schema_version() > 100) {
752 $pluginhost->load_data();
757 function login_sequence() {
758 if (SINGLE_USER_MODE
) {
760 authenticate_user("admin", null);
762 load_user_plugins($_SESSION["uid"]);
764 if (!validate_session()) $_SESSION["uid"] = false;
766 if (!$_SESSION["uid"]) {
768 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
769 $_SESSION["ref_schema_version"] = get_schema_version(true);
771 authenticate_user(null, null, true);
774 if (!$_SESSION["uid"]) {
776 setcookie(session_name(), '', time()-42000, '/');
783 /* bump login timestamp */
784 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
786 $_SESSION["last_login_update"] = time();
789 if ($_SESSION["uid"]) {
791 load_user_plugins($_SESSION["uid"]);
795 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
796 $_SESSION["uid"] . " AND
797 (SELECT COUNT(id) FROM ttrss_feeds WHERE
798 ttrss_feeds.id = feed_id) = 0");
800 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
801 $_SESSION["uid"] . " AND
802 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
803 ttrss_feed_categories.id = feed_id) = 0");
810 function truncate_string($str, $max_len, $suffix = '…') {
811 if (mb_strlen($str, "utf-8") > $max_len) {
812 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
819 function truncate_middle($str, $max_len, $suffix = '…') {
820 if (strlen($str) > $max_len) {
821 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
827 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
830 $source_tz = new DateTimeZone($source_tz);
831 } catch (Exception
$e) {
832 $source_tz = new DateTimeZone('UTC');
836 $dest_tz = new DateTimeZone($dest_tz);
837 } catch (Exception
$e) {
838 $dest_tz = new DateTimeZone('UTC');
841 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
842 return $dt->format('U') +
$dest_tz->getOffset($dt);
845 function make_local_datetime($timestamp, $long, $owner_uid = false,
846 $no_smart_dt = false, $eta_min = false) {
848 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
849 if (!$timestamp) $timestamp = '1970-01-01 0:00';
854 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
856 $timestamp = substr($timestamp, 0, 19);
858 # We store date in UTC internally
859 $dt = new DateTime($timestamp, $utc_tz);
861 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
863 if ($user_tz_string != 'Automatic') {
866 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
867 } catch (Exception
$e) {
871 $tz_offset = $user_tz->getOffset($dt);
873 $tz_offset = (int) -$_SESSION["clientTzOffset"];
876 $user_timestamp = $dt->format('U') +
$tz_offset;
879 return smart_date_time($user_timestamp,
880 $tz_offset, $owner_uid, $eta_min);
883 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
885 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
887 return date($format, $user_timestamp);
891 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
892 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
894 if ($eta_min && time() +
$tz_offset - $timestamp < 3600) {
895 return T_sprintf("%d min", date("i", time() +
$tz_offset - $timestamp));
896 } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
897 return date("G:i", $timestamp);
898 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
899 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
900 return date($format, $timestamp);
902 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
903 return date($format, $timestamp);
907 function sql_bool_to_bool($s) {
908 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
915 function bool_to_sql_bool($s) {
923 // Session caching removed due to causing wrong redirects to upgrade
924 // script when get_schema_version() is called on an obsolete session
925 // created on a previous schema version.
926 function get_schema_version($nocache = false) {
927 global $schema_version;
929 if (!$schema_version && !$nocache) {
930 $result = db_query("SELECT schema_version FROM ttrss_version");
931 $version = db_fetch_result($result, 0, "schema_version");
932 $schema_version = $version;
935 return $schema_version;
939 function sanity_check() {
940 require_once 'errors.php';
944 $schema_version = get_schema_version(true);
946 if ($schema_version != SCHEMA_VERSION
) {
950 if (DB_TYPE
== "mysql") {
951 $result = db_query("SELECT true", false);
952 if (db_num_rows($result) != 1) {
957 if (db_escape_string("testTEST") != "testTEST") {
961 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
964 function file_is_locked($filename) {
965 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
966 if (function_exists('flock')) {
967 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
969 if (flock($fp, LOCK_EX | LOCK_NB
)) {
980 return true; // consider the file always locked and skip the test
987 function make_lockfile($filename) {
988 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
990 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
991 $stat_h = fstat($fp);
992 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
994 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
995 if ($stat_h["ino"] != $stat_f["ino"] ||
996 $stat_h["dev"] != $stat_f["dev"]) {
1002 if (function_exists('posix_getpid')) {
1003 fwrite($fp, posix_getpid() . "\n");
1011 function make_stampfile($filename) {
1012 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1014 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1015 fwrite($fp, time() . "\n");
1016 flock($fp, LOCK_UN
);
1024 function sql_random_function() {
1025 if (DB_TYPE
== "mysql") {
1032 function getFeedUnread($feed, $is_cat = false) {
1033 return Feeds
::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1037 /*function get_pgsql_version() {
1038 $result = db_query("SELECT version() AS version");
1039 $version = explode(" ", db_fetch_result($result, 0, "version"));
1043 function checkbox_to_sql_bool($val) {
1044 return ($val == "on") ?
"true" : "false";
1047 /*function getFeedCatTitle($id) {
1049 return __("Special");
1050 } else if ($id < LABEL_BASE_INDEX) {
1051 return __("Labels");
1052 } else if ($id > 0) {
1053 $result = db_query("SELECT ttrss_feed_categories.title
1054 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1055 cat_id = ttrss_feed_categories.id");
1056 if (db_num_rows($result) == 1) {
1057 return db_fetch_result($result, 0, "title");
1059 return __("Uncategorized");
1062 return "getFeedCatTitle($id) failed";
1067 function uniqid_short() {
1068 return uniqid(base_convert(rand(), 10, 36));
1071 function make_init_params() {
1074 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1075 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1076 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1077 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1079 $params[strtolower($param)] = (int) get_pref($param);
1082 $params["icons_url"] = ICONS_URL
;
1083 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1084 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1085 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1086 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1087 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1088 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
1090 $theme = get_pref( "USER_CSS_THEME", false, false);
1091 $params["theme"] = theme_valid("$theme") ?
$theme : "";
1093 $params["plugins"] = implode(", ", PluginHost
::getInstance()->get_plugin_names());
1095 $params["php_platform"] = PHP_OS
;
1096 $params["php_version"] = PHP_VERSION
;
1098 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1100 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1101 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1103 $max_feed_id = db_fetch_result($result, 0, "mid");
1104 $num_feeds = db_fetch_result($result, 0, "nf");
1106 $params["max_feed_id"] = (int) $max_feed_id;
1107 $params["num_feeds"] = (int) $num_feeds;
1109 $params["hotkeys"] = get_hotkeys_map();
1111 $params["csrf_token"] = $_SESSION["csrf_token"];
1112 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1114 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1116 $params["icon_alert"] = base64_img("images/alert.png");
1117 $params["icon_information"] = base64_img("images/information.png");
1118 $params["icon_cross"] = base64_img("images/cross.png");
1119 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
1124 function get_hotkeys_info() {
1126 __("Navigation") => array(
1127 "next_feed" => __("Open next feed"),
1128 "prev_feed" => __("Open previous feed"),
1129 "next_article" => __("Open next article"),
1130 "prev_article" => __("Open previous article"),
1131 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1132 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1133 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1134 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1135 "search_dialog" => __("Show search dialog")),
1136 __("Article") => array(
1137 "toggle_mark" => __("Toggle starred"),
1138 "toggle_publ" => __("Toggle published"),
1139 "toggle_unread" => __("Toggle unread"),
1140 "edit_tags" => __("Edit tags"),
1141 "open_in_new_window" => __("Open in new window"),
1142 "catchup_below" => __("Mark below as read"),
1143 "catchup_above" => __("Mark above as read"),
1144 "article_scroll_down" => __("Scroll down"),
1145 "article_scroll_up" => __("Scroll up"),
1146 "select_article_cursor" => __("Select article under cursor"),
1147 "email_article" => __("Email article"),
1148 "close_article" => __("Close/collapse article"),
1149 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1150 "toggle_widescreen" => __("Toggle widescreen mode"),
1151 "toggle_embed_original" => __("Toggle embed original")),
1152 __("Article selection") => array(
1153 "select_all" => __("Select all articles"),
1154 "select_unread" => __("Select unread"),
1155 "select_marked" => __("Select starred"),
1156 "select_published" => __("Select published"),
1157 "select_invert" => __("Invert selection"),
1158 "select_none" => __("Deselect everything")),
1159 __("Feed") => array(
1160 "feed_refresh" => __("Refresh current feed"),
1161 "feed_unhide_read" => __("Un/hide read feeds"),
1162 "feed_subscribe" => __("Subscribe to feed"),
1163 "feed_edit" => __("Edit feed"),
1164 "feed_catchup" => __("Mark as read"),
1165 "feed_reverse" => __("Reverse headlines"),
1166 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1167 "feed_debug_update" => __("Debug feed update"),
1168 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1169 "catchup_all" => __("Mark all feeds as read"),
1170 "cat_toggle_collapse" => __("Un/collapse current category"),
1171 "toggle_combined_mode" => __("Toggle combined mode"),
1172 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1173 __("Go to") => array(
1174 "goto_all" => __("All articles"),
1175 "goto_fresh" => __("Fresh"),
1176 "goto_marked" => __("Starred"),
1177 "goto_published" => __("Published"),
1178 "goto_tagcloud" => __("Tag cloud"),
1179 "goto_prefs" => __("Preferences")),
1180 __("Other") => array(
1181 "create_label" => __("Create label"),
1182 "create_filter" => __("Create filter"),
1183 "collapse_sidebar" => __("Un/collapse sidebar"),
1184 "help_dialog" => __("Show help dialog"))
1187 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
1188 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1194 function get_hotkeys_map() {
1196 // "navigation" => array(
1199 "n" => "next_article",
1200 "p" => "prev_article",
1201 "(38)|up" => "prev_article",
1202 "(40)|down" => "next_article",
1203 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1204 // "^(40)|Ctrl-down" => "next_article_noscroll",
1205 "(191)|/" => "search_dialog",
1206 // "article" => array(
1207 "s" => "toggle_mark",
1208 "*s" => "toggle_publ",
1209 "u" => "toggle_unread",
1210 "*t" => "edit_tags",
1211 "o" => "open_in_new_window",
1212 "c p" => "catchup_below",
1213 "c n" => "catchup_above",
1214 "*n" => "article_scroll_down",
1215 "*p" => "article_scroll_up",
1216 "*(38)|Shift+up" => "article_scroll_up",
1217 "*(40)|Shift+down" => "article_scroll_down",
1218 "a *w" => "toggle_widescreen",
1219 "a e" => "toggle_embed_original",
1220 "e" => "email_article",
1221 "a q" => "close_article",
1222 // "article_selection" => array(
1223 "a a" => "select_all",
1224 "a u" => "select_unread",
1225 "a *u" => "select_marked",
1226 "a p" => "select_published",
1227 "a i" => "select_invert",
1228 "a n" => "select_none",
1230 "f r" => "feed_refresh",
1231 "f a" => "feed_unhide_read",
1232 "f s" => "feed_subscribe",
1233 "f e" => "feed_edit",
1234 "f q" => "feed_catchup",
1235 "f x" => "feed_reverse",
1236 "f g" => "feed_toggle_vgroup",
1237 "f *d" => "feed_debug_update",
1238 "f *g" => "feed_debug_viewfeed",
1239 "f *c" => "toggle_combined_mode",
1240 "f c" => "toggle_cdm_expanded",
1241 "*q" => "catchup_all",
1242 "x" => "cat_toggle_collapse",
1244 "g a" => "goto_all",
1245 "g f" => "goto_fresh",
1246 "g s" => "goto_marked",
1247 "g p" => "goto_published",
1248 "g t" => "goto_tagcloud",
1249 "g *p" => "goto_prefs",
1250 // "other" => array(
1251 "(9)|Tab" => "select_article_cursor", // tab
1252 "c l" => "create_label",
1253 "c f" => "create_filter",
1254 "c s" => "collapse_sidebar",
1255 "^(191)|Ctrl+/" => "help_dialog",
1258 if (get_pref('COMBINED_DISPLAY_MODE')) {
1259 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1260 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1263 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
1264 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1267 $prefixes = array();
1269 foreach (array_keys($hotkeys) as $hotkey) {
1270 $pair = explode(" ", $hotkey, 2);
1272 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1273 array_push($prefixes, $pair[0]);
1277 return array($prefixes, $hotkeys);
1280 function check_for_update() {
1281 if (defined("GIT_VERSION_TIMESTAMP")) {
1282 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1285 $content = json_decode($content, true);
1287 if ($content && isset($content["changeset"])) {
1288 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
1289 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
1291 return $content["changeset"]["id"];
1300 function make_runtime_info($disable_update_check = false) {
1303 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1304 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1306 $max_feed_id = db_fetch_result($result, 0, "mid");
1307 $num_feeds = db_fetch_result($result, 0, "nf");
1309 $data["max_feed_id"] = (int) $max_feed_id;
1310 $data["num_feeds"] = (int) $num_feeds;
1312 $data['last_article_id'] = Article
::getLastArticleId();
1313 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1315 $data['dep_ts'] = calculate_dep_timestamp();
1316 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1319 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
1320 $update_result = @check_for_update
();
1322 $data["update_result"] = $update_result;
1324 $_SESSION["last_version_check"] = time();
1327 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
1329 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1331 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1333 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
1336 $stamp_delta = time() - $stamp;
1338 if ($stamp_delta > 1800) {
1342 $_SESSION["daemon_stamp_check"] = time();
1345 $data['daemon_stamp_ok'] = $stamp_check;
1347 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1349 $data['daemon_stamp'] = $stamp_fmt;
1357 function search_to_sql($search, $search_language) {
1359 $keywords = str_getcsv(trim($search), " ");
1360 $query_keywords = array();
1361 $search_words = array();
1362 $search_query_leftover = array();
1364 if ($search_language)
1365 $search_language = db_escape_string(mb_strtolower($search_language));
1367 $search_language = "english";
1369 foreach ($keywords as $k) {
1370 if (strpos($k, "-") === 0) {
1377 $commandpair = explode(":", mb_strtolower($k), 2);
1379 switch ($commandpair[0]) {
1381 if ($commandpair[1]) {
1382 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1383 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1385 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1386 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1387 array_push($search_words, $k);
1391 if ($commandpair[1]) {
1392 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1393 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1395 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1396 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1397 array_push($search_words, $k);
1401 if ($commandpair[1]) {
1402 if ($commandpair[1] == "true")
1403 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1404 else if ($commandpair[1] == "false")
1405 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1407 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1408 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1410 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1411 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1412 if (!$not) array_push($search_words, $k);
1417 if ($commandpair[1]) {
1418 if ($commandpair[1] == "true")
1419 array_push($query_keywords, "($not (marked = true))");
1421 array_push($query_keywords, "($not (marked = false))");
1423 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1424 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1425 if (!$not) array_push($search_words, $k);
1429 if ($commandpair[1]) {
1430 if ($commandpair[1] == "true")
1431 array_push($query_keywords, "($not (published = true))");
1433 array_push($query_keywords, "($not (published = false))");
1436 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1437 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1438 if (!$not) array_push($search_words, $k);
1442 if ($commandpair[1]) {
1443 if ($commandpair[1] == "true")
1444 array_push($query_keywords, "($not (unread = true))");
1446 array_push($query_keywords, "($not (unread = false))");
1449 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1450 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1451 if (!$not) array_push($search_words, $k);
1455 if (strpos($k, "@") === 0) {
1457 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1458 $orig_ts = strtotime(substr($k, 1));
1459 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1461 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1463 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
1466 if (DB_TYPE
== "pgsql") {
1467 $k = mb_strtolower($k);
1468 array_push($search_query_leftover, $not ?
"!$k" : $k);
1470 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1471 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1474 if (!$not) array_push($search_words, $k);
1479 if (count($search_query_leftover) > 0) {
1480 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1482 if (DB_TYPE
== "pgsql") {
1483 array_push($query_keywords,
1484 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1489 $search_query_part = implode("AND", $query_keywords);
1491 return array($search_query_part, $search_words);
1494 function iframe_whitelisted($entry) {
1495 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1497 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
1500 foreach ($whitelist as $w) {
1501 if ($src == $w ||
$src == "www.$w")
1509 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1510 if (!$owner) $owner = $_SESSION["uid"];
1512 $res = trim($str); if (!$res) return '';
1514 $charset_hack = '<head>
1515 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1518 $res = trim($res); if (!$res) return '';
1520 libxml_use_internal_errors(true);
1522 $doc = new DOMDocument();
1523 $doc->loadHTML($charset_hack . $res);
1524 $xpath = new DOMXPath($doc);
1526 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
1527 $rewrite_base_url = $site_url ?
$site_url : SELF_URL_PATH
;
1529 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1531 foreach ($entries as $entry) {
1533 if ($entry->hasAttribute('href')) {
1534 $entry->setAttribute('href',
1535 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1537 $entry->setAttribute('rel', 'noopener noreferrer');
1540 if ($entry->hasAttribute('src')) {
1541 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1542 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1544 if (file_exists($cached_filename)) {
1546 // this is strictly cosmetic
1547 if ($entry->tagName
== 'img') {
1549 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1551 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1557 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1559 if ($entry->hasAttribute('srcset')) {
1560 $entry->removeAttribute('srcset');
1563 if ($entry->hasAttribute('sizes')) {
1564 $entry->removeAttribute('sizes');
1568 $entry->setAttribute('src', $src);
1571 if ($entry->nodeName
== 'img') {
1573 if ($entry->hasAttribute('src')) {
1574 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1576 if ($ttrss_uses_https && !$is_https_url) {
1578 if ($entry->hasAttribute('srcset')) {
1579 $entry->removeAttribute('srcset');
1582 if ($entry->hasAttribute('sizes')) {
1583 $entry->removeAttribute('sizes');
1588 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1589 $force_remove_images ||
$_SESSION["bw_limit"]) {
1591 $p = $doc->createElement('p');
1593 $a = $doc->createElement('a');
1594 $a->setAttribute('href', $entry->getAttribute('src'));
1596 $a->appendChild(new DOMText($entry->getAttribute('src')));
1597 $a->setAttribute('target', '_blank');
1598 $a->setAttribute('rel', 'noopener noreferrer');
1600 $p->appendChild($a);
1602 $entry->parentNode
->replaceChild($p, $entry);
1606 if (strtolower($entry->nodeName
) == "a") {
1607 $entry->setAttribute("target", "_blank");
1608 $entry->setAttribute("rel", "noopener noreferrer");
1612 $entries = $xpath->query('//iframe');
1613 foreach ($entries as $entry) {
1614 if (!iframe_whitelisted($entry)) {
1615 $entry->setAttribute('sandbox', 'allow-scripts');
1617 if ($_SERVER['HTTPS'] == "on") {
1618 $entry->setAttribute("src",
1619 str_replace("http://", "https://",
1620 $entry->getAttribute("src")));
1625 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1626 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1627 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1628 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1629 'dt', 'em', 'footer', 'figure', 'figcaption',
1630 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1631 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1632 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1633 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1634 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1635 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1637 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1639 $disallowed_attributes = array('id', 'style', 'class');
1641 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1642 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1643 if (is_array($retval)) {
1645 $allowed_elements = $retval[1];
1646 $disallowed_attributes = $retval[2];
1652 $doc->removeChild($doc->firstChild
); //remove doctype
1653 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1655 if ($highlight_words) {
1656 foreach ($highlight_words as $word) {
1658 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1660 $elements = $xpath->query("//*/text()");
1662 foreach ($elements as $child) {
1664 $fragment = $doc->createDocumentFragment();
1665 $text = $child->textContent
;
1667 while (($pos = mb_stripos($text, $word)) !== false) {
1668 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1669 $word = mb_substr($text, $pos, mb_strlen($word));
1670 $highlight = $doc->createElement('span');
1671 $highlight->appendChild(new DomText($word));
1672 $highlight->setAttribute('class', 'highlight');
1673 $fragment->appendChild($highlight);
1674 $text = mb_substr($text, $pos +
mb_strlen($word));
1677 if (!empty($text)) $fragment->appendChild(new DomText($text));
1679 $child->parentNode
->replaceChild($fragment, $child);
1684 $res = $doc->saveHTML();
1686 /* strip everything outside of <body>...</body> */
1688 $res_frag = array();
1689 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1690 return $res_frag[1];
1696 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1697 $xpath = new DOMXPath($doc);
1698 $entries = $xpath->query('//*');
1700 foreach ($entries as $entry) {
1701 if (!in_array($entry->nodeName
, $allowed_elements)) {
1702 $entry->parentNode
->removeChild($entry);
1705 if ($entry->hasAttributes()) {
1706 $attrs_to_remove = array();
1708 foreach ($entry->attributes
as $attr) {
1710 if (strpos($attr->nodeName
, 'on') === 0) {
1711 array_push($attrs_to_remove, $attr);
1714 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1715 array_push($attrs_to_remove, $attr);
1718 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1719 array_push($attrs_to_remove, $attr);
1723 foreach ($attrs_to_remove as $attr) {
1724 $entry->removeAttributeNode($attr);
1732 function trim_array($array) {
1734 array_walk($tmp, 'trim');
1738 function tag_is_valid($tag) {
1739 if ($tag == '') return false;
1740 if (is_numeric($tag)) return false;
1741 if (mb_strlen($tag) > 250) return false;
1743 if (!$tag) return false;
1748 function render_login_form() {
1749 header('Cache-Control: public');
1751 require_once "login_form.php";
1755 function T_sprintf() {
1756 $args = func_get_args();
1757 return vsprintf(__(array_shift($args)), $args);
1760 function print_checkpoint($n, $s) {
1761 $ts = microtime(true);
1762 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1766 function sanitize_tag($tag) {
1769 $tag = mb_strtolower($tag, 'utf-8');
1771 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1773 if (DB_TYPE
== "mysql") {
1774 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1780 function get_self_url_prefix() {
1781 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1782 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1784 return SELF_URL_PATH
;
1789 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1791 * @return string The Mozilla Firefox feed adding URL.
1793 function add_feed_url() {
1794 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1796 $url_path = get_self_url_prefix() .
1797 "/public.php?op=subscribe&feed_url=%s";
1799 } // function add_feed_url
1801 function encrypt_password($pass, $salt = '', $mode2 = false) {
1802 if ($salt && $mode2) {
1803 return "MODE2:" . hash('sha256', $salt . $pass);
1805 return "SHA1X:" . sha1("$salt:$pass");
1807 return "SHA1:" . sha1($pass);
1809 } // function encrypt_password
1811 function load_filters($feed_id, $owner_uid) {
1814 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1817 $null_cat_qpart = "cat_id IS NULL OR";
1819 $null_cat_qpart = "";
1821 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1822 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1824 $check_cats = join(",", array_merge(
1825 Feeds
::getParentCategories($cat_id, $owner_uid),
1828 while ($line = db_fetch_assoc($result)) {
1829 $filter_id = $line["id"];
1831 $result2 = db_query("SELECT
1832 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1833 FROM ttrss_filters2_rules AS r,
1834 ttrss_filter_types AS t
1836 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1837 (feed_id IS NULL OR feed_id = '$feed_id') AND
1838 filter_type = t.id AND filter_id = '$filter_id'");
1843 while ($rule_line = db_fetch_assoc($result2)) {
1844 # print_r($rule_line);
1847 $rule["reg_exp"] = $rule_line["reg_exp"];
1848 $rule["type"] = $rule_line["type_name"];
1849 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1851 array_push($rules, $rule);
1854 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1855 FROM ttrss_filters2_actions AS a,
1856 ttrss_filter_actions AS t
1858 action_id = t.id AND filter_id = '$filter_id'");
1860 while ($action_line = db_fetch_assoc($result2)) {
1861 # print_r($action_line);
1864 $action["type"] = $action_line["type_name"];
1865 $action["param"] = $action_line["action_param"];
1867 array_push($actions, $action);
1872 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1873 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1874 $filter["rules"] = $rules;
1875 $filter["actions"] = $actions;
1877 if (count($rules) > 0 && count($actions) > 0) {
1878 array_push($filters, $filter);
1885 function get_score_pic($score) {
1887 return "score_high.png";
1888 } else if ($score > 0) {
1889 return "score_half_high.png";
1890 } else if ($score < -100) {
1891 return "score_low.png";
1892 } else if ($score < 0) {
1893 return "score_half_low.png";
1895 return "score_neutral.png";
1899 function feed_has_icon($id) {
1900 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1903 function init_plugins() {
1904 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1909 function add_feed_category($feed_cat, $parent_cat_id = false) {
1911 if (!$feed_cat) return false;
1915 if ($parent_cat_id) {
1916 $parent_qpart = "parent_cat = '$parent_cat_id'";
1917 $parent_insert = "'$parent_cat_id'";
1919 $parent_qpart = "parent_cat IS NULL";
1920 $parent_insert = "NULL";
1923 $feed_cat = mb_substr($feed_cat, 0, 250);
1926 "SELECT id FROM ttrss_feed_categories
1927 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1929 if (db_num_rows($result) == 0) {
1932 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1933 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1944 * Fixes incomplete URLs by prepending "http://".
1945 * Also replaces feed:// with http://, and
1946 * prepends a trailing slash if the url is a domain name only.
1948 * @param string $url Possibly incomplete URL
1950 * @return string Fixed URL.
1952 function fix_url($url) {
1954 // support schema-less urls
1955 if (strpos($url, '//') === 0) {
1956 $url = 'https:' . $url;
1959 if (strpos($url, '://') === false) {
1960 $url = 'http://' . $url;
1961 } else if (substr($url, 0, 5) == 'feed:') {
1962 $url = 'http:' . substr($url, 5);
1965 //prepend slash if the URL has no slash in it
1966 // "http://www.example" -> "http://www.example/"
1967 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1971 //convert IDNA hostname to punycode if possible
1972 if (function_exists("idn_to_ascii")) {
1973 $parts = parse_url($url);
1974 if (mb_detect_encoding($parts['host']) != 'ASCII')
1976 $parts['host'] = idn_to_ascii($parts['host']);
1977 $url = build_url($parts);
1981 if ($url != "http:///")
1987 function validate_feed_url($url) {
1988 $parts = parse_url($url);
1990 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
1994 /* function save_email_address($email) {
1995 // FIXME: implement persistent storage of emails
1997 if (!$_SESSION['stored_emails'])
1998 $_SESSION['stored_emails'] = array();
2000 if (!in_array($email, $_SESSION['stored_emails']))
2001 array_push($_SESSION['stored_emails'], $email);
2005 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2007 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2009 $sql_is_cat = bool_to_sql_bool($is_cat);
2011 $result = db_query("SELECT access_key FROM ttrss_access_keys
2012 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2013 AND owner_uid = " . $owner_uid);
2015 if (db_num_rows($result) == 1) {
2016 return db_fetch_result($result, 0, "access_key");
2018 $key = db_escape_string(uniqid_short());
2020 $result = db_query("INSERT INTO ttrss_access_keys
2021 (access_key, feed_id, is_cat, owner_uid)
2022 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2029 function get_feeds_from_html($url, $content)
2031 $url = fix_url($url);
2032 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2034 libxml_use_internal_errors(true);
2036 $doc = new DOMDocument();
2037 $doc->loadHTML($content);
2038 $xpath = new DOMXPath($doc);
2039 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2040 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2041 $feedUrls = array();
2042 foreach ($entries as $entry) {
2043 if ($entry->hasAttribute('href')) {
2044 $title = $entry->getAttribute('title');
2046 $title = $entry->getAttribute('type');
2048 $feedUrl = rewrite_relative_url(
2049 $baseUrl, $entry->getAttribute('href')
2051 $feedUrls[$feedUrl] = $title;
2057 function is_html($content) {
2058 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2061 function url_is_html($url, $login = false, $pass = false) {
2062 return is_html(fetch_file_contents($url, false, $login, $pass));
2065 function build_url($parts) {
2066 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2069 function cleanup_url_path($path) {
2070 $path = str_replace("/./", "/", $path);
2071 $path = str_replace("//", "/", $path);
2077 * Converts a (possibly) relative URL to a absolute one.
2079 * @param string $url Base URL (i.e. from where the document is)
2080 * @param string $rel_url Possibly relative URL in the document
2082 * @return string Absolute URL
2084 function rewrite_relative_url($url, $rel_url) {
2085 if (strpos($rel_url, "://") !== false) {
2087 } else if (strpos($rel_url, "//") === 0) {
2088 # protocol-relative URL (rare but they exist)
2090 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2091 # magnet:, feed:, etc
2093 } else if (strpos($rel_url, "/") === 0) {
2094 $parts = parse_url($url);
2095 $parts['path'] = $rel_url;
2096 $parts['path'] = cleanup_url_path($parts['path']);
2098 return build_url($parts);
2101 $parts = parse_url($url);
2102 if (!isset($parts['path'])) {
2103 $parts['path'] = '/';
2105 $dir = $parts['path'];
2106 if (substr($dir, -1) !== '/') {
2107 $dir = dirname($parts['path']);
2108 $dir !== '/' && $dir .= '/';
2110 $parts['path'] = $dir . $rel_url;
2111 $parts['path'] = cleanup_url_path($parts['path']);
2113 return build_url($parts);
2117 function cleanup_tags($days = 14, $limit = 1000) {
2119 if (DB_TYPE
== "pgsql") {
2120 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2121 } else if (DB_TYPE
== "mysql") {
2122 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2127 while ($limit > 0) {
2130 $query = "SELECT ttrss_tags.id AS id
2131 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2132 WHERE post_int_id = int_id AND $interval_query AND
2133 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2135 $result = db_query($query);
2139 while ($line = db_fetch_assoc($result)) {
2140 array_push($ids, $line['id']);
2143 if (count($ids) > 0) {
2144 $ids = join(",", $ids);
2146 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2147 $tags_deleted +
= db_affected_rows($tmp_result);
2152 $limit -= $limit_part;
2155 return $tags_deleted;
2158 function print_user_stylesheet() {
2159 $value = get_pref('USER_STYLESHEET');
2162 print "<style type=\"text/css\">";
2163 print str_replace("<br/>", "\n", $value);
2169 function filter_to_sql($filter, $owner_uid) {
2172 if (DB_TYPE
== "pgsql")
2175 $reg_qpart = "REGEXP";
2177 foreach ($filter["rules"] AS $rule) {
2178 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2179 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2180 $rule['reg_exp']) !== FALSE;
2182 if ($regexp_valid) {
2184 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2186 switch ($rule["type"]) {
2188 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2189 $rule['reg_exp'] . "')";
2192 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2193 $rule['reg_exp'] . "')";
2196 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2197 $rule['reg_exp'] . "') OR LOWER(" .
2198 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2201 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2202 $rule['reg_exp'] . "')";
2205 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2206 $rule['reg_exp'] . "')";
2209 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2210 $rule['reg_exp'] . "')";
2214 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2216 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2217 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2220 if (isset($rule["cat_id"])) {
2222 if ($rule["cat_id"] > 0) {
2223 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2224 array_push($children, $rule["cat_id"]);
2226 $children = join(",", $children);
2228 $cat_qpart = "cat_id IN ($children)";
2230 $cat_qpart = "cat_id IS NULL";
2233 $qpart .= " AND $cat_qpart";
2236 $qpart .= " AND feed_id IS NOT NULL";
2238 array_push($query, "($qpart)");
2243 if (count($query) > 0) {
2244 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2246 $fullquery = "(false)";
2249 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2254 if (!function_exists('gzdecode')) {
2255 function gzdecode($string) { // no support for 2nd argument
2256 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2257 base64_encode($string));
2261 function get_random_bytes($length) {
2262 if (function_exists('openssl_random_pseudo_bytes')) {
2263 return openssl_random_pseudo_bytes($length);
2267 for ($i = 0; $i < $length; $i++
)
2268 $output .= chr(mt_rand(0, 255));
2274 function read_stdin() {
2275 $fp = fopen("php://stdin", "r");
2278 $line = trim(fgets($fp));
2286 function implements_interface($class, $interface) {
2287 return in_array($interface, class_implements($class));
2290 function get_minified_js($files) {
2291 require_once 'lib/jshrink/Minifier.php';
2295 foreach ($files as $js) {
2296 if (!isset($_GET['debug'])) {
2297 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2299 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2301 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2303 if ($header && $contents) {
2304 list($htag, $hversion) = explode(":", $header);
2306 if ($htag == "tt-rss" && $hversion == VERSION
) {
2313 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2314 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2318 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2325 function calculate_dep_timestamp() {
2326 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2330 foreach ($files as $file) {
2331 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2337 function T_js_decl($s1, $s2) {
2339 $s1 = preg_replace("/\n/", "", $s1);
2340 $s2 = preg_replace("/\n/", "", $s2);
2342 $s1 = preg_replace("/\"/", "\\\"", $s1);
2343 $s2 = preg_replace("/\"/", "\\\"", $s2);
2345 return "T_messages[\"$s1\"] = \"$s2\";\n";
2349 function init_js_translations() {
2351 print 'var T_messages = new Object();
2354 if (T_messages[msg]) {
2355 return T_messages[msg];
2361 function ngettext(msg1, msg2, n) {
2362 return __((parseInt(n) > 1) ? msg2 : msg1);
2365 $l10n = _get_reader();
2367 for ($i = 0; $i < $l10n->total
; $i++
) {
2368 $orig = $l10n->get_original_string($i);
2369 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2370 $key = explode(chr(0), $orig);
2371 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2372 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2374 $translation = __($orig);
2375 print T_js_decl($orig, $translation);
2380 function get_theme_path($theme) {
2381 $check = "themes/$theme";
2382 if (file_exists($check)) return $check;
2384 $check = "themes.local/$theme";
2385 if (file_exists($check)) return $check;
2388 function theme_valid($theme) {
2389 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2391 if (in_array($theme, $bundled_themes)) return true;
2393 $file = "themes/" . basename($theme);
2395 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2397 if (file_exists($file) && is_readable($file)) {
2398 $fh = fopen($file, "r");
2401 $header = fgets($fh);
2404 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2412 * @SuppressWarnings(unused)
2414 function error_json($code) {
2415 require_once "errors.php";
2417 @$message = $ERRORS[$code];
2419 return json_encode(array("error" =>
2420 array("code" => $code, "message" => $message)));
2424 /*function abs_to_rel_path($dir) {
2425 $tmp = str_replace(dirname(__DIR__), "", $dir);
2427 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2432 function get_upload_error_message($code) {
2435 0 => __('There is no error, the file uploaded with success'),
2436 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2437 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2438 3 => __('The uploaded file was only partially uploaded'),
2439 4 => __('No file was uploaded'),
2440 6 => __('Missing a temporary folder'),
2441 7 => __('Failed to write file to disk.'),
2442 8 => __('A PHP extension stopped the file upload.'),
2445 return $errors[$code];
2448 function base64_img($filename) {
2449 if (file_exists($filename)) {
2450 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2452 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));