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 'labels.php';
138 require_once 'controls.php';
140 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
141 ini_set('user_agent', SELF_USER_AGENT
);
143 require_once 'lib/pubsubhubbub/Publisher.php';
145 $schema_version = false;
147 function _debug_suppress($suppress) {
148 global $suppress_debugging;
150 $suppress_debugging = $suppress;
154 * Print a timestamped debug message.
156 * @param string $msg The debug message.
159 function _debug($msg, $show = true) {
160 global $suppress_debugging;
162 //echo "[$suppress_debugging] $msg $show\n";
164 if ($suppress_debugging) return false;
166 $ts = strftime("%H:%M:%S", time());
167 if (function_exists('posix_getpid')) {
168 $ts = "$ts/" . posix_getpid();
171 if ($show && !(defined('QUIET') && QUIET
)) {
172 print "[$ts] $msg\n";
175 if (defined('LOGFILE')) {
176 $fp = fopen(LOGFILE
, 'a+');
181 if (function_exists("flock")) {
184 // try to lock logfile for writing
185 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
196 fputs($fp, "[$ts] $msg\n");
198 if (function_exists("flock")) {
209 * Purge a feed old posts.
211 * @param mixed $link A database connection.
212 * @param mixed $feed_id The id of the purged feed.
213 * @param mixed $purge_interval Olderness of purged posts.
214 * @param boolean $debug Set to True to enable the debug. False by default.
218 function purge_feed($feed_id, $purge_interval, $debug = false) {
220 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
225 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
229 if (db_num_rows($result) == 1) {
230 $owner_uid = db_fetch_result($result, 0, "owner_uid");
233 if ($purge_interval == -1 ||
!$purge_interval) {
235 CCache
::update($feed_id, $owner_uid);
240 if (!$owner_uid) return;
242 if (FORCE_ARTICLE_PURGE
== 0) {
243 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
246 $purge_unread = true;
247 $purge_interval = FORCE_ARTICLE_PURGE
;
250 if (!$purge_unread) $query_limit = " unread = false AND ";
252 if (DB_TYPE
== "pgsql") {
253 $result = db_query("DELETE FROM ttrss_user_entries
255 WHERE ttrss_entries.id = ref_id AND
257 feed_id = '$feed_id' AND
259 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
263 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
264 marked = false AND feed_id = '$feed_id' AND
265 (SELECT date_updated FROM ttrss_entries WHERE
266 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
268 $result = db_query("DELETE FROM ttrss_user_entries
269 USING ttrss_user_entries, ttrss_entries
270 WHERE ttrss_entries.id = ref_id AND
272 feed_id = '$feed_id' AND
274 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
277 $rows = db_affected_rows($result);
279 CCache
::update($feed_id, $owner_uid);
282 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
286 } // function purge_feed
288 function feed_purge_interval($feed_id) {
290 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
291 WHERE id = '$feed_id'");
293 if (db_num_rows($result) == 1) {
294 $purge_interval = db_fetch_result($result, 0, "purge_interval");
295 $owner_uid = db_fetch_result($result, 0, "owner_uid");
297 if ($purge_interval == 0) $purge_interval = get_pref(
298 'PURGE_OLD_DAYS', $owner_uid);
300 return $purge_interval;
307 /*function get_feed_update_interval($feed_id) {
308 $result = db_query("SELECT owner_uid, update_interval FROM
309 ttrss_feeds WHERE id = '$feed_id'");
311 if (db_num_rows($result) == 1) {
312 $update_interval = db_fetch_result($result, 0, "update_interval");
313 $owner_uid = db_fetch_result($result, 0, "owner_uid");
315 if ($update_interval != 0) {
316 return $update_interval;
318 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
326 // TODO: multiple-argument way is deprecated, first parameter is a hash now
327 function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
328 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
330 global $fetch_last_error;
331 global $fetch_last_error_code;
332 global $fetch_last_error_content;
333 global $fetch_last_content_type;
334 global $fetch_curl_used;
336 $fetch_last_error = false;
337 $fetch_last_error_code = -1;
338 $fetch_last_error_content = "";
339 $fetch_last_content_type = "";
340 $fetch_curl_used = false;
342 if (!is_array($options)) {
344 // falling back on compatibility shim
345 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
348 for ($i = 0; $i < func_num_args(); $i++
) {
349 $tmp[$option_names[$i]] = func_get_arg($i);
355 "url" => func_get_arg(0),
356 "type" => @func_get_arg(1),
357 "login" => @func_get_arg(2),
358 "pass" => @func_get_arg(3),
359 "post_query" => @func_get_arg(4),
360 "timeout" => @func_get_arg(5),
361 "timestamp" => @func_get_arg(6),
362 "useragent" => @func_get_arg(7)
366 $url = $options["url"];
367 $type = isset($options["type"]) ?
$options["type"] : false;
368 $login = isset($options["login"]) ?
$options["login"] : false;
369 $pass = isset($options["pass"]) ?
$options["pass"] : false;
370 $post_query = isset($options["post_query"]) ?
$options["post_query"] : false;
371 $timeout = isset($options["timeout"]) ?
$options["timeout"] : false;
372 $timestamp = isset($options["timestamp"]) ?
$options["timestamp"] : 0;
373 $useragent = isset($options["useragent"]) ?
$options["useragent"] : false;
374 $followlocation = isset($options["followlocation"]) ?
$options["followlocation"] : true;
376 $url = ltrim($url, ' ');
377 $url = str_replace(' ', '%20', $url);
379 if (strpos($url, "//") === 0)
380 $url = 'http:' . $url;
382 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
384 $fetch_curl_used = true;
386 $ch = curl_init($url);
388 if ($timestamp && !$post_query) {
389 curl_setopt($ch, CURLOPT_HTTPHEADER
,
390 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
393 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
394 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
395 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("open_basedir") && $followlocation);
396 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
397 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
398 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
399 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
400 curl_setopt($ch, CURLOPT_USERAGENT
, $useragent ?
$useragent :
402 curl_setopt($ch, CURLOPT_ENCODING
, "");
403 //curl_setopt($ch, CURLOPT_REFERER, $url);
405 if (!ini_get("open_basedir")) {
406 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
409 if (defined('_CURL_HTTP_PROXY')) {
410 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
414 curl_setopt($ch, CURLOPT_POST
, true);
415 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
419 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
421 $contents = @curl_exec
($ch);
423 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
424 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
425 $contents = @curl_exec
($ch);
428 if ($contents === false) {
429 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
434 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
435 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
437 $fetch_last_error_code = $http_code;
439 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
440 if (curl_errno($ch) != 0) {
441 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
443 $fetch_last_error = "HTTP Code: $http_code";
445 $fetch_last_error_content = $contents;
455 $fetch_curl_used = false;
457 if ($login && $pass){
458 $url_parts = array();
460 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
462 $pass = urlencode($pass);
464 if ($url_parts[1] && $url_parts[2]) {
465 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
469 // TODO: should this support POST requests or not? idk
471 if (!$post_query && $timestamp) {
472 $context = stream_context_create(array(
475 'ignore_errors' => true,
476 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
477 'protocol_version'=> 1.1,
478 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
481 $context = stream_context_create(array(
484 'ignore_errors' => true,
485 'timeout' => $timeout ?
$timeout : FILE_FETCH_TIMEOUT
,
486 'protocol_version'=> 1.1
490 $old_error = error_get_last();
492 $data = @file_get_contents
($url, false, $context);
494 if (isset($http_response_header) && is_array($http_response_header)) {
495 foreach ($http_response_header as $h) {
496 if (substr(strtolower($h), 0, 13) == 'content-type:') {
497 $fetch_last_content_type = substr($h, 14);
498 // don't abort here b/c there might be more than one
499 // e.g. if we were being redirected -- last one is the right one
502 if (substr(strtolower($h), 0, 7) == 'http/1.') {
503 $fetch_last_error_code = (int) substr($h, 9, 3);
508 if ($fetch_last_error_code != 200) {
509 $error = error_get_last();
511 if ($error['message'] != $old_error['message']) {
512 $fetch_last_error = $error["message"];
514 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
517 $fetch_last_error_content = $data;
527 * Try to determine the favicon URL for a feed.
528 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
529 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
531 * @param string $url A feed or page URL
533 * @return mixed The favicon URL, or false if none was found.
535 function get_favicon_url($url) {
537 $favicon_url = false;
539 if ($html = @fetch_file_contents
($url)) {
541 libxml_use_internal_errors(true);
543 $doc = new DOMDocument();
544 $doc->loadHTML($html);
545 $xpath = new DOMXPath($doc);
547 $base = $xpath->query('/html/head/base');
548 foreach ($base as $b) {
549 $url = $b->getAttribute("href");
553 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
554 if (count($entries) > 0) {
555 foreach ($entries as $entry) {
556 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
563 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
566 } // function get_favicon_url
568 function initialize_user_prefs($uid, $profile = false) {
570 $uid = db_escape_string($uid);
574 $profile_qpart = "AND profile IS NULL";
576 $profile_qpart = "AND profile = '$profile'";
579 if (get_schema_version() < 63) $profile_qpart = "";
583 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
585 $u_result = db_query("SELECT pref_name
586 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
588 $active_prefs = array();
590 while ($line = db_fetch_assoc($u_result)) {
591 array_push($active_prefs, $line["pref_name"]);
594 while ($line = db_fetch_assoc($result)) {
595 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
596 // print "adding " . $line["pref_name"] . "<br>";
598 $line["def_value"] = db_escape_string($line["def_value"]);
599 $line["pref_name"] = db_escape_string($line["pref_name"]);
601 if (get_schema_version() < 63) {
602 db_query("INSERT INTO ttrss_user_prefs
603 (owner_uid,pref_name,value) VALUES
604 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
607 db_query("INSERT INTO ttrss_user_prefs
608 (owner_uid,pref_name,value, profile) VALUES
609 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
619 function get_ssl_certificate_id() {
620 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
621 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
622 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
623 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
624 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
626 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
627 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
628 $_SERVER["SSL_CLIENT_V_START"] .
629 $_SERVER["SSL_CLIENT_V_END"] .
630 $_SERVER["SSL_CLIENT_S_DN"]);
635 function authenticate_user($login, $password, $check_only = false) {
637 if (!SINGLE_USER_MODE
) {
640 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
642 $user_id = (int) $plugin->authenticate($login, $password);
645 $_SESSION["auth_module"] = strtolower(get_class($plugin));
650 if ($user_id && !$check_only) {
653 $_SESSION["uid"] = $user_id;
654 $_SESSION["version"] = VERSION_STATIC
;
656 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
657 WHERE id = '$user_id'");
659 $_SESSION["name"] = db_fetch_result($result, 0, "login");
660 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
661 $_SESSION["csrf_token"] = uniqid_short();
663 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
666 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
667 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
668 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
670 $_SESSION["last_version_check"] = time();
672 initialize_user_prefs($_SESSION["uid"]);
681 $_SESSION["uid"] = 1;
682 $_SESSION["name"] = "admin";
683 $_SESSION["access_level"] = 10;
685 $_SESSION["hide_hello"] = true;
686 $_SESSION["hide_logout"] = true;
688 $_SESSION["auth_module"] = false;
690 if (!$_SESSION["csrf_token"]) {
691 $_SESSION["csrf_token"] = uniqid_short();
694 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
696 initialize_user_prefs($_SESSION["uid"]);
702 function make_password($length = 8) {
705 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
709 while ($i < $length) {
710 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
712 if (!strstr($password, $char)) {
720 // this is called after user is created to initialize default feeds, labels
723 // user preferences are checked on every login, not here
725 function initialize_user($uid) {
727 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
728 values ('$uid', 'Tiny Tiny RSS: Forum',
729 'http://tt-rss.org/forum/rss.php')");
732 function logout_user() {
734 if (isset($_COOKIE[session_name()])) {
735 setcookie(session_name(), '', time()-42000, '/');
739 function validate_csrf($csrf_token) {
740 return $csrf_token == $_SESSION['csrf_token'];
743 function load_user_plugins($owner_uid, $pluginhost = false) {
745 if (!$pluginhost) $pluginhost = PluginHost
::getInstance();
747 if ($owner_uid && SCHEMA_VERSION
>= 100) {
748 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
750 $pluginhost->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
752 if (get_schema_version() > 100) {
753 $pluginhost->load_data();
758 function login_sequence() {
759 if (SINGLE_USER_MODE
) {
761 authenticate_user("admin", null);
763 load_user_plugins($_SESSION["uid"]);
765 if (!validate_session()) $_SESSION["uid"] = false;
767 if (!$_SESSION["uid"]) {
769 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
770 $_SESSION["ref_schema_version"] = get_schema_version(true);
772 authenticate_user(null, null, true);
775 if (!$_SESSION["uid"]) {
777 setcookie(session_name(), '', time()-42000, '/');
784 /* bump login timestamp */
785 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
787 $_SESSION["last_login_update"] = time();
790 if ($_SESSION["uid"]) {
792 load_user_plugins($_SESSION["uid"]);
796 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
797 $_SESSION["uid"] . " AND
798 (SELECT COUNT(id) FROM ttrss_feeds WHERE
799 ttrss_feeds.id = feed_id) = 0");
801 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
802 $_SESSION["uid"] . " AND
803 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
804 ttrss_feed_categories.id = feed_id) = 0");
811 function truncate_string($str, $max_len, $suffix = '…') {
812 if (mb_strlen($str, "utf-8") > $max_len) {
813 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
820 function truncate_middle($str, $max_len, $suffix = '…') {
821 if (strlen($str) > $max_len) {
822 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
828 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
831 $source_tz = new DateTimeZone($source_tz);
832 } catch (Exception
$e) {
833 $source_tz = new DateTimeZone('UTC');
837 $dest_tz = new DateTimeZone($dest_tz);
838 } catch (Exception
$e) {
839 $dest_tz = new DateTimeZone('UTC');
842 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
843 return $dt->format('U') +
$dest_tz->getOffset($dt);
846 function make_local_datetime($timestamp, $long, $owner_uid = false,
847 $no_smart_dt = false, $eta_min = false) {
849 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
850 if (!$timestamp) $timestamp = '1970-01-01 0:00';
855 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
857 $timestamp = substr($timestamp, 0, 19);
859 # We store date in UTC internally
860 $dt = new DateTime($timestamp, $utc_tz);
862 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
864 if ($user_tz_string != 'Automatic') {
867 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
868 } catch (Exception
$e) {
872 $tz_offset = $user_tz->getOffset($dt);
874 $tz_offset = (int) -$_SESSION["clientTzOffset"];
877 $user_timestamp = $dt->format('U') +
$tz_offset;
880 return smart_date_time($user_timestamp,
881 $tz_offset, $owner_uid, $eta_min);
884 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
886 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
888 return date($format, $user_timestamp);
892 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
893 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
895 if ($eta_min && time() +
$tz_offset - $timestamp < 3600) {
896 return T_sprintf("%d min", date("i", time() +
$tz_offset - $timestamp));
897 } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
898 return date("G:i", $timestamp);
899 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
900 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
901 return date($format, $timestamp);
903 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
904 return date($format, $timestamp);
908 function sql_bool_to_bool($s) {
909 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
916 function bool_to_sql_bool($s) {
924 // Session caching removed due to causing wrong redirects to upgrade
925 // script when get_schema_version() is called on an obsolete session
926 // created on a previous schema version.
927 function get_schema_version($nocache = false) {
928 global $schema_version;
930 if (!$schema_version && !$nocache) {
931 $result = db_query("SELECT schema_version FROM ttrss_version");
932 $version = db_fetch_result($result, 0, "schema_version");
933 $schema_version = $version;
936 return $schema_version;
940 function sanity_check() {
941 require_once 'errors.php';
945 $schema_version = get_schema_version(true);
947 if ($schema_version != SCHEMA_VERSION
) {
951 if (DB_TYPE
== "mysql") {
952 $result = db_query("SELECT true", false);
953 if (db_num_rows($result) != 1) {
958 if (db_escape_string("testTEST") != "testTEST") {
962 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
965 function file_is_locked($filename) {
966 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
967 if (function_exists('flock')) {
968 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
970 if (flock($fp, LOCK_EX | LOCK_NB
)) {
981 return true; // consider the file always locked and skip the test
988 function make_lockfile($filename) {
989 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
991 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
992 $stat_h = fstat($fp);
993 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
995 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
996 if ($stat_h["ino"] != $stat_f["ino"] ||
997 $stat_h["dev"] != $stat_f["dev"]) {
1003 if (function_exists('posix_getpid')) {
1004 fwrite($fp, posix_getpid() . "\n");
1012 function make_stampfile($filename) {
1013 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1015 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1016 fwrite($fp, time() . "\n");
1017 flock($fp, LOCK_UN
);
1025 function sql_random_function() {
1026 if (DB_TYPE
== "mysql") {
1033 function getAllCounters() {
1034 $data = getGlobalCounters();
1036 $data = array_merge($data, getVirtCounters());
1037 $data = array_merge($data, getLabelCounters());
1038 $data = array_merge($data, getFeedCounters());
1039 $data = array_merge($data, getCategoryCounters());
1044 function getCategoryCounters() {
1047 /* Labels category */
1049 $cv = array("id" => -2, "kind" => "cat",
1050 "counter" => Feeds
::getCategoryUnread(-2));
1052 array_push($ret_arr, $cv);
1054 $result = db_query("SELECT id AS cat_id, value AS unread,
1055 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1056 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1057 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1058 WHERE ttrss_cat_counters_cache.feed_id = id AND
1059 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1060 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1062 while ($line = db_fetch_assoc($result)) {
1063 $line["cat_id"] = (int) $line["cat_id"];
1065 if ($line["num_children"] > 0) {
1066 $child_counter = Feeds
::getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
1071 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1072 "counter" => $line["unread"] +
$child_counter);
1074 array_push($ret_arr, $cv);
1077 /* Special case: NULL category doesn't actually exist in the DB */
1079 $cv = array("id" => 0, "kind" => "cat",
1080 "counter" => (int) CCache
::find(0, $_SESSION["uid"], true));
1082 array_push($ret_arr, $cv);
1087 function getFeedUnread($feed, $is_cat = false) {
1088 return Feeds
::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1091 function getGlobalCounters($global_unread = -1) {
1094 if ($global_unread == -1) {
1095 $global_unread = Feeds
::getGlobalUnread();
1098 $cv = array("id" => "global-unread",
1099 "counter" => (int) $global_unread);
1101 array_push($ret_arr, $cv);
1103 $result = db_query("SELECT COUNT(id) AS fn FROM
1104 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1106 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1108 $cv = array("id" => "subscribed-feeds",
1109 "counter" => (int) $subscribed_feeds);
1111 array_push($ret_arr, $cv);
1116 function getVirtCounters() {
1120 for ($i = 0; $i >= -4; $i--) {
1122 $count = getFeedUnread($i);
1124 if ($i == 0 ||
$i == -1 ||
$i == -2)
1125 $auxctr = Feeds
::getFeedArticles($i, false);
1129 $cv = array("id" => $i,
1130 "counter" => (int) $count,
1131 "auxcounter" => (int) $auxctr);
1133 // if (get_pref('EXTENDED_FEEDLIST'))
1134 // $cv["xmsg"] = getFeedArticles($i)." ".__("total");
1136 array_push($ret_arr, $cv);
1139 $feeds = PluginHost
::getInstance()->get_feeds(-1);
1141 if (is_array($feeds)) {
1142 foreach ($feeds as $feed) {
1143 $cv = array("id" => PluginHost
::pfeed_to_feed_id($feed['id']),
1144 "counter" => $feed['sender']->get_unread($feed['id']));
1146 if (method_exists($feed['sender'], 'get_total'))
1147 $cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
1149 array_push($ret_arr, $cv);
1156 function getLabelCounters($descriptions = false) {
1160 $owner_uid = $_SESSION["uid"];
1162 $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total
1163 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1164 (ttrss_labels2.id = label_id)
1165 LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id
1166 WHERE ttrss_labels2.owner_uid = $owner_uid AND u1.owner_uid = $owner_uid
1167 GROUP BY ttrss_labels2.id,
1168 ttrss_labels2.caption");
1170 while ($line = db_fetch_assoc($result)) {
1172 $id = label_to_feed_id($line["id"]);
1174 $cv = array("id" => $id,
1175 "counter" => (int) $line["unread"],
1176 "auxcounter" => (int) $line["total"]);
1179 $cv["description"] = $line["caption"];
1181 array_push($ret_arr, $cv);
1187 function getFeedCounters($active_feed = false) {
1191 $query = "SELECT ttrss_feeds.id,
1193 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1194 last_error, value AS count
1195 FROM ttrss_feeds, ttrss_counters_cache
1196 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1197 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1198 AND ttrss_counters_cache.feed_id = id";
1200 $result = db_query($query);
1202 while ($line = db_fetch_assoc($result)) {
1205 $count = $line["count"];
1206 $last_error = htmlspecialchars($line["last_error"]);
1208 $last_updated = make_local_datetime($line['last_updated'], false);
1210 $has_img = feed_has_icon($id);
1212 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1215 $cv = array("id" => $id,
1216 "updated" => $last_updated,
1217 "counter" => (int) $count,
1218 "has_img" => (int) $has_img);
1221 $cv["error"] = $last_error;
1223 // if (get_pref('EXTENDED_FEEDLIST'))
1224 // $cv["xmsg"] = getFeedArticles($id)." ".__("total");
1226 if ($active_feed && $id == $active_feed)
1227 $cv["title"] = truncate_string($line["title"], 30);
1229 array_push($ret_arr, $cv);
1236 /*function get_pgsql_version() {
1237 $result = db_query("SELECT version() AS version");
1238 $version = explode(" ", db_fetch_result($result, 0, "version"));
1242 function checkbox_to_sql_bool($val) {
1243 return ($val == "on") ?
"true" : "false";
1246 /*function getFeedCatTitle($id) {
1248 return __("Special");
1249 } else if ($id < LABEL_BASE_INDEX) {
1250 return __("Labels");
1251 } else if ($id > 0) {
1252 $result = db_query("SELECT ttrss_feed_categories.title
1253 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1254 cat_id = ttrss_feed_categories.id");
1255 if (db_num_rows($result) == 1) {
1256 return db_fetch_result($result, 0, "title");
1258 return __("Uncategorized");
1261 return "getFeedCatTitle($id) failed";
1266 function uniqid_short() {
1267 return uniqid(base_convert(rand(), 10, 36));
1270 function make_init_params() {
1273 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1274 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1275 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1276 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1278 $params[strtolower($param)] = (int) get_pref($param);
1281 $params["icons_url"] = ICONS_URL
;
1282 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1283 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1284 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1285 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1286 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1287 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
1289 $theme = get_pref( "USER_CSS_THEME", false, false);
1290 $params["theme"] = theme_valid("$theme") ?
$theme : "";
1292 $params["plugins"] = implode(", ", PluginHost
::getInstance()->get_plugin_names());
1294 $params["php_platform"] = PHP_OS
;
1295 $params["php_version"] = PHP_VERSION
;
1297 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1299 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1300 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1302 $max_feed_id = db_fetch_result($result, 0, "mid");
1303 $num_feeds = db_fetch_result($result, 0, "nf");
1305 $params["max_feed_id"] = (int) $max_feed_id;
1306 $params["num_feeds"] = (int) $num_feeds;
1308 $params["hotkeys"] = get_hotkeys_map();
1310 $params["csrf_token"] = $_SESSION["csrf_token"];
1311 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1313 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1315 $params["icon_alert"] = base64_img("images/alert.png");
1316 $params["icon_information"] = base64_img("images/information.png");
1317 $params["icon_cross"] = base64_img("images/cross.png");
1318 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
1323 function get_hotkeys_info() {
1325 __("Navigation") => array(
1326 "next_feed" => __("Open next feed"),
1327 "prev_feed" => __("Open previous feed"),
1328 "next_article" => __("Open next article"),
1329 "prev_article" => __("Open previous article"),
1330 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1331 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1332 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1333 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1334 "search_dialog" => __("Show search dialog")),
1335 __("Article") => array(
1336 "toggle_mark" => __("Toggle starred"),
1337 "toggle_publ" => __("Toggle published"),
1338 "toggle_unread" => __("Toggle unread"),
1339 "edit_tags" => __("Edit tags"),
1340 "open_in_new_window" => __("Open in new window"),
1341 "catchup_below" => __("Mark below as read"),
1342 "catchup_above" => __("Mark above as read"),
1343 "article_scroll_down" => __("Scroll down"),
1344 "article_scroll_up" => __("Scroll up"),
1345 "select_article_cursor" => __("Select article under cursor"),
1346 "email_article" => __("Email article"),
1347 "close_article" => __("Close/collapse article"),
1348 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1349 "toggle_widescreen" => __("Toggle widescreen mode"),
1350 "toggle_embed_original" => __("Toggle embed original")),
1351 __("Article selection") => array(
1352 "select_all" => __("Select all articles"),
1353 "select_unread" => __("Select unread"),
1354 "select_marked" => __("Select starred"),
1355 "select_published" => __("Select published"),
1356 "select_invert" => __("Invert selection"),
1357 "select_none" => __("Deselect everything")),
1358 __("Feed") => array(
1359 "feed_refresh" => __("Refresh current feed"),
1360 "feed_unhide_read" => __("Un/hide read feeds"),
1361 "feed_subscribe" => __("Subscribe to feed"),
1362 "feed_edit" => __("Edit feed"),
1363 "feed_catchup" => __("Mark as read"),
1364 "feed_reverse" => __("Reverse headlines"),
1365 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1366 "feed_debug_update" => __("Debug feed update"),
1367 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1368 "catchup_all" => __("Mark all feeds as read"),
1369 "cat_toggle_collapse" => __("Un/collapse current category"),
1370 "toggle_combined_mode" => __("Toggle combined mode"),
1371 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1372 __("Go to") => array(
1373 "goto_all" => __("All articles"),
1374 "goto_fresh" => __("Fresh"),
1375 "goto_marked" => __("Starred"),
1376 "goto_published" => __("Published"),
1377 "goto_tagcloud" => __("Tag cloud"),
1378 "goto_prefs" => __("Preferences")),
1379 __("Other") => array(
1380 "create_label" => __("Create label"),
1381 "create_filter" => __("Create filter"),
1382 "collapse_sidebar" => __("Un/collapse sidebar"),
1383 "help_dialog" => __("Show help dialog"))
1386 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
1387 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1393 function get_hotkeys_map() {
1395 // "navigation" => array(
1398 "n" => "next_article",
1399 "p" => "prev_article",
1400 "(38)|up" => "prev_article",
1401 "(40)|down" => "next_article",
1402 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1403 // "^(40)|Ctrl-down" => "next_article_noscroll",
1404 "(191)|/" => "search_dialog",
1405 // "article" => array(
1406 "s" => "toggle_mark",
1407 "*s" => "toggle_publ",
1408 "u" => "toggle_unread",
1409 "*t" => "edit_tags",
1410 "o" => "open_in_new_window",
1411 "c p" => "catchup_below",
1412 "c n" => "catchup_above",
1413 "*n" => "article_scroll_down",
1414 "*p" => "article_scroll_up",
1415 "*(38)|Shift+up" => "article_scroll_up",
1416 "*(40)|Shift+down" => "article_scroll_down",
1417 "a *w" => "toggle_widescreen",
1418 "a e" => "toggle_embed_original",
1419 "e" => "email_article",
1420 "a q" => "close_article",
1421 // "article_selection" => array(
1422 "a a" => "select_all",
1423 "a u" => "select_unread",
1424 "a *u" => "select_marked",
1425 "a p" => "select_published",
1426 "a i" => "select_invert",
1427 "a n" => "select_none",
1429 "f r" => "feed_refresh",
1430 "f a" => "feed_unhide_read",
1431 "f s" => "feed_subscribe",
1432 "f e" => "feed_edit",
1433 "f q" => "feed_catchup",
1434 "f x" => "feed_reverse",
1435 "f g" => "feed_toggle_vgroup",
1436 "f *d" => "feed_debug_update",
1437 "f *g" => "feed_debug_viewfeed",
1438 "f *c" => "toggle_combined_mode",
1439 "f c" => "toggle_cdm_expanded",
1440 "*q" => "catchup_all",
1441 "x" => "cat_toggle_collapse",
1443 "g a" => "goto_all",
1444 "g f" => "goto_fresh",
1445 "g s" => "goto_marked",
1446 "g p" => "goto_published",
1447 "g t" => "goto_tagcloud",
1448 "g *p" => "goto_prefs",
1449 // "other" => array(
1450 "(9)|Tab" => "select_article_cursor", // tab
1451 "c l" => "create_label",
1452 "c f" => "create_filter",
1453 "c s" => "collapse_sidebar",
1454 "^(191)|Ctrl+/" => "help_dialog",
1457 if (get_pref('COMBINED_DISPLAY_MODE')) {
1458 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1459 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1462 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
1463 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1466 $prefixes = array();
1468 foreach (array_keys($hotkeys) as $hotkey) {
1469 $pair = explode(" ", $hotkey, 2);
1471 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1472 array_push($prefixes, $pair[0]);
1476 return array($prefixes, $hotkeys);
1479 function check_for_update() {
1480 if (defined("GIT_VERSION_TIMESTAMP")) {
1481 $content = @fetch_file_contents
(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1484 $content = json_decode($content, true);
1486 if ($content && isset($content["changeset"])) {
1487 if ((int)GIT_VERSION_TIMESTAMP
< (int)$content["changeset"]["timestamp"] &&
1488 GIT_VERSION_HEAD
!= $content["changeset"]["id"]) {
1490 return $content["changeset"]["id"];
1499 function make_runtime_info($disable_update_check = false) {
1502 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1503 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1505 $max_feed_id = db_fetch_result($result, 0, "mid");
1506 $num_feeds = db_fetch_result($result, 0, "nf");
1508 $data["max_feed_id"] = (int) $max_feed_id;
1509 $data["num_feeds"] = (int) $num_feeds;
1511 $data['last_article_id'] = Article
::getLastArticleId();
1512 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1514 $data['dep_ts'] = calculate_dep_timestamp();
1515 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1518 if (CHECK_FOR_UPDATES
&& !$disable_update_check && $_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
1519 $update_result = @check_for_update
();
1521 $data["update_result"] = $update_result;
1523 $_SESSION["last_version_check"] = time();
1526 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
1528 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1530 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1532 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
1535 $stamp_delta = time() - $stamp;
1537 if ($stamp_delta > 1800) {
1541 $_SESSION["daemon_stamp_check"] = time();
1544 $data['daemon_stamp_ok'] = $stamp_check;
1546 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1548 $data['daemon_stamp'] = $stamp_fmt;
1556 function search_to_sql($search, $search_language) {
1558 $keywords = str_getcsv(trim($search), " ");
1559 $query_keywords = array();
1560 $search_words = array();
1561 $search_query_leftover = array();
1563 if ($search_language)
1564 $search_language = db_escape_string(mb_strtolower($search_language));
1566 $search_language = "english";
1568 foreach ($keywords as $k) {
1569 if (strpos($k, "-") === 0) {
1576 $commandpair = explode(":", mb_strtolower($k), 2);
1578 switch ($commandpair[0]) {
1580 if ($commandpair[1]) {
1581 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1582 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1584 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1585 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1586 array_push($search_words, $k);
1590 if ($commandpair[1]) {
1591 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1592 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1594 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1595 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1596 array_push($search_words, $k);
1600 if ($commandpair[1]) {
1601 if ($commandpair[1] == "true")
1602 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1603 else if ($commandpair[1] == "false")
1604 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1606 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1607 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1609 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1610 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1611 if (!$not) array_push($search_words, $k);
1616 if ($commandpair[1]) {
1617 if ($commandpair[1] == "true")
1618 array_push($query_keywords, "($not (marked = true))");
1620 array_push($query_keywords, "($not (marked = false))");
1622 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1623 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1624 if (!$not) array_push($search_words, $k);
1628 if ($commandpair[1]) {
1629 if ($commandpair[1] == "true")
1630 array_push($query_keywords, "($not (published = true))");
1632 array_push($query_keywords, "($not (published = false))");
1635 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1636 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1637 if (!$not) array_push($search_words, $k);
1641 if ($commandpair[1]) {
1642 if ($commandpair[1] == "true")
1643 array_push($query_keywords, "($not (unread = true))");
1645 array_push($query_keywords, "($not (unread = false))");
1648 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1649 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1650 if (!$not) array_push($search_words, $k);
1654 if (strpos($k, "@") === 0) {
1656 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1657 $orig_ts = strtotime(substr($k, 1));
1658 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1660 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1662 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
1665 if (DB_TYPE
== "pgsql") {
1666 $k = mb_strtolower($k);
1667 array_push($search_query_leftover, $not ?
"!$k" : $k);
1669 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1670 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1673 if (!$not) array_push($search_words, $k);
1678 if (count($search_query_leftover) > 0) {
1679 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1681 if (DB_TYPE
== "pgsql") {
1682 array_push($query_keywords,
1683 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1688 $search_query_part = implode("AND", $query_keywords);
1690 return array($search_query_part, $search_words);
1693 function iframe_whitelisted($entry) {
1694 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1696 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST
);
1699 foreach ($whitelist as $w) {
1700 if ($src == $w ||
$src == "www.$w")
1708 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1709 if (!$owner) $owner = $_SESSION["uid"];
1711 $res = trim($str); if (!$res) return '';
1713 $charset_hack = '<head>
1714 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1717 $res = trim($res); if (!$res) return '';
1719 libxml_use_internal_errors(true);
1721 $doc = new DOMDocument();
1722 $doc->loadHTML($charset_hack . $res);
1723 $xpath = new DOMXPath($doc);
1725 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME
) === 'https';
1726 $rewrite_base_url = $site_url ?
$site_url : SELF_URL_PATH
;
1728 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1730 foreach ($entries as $entry) {
1732 if ($entry->hasAttribute('href')) {
1733 $entry->setAttribute('href',
1734 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1736 $entry->setAttribute('rel', 'noopener noreferrer');
1739 if ($entry->hasAttribute('src')) {
1740 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1741 $cached_filename = CACHE_DIR
. '/images/' . sha1($src);
1743 if (file_exists($cached_filename)) {
1745 // this is strictly cosmetic
1746 if ($entry->tagName
== 'img') {
1748 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "video") {
1750 } else if ($entry->parentNode
&& $entry->parentNode
->tagName
== "audio") {
1756 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1758 if ($entry->hasAttribute('srcset')) {
1759 $entry->removeAttribute('srcset');
1762 if ($entry->hasAttribute('sizes')) {
1763 $entry->removeAttribute('sizes');
1767 $entry->setAttribute('src', $src);
1770 if ($entry->nodeName
== 'img') {
1772 if ($entry->hasAttribute('src')) {
1773 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME
) === 'https';
1775 if ($ttrss_uses_https && !$is_https_url) {
1777 if ($entry->hasAttribute('srcset')) {
1778 $entry->removeAttribute('srcset');
1781 if ($entry->hasAttribute('sizes')) {
1782 $entry->removeAttribute('sizes');
1787 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1788 $force_remove_images ||
$_SESSION["bw_limit"]) {
1790 $p = $doc->createElement('p');
1792 $a = $doc->createElement('a');
1793 $a->setAttribute('href', $entry->getAttribute('src'));
1795 $a->appendChild(new DOMText($entry->getAttribute('src')));
1796 $a->setAttribute('target', '_blank');
1797 $a->setAttribute('rel', 'noopener noreferrer');
1799 $p->appendChild($a);
1801 $entry->parentNode
->replaceChild($p, $entry);
1805 if (strtolower($entry->nodeName
) == "a") {
1806 $entry->setAttribute("target", "_blank");
1807 $entry->setAttribute("rel", "noopener noreferrer");
1811 $entries = $xpath->query('//iframe');
1812 foreach ($entries as $entry) {
1813 if (!iframe_whitelisted($entry)) {
1814 $entry->setAttribute('sandbox', 'allow-scripts');
1816 if ($_SERVER['HTTPS'] == "on") {
1817 $entry->setAttribute("src",
1818 str_replace("http://", "https://",
1819 $entry->getAttribute("src")));
1824 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1825 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1826 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1827 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1828 'dt', 'em', 'footer', 'figure', 'figcaption',
1829 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1830 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1831 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1832 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1833 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1834 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1836 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1838 $disallowed_attributes = array('id', 'style', 'class');
1840 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
1841 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1842 if (is_array($retval)) {
1844 $allowed_elements = $retval[1];
1845 $disallowed_attributes = $retval[2];
1851 $doc->removeChild($doc->firstChild
); //remove doctype
1852 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1854 if ($highlight_words) {
1855 foreach ($highlight_words as $word) {
1857 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1859 $elements = $xpath->query("//*/text()");
1861 foreach ($elements as $child) {
1863 $fragment = $doc->createDocumentFragment();
1864 $text = $child->textContent
;
1866 while (($pos = mb_stripos($text, $word)) !== false) {
1867 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1868 $word = mb_substr($text, $pos, mb_strlen($word));
1869 $highlight = $doc->createElement('span');
1870 $highlight->appendChild(new DomText($word));
1871 $highlight->setAttribute('class', 'highlight');
1872 $fragment->appendChild($highlight);
1873 $text = mb_substr($text, $pos +
mb_strlen($word));
1876 if (!empty($text)) $fragment->appendChild(new DomText($text));
1878 $child->parentNode
->replaceChild($fragment, $child);
1883 $res = $doc->saveHTML();
1885 /* strip everything outside of <body>...</body> */
1887 $res_frag = array();
1888 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1889 return $res_frag[1];
1895 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1896 $xpath = new DOMXPath($doc);
1897 $entries = $xpath->query('//*');
1899 foreach ($entries as $entry) {
1900 if (!in_array($entry->nodeName
, $allowed_elements)) {
1901 $entry->parentNode
->removeChild($entry);
1904 if ($entry->hasAttributes()) {
1905 $attrs_to_remove = array();
1907 foreach ($entry->attributes
as $attr) {
1909 if (strpos($attr->nodeName
, 'on') === 0) {
1910 array_push($attrs_to_remove, $attr);
1913 if ($attr->nodeName
== 'href' && stripos($attr->value
, 'javascript:') === 0) {
1914 array_push($attrs_to_remove, $attr);
1917 if (in_array($attr->nodeName
, $disallowed_attributes)) {
1918 array_push($attrs_to_remove, $attr);
1922 foreach ($attrs_to_remove as $attr) {
1923 $entry->removeAttributeNode($attr);
1931 function trim_array($array) {
1933 array_walk($tmp, 'trim');
1937 function tag_is_valid($tag) {
1938 if ($tag == '') return false;
1939 if (is_numeric($tag)) return false;
1940 if (mb_strlen($tag) > 250) return false;
1942 if (!$tag) return false;
1947 function render_login_form() {
1948 header('Cache-Control: public');
1950 require_once "login_form.php";
1954 function T_sprintf() {
1955 $args = func_get_args();
1956 return vsprintf(__(array_shift($args)), $args);
1959 function print_checkpoint($n, $s) {
1960 $ts = microtime(true);
1961 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1965 function sanitize_tag($tag) {
1968 $tag = mb_strtolower($tag, 'utf-8');
1970 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1972 if (DB_TYPE
== "mysql") {
1973 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1979 function get_self_url_prefix() {
1980 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
1981 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
1983 return SELF_URL_PATH
;
1988 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1990 * @return string The Mozilla Firefox feed adding URL.
1992 function add_feed_url() {
1993 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1995 $url_path = get_self_url_prefix() .
1996 "/public.php?op=subscribe&feed_url=%s";
1998 } // function add_feed_url
2000 function encrypt_password($pass, $salt = '', $mode2 = false) {
2001 if ($salt && $mode2) {
2002 return "MODE2:" . hash('sha256', $salt . $pass);
2004 return "SHA1X:" . sha1("$salt:$pass");
2006 return "SHA1:" . sha1($pass);
2008 } // function encrypt_password
2010 function load_filters($feed_id, $owner_uid) {
2013 $cat_id = (int)getFeedCategory($feed_id);
2016 $null_cat_qpart = "cat_id IS NULL OR";
2018 $null_cat_qpart = "";
2020 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
2021 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
2023 $check_cats = join(",", array_merge(
2024 Feeds
::getParentCategories($cat_id, $owner_uid),
2027 while ($line = db_fetch_assoc($result)) {
2028 $filter_id = $line["id"];
2030 $result2 = db_query("SELECT
2031 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
2032 FROM ttrss_filters2_rules AS r,
2033 ttrss_filter_types AS t
2035 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
2036 (feed_id IS NULL OR feed_id = '$feed_id') AND
2037 filter_type = t.id AND filter_id = '$filter_id'");
2042 while ($rule_line = db_fetch_assoc($result2)) {
2043 # print_r($rule_line);
2046 $rule["reg_exp"] = $rule_line["reg_exp"];
2047 $rule["type"] = $rule_line["type_name"];
2048 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
2050 array_push($rules, $rule);
2053 $result2 = db_query("SELECT a.action_param,t.name AS type_name
2054 FROM ttrss_filters2_actions AS a,
2055 ttrss_filter_actions AS t
2057 action_id = t.id AND filter_id = '$filter_id'");
2059 while ($action_line = db_fetch_assoc($result2)) {
2060 # print_r($action_line);
2063 $action["type"] = $action_line["type_name"];
2064 $action["param"] = $action_line["action_param"];
2066 array_push($actions, $action);
2071 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
2072 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
2073 $filter["rules"] = $rules;
2074 $filter["actions"] = $actions;
2076 if (count($rules) > 0 && count($actions) > 0) {
2077 array_push($filters, $filter);
2084 function get_score_pic($score) {
2086 return "score_high.png";
2087 } else if ($score > 0) {
2088 return "score_half_high.png";
2089 } else if ($score < -100) {
2090 return "score_low.png";
2091 } else if ($score < 0) {
2092 return "score_half_low.png";
2094 return "score_neutral.png";
2098 function feed_has_icon($id) {
2099 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
2102 function init_plugins() {
2103 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
2108 function add_feed_category($feed_cat, $parent_cat_id = false) {
2110 if (!$feed_cat) return false;
2114 if ($parent_cat_id) {
2115 $parent_qpart = "parent_cat = '$parent_cat_id'";
2116 $parent_insert = "'$parent_cat_id'";
2118 $parent_qpart = "parent_cat IS NULL";
2119 $parent_insert = "NULL";
2122 $feed_cat = mb_substr($feed_cat, 0, 250);
2125 "SELECT id FROM ttrss_feed_categories
2126 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
2128 if (db_num_rows($result) == 0) {
2131 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
2132 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
2143 * Fixes incomplete URLs by prepending "http://".
2144 * Also replaces feed:// with http://, and
2145 * prepends a trailing slash if the url is a domain name only.
2147 * @param string $url Possibly incomplete URL
2149 * @return string Fixed URL.
2151 function fix_url($url) {
2153 // support schema-less urls
2154 if (strpos($url, '//') === 0) {
2155 $url = 'https:' . $url;
2158 if (strpos($url, '://') === false) {
2159 $url = 'http://' . $url;
2160 } else if (substr($url, 0, 5) == 'feed:') {
2161 $url = 'http:' . substr($url, 5);
2164 //prepend slash if the URL has no slash in it
2165 // "http://www.example" -> "http://www.example/"
2166 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
2170 //convert IDNA hostname to punycode if possible
2171 if (function_exists("idn_to_ascii")) {
2172 $parts = parse_url($url);
2173 if (mb_detect_encoding($parts['host']) != 'ASCII')
2175 $parts['host'] = idn_to_ascii($parts['host']);
2176 $url = build_url($parts);
2180 if ($url != "http:///")
2186 function validate_feed_url($url) {
2187 $parts = parse_url($url);
2189 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
2193 /* function save_email_address($email) {
2194 // FIXME: implement persistent storage of emails
2196 if (!$_SESSION['stored_emails'])
2197 $_SESSION['stored_emails'] = array();
2199 if (!in_array($email, $_SESSION['stored_emails']))
2200 array_push($_SESSION['stored_emails'], $email);
2204 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2206 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2208 $sql_is_cat = bool_to_sql_bool($is_cat);
2210 $result = db_query("SELECT access_key FROM ttrss_access_keys
2211 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2212 AND owner_uid = " . $owner_uid);
2214 if (db_num_rows($result) == 1) {
2215 return db_fetch_result($result, 0, "access_key");
2217 $key = db_escape_string(uniqid_short());
2219 $result = db_query("INSERT INTO ttrss_access_keys
2220 (access_key, feed_id, is_cat, owner_uid)
2221 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2228 function get_feeds_from_html($url, $content)
2230 $url = fix_url($url);
2231 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
2233 libxml_use_internal_errors(true);
2235 $doc = new DOMDocument();
2236 $doc->loadHTML($content);
2237 $xpath = new DOMXPath($doc);
2238 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2239 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2240 $feedUrls = array();
2241 foreach ($entries as $entry) {
2242 if ($entry->hasAttribute('href')) {
2243 $title = $entry->getAttribute('title');
2245 $title = $entry->getAttribute('type');
2247 $feedUrl = rewrite_relative_url(
2248 $baseUrl, $entry->getAttribute('href')
2250 $feedUrls[$feedUrl] = $title;
2256 function is_html($content) {
2257 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2260 function url_is_html($url, $login = false, $pass = false) {
2261 return is_html(fetch_file_contents($url, false, $login, $pass));
2264 function build_url($parts) {
2265 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2268 function cleanup_url_path($path) {
2269 $path = str_replace("/./", "/", $path);
2270 $path = str_replace("//", "/", $path);
2276 * Converts a (possibly) relative URL to a absolute one.
2278 * @param string $url Base URL (i.e. from where the document is)
2279 * @param string $rel_url Possibly relative URL in the document
2281 * @return string Absolute URL
2283 function rewrite_relative_url($url, $rel_url) {
2284 if (strpos($rel_url, "://") !== false) {
2286 } else if (strpos($rel_url, "//") === 0) {
2287 # protocol-relative URL (rare but they exist)
2289 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2290 # magnet:, feed:, etc
2292 } else if (strpos($rel_url, "/") === 0) {
2293 $parts = parse_url($url);
2294 $parts['path'] = $rel_url;
2295 $parts['path'] = cleanup_url_path($parts['path']);
2297 return build_url($parts);
2300 $parts = parse_url($url);
2301 if (!isset($parts['path'])) {
2302 $parts['path'] = '/';
2304 $dir = $parts['path'];
2305 if (substr($dir, -1) !== '/') {
2306 $dir = dirname($parts['path']);
2307 $dir !== '/' && $dir .= '/';
2309 $parts['path'] = $dir . $rel_url;
2310 $parts['path'] = cleanup_url_path($parts['path']);
2312 return build_url($parts);
2316 function cleanup_tags($days = 14, $limit = 1000) {
2318 if (DB_TYPE
== "pgsql") {
2319 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2320 } else if (DB_TYPE
== "mysql") {
2321 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2326 while ($limit > 0) {
2329 $query = "SELECT ttrss_tags.id AS id
2330 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2331 WHERE post_int_id = int_id AND $interval_query AND
2332 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2334 $result = db_query($query);
2338 while ($line = db_fetch_assoc($result)) {
2339 array_push($ids, $line['id']);
2342 if (count($ids) > 0) {
2343 $ids = join(",", $ids);
2345 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2346 $tags_deleted +
= db_affected_rows($tmp_result);
2351 $limit -= $limit_part;
2354 return $tags_deleted;
2357 function print_user_stylesheet() {
2358 $value = get_pref('USER_STYLESHEET');
2361 print "<style type=\"text/css\">";
2362 print str_replace("<br/>", "\n", $value);
2368 function filter_to_sql($filter, $owner_uid) {
2371 if (DB_TYPE
== "pgsql")
2374 $reg_qpart = "REGEXP";
2376 foreach ($filter["rules"] AS $rule) {
2377 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2378 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2379 $rule['reg_exp']) !== FALSE;
2381 if ($regexp_valid) {
2383 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2385 switch ($rule["type"]) {
2387 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2388 $rule['reg_exp'] . "')";
2391 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2392 $rule['reg_exp'] . "')";
2395 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2396 $rule['reg_exp'] . "') OR LOWER(" .
2397 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2400 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2401 $rule['reg_exp'] . "')";
2404 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2405 $rule['reg_exp'] . "')";
2408 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2409 $rule['reg_exp'] . "')";
2413 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2415 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2416 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2419 if (isset($rule["cat_id"])) {
2421 if ($rule["cat_id"] > 0) {
2422 $children = Feeds
::getChildCategories($rule["cat_id"], $owner_uid);
2423 array_push($children, $rule["cat_id"]);
2425 $children = join(",", $children);
2427 $cat_qpart = "cat_id IN ($children)";
2429 $cat_qpart = "cat_id IS NULL";
2432 $qpart .= " AND $cat_qpart";
2435 $qpart .= " AND feed_id IS NOT NULL";
2437 array_push($query, "($qpart)");
2442 if (count($query) > 0) {
2443 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
2445 $fullquery = "(false)";
2448 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2453 if (!function_exists('gzdecode')) {
2454 function gzdecode($string) { // no support for 2nd argument
2455 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2456 base64_encode($string));
2460 function get_random_bytes($length) {
2461 if (function_exists('openssl_random_pseudo_bytes')) {
2462 return openssl_random_pseudo_bytes($length);
2466 for ($i = 0; $i < $length; $i++
)
2467 $output .= chr(mt_rand(0, 255));
2473 function read_stdin() {
2474 $fp = fopen("php://stdin", "r");
2477 $line = trim(fgets($fp));
2485 function getFeedCategory($feed) {
2486 $result = db_query("SELECT cat_id FROM ttrss_feeds
2487 WHERE id = '$feed'");
2489 if (db_num_rows($result) > 0) {
2490 return db_fetch_result($result, 0, "cat_id");
2497 function implements_interface($class, $interface) {
2498 return in_array($interface, class_implements($class));
2501 function get_minified_js($files) {
2502 require_once 'lib/jshrink/Minifier.php';
2506 foreach ($files as $js) {
2507 if (!isset($_GET['debug'])) {
2508 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
2510 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2512 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2514 if ($header && $contents) {
2515 list($htag, $hversion) = explode(":", $header);
2517 if ($htag == "tt-rss" && $hversion == VERSION
) {
2524 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
2525 file_put_contents($cached_file, "tt-rss:" . VERSION
. "\n" . $minified);
2529 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2536 function calculate_dep_timestamp() {
2537 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2541 foreach ($files as $file) {
2542 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2548 function T_js_decl($s1, $s2) {
2550 $s1 = preg_replace("/\n/", "", $s1);
2551 $s2 = preg_replace("/\n/", "", $s2);
2553 $s1 = preg_replace("/\"/", "\\\"", $s1);
2554 $s2 = preg_replace("/\"/", "\\\"", $s2);
2556 return "T_messages[\"$s1\"] = \"$s2\";\n";
2560 function init_js_translations() {
2562 print 'var T_messages = new Object();
2565 if (T_messages[msg]) {
2566 return T_messages[msg];
2572 function ngettext(msg1, msg2, n) {
2573 return __((parseInt(n) > 1) ? msg2 : msg1);
2576 $l10n = _get_reader();
2578 for ($i = 0; $i < $l10n->total
; $i++
) {
2579 $orig = $l10n->get_original_string($i);
2580 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2581 $key = explode(chr(0), $orig);
2582 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2583 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2585 $translation = __($orig);
2586 print T_js_decl($orig, $translation);
2591 function label_to_feed_id($label) {
2592 return LABEL_BASE_INDEX
- 1 - abs($label);
2595 function feed_to_label_id($feed) {
2596 return LABEL_BASE_INDEX
- 1 +
abs($feed);
2599 function get_theme_path($theme) {
2600 $check = "themes/$theme";
2601 if (file_exists($check)) return $check;
2603 $check = "themes.local/$theme";
2604 if (file_exists($check)) return $check;
2607 function theme_valid($theme) {
2608 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2610 if (in_array($theme, $bundled_themes)) return true;
2612 $file = "themes/" . basename($theme);
2614 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2616 if (file_exists($file) && is_readable($file)) {
2617 $fh = fopen($file, "r");
2620 $header = fgets($fh);
2623 return strpos($header, "supports-version:" . VERSION_STATIC
) !== FALSE;
2631 * @SuppressWarnings(unused)
2633 function error_json($code) {
2634 require_once "errors.php";
2636 @$message = $ERRORS[$code];
2638 return json_encode(array("error" =>
2639 array("code" => $code, "message" => $message)));
2643 function abs_to_rel_path($dir) {
2644 $tmp = str_replace(dirname(__DIR__
), "", $dir);
2646 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2651 function get_upload_error_message($code) {
2654 0 => __('There is no error, the file uploaded with success'),
2655 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2656 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2657 3 => __('The uploaded file was only partially uploaded'),
2658 4 => __('No file was uploaded'),
2659 6 => __('Missing a temporary folder'),
2660 7 => __('Failed to write file to disk.'),
2661 8 => __('A PHP extension stopped the file upload.'),
2664 return $errors[$code];
2667 function base64_img($filename) {
2668 if (file_exists($filename)) {
2669 $ext = pathinfo($filename, PATHINFO_EXTENSION
);
2671 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));