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 // feed updating stuff
57 define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
58 define_default('DAEMON_FEED_LIMIT', 500);
59 define_default('DAEMON_SLEEP_INTERVAL', 120);
60 define_default('_MIN_CACHE_FILE_SIZE', 1024);
62 if (DB_TYPE
== "pgsql") {
63 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
65 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
69 * Return available translations names.
72 * @return array A array of available translations.
74 function get_translations() {
76 "auto" => "Detect automatically",
77 "ar_SA" => "العربيّة (Arabic)",
78 "bg_BG" => "Bulgarian",
83 "el_GR" => "Ελληνικά",
84 "es_ES" => "Español (España)",
87 "fr_FR" => "Français",
88 "hu_HU" => "Magyar (Hungarian)",
89 "it_IT" => "Italiano",
90 "ja_JP" => "日本語 (Japanese)",
91 "lv_LV" => "Latviešu",
92 "nb_NO" => "Norwegian bokmål",
96 "pt_BR" => "Portuguese/Brazil",
97 "pt_PT" => "Portuguese/Portugal",
98 "zh_CN" => "Simplified Chinese",
99 "zh_TW" => "Traditional Chinese",
100 "sv_SE" => "Svenska",
102 "tr_TR" => "Türkçe");
107 require_once "lib/accept-to-gettext.php";
108 require_once "lib/gettext/gettext.inc";
110 function startup_gettext() {
112 # Get locale from Accept-Language header
113 $lang = al2gt(array_keys(get_translations()), "text/html");
115 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
116 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
119 if ($_SESSION["uid"] && get_schema_version() >= 120) {
120 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
122 if ($pref_lang && $pref_lang != 'auto') {
128 if (defined('LC_MESSAGES')) {
129 _setlocale(LC_MESSAGES
, $lang);
130 } else if (defined('LC_ALL')) {
131 _setlocale(LC_ALL
, $lang);
134 _bindtextdomain("messages", "locale");
136 _textdomain("messages");
137 _bind_textdomain_codeset("messages", "UTF-8");
141 require_once 'db-prefs.php';
142 require_once 'version.php';
143 require_once 'controls.php';
145 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
146 ini_set('user_agent', SELF_USER_AGENT
);
148 require_once 'lib/pubsubhubbub/Publisher.php';
150 $schema_version = false;
152 function _debug_suppress($suppress) {
153 global $suppress_debugging;
155 $suppress_debugging = $suppress;
159 * Print a timestamped debug message.
161 * @param string $msg The debug message.
164 function _debug($msg, $show = true) {
165 global $suppress_debugging;
167 //echo "[$suppress_debugging] $msg $show\n";
169 if ($suppress_debugging) return false;
171 $ts = strftime("%H:%M:%S", time());
172 if (function_exists('posix_getpid')) {
173 $ts = "$ts/" . posix_getpid();
176 if ($show && !(defined('QUIET') && QUIET
)) {
177 print "[$ts] $msg\n";
180 if (defined('LOGFILE')) {
181 $fp = fopen(LOGFILE
, 'a+');
186 if (function_exists("flock")) {
189 // try to lock logfile for writing
190 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
201 fputs($fp, "[$ts] $msg\n");
203 if (function_exists("flock")) {
214 * Purge a feed old posts.
216 * @param mixed $link A database connection.
217 * @param mixed $feed_id The id of the purged feed.
218 * @param mixed $purge_interval Olderness of purged posts.
219 * @param boolean $debug Set to True to enable the debug. False by default.
223 function purge_feed($feed_id, $purge_interval, $debug = false) {
225 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
230 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
234 if (db_num_rows($result) == 1) {
235 $owner_uid = db_fetch_result($result, 0, "owner_uid");
238 if ($purge_interval == -1 ||
!$purge_interval) {
240 CCache
::update($feed_id, $owner_uid);
245 if (!$owner_uid) return;
247 if (FORCE_ARTICLE_PURGE
== 0) {
248 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
251 $purge_unread = true;
252 $purge_interval = FORCE_ARTICLE_PURGE
;
255 if (!$purge_unread) $query_limit = " unread = false AND ";
257 if (DB_TYPE
== "pgsql") {
258 $result = db_query("DELETE FROM ttrss_user_entries
260 WHERE ttrss_entries.id = ref_id AND
262 feed_id = '$feed_id' AND
264 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
268 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
269 marked = false AND feed_id = '$feed_id' AND
270 (SELECT date_updated FROM ttrss_entries WHERE
271 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
273 $result = db_query("DELETE FROM ttrss_user_entries
274 USING ttrss_user_entries, ttrss_entries
275 WHERE ttrss_entries.id = ref_id AND
277 feed_id = '$feed_id' AND
279 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
282 $rows = db_affected_rows($result);
284 CCache
::update($feed_id, $owner_uid);
287 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
291 } // function purge_feed
293 function feed_purge_interval($feed_id) {
295 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
296 WHERE id = '$feed_id'");
298 if (db_num_rows($result) == 1) {
299 $purge_interval = db_fetch_result($result, 0, "purge_interval");
300 $owner_uid = db_fetch_result($result, 0, "owner_uid");
302 if ($purge_interval == 0) $purge_interval = get_pref(
303 'PURGE_OLD_DAYS', $owner_uid);
305 return $purge_interval;
312 /*function get_feed_update_interval($feed_id) {
313 $result = db_query("SELECT owner_uid, update_interval FROM
314 ttrss_feeds WHERE id = '$feed_id'");
316 if (db_num_rows($result) == 1) {
317 $update_interval = db_fetch_result($result, 0, "update_interval");
318 $owner_uid = db_fetch_result($result, 0, "owner_uid");
320 if ($update_interval != 0) {
321 return $update_interval;
323 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
331 // TODO: multiple-argument way is deprecated, first parameter is a hash now
332 function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
333 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
335 global $fetch_last_error;
336 global $fetch_last_error_code;
337 global $fetch_last_error_content;
338 global $fetch_last_content_type;
339 global $fetch_curl_used;
341 $fetch_last_error = false;
342 $fetch_last_error_code = -1;
343 $fetch_last_error_content = "";
344 $fetch_last_content_type = "";
345 $fetch_curl_used = false;
347 if (!is_array($options)) {
349 // falling back on compatibility shim
350 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
353 for ($i = 0; $i < func_num_args(); $i++
) {
354 $tmp[$option_names[$i]] = func_get_arg($i);
360 "url" => func_get_arg(0),
361 "type" => @func_get_arg(1),
362 "login" => @func_get_arg(2),
363 "pass" => @func_get_arg(3),
364 "post_query" => @func_get_arg(4),
365 "timeout" => @func_get_arg(5),
366 "timestamp" => @func_get_arg(6),
367 "useragent" => @func_get_arg(7)
371 $url = $options["url"];
372 $type = isset($options["type"]) ?
$options["type"] : false;
373 $login = isset($options["login"]) ?
$options["login"] : false;
374 $pass = isset($options["pass"]) ?
$options["pass"] : false;
375 $post_query = isset($options["post_query"]) ?
$options["post_query"] : false;
376 $timeout = isset($options["timeout"]) ?
$options["timeout"] : false;
377 $timestamp = isset($options["timestamp"]) ?
$options["timestamp"] : 0;
378 $useragent = isset($options["useragent"]) ?
$options["useragent"] : false;
379 $followlocation = isset($options["followlocation"]) ?
$options["followlocation"] : true;
381 $url = ltrim($url, ' ');
382 $url = str_replace(' ', '%20', $url);
384 if (strpos($url, "//") === 0)
385 $url = 'http:' . $url;
387 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
389 $fetch_curl_used = true;
391 $ch = curl_init($url);
393 if ($timestamp && !$post_query) {
394 curl_setopt($ch, CURLOPT_HTTPHEADER
,
395 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
398 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
399 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
400 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("open_basedir") && $followlocation);
401 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
402 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
403 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
404 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
405 curl_setopt($ch, CURLOPT_USERAGENT
, $useragent ?
$useragent :
407 curl_setopt($ch, CURLOPT_ENCODING
, "");
408 //curl_setopt($ch, CURLOPT_REFERER, $url);
410 if (!ini_get("open_basedir")) {
411 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
414 if (defined('_CURL_HTTP_PROXY')) {
415 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
419 curl_setopt($ch, CURLOPT_POST
, true);
420 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
424 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
426 $contents = @curl_exec
($ch);
428 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
429 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
430 $contents = @curl_exec
($ch);
433 if ($contents === false) {
434 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
439 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
440 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
442 $fetch_last_error_code = $http_code;
444 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
445 if (curl_errno($ch) != 0) {
446 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
448 $fetch_last_error = "HTTP Code: $http_code";
450 $fetch_last_error_content = $contents;
460 $fetch_curl_used = false;
462 if ($login && $pass){
463 $url_parts = array();
465 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
467 $pass = urlencode($pass);
469 if ($url_parts[1] && $url_parts[2]) {
470 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
474 // TODO: should this support POST requests or not? idk
476 if (!$post_query && $timestamp) {
477 $context = stream_context_create(array(
480 'ignore_errors' => true,
481 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
482 'protocol_version'=> 1.1,
483 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
486 $context = stream_context_create(array(
489 'ignore_errors' => true,
490 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
491 'protocol_version'=> 1.1
495 $old_error = error_get_last();
497 $data = @file_get_contents
($url, false, $context);
499 if (isset($http_response_header) && is_array($http_response_header)) {
500 foreach ($http_response_header as $h) {
501 if (substr(strtolower($h), 0, 13) == 'content-type:') {
502 $fetch_last_content_type = substr($h, 14);
503 // don't abort here b/c there might be more than one
504 // e.g. if we were being redirected -- last one is the right one
507 if (substr(strtolower($h), 0, 7) == 'http/1.') {
508 $fetch_last_error_code = (int) substr($h, 9, 3);
513 if ($fetch_last_error_code != 200) {
514 $error = error_get_last();
516 if ($error['message'] != $old_error['message']) {
517 $fetch_last_error = $error["message"];
519 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
522 $fetch_last_error_content = $data;
532 * Try to determine the favicon URL for a feed.
533 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
534 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
536 * @param string $url A feed or page URL
538 * @return mixed The favicon URL, or false if none was found.
540 function get_favicon_url($url) {
542 $favicon_url = false;
544 if ($html = @fetch_file_contents
($url)) {
546 libxml_use_internal_errors(true);
548 $doc = new DOMDocument();
549 $doc->loadHTML($html);
550 $xpath = new DOMXPath($doc);
552 $base = $xpath->query('/html/head/base');
553 foreach ($base as $b) {
554 $url = $b->getAttribute("href");
558 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
559 if (count($entries) > 0) {
560 foreach ($entries as $entry) {
561 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
568 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
571 } // function get_favicon_url
573 function initialize_user_prefs($uid, $profile = false) {
575 $uid = db_escape_string($uid);
579 $profile_qpart = "AND profile IS NULL";
581 $profile_qpart = "AND profile = '$profile'";
584 if (get_schema_version() < 63) $profile_qpart = "";
588 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
590 $u_result = db_query("SELECT pref_name
591 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
593 $active_prefs = array();
595 while ($line = db_fetch_assoc($u_result)) {
596 array_push($active_prefs, $line["pref_name"]);
599 while ($line = db_fetch_assoc($result)) {
600 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
601 // print "adding " . $line["pref_name"] . "<br>";
603 $line["def_value"] = db_escape_string($line["def_value"]);
604 $line["pref_name"] = db_escape_string($line["pref_name"]);
606 if (get_schema_version() < 63) {
607 db_query("INSERT INTO ttrss_user_prefs
608 (owner_uid,pref_name,value) VALUES
609 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
612 db_query("INSERT INTO ttrss_user_prefs
613 (owner_uid,pref_name,value, profile) VALUES
614 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
624 function get_ssl_certificate_id() {
625 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
626 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
627 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
628 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
629 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
631 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
632 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
633 $_SERVER["SSL_CLIENT_V_START"] .
634 $_SERVER["SSL_CLIENT_V_END"] .
635 $_SERVER["SSL_CLIENT_S_DN"]);
640 function authenticate_user($login, $password, $check_only = false) {
642 if (!SINGLE_USER_MODE
) {
645 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
647 $user_id = (int) $plugin->authenticate($login, $password);
650 $_SESSION["auth_module"] = strtolower(get_class($plugin));
655 if ($user_id && !$check_only) {
658 $_SESSION["uid"] = $user_id;
659 $_SESSION["version"] = VERSION_STATIC
;
661 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
662 WHERE id = '$user_id'");
664 $_SESSION["name"] = db_fetch_result($result, 0, "login");
665 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
666 $_SESSION["csrf_token"] = uniqid_short();
668 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
671 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
672 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
673 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
675 $_SESSION["last_version_check"] = time();
677 initialize_user_prefs($_SESSION["uid"]);
686 $_SESSION["uid"] = 1;
687 $_SESSION["name"] = "admin";
688 $_SESSION["access_level"] = 10;
690 $_SESSION["hide_hello"] = true;
691 $_SESSION["hide_logout"] = true;
693 $_SESSION["auth_module"] = false;
695 if (!$_SESSION["csrf_token"]) {
696 $_SESSION["csrf_token"] = uniqid_short();
699 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
701 initialize_user_prefs($_SESSION["uid"]);
707 function make_password($length = 8) {
710 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
714 while ($i < $length) {
715 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
717 if (!strstr($password, $char)) {
725 // this is called after user is created to initialize default feeds, labels
728 // user preferences are checked on every login, not here
730 function initialize_user($uid) {
732 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
733 values ('$uid', 'Tiny Tiny RSS: Forum',
734 'http://tt-rss.org/forum/rss.php')");
737 function logout_user() {
739 if (isset($_COOKIE[session_name()])) {
740 setcookie(session_name(), '', time()-42000, '/');
744 function validate_csrf($csrf_token) {
745 return $csrf_token == $_SESSION['csrf_token'];
748 function load_user_plugins($owner_uid, $pluginhost = false) {
750 if (!$pluginhost) $pluginhost = PluginHost
::getInstance();
752 if ($owner_uid && SCHEMA_VERSION
>= 100) {
753 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
755 $pluginhost->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
757 if (get_schema_version() > 100) {
758 $pluginhost->load_data();
763 function login_sequence() {
764 if (SINGLE_USER_MODE
) {
766 authenticate_user("admin", null);
768 load_user_plugins($_SESSION["uid"]);
770 if (!validate_session()) $_SESSION["uid"] = false;
772 if (!$_SESSION["uid"]) {
774 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
775 $_SESSION["ref_schema_version"] = get_schema_version(true);
777 authenticate_user(null, null, true);
780 if (!$_SESSION["uid"]) {
782 setcookie(session_name(), '', time()-42000, '/');
789 /* bump login timestamp */
790 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
792 $_SESSION["last_login_update"] = time();
795 if ($_SESSION["uid"]) {
797 load_user_plugins($_SESSION["uid"]);
801 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
802 $_SESSION["uid"] . " AND
803 (SELECT COUNT(id) FROM ttrss_feeds WHERE
804 ttrss_feeds.id = feed_id) = 0");
806 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
807 $_SESSION["uid"] . " AND
808 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
809 ttrss_feed_categories.id = feed_id) = 0");
816 function truncate_string($str, $max_len, $suffix = '…') {
817 if (mb_strlen($str, "utf-8") > $max_len) {
818 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
825 function truncate_middle($str, $max_len, $suffix = '…') {
826 if (strlen($str) > $max_len) {
827 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
833 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
836 $source_tz = new DateTimeZone($source_tz);
837 } catch (Exception
$e) {
838 $source_tz = new DateTimeZone('UTC');
842 $dest_tz = new DateTimeZone($dest_tz);
843 } catch (Exception
$e) {
844 $dest_tz = new DateTimeZone('UTC');
847 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
848 return $dt->format('U') +
$dest_tz->getOffset($dt);
851 function make_local_datetime($timestamp, $long, $owner_uid = false,
852 $no_smart_dt = false, $eta_min = false) {
854 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
855 if (!$timestamp) $timestamp = '1970-01-01 0:00';
860 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
862 $timestamp = substr($timestamp, 0, 19);
864 # We store date in UTC internally
865 $dt = new DateTime($timestamp, $utc_tz);
867 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
869 if ($user_tz_string != 'Automatic') {
872 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
873 } catch (Exception
$e) {
877 $tz_offset = $user_tz->getOffset($dt);
879 $tz_offset = (int) -$_SESSION["clientTzOffset"];
882 $user_timestamp = $dt->format('U') +
$tz_offset;
885 return smart_date_time($user_timestamp,
886 $tz_offset, $owner_uid, $eta_min);
889 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
891 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
893 return date($format, $user_timestamp);
897 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
898 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
900 if ($eta_min && time() +
$tz_offset - $timestamp < 3600) {
901 return T_sprintf("%d min", date("i", time() +
$tz_offset - $timestamp));
902 } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
903 return date("G:i", $timestamp);
904 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
905 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
906 return date($format, $timestamp);
908 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
909 return date($format, $timestamp);
913 function sql_bool_to_bool($s) {
914 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
921 function bool_to_sql_bool($s) {
929 // Session caching removed due to causing wrong redirects to upgrade
930 // script when get_schema_version() is called on an obsolete session
931 // created on a previous schema version.
932 function get_schema_version($nocache = false) {
933 global $schema_version;
935 if (!$schema_version && !$nocache) {
936 $result = db_query("SELECT schema_version FROM ttrss_version");
937 $version = db_fetch_result($result, 0, "schema_version");
938 $schema_version = $version;
941 return $schema_version;
945 function sanity_check() {
946 require_once 'errors.php';
950 $schema_version = get_schema_version(true);
952 if ($schema_version != SCHEMA_VERSION
) {
956 if (DB_TYPE
== "mysql") {
957 $result = db_query("SELECT true", false);
958 if (db_num_rows($result) != 1) {
963 if (db_escape_string("testTEST") != "testTEST") {
967 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
970 function file_is_locked($filename) {
971 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
972 if (function_exists('flock')) {
973 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
975 if (flock($fp, LOCK_EX | LOCK_NB
)) {
986 return true; // consider the file always locked and skip the test
993 function make_lockfile($filename) {
994 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
996 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
997 $stat_h = fstat($fp);
998 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
1000 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
1001 if ($stat_h["ino"] != $stat_f["ino"] ||
1002 $stat_h["dev"] != $stat_f["dev"]) {
1008 if (function_exists('posix_getpid')) {
1009 fwrite($fp, posix_getpid() . "\n");
1017 function make_stampfile($filename) {
1018 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1020 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1021 fwrite($fp, time() . "\n");
1022 flock($fp, LOCK_UN
);
1030 function sql_random_function() {
1031 if (DB_TYPE
== "mysql") {
1038 function getFeedUnread($feed, $is_cat = false) {
1039 return Feeds
::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1043 /*function get_pgsql_version() {
1044 $result = db_query("SELECT version() AS version");
1045 $version = explode(" ", db_fetch_result($result, 0, "version"));
1049 function checkbox_to_sql_bool($val) {
1050 return ($val == "on") ?
"true" : "false";
1053 /*function getFeedCatTitle($id) {
1055 return __("Special");
1056 } else if ($id < LABEL_BASE_INDEX) {
1057 return __("Labels");
1058 } else if ($id > 0) {
1059 $result = db_query("SELECT ttrss_feed_categories.title
1060 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1061 cat_id = ttrss_feed_categories.id");
1062 if (db_num_rows($result) == 1) {
1063 return db_fetch_result($result, 0, "title");
1065 return __("Uncategorized");
1068 return "getFeedCatTitle($id) failed";
1073 function uniqid_short() {
1074 return uniqid(base_convert(rand(), 10, 36));
1077 function make_init_params() {
1080 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1081 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1082 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1083 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1085 $params[strtolower($param)] = (int) get_pref($param);
1088 $params["icons_url"] = ICONS_URL
;
1089 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1090 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1091 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1092 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1093 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1094 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
1096 $theme = get_pref( "USER_CSS_THEME", false, false);
1097 $params["theme"] = theme_valid("$theme") ?
$theme : "";
1099 $params["plugins"] = implode(", ", PluginHost
::getInstance()->get_plugin_names());
1101 $params["php_platform"] = PHP_OS
;
1102 $params["php_version"] = PHP_VERSION
;
1104 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1106 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1107 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1109 $max_feed_id = db_fetch_result($result, 0, "mid");
1110 $num_feeds = db_fetch_result($result, 0, "nf");
1112 $params["max_feed_id"] = (int) $max_feed_id;
1113 $params["num_feeds"] = (int) $num_feeds;
1115 $params["hotkeys"] = get_hotkeys_map();
1117 $params["csrf_token"] = $_SESSION["csrf_token"];
1118 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1120 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1122 $params["icon_alert"] = base64_img("images/alert.png");
1123 $params["icon_information"] = base64_img("images/information.png");
1124 $params["icon_cross"] = base64_img("images/cross.png");
1125 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
1130 function get_hotkeys_info() {
1132 __("Navigation") => array(
1133 "next_feed" => __("Open next feed"),
1134 "prev_feed" => __("Open previous feed"),
1135 "next_article" => __("Open next article"),
1136 "prev_article" => __("Open previous article"),
1137 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1138 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1139 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1140 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1141 "search_dialog" => __("Show search dialog")),
1142 __("Article") => array(
1143 "toggle_mark" => __("Toggle starred"),
1144 "toggle_publ" => __("Toggle published"),
1145 "toggle_unread" => __("Toggle unread"),
1146 "edit_tags" => __("Edit tags"),
1147 "open_in_new_window" => __("Open in new window"),
1148 "catchup_below" => __("Mark below as read"),
1149 "catchup_above" => __("Mark above as read"),
1150 "article_scroll_down" => __("Scroll down"),
1151 "article_scroll_up" => __("Scroll up"),
1152 "select_article_cursor" => __("Select article under cursor"),
1153 "email_article" => __("Email article"),
1154 "close_article" => __("Close/collapse article"),
1155 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1156 "toggle_widescreen" => __("Toggle widescreen mode"),
1157 "toggle_embed_original" => __("Toggle embed original")),
1158 __("Article selection") => array(
1159 "select_all" => __("Select all articles"),
1160 "select_unread" => __("Select unread"),
1161 "select_marked" => __("Select starred"),
1162 "select_published" => __("Select published"),
1163 "select_invert" => __("Invert selection"),
1164 "select_none" => __("Deselect everything")),
1165 __("Feed") => array(
1166 "feed_refresh" => __("Refresh current feed"),
1167 "feed_unhide_read" => __("Un/hide read feeds"),
1168 "feed_subscribe" => __("Subscribe to feed"),
1169 "feed_edit" => __("Edit feed"),
1170 "feed_catchup" => __("Mark as read"),
1171 "feed_reverse" => __("Reverse headlines"),
1172 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1173 "feed_debug_update" => __("Debug feed update"),
1174 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1175 "catchup_all" => __("Mark all feeds as read"),
1176 "cat_toggle_collapse" => __("Un/collapse current category"),
1177 "toggle_combined_mode" => __("Toggle combined mode"),
1178 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1179 __("Go to") => array(
1180 "goto_all" => __("All articles"),
1181 "goto_fresh" => __("Fresh"),
1182 "goto_marked" => __("Starred"),
1183 "goto_published" => __("Published"),
1184 "goto_tagcloud" => __("Tag cloud"),
1185 "goto_prefs" => __("Preferences")),
1186 __("Other") => array(
1187 "create_label" => __("Create label"),
1188 "create_filter" => __("Create filter"),
1189 "collapse_sidebar" => __("Un/collapse sidebar"),
1190 "help_dialog" => __("Show help dialog"))
1193 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
1194 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1200 function get_hotkeys_map() {
1202 // "navigation" => array(
1205 "n" => "next_article",
1206 "p" => "prev_article",
1207 "(38)|up" => "prev_article",
1208 "(40)|down" => "next_article",
1209 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1210 // "^(40)|Ctrl-down" => "next_article_noscroll",
1211 "(191)|/" => "search_dialog",
1212 // "article" => array(
1213 "s" => "toggle_mark",
1214 "*s" => "toggle_publ",
1215 "u" => "toggle_unread",
1216 "*t" => "edit_tags",
1217 "o" => "open_in_new_window",
1218 "c p" => "catchup_below",
1219 "c n" => "catchup_above",
1220 "*n" => "article_scroll_down",
1221 "*p" => "article_scroll_up",
1222 "*(38)|Shift+up" => "article_scroll_up",
1223 "*(40)|Shift+down" => "article_scroll_down",
1224 "a *w" => "toggle_widescreen",
1225 "a e" => "toggle_embed_original",
1226 "e" => "email_article",
1227 "a q" => "close_article",
1228 // "article_selection" => array(
1229 "a a" => "select_all",
1230 "a u" => "select_unread",
1231 "a *u" => "select_marked",
1232 "a p" => "select_published",
1233 "a i" => "select_invert",
1234 "a n" => "select_none",
1236 "f r" => "feed_refresh",
1237 "f a" => "feed_unhide_read",
1238 "f s" => "feed_subscribe",
1239 "f e" => "feed_edit",
1240 "f q" => "feed_catchup",
1241 "f x" => "feed_reverse",
1242 "f g" => "feed_toggle_vgroup",
1243 "f *d" => "feed_debug_update",
1244 "f *g" => "feed_debug_viewfeed",
1245 "f *c" => "toggle_combined_mode",
1246 "f c" => "toggle_cdm_expanded",
1247 "*q" => "catchup_all",
1248 "x" => "cat_toggle_collapse",
1250 "g a" => "goto_all",
1251 "g f" => "goto_fresh",
1252 "g s" => "goto_marked",
1253 "g p" => "goto_published",
1254 "g t" => "goto_tagcloud",
1255 "g *p" => "goto_prefs",
1256 // "other" => array(
1257 "(9)|Tab" => "select_article_cursor", // tab
1258 "c l" => "create_label",
1259 "c f" => "create_filter",
1260 "c s" => "collapse_sidebar",
1261 "^(191)|Ctrl+/" => "help_dialog",
1264 if (get_pref('COMBINED_DISPLAY_MODE')) {
1265 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1266 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1269 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
1270 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1273 $prefixes = array();
1275 foreach (array_keys($hotkeys) as $hotkey) {
1276 $pair = explode(" ", $hotkey, 2);
1278 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1279 array_push($prefixes, $pair[0]);
1283 return array($prefixes, $hotkeys);
1286 function check_for_update() {
1287 if (defined("GIT_VERSION_TIMESTAMP")) {
1288 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1291 $content = json_decode($content, true);
1293 if ($content && isset($content["changeset"])) {
1294 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
1295 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
1297 return $content["changeset"]["id"];
1306 function make_runtime_info($disable_update_check = false) {
1309 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1310 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1312 $max_feed_id = db_fetch_result($result, 0, "mid");
1313 $num_feeds = db_fetch_result($result, 0, "nf");
1315 $data["max_feed_id"] = (int) $max_feed_id;
1316 $data["num_feeds"] = (int) $num_feeds;
1318 $data['last_article_id'] = Article
::getLastArticleId();
1319 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1321 $data['dep_ts'] = calculate_dep_timestamp();
1322 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1325 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
1326 $update_result = @check_for_update
();
1328 $data["update_result"] = $update_result;
1330 $_SESSION["last_version_check"] = time();
1333 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
1335 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1337 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1339 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
1342 $stamp_delta = time() - $stamp;
1344 if ($stamp_delta > 1800) {
1348 $_SESSION["daemon_stamp_check"] = time();
1351 $data['daemon_stamp_ok'] = $stamp_check;
1353 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1355 $data['daemon_stamp'] = $stamp_fmt;
1363 function search_to_sql($search, $search_language) {
1365 $keywords = str_getcsv(trim($search), " ");
1366 $query_keywords = array();
1367 $search_words = array();
1368 $search_query_leftover = array();
1370 if ($search_language)
1371 $search_language = db_escape_string(mb_strtolower($search_language));
1373 $search_language = "english";
1375 foreach ($keywords as $k) {
1376 if (strpos($k, "-") === 0) {
1383 $commandpair = explode(":", mb_strtolower($k), 2);
1385 switch ($commandpair[0]) {
1387 if ($commandpair[1]) {
1388 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1389 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1391 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1392 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1393 array_push($search_words, $k);
1397 if ($commandpair[1]) {
1398 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1399 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1401 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1402 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1403 array_push($search_words, $k);
1407 if ($commandpair[1]) {
1408 if ($commandpair[1] == "true")
1409 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1410 else if ($commandpair[1] == "false")
1411 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1413 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1414 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1416 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1417 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1418 if (!$not) array_push($search_words, $k);
1423 if ($commandpair[1]) {
1424 if ($commandpair[1] == "true")
1425 array_push($query_keywords, "($not (marked = true))");
1427 array_push($query_keywords, "($not (marked = false))");
1429 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1430 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1431 if (!$not) array_push($search_words, $k);
1435 if ($commandpair[1]) {
1436 if ($commandpair[1] == "true")
1437 array_push($query_keywords, "($not (published = true))");
1439 array_push($query_keywords, "($not (published = false))");
1442 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1443 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1444 if (!$not) array_push($search_words, $k);
1448 if ($commandpair[1]) {
1449 if ($commandpair[1] == "true")
1450 array_push($query_keywords, "($not (unread = true))");
1452 array_push($query_keywords, "($not (unread = false))");
1455 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1456 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1457 if (!$not) array_push($search_words, $k);
1461 if (strpos($k, "@") === 0) {
1463 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1464 $orig_ts = strtotime(substr($k, 1));
1465 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1467 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1469 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
1472 if (DB_TYPE
== "pgsql") {
1473 $k = mb_strtolower($k);
1474 array_push($search_query_leftover, $not ?
"!$k" : $k);
1476 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1477 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1480 if (!$not) array_push($search_words, $k);
1485 if (count($search_query_leftover) > 0) {
1486 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1488 if (DB_TYPE
== "pgsql") {
1489 array_push($query_keywords,
1490 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1495 $search_query_part = implode("AND", $query_keywords);
1497 return array($search_query_part, $search_words);
1500 function iframe_whitelisted($entry) {
1501 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1503 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
1506 foreach ($whitelist as $w) {
1507 if ($src == $w ||
$src == "www.$w")
1515 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1516 if (!$owner) $owner = $_SESSION["uid"];
1518 $res = trim($str); if (!$res) return '';
1520 $charset_hack = '<head>
1521 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1524 $res = trim($res); if (!$res) return '';
1526 libxml_use_internal_errors(true);
1528 $doc = new DOMDocument();
1529 $doc->loadHTML($charset_hack . $res);
1530 $xpath = new DOMXPath($doc);
1532 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
1533 $rewrite_base_url = $site_url ?
$site_url : SELF_URL_PATH
;
1535 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1537 foreach ($entries as $entry) {
1539 if ($entry->hasAttribute('href')) {
1540 $entry->setAttribute('href',
1541 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1543 $entry->setAttribute('rel', 'noopener noreferrer');
1546 if ($entry->hasAttribute('src')) {
1547 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1548 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1550 if (file_exists($cached_filename)) {
1552 // this is strictly cosmetic
1553 if ($entry->tagName
== 'img') {
1555 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1557 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1563 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1565 if ($entry->hasAttribute('srcset')) {
1566 $entry->removeAttribute('srcset');
1569 if ($entry->hasAttribute('sizes')) {
1570 $entry->removeAttribute('sizes');
1574 $entry->setAttribute('src', $src);
1577 if ($entry->nodeName
== 'img') {
1579 if ($entry->hasAttribute('src')) {
1580 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1582 if ($ttrss_uses_https && !$is_https_url) {
1584 if ($entry->hasAttribute('srcset')) {
1585 $entry->removeAttribute('srcset');
1588 if ($entry->hasAttribute('sizes')) {
1589 $entry->removeAttribute('sizes');
1594 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1595 $force_remove_images ||
$_SESSION["bw_limit"]) {
1597 $p = $doc->createElement('p');
1599 $a = $doc->createElement('a');
1600 $a->setAttribute('href', $entry->getAttribute('src'));
1602 $a->appendChild(new DOMText($entry->getAttribute('src')));
1603 $a->setAttribute('target', '_blank');
1604 $a->setAttribute('rel', 'noopener noreferrer');
1606 $p->appendChild($a);
1608 $entry->parentNode
->replaceChild($p, $entry);
1612 if (strtolower($entry->nodeName
) == "a") {
1613 $entry->setAttribute("target", "_blank");
1614 $entry->setAttribute("rel", "noopener noreferrer");
1618 $entries = $xpath->query('//iframe');
1619 foreach ($entries as $entry) {
1620 if (!iframe_whitelisted($entry)) {
1621 $entry->setAttribute('sandbox', 'allow-scripts');
1623 if ($_SERVER['HTTPS'] == "on") {
1624 $entry->setAttribute("src",
1625 str_replace("http://", "https://",
1626 $entry->getAttribute("src")));
1631 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1632 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1633 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1634 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1635 'dt', 'em', 'footer', 'figure', 'figcaption',
1636 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1637 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1638 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1639 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1640 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1641 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1643 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1645 $disallowed_attributes = array('id', 'style', 'class');
1647 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1648 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1649 if (is_array($retval)) {
1651 $allowed_elements = $retval[1];
1652 $disallowed_attributes = $retval[2];
1658 $doc->removeChild($doc->firstChild
); //remove doctype
1659 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1661 if ($highlight_words) {
1662 foreach ($highlight_words as $word) {
1664 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1666 $elements = $xpath->query("//*/text()");
1668 foreach ($elements as $child) {
1670 $fragment = $doc->createDocumentFragment();
1671 $text = $child->textContent
;
1673 while (($pos = mb_stripos($text, $word)) !== false) {
1674 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1675 $word = mb_substr($text, $pos, mb_strlen($word));
1676 $highlight = $doc->createElement('span');
1677 $highlight->appendChild(new DomText($word));
1678 $highlight->setAttribute('class', 'highlight');
1679 $fragment->appendChild($highlight);
1680 $text = mb_substr($text, $pos +
mb_strlen($word));
1683 if (!empty($text)) $fragment->appendChild(new DomText($text));
1685 $child->parentNode
->replaceChild($fragment, $child);
1690 $res = $doc->saveHTML();
1692 /* strip everything outside of <body>...</body> */
1694 $res_frag = array();
1695 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1696 return $res_frag[1];
1702 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1703 $xpath = new DOMXPath($doc);
1704 $entries = $xpath->query('//*');
1706 foreach ($entries as $entry) {
1707 if (!in_array($entry->nodeName
, $allowed_elements)) {
1708 $entry->parentNode
->removeChild($entry);
1711 if ($entry->hasAttributes()) {
1712 $attrs_to_remove = array();
1714 foreach ($entry->attributes
as $attr) {
1716 if (strpos($attr->nodeName
, 'on') === 0) {
1717 array_push($attrs_to_remove, $attr);
1720 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1721 array_push($attrs_to_remove, $attr);
1724 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1725 array_push($attrs_to_remove, $attr);
1729 foreach ($attrs_to_remove as $attr) {
1730 $entry->removeAttributeNode($attr);
1738 function trim_array($array) {
1740 array_walk($tmp, 'trim');
1744 function tag_is_valid($tag) {
1745 if ($tag == '') return false;
1746 if (is_numeric($tag)) return false;
1747 if (mb_strlen($tag) > 250) return false;
1749 if (!$tag) return false;
1754 function render_login_form() {
1755 header('Cache-Control: public');
1757 require_once "login_form.php";
1761 function T_sprintf() {
1762 $args = func_get_args();
1763 return vsprintf(__(array_shift($args)), $args);
1766 function print_checkpoint($n, $s) {
1767 $ts = microtime(true);
1768 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1772 function sanitize_tag($tag) {
1775 $tag = mb_strtolower($tag, 'utf-8');
1777 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1779 if (DB_TYPE
== "mysql") {
1780 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1786 function get_self_url_prefix() {
1787 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1788 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1790 return SELF_URL_PATH
;
1795 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1797 * @return string The Mozilla Firefox feed adding URL.
1799 function add_feed_url() {
1800 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1802 $url_path = get_self_url_prefix() .
1803 "/public.php?op=subscribe&feed_url=%s";
1805 } // function add_feed_url
1807 function encrypt_password($pass, $salt = '', $mode2 = false) {
1808 if ($salt && $mode2) {
1809 return "MODE2:" . hash('sha256', $salt . $pass);
1811 return "SHA1X:" . sha1("$salt:$pass");
1813 return "SHA1:" . sha1($pass);
1815 } // function encrypt_password
1817 function load_filters($feed_id, $owner_uid) {
1820 $cat_id = (int)Feeds
::getFeedCategory($feed_id);
1823 $null_cat_qpart = "cat_id IS NULL OR";
1825 $null_cat_qpart = "";
1827 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1828 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1830 $check_cats = join(",", array_merge(
1831 Feeds
::getParentCategories($cat_id, $owner_uid),
1834 while ($line = db_fetch_assoc($result)) {
1835 $filter_id = $line["id"];
1837 $result2 = db_query("SELECT
1838 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1839 FROM ttrss_filters2_rules AS r,
1840 ttrss_filter_types AS t
1842 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1843 (feed_id IS NULL OR feed_id = '$feed_id') AND
1844 filter_type = t.id AND filter_id = '$filter_id'");
1849 while ($rule_line = db_fetch_assoc($result2)) {
1850 # print_r($rule_line);
1853 $rule["reg_exp"] = $rule_line["reg_exp"];
1854 $rule["type"] = $rule_line["type_name"];
1855 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1857 array_push($rules, $rule);
1860 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1861 FROM ttrss_filters2_actions AS a,
1862 ttrss_filter_actions AS t
1864 action_id = t.id AND filter_id = '$filter_id'");
1866 while ($action_line = db_fetch_assoc($result2)) {
1867 # print_r($action_line);
1870 $action["type"] = $action_line["type_name"];
1871 $action["param"] = $action_line["action_param"];
1873 array_push($actions, $action);
1878 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1879 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1880 $filter["rules"] = $rules;
1881 $filter["actions"] = $actions;
1883 if (count($rules) > 0 && count($actions) > 0) {
1884 array_push($filters, $filter);
1891 function get_score_pic($score) {
1893 return "score_high.png";
1894 } else if ($score > 0) {
1895 return "score_half_high.png";
1896 } else if ($score < -100) {
1897 return "score_low.png";
1898 } else if ($score < 0) {
1899 return "score_half_low.png";
1901 return "score_neutral.png";
1905 function feed_has_icon($id) {
1906 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
1909 function init_plugins() {
1910 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
1915 function add_feed_category($feed_cat, $parent_cat_id = false) {
1917 if (!$feed_cat) return false;
1921 if ($parent_cat_id) {
1922 $parent_qpart = "parent_cat = '$parent_cat_id'";
1923 $parent_insert = "'$parent_cat_id'";
1925 $parent_qpart = "parent_cat IS NULL";
1926 $parent_insert = "NULL";
1929 $feed_cat = mb_substr($feed_cat, 0, 250);
1932 "SELECT id FROM ttrss_feed_categories
1933 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1935 if (db_num_rows($result) == 0) {
1938 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1939 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1950 * Fixes incomplete URLs by prepending "http://".
1951 * Also replaces feed:// with http://, and
1952 * prepends a trailing slash if the url is a domain name only.
1954 * @param string $url Possibly incomplete URL
1956 * @return string Fixed URL.
1958 function fix_url($url) {
1960 // support schema-less urls
1961 if (strpos($url, '//') === 0) {
1962 $url = 'https:' . $url;
1965 if (strpos($url, '://') === false) {
1966 $url = 'http://' . $url;
1967 } else if (substr($url, 0, 5) == 'feed:') {
1968 $url = 'http:' . substr($url, 5);
1971 //prepend slash if the URL has no slash in it
1972 // "http://www.example" -> "http://www.example/"
1973 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
1977 //convert IDNA hostname to punycode if possible
1978 if (function_exists("idn_to_ascii")) {
1979 $parts = parse_url($url);
1980 if (mb_detect_encoding($parts['host']) != 'ASCII')
1982 $parts['host'] = idn_to_ascii($parts['host']);
1983 $url = build_url($parts);
1987 if ($url != "http:///")
1993 function validate_feed_url($url) {
1994 $parts = parse_url($url);
1996 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2000 /* function save_email_address($email) {
2001 // FIXME: implement persistent storage of emails
2003 if (!$_SESSION['stored_emails'])
2004 $_SESSION['stored_emails'] = array();
2006 if (!in_array($email, $_SESSION['stored_emails']))
2007 array_push($_SESSION['stored_emails'], $email);
2011 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2013 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2015 $sql_is_cat = bool_to_sql_bool($is_cat);
2017 $result = db_query("SELECT access_key FROM ttrss_access_keys
2018 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2019 AND owner_uid = " . $owner_uid);
2021 if (db_num_rows($result) == 1) {
2022 return db_fetch_result($result, 0, "access_key");
2024 $key = db_escape_string(uniqid_short());
2026 $result = db_query("INSERT INTO ttrss_access_keys
2027 (access_key, feed_id, is_cat, owner_uid)
2028 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2035 function get_feeds_from_html($url, $content)
2037 $url = fix_url($url);
2038 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2040 libxml_use_internal_errors(true);
2042 $doc = new DOMDocument();
2043 $doc->loadHTML($content);
2044 $xpath = new DOMXPath($doc);
2045 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2046 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2047 $feedUrls = array();
2048 foreach ($entries as $entry) {
2049 if ($entry->hasAttribute('href')) {
2050 $title = $entry->getAttribute('title');
2052 $title = $entry->getAttribute('type');
2054 $feedUrl = rewrite_relative_url(
2055 $baseUrl, $entry->getAttribute('href')
2057 $feedUrls[$feedUrl] = $title;
2063 function is_html($content) {
2064 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2067 function url_is_html($url, $login = false, $pass = false) {
2068 return is_html(fetch_file_contents($url, false, $login, $pass));
2071 function build_url($parts) {
2072 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2075 function cleanup_url_path($path) {
2076 $path = str_replace("/./", "/", $path);
2077 $path = str_replace("//", "/", $path);
2083 * Converts a (possibly) relative URL to a absolute one.
2085 * @param string $url Base URL (i.e. from where the document is)
2086 * @param string $rel_url Possibly relative URL in the document
2088 * @return string Absolute URL
2090 function rewrite_relative_url($url, $rel_url) {
2091 if (strpos($rel_url, "://") !== false) {
2093 } else if (strpos($rel_url, "//") === 0) {
2094 # protocol-relative URL (rare but they exist)
2096 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2097 # magnet:, feed:, etc
2099 } else if (strpos($rel_url, "/") === 0) {
2100 $parts = parse_url($url);
2101 $parts['path'] = $rel_url;
2102 $parts['path'] = cleanup_url_path($parts['path']);
2104 return build_url($parts);
2107 $parts = parse_url($url);
2108 if (!isset($parts['path'])) {
2109 $parts['path'] = '/';
2111 $dir = $parts['path'];
2112 if (substr($dir, -1) !== '/') {
2113 $dir = dirname($parts['path']);
2114 $dir !== '/' && $dir .= '/';
2116 $parts['path'] = $dir . $rel_url;
2117 $parts['path'] = cleanup_url_path($parts['path']);
2119 return build_url($parts);
2123 function cleanup_tags($days = 14, $limit = 1000) {
2125 if (DB_TYPE
== "pgsql") {
2126 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2127 } else if (DB_TYPE
== "mysql") {
2128 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2133 while ($limit > 0) {
2136 $query = "SELECT ttrss_tags.id AS id
2137 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2138 WHERE post_int_id = int_id AND $interval_query AND
2139 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2141 $result = db_query($query);
2145 while ($line = db_fetch_assoc($result)) {
2146 array_push($ids, $line['id']);
2149 if (count($ids) > 0) {
2150 $ids = join(",", $ids);
2152 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2153 $tags_deleted +
= db_affected_rows($tmp_result);
2158 $limit -= $limit_part;
2161 return $tags_deleted;
2164 function print_user_stylesheet() {
2165 $value = get_pref('USER_STYLESHEET');
2168 print "<style type=\"text/css\">";
2169 print str_replace("<br/>", "\n", $value);
2175 function filter_to_sql($filter, $owner_uid) {
2178 if (DB_TYPE
== "pgsql")
2181 $reg_qpart = "REGEXP";
2183 foreach ($filter["rules"] AS $rule) {
2184 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2185 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2186 $rule['reg_exp']) !== FALSE;
2188 if ($regexp_valid) {
2190 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2192 switch ($rule["type"]) {
2194 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2195 $rule['reg_exp'] . "')";
2198 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2199 $rule['reg_exp'] . "')";
2202 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2203 $rule['reg_exp'] . "') OR LOWER(" .
2204 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2207 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2208 $rule['reg_exp'] . "')";
2211 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2212 $rule['reg_exp'] . "')";
2215 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2216 $rule['reg_exp'] . "')";
2220 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2222 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2223 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2226 if (isset($rule["cat_id"])) {
2228 if ($rule["cat_id"] > 0) {
2229 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2230 array_push($children, $rule["cat_id"]);
2232 $children = join(",", $children);
2234 $cat_qpart = "cat_id IN ($children)";
2236 $cat_qpart = "cat_id IS NULL";
2239 $qpart .= " AND $cat_qpart";
2242 $qpart .= " AND feed_id IS NOT NULL";
2244 array_push($query, "($qpart)");
2249 if (count($query) > 0) {
2250 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2252 $fullquery = "(false)";
2255 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2260 if (!function_exists('gzdecode')) {
2261 function gzdecode($string) { // no support for 2nd argument
2262 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2263 base64_encode($string));
2267 function get_random_bytes($length) {
2268 if (function_exists('openssl_random_pseudo_bytes')) {
2269 return openssl_random_pseudo_bytes($length);
2273 for ($i = 0; $i < $length; $i++
)
2274 $output .= chr(mt_rand(0, 255));
2280 function read_stdin() {
2281 $fp = fopen("php://stdin", "r");
2284 $line = trim(fgets($fp));
2292 function implements_interface($class, $interface) {
2293 return in_array($interface, class_implements($class));
2296 function get_minified_js($files) {
2297 require_once 'lib/jshrink/Minifier.php';
2301 foreach ($files as $js) {
2302 if (!isset($_GET['debug'])) {
2303 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2305 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2307 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2309 if ($header && $contents) {
2310 list($htag, $hversion) = explode(":", $header);
2312 if ($htag == "tt-rss" && $hversion == VERSION
) {
2319 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2320 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2324 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2331 function calculate_dep_timestamp() {
2332 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2336 foreach ($files as $file) {
2337 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2343 function T_js_decl($s1, $s2) {
2345 $s1 = preg_replace("/\n/", "", $s1);
2346 $s2 = preg_replace("/\n/", "", $s2);
2348 $s1 = preg_replace("/\"/", "\\\"", $s1);
2349 $s2 = preg_replace("/\"/", "\\\"", $s2);
2351 return "T_messages[\"$s1\"] = \"$s2\";\n";
2355 function init_js_translations() {
2357 print 'var T_messages = new Object();
2360 if (T_messages[msg]) {
2361 return T_messages[msg];
2367 function ngettext(msg1, msg2, n) {
2368 return __((parseInt(n) > 1) ? msg2 : msg1);
2371 $l10n = _get_reader();
2373 for ($i = 0; $i < $l10n->total
; $i++
) {
2374 $orig = $l10n->get_original_string($i);
2375 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2376 $key = explode(chr(0), $orig);
2377 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2378 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2380 $translation = __($orig);
2381 print T_js_decl($orig, $translation);
2386 function get_theme_path($theme) {
2387 $check = "themes/$theme";
2388 if (file_exists($check)) return $check;
2390 $check = "themes.local/$theme";
2391 if (file_exists($check)) return $check;
2394 function theme_valid($theme) {
2395 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2397 if (in_array($theme, $bundled_themes)) return true;
2399 $file = "themes/" . basename($theme);
2401 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2403 if (file_exists($file) && is_readable($file)) {
2404 $fh = fopen($file, "r");
2407 $header = fgets($fh);
2410 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2418 * @SuppressWarnings(unused)
2420 function error_json($code) {
2421 require_once "errors.php";
2423 @$message = $ERRORS[$code];
2425 return json_encode(array("error" =>
2426 array("code" => $code, "message" => $message)));
2430 /*function abs_to_rel_path($dir) {
2431 $tmp = str_replace(dirname(__DIR__), "", $dir);
2433 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2438 function get_upload_error_message($code) {
2441 0 => __('There is no error, the file uploaded with success'),
2442 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2443 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2444 3 => __('The uploaded file was only partially uploaded'),
2445 4 => __('No file was uploaded'),
2446 6 => __('Missing a temporary folder'),
2447 7 => __('Failed to write file to disk.'),
2448 8 => __('A PHP extension stopped the file upload.'),
2451 return $errors[$code];
2454 function base64_img($filename) {
2455 if (file_exists($filename)) {
2456 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2458 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));