2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 106);
5 $fetch_last_error = false;
8 function __autoload($class) {
9 $class_file = str_replace("_", "/", strtolower(basename($class)));
11 $file = dirname(__FILE__
)."/../classes/$class_file.php";
13 if (file_exists($file)) {
19 mb_internal_encoding("UTF-8");
20 date_default_timezone_set('UTC');
21 if (defined('E_DEPRECATED')) {
22 error_reporting(E_ALL
& ~E_NOTICE
& ~E_DEPRECATED
);
24 error_reporting(E_ALL
& ~E_NOTICE
);
27 require_once 'config.php';
29 if (DB_TYPE
== "pgsql") {
30 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
32 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
35 define('THEME_VERSION_REQUIRED', 1.1);
38 * Return available translations names.
41 * @return array A array of available translations.
43 function get_translations() {
45 "auto" => "Detect automatically",
51 "fr_FR" => "Français",
52 "hu_HU" => "Magyar (Hungarian)",
53 "it_IT" => "Italiano",
54 "ja_JP" => "日本語 (Japanese)",
55 "lv_LV" => "Latviešu",
56 "nb_NO" => "Norwegian bokmål",
59 "pt_BR" => "Portuguese/Brazil",
60 "zh_CN" => "Simplified Chinese");
65 require_once "lib/accept-to-gettext.php";
66 require_once "lib/gettext/gettext.inc";
69 function startup_gettext() {
71 # Get locale from Accept-Language header
72 $lang = al2gt(array_keys(get_translations()), "text/html");
74 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
75 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
78 if ($_SESSION["language"] && $_SESSION["language"] != "auto") {
79 $lang = $_SESSION["language"];
83 if (defined('LC_MESSAGES')) {
84 _setlocale(LC_MESSAGES
, $lang);
85 } else if (defined('LC_ALL')) {
86 _setlocale(LC_ALL
, $lang);
89 _bindtextdomain("messages", "locale");
91 _textdomain("messages");
92 _bind_textdomain_codeset("messages", "UTF-8");
98 require_once 'db-prefs.php';
99 require_once 'version.php';
100 require_once 'ccache.php';
101 require_once 'labels.php';
103 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
104 ini_set('user_agent', SELF_USER_AGENT
);
106 require_once 'lib/pubsubhubbub/publisher.php';
109 $utc_tz = new DateTimeZone('UTC');
110 $schema_version = false;
113 * Print a timestamped debug message.
115 * @param string $msg The debug message.
118 function _debug($msg) {
119 $ts = strftime("%H:%M:%S", time());
120 if (function_exists('posix_getpid')) {
121 $ts = "$ts/" . posix_getpid();
124 if (!(defined('QUIET') && QUIET
)) {
125 print "[$ts] $msg\n";
128 if (defined('LOGFILE')) {
129 $fp = fopen(LOGFILE
, 'a+');
132 fputs($fp, "[$ts] $msg\n");
140 * Purge a feed old posts.
142 * @param mixed $link A database connection.
143 * @param mixed $feed_id The id of the purged feed.
144 * @param mixed $purge_interval Olderness of purged posts.
145 * @param boolean $debug Set to True to enable the debug. False by default.
149 function purge_feed($link, $feed_id, $purge_interval, $debug = false) {
151 if (!$purge_interval) $purge_interval = feed_purge_interval($link, $feed_id);
155 $result = db_query($link,
156 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
160 if (db_num_rows($result) == 1) {
161 $owner_uid = db_fetch_result($result, 0, "owner_uid");
164 if ($purge_interval == -1 ||
!$purge_interval) {
166 ccache_update($link, $feed_id, $owner_uid);
171 if (!$owner_uid) return;
173 if (FORCE_ARTICLE_PURGE
== 0) {
174 $purge_unread = get_pref($link, "PURGE_UNREAD_ARTICLES",
177 $purge_unread = true;
178 $purge_interval = FORCE_ARTICLE_PURGE
;
181 if (!$purge_unread) $query_limit = " unread = false AND ";
183 if (DB_TYPE
== "pgsql") {
184 $pg_version = get_pgsql_version($link);
186 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
188 $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
189 ttrss_entries.id = ref_id AND
191 feed_id = '$feed_id' AND
193 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
197 $result = db_query($link, "DELETE FROM ttrss_user_entries
199 WHERE ttrss_entries.id = ref_id AND
201 feed_id = '$feed_id' AND
203 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
206 $rows = pg_affected_rows($result);
210 /* $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
211 marked = false AND feed_id = '$feed_id' AND
212 (SELECT date_updated FROM ttrss_entries WHERE
213 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
215 $result = db_query($link, "DELETE FROM ttrss_user_entries
216 USING ttrss_user_entries, ttrss_entries
217 WHERE ttrss_entries.id = ref_id AND
219 feed_id = '$feed_id' AND
221 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
223 $rows = mysql_affected_rows($link);
227 ccache_update($link, $feed_id, $owner_uid);
230 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
234 } // function purge_feed
236 function feed_purge_interval($link, $feed_id) {
238 $result = db_query($link, "SELECT purge_interval, owner_uid FROM ttrss_feeds
239 WHERE id = '$feed_id'");
241 if (db_num_rows($result) == 1) {
242 $purge_interval = db_fetch_result($result, 0, "purge_interval");
243 $owner_uid = db_fetch_result($result, 0, "owner_uid");
245 if ($purge_interval == 0) $purge_interval = get_pref($link,
246 'PURGE_OLD_DAYS', $owner_uid);
248 return $purge_interval;
255 function purge_orphans($link, $do_output = false) {
257 // purge orphaned posts in main content table
258 $result = db_query($link, "DELETE FROM ttrss_entries WHERE
259 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
262 $rows = db_affected_rows($link, $result);
263 _debug("Purged $rows orphaned posts.");
267 function get_feed_update_interval($link, $feed_id) {
268 $result = db_query($link, "SELECT owner_uid, update_interval FROM
269 ttrss_feeds WHERE id = '$feed_id'");
271 if (db_num_rows($result) == 1) {
272 $update_interval = db_fetch_result($result, 0, "update_interval");
273 $owner_uid = db_fetch_result($result, 0, "owner_uid");
275 if ($update_interval != 0) {
276 return $update_interval;
278 return get_pref($link, 'DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
286 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false) {
287 $login = urlencode($login);
288 $pass = urlencode($pass);
290 global $fetch_last_error;
292 if (function_exists('curl_init') && !ini_get("open_basedir")) {
294 if (ini_get("safe_mode")) {
295 $ch = curl_init(geturl($url));
297 $ch = curl_init($url);
300 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : 15);
301 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : 45);
302 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("safe_mode"));
303 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
304 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
305 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
306 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
307 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
308 curl_setopt($ch, CURLOPT_USERAGENT
, SELF_USER_AGENT
);
309 curl_setopt($ch, CURLOPT_ENCODING
, "gzip");
310 curl_setopt($ch, CURLOPT_REFERER
, $url);
313 curl_setopt($ch, CURLOPT_POST
, true);
314 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
318 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
320 $contents = @curl_exec
($ch);
322 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
323 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
324 $contents = @curl_exec
($ch);
327 if ($contents === false) {
328 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
333 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
334 $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
336 if ($http_code != 200 ||
$type && strpos($content_type, "$type") === false) {
337 if (curl_errno($ch) != 0) {
338 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
340 $fetch_last_error = "HTTP Code: $http_code";
350 if ($login && $pass ){
351 $url_parts = array();
353 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
355 if ($url_parts[1] && $url_parts[2]) {
356 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
360 $data = @file_get_contents
($url);
362 $gzdecoded = gzdecode($data);
363 if ($gzdecoded) $data = $gzdecoded;
365 if (!$data && function_exists('error_get_last')) {
366 $error = error_get_last();
367 $fetch_last_error = $error["message"];
375 * Try to determine the favicon URL for a feed.
376 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
377 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
379 * @param string $url A feed or page URL
381 * @return mixed The favicon URL, or false if none was found.
383 function get_favicon_url($url) {
385 $favicon_url = false;
387 if ($html = @fetch_file_contents
($url)) {
389 libxml_use_internal_errors(true);
391 $doc = new DOMDocument();
392 $doc->loadHTML($html);
393 $xpath = new DOMXPath($doc);
395 $base = $xpath->query('/html/head/base');
396 foreach ($base as $b) {
397 $url = $b->getAttribute("href");
401 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
402 if (count($entries) > 0) {
403 foreach ($entries as $entry) {
404 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
411 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
414 } // function get_favicon_url
416 function check_feed_favicon($site_url, $feed, $link) {
417 # print "FAVICON [$site_url]: $favicon_url\n";
419 $icon_file = ICONS_DIR
. "/$feed.ico";
421 if (!file_exists($icon_file)) {
422 $favicon_url = get_favicon_url($site_url);
425 // Limiting to "image" type misses those served with text/plain
426 $contents = fetch_file_contents($favicon_url); // , "image");
429 // Crude image type matching.
430 // Patterns gleaned from the file(1) source code.
431 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
432 // 0 string \000\000\001\000 MS Windows icon resource
433 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
435 elseif (preg_match('/^GIF8/', $contents)) {
436 // 0 string GIF8 GIF image data
437 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
439 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
440 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
441 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
443 elseif (preg_match('/^\xff\xd8/', $contents)) {
444 // 0 beshort 0xffd8 JPEG image data
445 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
448 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
454 $fp = @fopen
($icon_file, "w");
457 fwrite($fp, $contents);
459 chmod($icon_file, 0644);
466 function print_select($id, $default, $values, $attributes = "") {
467 print "<select name=\"$id\" id=\"$id\" $attributes>";
468 foreach ($values as $v) {
470 $sel = "selected=\"1\"";
476 print "<option value=\"$v\" $sel>$v</option>";
481 function print_select_hash($id, $default, $values, $attributes = "") {
482 print "<select name=\"$id\" id='$id' $attributes>";
483 foreach (array_keys($values) as $v) {
485 $sel = 'selected="selected"';
491 print "<option $sel value=\"$v\">".$values[$v]."</option>";
497 function print_radio($id, $default, $true_is, $values, $attributes = "") {
498 foreach ($values as $v) {
505 if ($v == $true_is) {
506 $sel .= " value=\"1\"";
508 $sel .= " value=\"0\"";
511 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
512 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
517 function initialize_user_prefs($link, $uid, $profile = false) {
519 $uid = db_escape_string($uid);
523 $profile_qpart = "AND profile IS NULL";
525 $profile_qpart = "AND profile = '$profile'";
528 if (get_schema_version($link) < 63) $profile_qpart = "";
530 db_query($link, "BEGIN");
532 $result = db_query($link, "SELECT pref_name,def_value FROM ttrss_prefs");
534 $u_result = db_query($link, "SELECT pref_name
535 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
537 $active_prefs = array();
539 while ($line = db_fetch_assoc($u_result)) {
540 array_push($active_prefs, $line["pref_name"]);
543 while ($line = db_fetch_assoc($result)) {
544 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
545 // print "adding " . $line["pref_name"] . "<br>";
547 if (get_schema_version($link) < 63) {
548 db_query($link, "INSERT INTO ttrss_user_prefs
549 (owner_uid,pref_name,value) VALUES
550 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
553 db_query($link, "INSERT INTO ttrss_user_prefs
554 (owner_uid,pref_name,value, profile) VALUES
555 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
561 db_query($link, "COMMIT");
565 function get_ssl_certificate_id() {
566 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
567 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
568 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
569 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
570 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
575 function authenticate_user($link, $login, $password, $check_only = false) {
577 if (!SINGLE_USER_MODE
) {
582 foreach ($pluginhost->get_hooks($pluginhost::HOOK_AUTH_USER
) as $plugin) {
584 $user_id = (int) $plugin->authenticate($login, $password);
587 $_SESSION["auth_module"] = strtolower(get_class($plugin));
592 if ($user_id && !$check_only) {
593 $_SESSION["uid"] = $user_id;
595 $result = db_query($link, "SELECT login,access_level,pwd_hash FROM ttrss_users
596 WHERE id = '$user_id'");
598 $_SESSION["name"] = db_fetch_result($result, 0, "login");
599 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
600 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
602 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
605 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
606 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
608 $_SESSION["last_version_check"] = time();
610 initialize_user_prefs($link, $_SESSION["uid"]);
619 $_SESSION["uid"] = 1;
620 $_SESSION["name"] = "admin";
621 $_SESSION["access_level"] = 10;
623 $_SESSION["hide_hello"] = true;
624 $_SESSION["hide_logout"] = true;
626 $_SESSION["auth_module"] = false;
628 if (!$_SESSION["csrf_token"]) {
629 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
632 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
634 initialize_user_prefs($link, $_SESSION["uid"]);
640 function make_password($length = 8) {
643 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
647 while ($i < $length) {
648 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
650 if (!strstr($password, $char)) {
658 // this is called after user is created to initialize default feeds, labels
661 // user preferences are checked on every login, not here
663 function initialize_user($link, $uid) {
665 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
666 values ('$uid', 'Tiny Tiny RSS: New Releases',
667 'http://tt-rss.org/releases.rss')");
669 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
670 values ('$uid', 'Tiny Tiny RSS: Forum',
671 'http://tt-rss.org/forum/rss.php')");
674 function logout_user() {
676 if (isset($_COOKIE[session_name()])) {
677 setcookie(session_name(), '', time()-42000, '/');
681 function validate_csrf($csrf_token) {
682 return $csrf_token == $_SESSION['csrf_token'];
685 function validate_session($link) {
686 if (SINGLE_USER_MODE
) return true;
688 $check_ip = $_SESSION['ip_address'];
690 switch (SESSION_CHECK_ADDRESS
) {
695 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
698 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.'));
699 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
703 if ($check_ip && strpos($_SERVER['REMOTE_ADDR'], $check_ip) !== 0) {
704 $_SESSION["login_error_msg"] =
705 __("Session failed to validate (incorrect IP)");
709 if ($_SESSION["ref_schema_version"] != get_schema_version($link, true))
712 if ($_SESSION["uid"]) {
714 $result = db_query($link,
715 "SELECT pwd_hash FROM ttrss_users WHERE id = '".$_SESSION["uid"]."'");
717 $pwd_hash = db_fetch_result($result, 0, "pwd_hash");
719 if ($pwd_hash != $_SESSION["pwd_hash"]) {
724 /* if ($_SESSION["cookie_lifetime"] && $_SESSION["uid"]) {
726 //print_r($_SESSION);
728 if (time() > $_SESSION["cookie_lifetime"]) {
736 function load_user_plugins($link, $owner_uid) {
738 $plugins = get_pref($link, "_ENABLED_PLUGINS", $owner_uid);
741 $pluginhost->load($plugins, $pluginhost::KIND_USER
, $owner_uid);
743 if (get_schema_version($link) > 100) {
744 $pluginhost->load_data();
749 function login_sequence($link) {
750 $_SESSION["prefs_cache"] = false;
752 if (SINGLE_USER_MODE
) {
753 authenticate_user($link, "admin", null);
755 load_user_plugins($link, $_SESSION["uid"]);
757 if (!$_SESSION["uid"] ||
!validate_session($link)) {
759 if (AUTH_AUTO_LOGIN
&& authenticate_user($link, null, null)) {
760 $_SESSION["ref_schema_version"] = get_schema_version($link, true);
762 authenticate_user($link, null, null, true);
765 if (!$_SESSION["uid"]) render_login_form($link);
768 /* bump login timestamp */
769 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
773 if ($_SESSION["uid"] && $_SESSION["language"] && SESSION_COOKIE_LIFETIME
> 0) {
774 setcookie("ttrss_lang", $_SESSION["language"],
775 time() + SESSION_COOKIE_LIFETIME
);
778 if ($_SESSION["uid"]) {
780 load_user_plugins($link, $_SESSION["uid"]);
785 function truncate_string($str, $max_len, $suffix = '…') {
786 if (mb_strlen($str, "utf-8") > $max_len - 3) {
787 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
793 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
796 $source_tz = new DateTimeZone($source_tz);
797 } catch (Exception
$e) {
798 $source_tz = new DateTimeZone('UTC');
802 $dest_tz = new DateTimeZone($dest_tz);
803 } catch (Exception
$e) {
804 $dest_tz = new DateTimeZone('UTC');
807 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
808 return $dt->format('U') +
$dest_tz->getOffset($dt);
811 function make_local_datetime($link, $timestamp, $long, $owner_uid = false,
812 $no_smart_dt = false) {
814 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
815 if (!$timestamp) $timestamp = '1970-01-01 0:00';
820 # We store date in UTC internally
821 $dt = new DateTime($timestamp, $utc_tz);
823 if ($tz_offset == -1) {
825 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $owner_uid);
828 $user_tz = new DateTimeZone($user_tz_string);
829 } catch (Exception
$e) {
833 $tz_offset = $user_tz->getOffset($dt);
836 $user_timestamp = $dt->format('U') +
$tz_offset;
839 return smart_date_time($link, $user_timestamp,
840 $tz_offset, $owner_uid);
843 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
845 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
847 return date($format, $user_timestamp);
851 function smart_date_time($link, $timestamp, $tz_offset = 0, $owner_uid = false) {
852 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
854 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
855 return date("G:i", $timestamp);
856 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
857 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
858 return date($format, $timestamp);
860 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
861 return date($format, $timestamp);
865 function sql_bool_to_bool($s) {
866 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
873 function bool_to_sql_bool($s) {
881 // Session caching removed due to causing wrong redirects to upgrade
882 // script when get_schema_version() is called on an obsolete session
883 // created on a previous schema version.
884 function get_schema_version($link, $nocache = false) {
885 global $schema_version;
887 if (!$schema_version) {
888 $result = db_query($link, "SELECT schema_version FROM ttrss_version");
889 $version = db_fetch_result($result, 0, "schema_version");
890 $schema_version = $version;
893 return $schema_version;
897 function sanity_check($link) {
898 require_once 'errors.php';
901 $schema_version = get_schema_version($link, true);
903 if ($schema_version != SCHEMA_VERSION
) {
907 if (DB_TYPE
== "mysql") {
908 $result = db_query($link, "SELECT true", false);
909 if (db_num_rows($result) != 1) {
914 if (db_escape_string("testTEST") != "testTEST") {
918 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
921 function file_is_locked($filename) {
922 if (function_exists('flock')) {
923 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
925 if (flock($fp, LOCK_EX | LOCK_NB
)) {
936 return true; // consider the file always locked and skip the test
939 function make_lockfile($filename) {
940 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
942 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
943 if (function_exists('posix_getpid')) {
944 fwrite($fp, posix_getpid() . "\n");
952 function make_stampfile($filename) {
953 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
955 if (flock($fp, LOCK_EX | LOCK_NB
)) {
956 fwrite($fp, time() . "\n");
965 function sql_random_function() {
966 if (DB_TYPE
== "mysql") {
973 function catchup_feed($link, $feed, $cat_view, $owner_uid = false, $max_id = false) {
975 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
977 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
979 $ref_check_qpart = ($max_id &&
980 !get_pref($link, 'REVERSE_HEADLINES')) ?
"ref_id <= '$max_id'" : "true";
982 if (is_numeric($feed)) {
988 $children = getChildCategories($link, $feed, $owner_uid);
989 array_push($children, $feed);
991 $children = join(",", $children);
993 $cat_qpart = "cat_id IN ($children)";
995 $cat_qpart = "cat_id IS NULL";
998 db_query($link, "UPDATE ttrss_user_entries
999 SET unread = false,last_read = NOW()
1000 WHERE feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart)
1001 AND $ref_check_qpart AND unread = true
1002 AND owner_uid = $owner_uid");
1004 } else if ($feed == -2) {
1006 db_query($link, "UPDATE ttrss_user_entries
1007 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1008 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1009 AND $ref_check_qpart
1010 AND unread = true AND owner_uid = $owner_uid");
1013 } else if ($feed > 0) {
1015 db_query($link, "UPDATE ttrss_user_entries
1016 SET unread = false,last_read = NOW()
1017 WHERE feed_id = '$feed'
1018 AND $ref_check_qpart AND unread = true
1019 AND owner_uid = $owner_uid");
1021 } else if ($feed < 0 && $feed > -10) { // special, like starred
1024 db_query($link, "UPDATE ttrss_user_entries
1025 SET unread = false,last_read = NOW()
1027 AND $ref_check_qpart AND unread = true
1028 AND owner_uid = $owner_uid");
1032 db_query($link, "UPDATE ttrss_user_entries
1033 SET unread = false,last_read = NOW()
1034 WHERE published = true
1035 AND $ref_check_qpart AND unread = true
1036 AND owner_uid = $owner_uid");
1041 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE");
1043 if (DB_TYPE
== "pgsql") {
1044 $match_part = "updated > NOW() - INTERVAL '$intl hour' ";
1046 $match_part = "updated > DATE_SUB(NOW(),
1047 INTERVAL $intl HOUR) ";
1050 $result = db_query($link, "SELECT id FROM ttrss_entries,
1051 ttrss_user_entries WHERE $match_part AND
1053 ttrss_user_entries.ref_id = ttrss_entries.id AND
1054 owner_uid = $owner_uid");
1056 $affected_ids = array();
1058 while ($line = db_fetch_assoc($result)) {
1059 array_push($affected_ids, $line["id"]);
1062 catchupArticlesById($link, $affected_ids, 0);
1066 db_query($link, "UPDATE ttrss_user_entries
1067 SET unread = false,last_read = NOW()
1068 WHERE $ref_check_qpart AND unread = true AND
1069 owner_uid = $owner_uid");
1072 } else if ($feed < -10) { // label
1074 $label_id = -$feed - 11;
1076 db_query($link, "UPDATE ttrss_user_entries, ttrss_user_labels2
1077 SET unread = false, last_read = NOW()
1078 WHERE label_id = '$label_id' AND unread = true
1079 AND $ref_check_qpart
1080 AND owner_uid = '$owner_uid' AND ref_id = article_id");
1084 ccache_update($link, $feed, $owner_uid, $cat_view);
1087 db_query($link, "BEGIN");
1089 $tag_name = db_escape_string($feed);
1091 $result = db_query($link, "SELECT post_int_id FROM ttrss_tags
1092 WHERE tag_name = '$tag_name' AND owner_uid = $owner_uid");
1094 while ($line = db_fetch_assoc($result)) {
1095 db_query($link, "UPDATE ttrss_user_entries SET
1096 unread = false, last_read = NOW()
1097 WHERE $ref_check_qpart AND unread = true
1098 AND int_id = " . $line["post_int_id"]);
1100 db_query($link, "COMMIT");
1104 function getAllCounters($link) {
1105 $data = getGlobalCounters($link);
1107 $data = array_merge($data, getVirtCounters($link));
1108 $data = array_merge($data, getLabelCounters($link));
1109 $data = array_merge($data, getFeedCounters($link, $active_feed));
1110 $data = array_merge($data, getCategoryCounters($link));
1115 function getCategoryTitle($link, $cat_id) {
1117 if ($cat_id == -1) {
1118 return __("Special");
1119 } else if ($cat_id == -2) {
1120 return __("Labels");
1123 $result = db_query($link, "SELECT title FROM ttrss_feed_categories WHERE
1126 if (db_num_rows($result) == 1) {
1127 return db_fetch_result($result, 0, "title");
1129 return __("Uncategorized");
1135 function getCategoryCounters($link) {
1138 /* Labels category */
1140 $cv = array("id" => -2, "kind" => "cat",
1141 "counter" => getCategoryUnread($link, -2));
1143 array_push($ret_arr, $cv);
1145 $result = db_query($link, "SELECT id AS cat_id, value AS unread,
1146 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1147 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1148 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1149 WHERE ttrss_cat_counters_cache.feed_id = id AND
1150 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1151 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1153 while ($line = db_fetch_assoc($result)) {
1154 $line["cat_id"] = (int) $line["cat_id"];
1156 if ($line["num_children"] > 0) {
1157 $child_counter = getCategoryChildrenUnread($link, $line["cat_id"], $_SESSION["uid"]);
1162 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1163 "counter" => $line["unread"] +
$child_counter);
1165 array_push($ret_arr, $cv);
1168 /* Special case: NULL category doesn't actually exist in the DB */
1170 $cv = array("id" => 0, "kind" => "cat",
1171 "counter" => (int) ccache_find($link, 0, $_SESSION["uid"], true));
1173 array_push($ret_arr, $cv);
1178 // only accepts real cats (>= 0)
1179 function getCategoryChildrenUnread($link, $cat, $owner_uid = false) {
1180 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1182 $result = db_query($link, "SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1183 AND owner_uid = $owner_uid");
1187 while ($line = db_fetch_assoc($result)) {
1188 $unread +
= getCategoryUnread($link, $line["id"], $owner_uid);
1189 $unread +
= getCategoryChildrenUnread($link, $line["id"], $owner_uid);
1195 function getCategoryUnread($link, $cat, $owner_uid = false) {
1197 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1202 $cat_query = "cat_id = '$cat'";
1204 $cat_query = "cat_id IS NULL";
1207 $result = db_query($link, "SELECT id FROM ttrss_feeds WHERE $cat_query
1208 AND owner_uid = " . $owner_uid);
1210 $cat_feeds = array();
1211 while ($line = db_fetch_assoc($result)) {
1212 array_push($cat_feeds, "feed_id = " . $line["id"]);
1215 if (count($cat_feeds) == 0) return 0;
1217 $match_part = implode(" OR ", $cat_feeds);
1219 $result = db_query($link, "SELECT COUNT(int_id) AS unread
1220 FROM ttrss_user_entries
1221 WHERE unread = true AND ($match_part)
1222 AND owner_uid = " . $owner_uid);
1226 # this needs to be rewritten
1227 while ($line = db_fetch_assoc($result)) {
1228 $unread +
= $line["unread"];
1232 } else if ($cat == -1) {
1233 return getFeedUnread($link, -1) +
getFeedUnread($link, -2) +
getFeedUnread($link, -3) +
getFeedUnread($link, 0);
1234 } else if ($cat == -2) {
1236 $result = db_query($link, "
1237 SELECT COUNT(unread) AS unread FROM
1238 ttrss_user_entries, ttrss_user_labels2
1239 WHERE article_id = ref_id AND unread = true
1240 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1242 $unread = db_fetch_result($result, 0, "unread");
1249 function getFeedUnread($link, $feed, $is_cat = false) {
1250 return getFeedArticles($link, $feed, $is_cat, true, $_SESSION["uid"]);
1253 function getLabelUnread($link, $label_id, $owner_uid = false) {
1254 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1256 $result = db_query($link, "SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1257 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1259 if (db_num_rows($result) != 0) {
1260 return db_fetch_result($result, 0, "unread");
1266 function getFeedArticles($link, $feed, $is_cat = false, $unread_only = false,
1267 $owner_uid = false) {
1269 $n_feed = (int) $feed;
1270 $need_entries = false;
1272 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1275 $unread_qpart = "unread = true";
1277 $unread_qpart = "true";
1281 return getCategoryUnread($link, $n_feed, $owner_uid);
1282 } else if ($n_feed == -6) {
1284 } else if ($feed != "0" && $n_feed == 0) {
1286 $feed = db_escape_string($feed);
1288 $result = db_query($link, "SELECT SUM((SELECT COUNT(int_id)
1289 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1290 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1291 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1292 return db_fetch_result($result, 0, "count");
1294 } else if ($n_feed == -1) {
1295 $match_part = "marked = true";
1296 } else if ($n_feed == -2) {
1297 $match_part = "published = true";
1298 } else if ($n_feed == -3) {
1299 $match_part = "unread = true AND score >= 0";
1301 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
1303 if (DB_TYPE
== "pgsql") {
1304 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1306 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1309 $need_entries = true;
1311 } else if ($n_feed == -4) {
1312 $match_part = "true";
1313 } else if ($n_feed >= 0) {
1316 $match_part = "feed_id = '$n_feed'";
1318 $match_part = "feed_id IS NULL";
1321 } else if ($feed < -10) {
1323 $label_id = -$feed - 11;
1325 return getLabelUnread($link, $label_id, $owner_uid);
1331 if ($need_entries) {
1332 $from_qpart = "ttrss_user_entries,ttrss_entries";
1333 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1335 $from_qpart = "ttrss_user_entries";
1338 $query = "SELECT count(int_id) AS unread
1339 FROM $from_qpart WHERE
1340 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1342 //echo "[$feed/$query]\n";
1344 $result = db_query($link, $query);
1348 $result = db_query($link, "SELECT COUNT(post_int_id) AS unread
1349 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1350 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1351 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1354 $unread = db_fetch_result($result, 0, "unread");
1359 function getGlobalUnread($link, $user_id = false) {
1362 $user_id = $_SESSION["uid"];
1365 $result = db_query($link, "SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1366 WHERE owner_uid = '$user_id' AND feed_id > 0");
1368 $c_id = db_fetch_result($result, 0, "c_id");
1373 function getGlobalCounters($link, $global_unread = -1) {
1376 if ($global_unread == -1) {
1377 $global_unread = getGlobalUnread($link);
1380 $cv = array("id" => "global-unread",
1381 "counter" => (int) $global_unread);
1383 array_push($ret_arr, $cv);
1385 $result = db_query($link, "SELECT COUNT(id) AS fn FROM
1386 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1388 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1390 $cv = array("id" => "subscribed-feeds",
1391 "counter" => (int) $subscribed_feeds);
1393 array_push($ret_arr, $cv);
1398 function getVirtCounters($link) {
1402 for ($i = 0; $i >= -4; $i--) {
1404 $count = getFeedUnread($link, $i);
1406 $cv = array("id" => $i,
1407 "counter" => (int) $count);
1409 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1410 // $cv["xmsg"] = getFeedArticles($link, $i)." ".__("total");
1412 array_push($ret_arr, $cv);
1418 function getLabelCounters($link, $descriptions = false) {
1422 $owner_uid = $_SESSION["uid"];
1424 $result = db_query($link, "SELECT id,caption,COUNT(unread) AS unread
1425 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1426 (ttrss_labels2.id = label_id)
1427 LEFT JOIN ttrss_user_entries ON (ref_id = article_id AND unread = true)
1428 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1429 ttrss_labels2.caption");
1431 while ($line = db_fetch_assoc($result)) {
1433 $id = -$line["id"] - 11;
1435 $label_name = $line["caption"];
1436 $count = $line["unread"];
1438 $cv = array("id" => $id,
1439 "counter" => (int) $count);
1442 $cv["description"] = $label_name;
1444 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1445 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1447 array_push($ret_arr, $cv);
1453 function getFeedCounters($link, $active_feed = false) {
1457 $query = "SELECT ttrss_feeds.id,
1459 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1460 last_error, value AS count
1461 FROM ttrss_feeds, ttrss_counters_cache
1462 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1463 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1464 AND ttrss_counters_cache.feed_id = id";
1466 $result = db_query($link, $query);
1467 $fctrs_modified = false;
1469 while ($line = db_fetch_assoc($result)) {
1472 $count = $line["count"];
1473 $last_error = htmlspecialchars($line["last_error"]);
1475 $last_updated = make_local_datetime($link, $line['last_updated'], false);
1477 $has_img = feed_has_icon($id);
1479 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1482 $cv = array("id" => $id,
1483 "updated" => $last_updated,
1484 "counter" => (int) $count,
1485 "has_img" => (int) $has_img);
1488 $cv["error"] = $last_error;
1490 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1491 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1493 if ($active_feed && $id == $active_feed)
1494 $cv["title"] = truncate_string($line["title"], 30);
1496 array_push($ret_arr, $cv);
1503 function get_pgsql_version($link) {
1504 $result = db_query($link, "SELECT version() AS version");
1505 $version = explode(" ", db_fetch_result($result, 0, "version"));
1510 * @return array (code => Status code, message => error message if available)
1512 * 0 - OK, Feed already exists
1513 * 1 - OK, Feed added
1515 * 3 - URL content is HTML, no feeds available
1516 * 4 - URL content is HTML which contains multiple feeds.
1517 * Here you should call extractfeedurls in rpc-backend
1518 * to get all possible feeds.
1519 * 5 - Couldn't download the URL content.
1521 function subscribe_to_feed($link, $url, $cat_id = 0,
1522 $auth_login = '', $auth_pass = '', $need_auth = false) {
1524 global $fetch_last_error;
1526 require_once "include/rssfuncs.php";
1528 $url = fix_url($url);
1530 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1532 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1535 return array("code" => 5, "message" => $fetch_last_error);
1538 if (is_html($contents)) {
1539 $feedUrls = get_feeds_from_html($url, $contents);
1541 if (count($feedUrls) == 0) {
1542 return array("code" => 3);
1543 } else if (count($feedUrls) > 1) {
1544 return array("code" => 4, "feeds" => $feedUrls);
1546 //use feed url as new URL
1547 $url = key($feedUrls);
1550 if ($cat_id == "0" ||
!$cat_id) {
1551 $cat_qpart = "NULL";
1553 $cat_qpart = "'$cat_id'";
1556 $result = db_query($link,
1557 "SELECT id FROM ttrss_feeds
1558 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1560 if (db_num_rows($result) == 0) {
1561 $result = db_query($link,
1562 "INSERT INTO ttrss_feeds
1563 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method)
1564 VALUES ('".$_SESSION["uid"]."', '$url',
1565 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0)");
1567 $result = db_query($link,
1568 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1569 AND owner_uid = " . $_SESSION["uid"]);
1571 $feed_id = db_fetch_result($result, 0, "id");
1574 update_rss_feed($link, $feed_id, true);
1577 return array("code" => 1);
1579 return array("code" => 0);
1583 function print_feed_select($link, $id, $default_id = "",
1584 $attributes = "", $include_all_feeds = true,
1585 $root_id = false, $nest_level = 0) {
1588 print "<select id=\"$id\" name=\"$id\" $attributes>";
1589 if ($include_all_feeds) {
1590 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1591 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1595 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1598 $parent_qpart = "parent_cat = '$root_id'";
1600 $parent_qpart = "parent_cat IS NULL";
1602 $result = db_query($link, "SELECT id,title,
1603 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1604 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1605 FROM ttrss_feed_categories
1606 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1608 while ($line = db_fetch_assoc($result)) {
1610 for ($i = 0; $i < $nest_level; $i++
)
1611 $line["title"] = " - " . $line["title"];
1613 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1615 printf("<option $is_selected value='CAT:%d'>%s</option>",
1616 $line["id"], htmlspecialchars($line["title"]));
1618 if ($line["num_children"] > 0)
1619 print_feed_select($link, $id, $default_id, $attributes,
1620 $include_all_feeds, $line["id"], $nest_level+
1);
1622 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1623 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1625 while ($fline = db_fetch_assoc($feed_result)) {
1626 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1628 $fline["title"] = " + " . $fline["title"];
1630 for ($i = 0; $i < $nest_level; $i++
)
1631 $fline["title"] = " - " . $fline["title"];
1633 printf("<option $is_selected value='%d'>%s</option>",
1634 $fline["id"], htmlspecialchars($fline["title"]));
1639 $is_selected = ($default_id == "CAT:0") ?
"selected=\"1\"" : "";
1641 printf("<option $is_selected value='CAT:0'>%s</option>",
1642 __("Uncategorized"));
1644 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1645 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1647 while ($fline = db_fetch_assoc($feed_result)) {
1648 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1650 $fline["title"] = " + " . $fline["title"];
1652 for ($i = 0; $i < $nest_level; $i++
)
1653 $fline["title"] = " - " . $fline["title"];
1655 printf("<option $is_selected value='%d'>%s</option>",
1656 $fline["id"], htmlspecialchars($fline["title"]));
1661 $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1662 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1664 while ($line = db_fetch_assoc($result)) {
1666 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1668 printf("<option $is_selected value='%d'>%s</option>",
1669 $line["id"], htmlspecialchars($line["title"]));
1678 function print_feed_cat_select($link, $id, $default_id,
1679 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1682 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1686 $parent_qpart = "parent_cat = '$root_id'";
1688 $parent_qpart = "parent_cat IS NULL";
1690 $result = db_query($link, "SELECT id,title,
1691 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1692 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1693 FROM ttrss_feed_categories
1694 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1696 while ($line = db_fetch_assoc($result)) {
1697 if ($line["id"] == $default_id) {
1698 $is_selected = "selected=\"1\"";
1703 for ($i = 0; $i < $nest_level; $i++
)
1704 $line["title"] = " - " . $line["title"];
1707 printf("<option $is_selected value='%d'>%s</option>",
1708 $line["id"], htmlspecialchars($line["title"]));
1710 if ($line["num_children"] > 0)
1711 print_feed_cat_select($link, $id, $default_id, $attributes,
1712 $include_all_cats, $line["id"], $nest_level+
1);
1716 if ($include_all_cats) {
1717 if (db_num_rows($result) > 0) {
1718 print "<option disabled=\"1\">--------</option>";
1721 if ($default_id == 0) {
1722 $is_selected = "selected=\"1\"";
1727 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1733 function checkbox_to_sql_bool($val) {
1734 return ($val == "on") ?
"true" : "false";
1737 function getFeedCatTitle($link, $id) {
1739 return __("Special");
1740 } else if ($id < -10) {
1741 return __("Labels");
1742 } else if ($id > 0) {
1743 $result = db_query($link, "SELECT ttrss_feed_categories.title
1744 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1745 cat_id = ttrss_feed_categories.id");
1746 if (db_num_rows($result) == 1) {
1747 return db_fetch_result($result, 0, "title");
1749 return __("Uncategorized");
1752 return "getFeedCatTitle($id) failed";
1757 function getFeedIcon($id) {
1760 return "images/archive.png";
1763 return "images/mark_set.svg";
1766 return "images/pub_set.svg";
1769 return "images/fresh.png";
1772 return "images/tag.png";
1775 return "images/recently_read.png";
1779 return "images/label.png";
1781 if (file_exists(ICONS_DIR
. "/$id.ico"))
1782 return ICONS_URL
. "/$id.ico";
1788 function getFeedTitle($link, $id, $cat = false) {
1790 return getCategoryTitle($link, $id);
1791 } else if ($id == -1) {
1792 return __("Starred articles");
1793 } else if ($id == -2) {
1794 return __("Published articles");
1795 } else if ($id == -3) {
1796 return __("Fresh articles");
1797 } else if ($id == -4) {
1798 return __("All articles");
1799 } else if ($id === 0 ||
$id === "0") {
1800 return __("Archived articles");
1801 } else if ($id == -6) {
1802 return __("Recently read");
1803 } else if ($id < -10) {
1804 $label_id = -$id - 11;
1805 $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1806 if (db_num_rows($result) == 1) {
1807 return db_fetch_result($result, 0, "caption");
1809 return "Unknown label ($label_id)";
1812 } else if (is_numeric($id) && $id > 0) {
1813 $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1814 if (db_num_rows($result) == 1) {
1815 return db_fetch_result($result, 0, "title");
1817 return "Unknown feed ($id)";
1824 function make_init_params($link) {
1827 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1828 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1829 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", "DEFAULT_ARTICLE_LIMIT",
1830 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1832 $params[strtolower($param)] = (int) get_pref($link, $param);
1835 $params["icons_url"] = ICONS_URL
;
1836 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1837 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1838 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1839 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1840 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1842 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1843 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1845 $max_feed_id = db_fetch_result($result, 0, "mid");
1846 $num_feeds = db_fetch_result($result, 0, "nf");
1848 $params["max_feed_id"] = (int) $max_feed_id;
1849 $params["num_feeds"] = (int) $num_feeds;
1851 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1852 $params["hotkeys"] = get_hotkeys_map($link);
1854 $params["csrf_token"] = $_SESSION["csrf_token"];
1855 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1857 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1862 function get_hotkeys_info($link) {
1864 __("Navigation") => array(
1865 "next_feed" => __("Open next feed"),
1866 "prev_feed" => __("Open previous feed"),
1867 "next_article" => __("Open next article"),
1868 "prev_article" => __("Open previous article"),
1869 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1870 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1871 "search_dialog" => __("Show search dialog")),
1872 __("Article") => array(
1873 "toggle_mark" => __("Toggle starred"),
1874 "toggle_publ" => __("Toggle published"),
1875 "toggle_unread" => __("Toggle unread"),
1876 "edit_tags" => __("Edit tags"),
1877 "dismiss_selected" => __("Dismiss selected"),
1878 "dismiss_read" => __("Dismiss read"),
1879 "open_in_new_window" => __("Open in new window"),
1880 "catchup_below" => __("Mark below as read"),
1881 "catchup_above" => __("Mark above as read"),
1882 "article_scroll_down" => __("Scroll down"),
1883 "article_scroll_up" => __("Scroll up"),
1884 "select_article_cursor" => __("Select article under cursor"),
1885 "email_article" => __("Email article"),
1886 "close_article" => __("Close/collapse article"),
1887 "toggle_widescreen" => __("Toggle widescreen mode"),
1888 "toggle_embed_original" => __("Toggle embed original")),
1889 __("Article selection") => array(
1890 "select_all" => __("Select all articles"),
1891 "select_unread" => __("Select unread"),
1892 "select_marked" => __("Select starred"),
1893 "select_published" => __("Select published"),
1894 "select_invert" => __("Invert selection"),
1895 "select_none" => __("Deselect everything")),
1896 __("Feed") => array(
1897 "feed_refresh" => __("Refresh current feed"),
1898 "feed_unhide_read" => __("Un/hide read feeds"),
1899 "feed_subscribe" => __("Subscribe to feed"),
1900 "feed_edit" => __("Edit feed"),
1901 "feed_catchup" => __("Mark as read"),
1902 "feed_reverse" => __("Reverse headlines"),
1903 "feed_debug_update" => __("Debug feed update"),
1904 "catchup_all" => __("Mark all feeds as read"),
1905 "cat_toggle_collapse" => __("Un/collapse current category"),
1906 "toggle_combined_mode" => __("Toggle combined mode")),
1907 __("Go to") => array(
1908 "goto_all" => __("All articles"),
1909 "goto_fresh" => __("Fresh"),
1910 "goto_marked" => __("Starred"),
1911 "goto_published" => __("Published"),
1912 "goto_tagcloud" => __("Tag cloud"),
1913 "goto_prefs" => __("Preferences")),
1914 __("Other") => array(
1915 "create_label" => __("Create label"),
1916 "create_filter" => __("Create filter"),
1917 "collapse_sidebar" => __("Un/collapse sidebar"),
1918 "help_dialog" => __("Show help dialog"))
1924 function get_hotkeys_map($link) {
1926 // "navigation" => array(
1929 "n" => "next_article",
1930 "p" => "prev_article",
1931 "(38)|up" => "prev_article",
1932 "(40)|down" => "next_article",
1933 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1934 // "^(40)|Ctrl-down" => "next_article_noscroll",
1935 "(191)|/" => "search_dialog",
1936 // "article" => array(
1937 "s" => "toggle_mark",
1938 "*s" => "toggle_publ",
1939 "u" => "toggle_unread",
1940 "*t" => "edit_tags",
1941 "*d" => "dismiss_selected",
1942 "*x" => "dismiss_read",
1943 "o" => "open_in_new_window",
1944 "c p" => "catchup_below",
1945 "c n" => "catchup_above",
1946 "*n" => "article_scroll_down",
1947 "*p" => "article_scroll_up",
1948 "*(38)|Shift+up" => "article_scroll_up",
1949 "*(40)|Shift+down" => "article_scroll_down",
1950 "a *w" => "toggle_widescreen",
1951 "a e" => "toggle_embed_original",
1952 "e" => "email_article",
1953 "a q" => "close_article",
1954 // "article_selection" => array(
1955 "a a" => "select_all",
1956 "a u" => "select_unread",
1957 "a *u" => "select_marked",
1958 "a p" => "select_published",
1959 "a i" => "select_invert",
1960 "a n" => "select_none",
1962 "f r" => "feed_refresh",
1963 "f a" => "feed_unhide_read",
1964 "f s" => "feed_subscribe",
1965 "f e" => "feed_edit",
1966 "f q" => "feed_catchup",
1967 "f x" => "feed_reverse",
1968 "f *d" => "feed_debug_update",
1969 "f *c" => "toggle_combined_mode",
1970 "*q" => "catchup_all",
1971 "x" => "cat_toggle_collapse",
1973 "g a" => "goto_all",
1974 "g f" => "goto_fresh",
1975 "g s" => "goto_marked",
1976 "g p" => "goto_published",
1977 "g t" => "goto_tagcloud",
1978 "g *p" => "goto_prefs",
1979 // "other" => array(
1980 "(9)|Tab" => "select_article_cursor", // tab
1981 "c l" => "create_label",
1982 "c f" => "create_filter",
1983 "c s" => "collapse_sidebar",
1984 "^(191)|Ctrl+/" => "help_dialog",
1987 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
1988 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1989 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1993 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP
) as $plugin) {
1994 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1997 $prefixes = array();
1999 foreach (array_keys($hotkeys) as $hotkey) {
2000 $pair = explode(" ", $hotkey, 2);
2002 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2003 array_push($prefixes, $pair[0]);
2007 return array($prefixes, $hotkeys);
2010 function make_runtime_info($link) {
2013 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2014 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2016 $max_feed_id = db_fetch_result($result, 0, "mid");
2017 $num_feeds = db_fetch_result($result, 0, "nf");
2019 $data["max_feed_id"] = (int) $max_feed_id;
2020 $data["num_feeds"] = (int) $num_feeds;
2022 $data['last_article_id'] = getLastArticleId($link);
2023 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2025 $data['dep_ts'] = calculate_dep_timestamp();
2027 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2029 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2031 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2033 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2036 $stamp_delta = time() - $stamp;
2038 if ($stamp_delta > 1800) {
2042 $_SESSION["daemon_stamp_check"] = time();
2045 $data['daemon_stamp_ok'] = $stamp_check;
2047 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2049 $data['daemon_stamp'] = $stamp_fmt;
2054 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2055 $new_version_details = @check_for_update
($link);
2057 $data['new_version_available'] = (int) ($new_version_details != false);
2059 $_SESSION["last_version_check"] = time();
2060 $_SESSION["version_data"] = $new_version_details;
2066 function search_to_sql($link, $search) {
2068 $search_query_part = "";
2070 $keywords = explode(" ", $search);
2071 $query_keywords = array();
2073 foreach ($keywords as $k) {
2074 if (strpos($k, "-") === 0) {
2081 $commandpair = explode(":", mb_strtolower($k), 2);
2083 if ($commandpair[0] == "note" && $commandpair[1]) {
2085 if ($commandpair[1] == "true")
2086 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2088 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2090 } else if ($commandpair[0] == "star" && $commandpair[1]) {
2092 if ($commandpair[1] == "true")
2093 array_push($query_keywords, "($not (marked = true))");
2095 array_push($query_keywords, "($not (marked = false))");
2097 } else if ($commandpair[0] == "pub" && $commandpair[1]) {
2099 if ($commandpair[1] == "true")
2100 array_push($query_keywords, "($not (published = true))");
2102 array_push($query_keywords, "($not (published = false))");
2104 } else if (strpos($k, "@") === 0) {
2106 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2107 $orig_ts = strtotime(substr($k, 1));
2108 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2110 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2112 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2114 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2115 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2119 $search_query_part = implode("AND", $query_keywords);
2121 return $search_query_part;
2124 function getParentCategories($link, $cat, $owner_uid) {
2127 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2128 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2130 while ($line = db_fetch_assoc($result)) {
2131 array_push($rv, $line["parent_cat"]);
2132 $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2138 function getChildCategories($link, $cat, $owner_uid) {
2141 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2142 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2144 while ($line = db_fetch_assoc($result)) {
2145 array_push($rv, $line["id"]);
2146 $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2152 function queryFeedHeadlines($link, $feed, $limit, $view_mode, $cat_view, $search, $search_mode, $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, $ignore_vfeed_group = false) {
2154 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2156 $ext_tables_part = "";
2160 if (SPHINX_ENABLED
) {
2161 $ids = join(",", @sphinx_search
($search, 0, 500));
2164 $search_query_part = "ref_id IN ($ids) AND ";
2166 $search_query_part = "ref_id = -1 AND ";
2169 $search_query_part = search_to_sql($link, $search);
2170 $search_query_part .= " AND ";
2174 $search_query_part = "";
2179 if (DB_TYPE
== "pgsql") {
2180 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2182 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2185 $override_order = "updated DESC";
2187 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2189 // Try to check if SQL regexp implementation chokes on a valid regexp
2190 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2191 ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2192 WHERE $filter_query_part LIMIT 1", false);
2195 $test = db_fetch_result($result, 0, "true_val");
2198 $filter_query_part = "false AND";
2200 $filter_query_part .= " AND";
2203 $filter_query_part = "false AND";
2207 $filter_query_part = "";
2211 $since_id_part = "ttrss_entries.id > $since_id AND ";
2213 $since_id_part = "";
2216 $view_query_part = "";
2218 if ($view_mode == "adaptive" ||
$view_query_part == "noscores") {
2220 $view_query_part = " ";
2221 } else if ($feed != -1) {
2222 $unread = getFeedUnread($link, $feed, $cat_view);
2224 if ($cat_view && $feed > 0 && $include_children)
2225 $unread +
= getCategoryChildrenUnread($link, $feed);
2228 $view_query_part = " unread = true AND ";
2233 if ($view_mode == "marked") {
2234 $view_query_part = " marked = true AND ";
2237 if ($view_mode == "published") {
2238 $view_query_part = " published = true AND ";
2241 if ($view_mode == "unread") {
2242 $view_query_part = " unread = true AND ";
2245 if ($view_mode == "updated") {
2246 $view_query_part = " (last_read is null and unread = false) AND ";
2250 $limit_query_part = "LIMIT " . $limit;
2253 $allow_archived = false;
2255 $vfeed_query_part = "";
2257 // override query strategy and enable feed display when searching globally
2258 if ($search && $search_mode == "all_feeds") {
2259 $query_strategy_part = "true";
2260 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2262 } else if (!is_numeric($feed)) {
2263 $query_strategy_part = "true";
2264 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2265 id = feed_id) as feed_title,";
2266 } else if ($search && $search_mode == "this_cat") {
2267 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2270 if ($include_children) {
2271 $subcats = getChildCategories($link, $feed, $owner_uid);
2272 array_push($subcats, $feed);
2273 $cats_qpart = join(",", $subcats);
2275 $cats_qpart = $feed;
2278 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2281 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2284 } else if ($feed > 0) {
2289 if ($include_children) {
2291 $subcats = getChildCategories($link, $feed, $owner_uid);
2293 array_push($subcats, $feed);
2294 $query_strategy_part = "cat_id IN (".
2295 implode(",", $subcats).")";
2298 $query_strategy_part = "cat_id = '$feed'";
2302 $query_strategy_part = "cat_id IS NULL";
2305 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2308 $query_strategy_part = "feed_id = '$feed'";
2310 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2311 $query_strategy_part = "feed_id IS NULL";
2312 $allow_archived = true;
2313 } else if ($feed == 0 && $cat_view) { // uncategorized
2314 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2315 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2316 } else if ($feed == -1) { // starred virtual feed
2317 $query_strategy_part = "marked = true";
2318 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2319 $allow_archived = true;
2321 if (!$override_order) $override_order = "last_marked DESC, updated DESC";
2323 } else if ($feed == -2) { // published virtual feed OR labels category
2326 $query_strategy_part = "published = true";
2327 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2328 $allow_archived = true;
2330 if (!$override_order) $override_order = "last_published DESC, updated DESC";
2332 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2334 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2336 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2337 ttrss_user_labels2.article_id = ref_id";
2340 } else if ($feed == -6) { // recently read
2341 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2342 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2343 $allow_archived = true;
2345 if (!$override_order) $override_order = "last_read DESC";
2346 } else if ($feed == -3) { // fresh virtual feed
2347 $query_strategy_part = "unread = true AND score >= 0";
2349 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2351 if (DB_TYPE
== "pgsql") {
2352 $query_strategy_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
2354 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2357 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2358 } else if ($feed == -4) { // all articles virtual feed
2359 $query_strategy_part = "true";
2360 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2361 } else if ($feed <= -10) { // labels
2362 $label_id = -$feed - 11;
2364 $query_strategy_part = "label_id = '$label_id' AND
2365 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2366 ttrss_user_labels2.article_id = ref_id";
2368 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2369 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2370 $allow_archived = true;
2373 $query_strategy_part = "true";
2376 if (get_pref($link, "SORT_HEADLINES_BY_FEED_DATE", $owner_uid)) {
2377 $date_sort_field = "updated";
2379 $date_sort_field = "date_entered";
2382 if (get_pref($link, 'REVERSE_HEADLINES', $owner_uid)) {
2383 $order_by = "$date_sort_field";
2385 $order_by = "$date_sort_field DESC";
2388 if ($view_mode != "noscores") {
2389 $order_by = "score DESC, $order_by";
2392 if ($override_order) {
2393 $order_by = $override_order;
2399 $feed_title = T_sprintf("Search results: %s", $search);
2402 $feed_title = getCategoryTitle($link, $feed);
2404 if (is_numeric($feed) && $feed > 0) {
2405 $result = db_query($link, "SELECT title,site_url,last_error
2406 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2408 $feed_title = db_fetch_result($result, 0, "title");
2409 $feed_site_url = db_fetch_result($result, 0, "site_url");
2410 $last_error = db_fetch_result($result, 0, "last_error");
2412 $feed_title = getFeedTitle($link, $feed);
2417 $content_query_part = "content as content_preview, cached_content, ";
2419 if (is_numeric($feed)) {
2422 $feed_kind = "Feeds";
2424 $feed_kind = "Labels";
2427 if ($limit_query_part) {
2428 $offset_query_part = "OFFSET $offset";
2431 // proper override_order applied above
2432 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2433 if (!$override_order) {
2434 $order_by = "ttrss_feeds.title, $order_by";
2436 $order_by = "ttrss_feeds.title, $override_order";
2440 if (!$allow_archived) {
2441 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2442 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2445 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2446 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2449 $query = "SELECT DISTINCT
2452 ttrss_entries.id,ttrss_entries.title,
2456 always_display_enclosures,
2463 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2464 last_marked, last_published,
2465 ".SUBSTRING_FOR_DATE
."(last_read,1,19) as last_read_noms,
2468 ".SUBSTRING_FOR_DATE
."(updated,1,19) as updated_noms,
2474 ttrss_user_entries.ref_id = ttrss_entries.id AND
2475 ttrss_user_entries.owner_uid = '$owner_uid' AND
2480 $query_strategy_part ORDER BY $order_by
2481 $limit_query_part $offset_query_part";
2483 if ($_REQUEST["debug"]) print $query;
2485 $result = db_query($link, $query);
2490 $select_qpart = "SELECT DISTINCT " .
2494 "ttrss_entries.id as id," .
2507 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2508 "last_marked, last_published, " .
2509 SUBSTRING_FOR_DATE
. "(last_read,1,19) as last_read_noms," .
2512 $content_query_part .
2513 SUBSTRING_FOR_DATE
. "(updated,1,19) as updated_noms," .
2516 $feed_kind = "Tags";
2517 $all_tags = explode(",", $feed);
2518 if ($search_mode == 'any') {
2519 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2520 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2521 $where_qpart = " WHERE " .
2522 "ref_id = ttrss_entries.id AND " .
2523 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2524 "post_int_id = int_id AND $tag_sql AND " .
2526 $search_query_part .
2527 $query_strategy_part . " ORDER BY $order_by " .
2532 $sub_selects = array();
2533 $sub_ands = array();
2534 foreach ($all_tags as $term) {
2535 array_push($sub_selects, "(SELECT post_int_id from ttrss_tags WHERE tag_name = " . db_quote($term) . " AND owner_uid = $owner_uid) as A$i");
2542 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2547 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2548 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2549 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2550 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2552 // error_log("TAG SQL: " . $tag_sql);
2553 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2555 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2556 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2559 return array($result, $feed_title, $feed_site_url, $last_error);
2563 function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2564 if (!$owner) $owner = $_SESSION["uid"];
2566 $res = trim($str); if (!$res) return '';
2568 if (strpos($res, "href=") === false)
2569 $res = rewrite_urls($res);
2571 $charset_hack = '<head>
2572 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2575 $res = trim($res); if (!$res) return '';
2577 libxml_use_internal_errors(true);
2579 $doc = new DOMDocument();
2580 $doc->loadHTML($charset_hack . $res);
2581 $xpath = new DOMXPath($doc);
2583 $entries = $xpath->query('(//a[@href]|//img[@src])');
2585 foreach ($entries as $entry) {
2589 if ($entry->hasAttribute('href'))
2590 $entry->setAttribute('href',
2591 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2593 if ($entry->hasAttribute('src')) {
2594 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2596 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2598 if (file_exists($cached_filename)) {
2599 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2602 $entry->setAttribute('src', $src);
2605 if ($entry->nodeName
== 'img') {
2606 if (($owner && get_pref($link, "STRIP_IMAGES", $owner)) ||
2607 $force_remove_images) {
2609 $p = $doc->createElement('p');
2611 $a = $doc->createElement('a');
2612 $a->setAttribute('href', $entry->getAttribute('src'));
2614 $a->appendChild(new DOMText($entry->getAttribute('src')));
2615 $a->setAttribute('target', '_blank');
2617 $p->appendChild($a);
2619 $entry->parentNode
->replaceChild($p, $entry);
2624 if (strtolower($entry->nodeName
) == "a") {
2625 $entry->setAttribute("target", "_blank");
2629 $entries = $xpath->query('//iframe');
2630 foreach ($entries as $entry) {
2631 $entry->setAttribute('sandbox', 'allow-scripts');
2637 if (isset($pluginhost)) {
2638 foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE
) as $plugin) {
2639 $doc = $plugin->hook_sanitize($doc, $site_url);
2643 $doc->removeChild($doc->firstChild
); //remove doctype
2644 $doc = strip_harmful_tags($doc);
2645 $res = $doc->saveHTML();
2649 function strip_harmful_tags($doc) {
2650 $entries = $doc->getElementsByTagName("*");
2652 $allowed_elements = array('a', 'address', 'audio', 'article',
2653 'b', 'big', 'blockquote', 'body', 'br', 'cite',
2654 'code', 'dd', 'del', 'details', 'div', 'dl', 'font',
2655 'dt', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
2656 'header', 'html', 'i', 'img', 'ins', 'kbd',
2657 'li', 'nav', 'ol', 'p', 'pre', 'q', 's','small',
2658 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2659 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
2660 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2662 if ($_SESSION['hasSandbox']) array_push($allowed_elements, 'iframe');
2664 $disallowed_attributes = array('id', 'style', 'class');
2666 foreach ($entries as $entry) {
2667 if (!in_array($entry->nodeName
, $allowed_elements)) {
2668 $entry->parentNode
->removeChild($entry);
2671 if ($entry->hasAttributes()) {
2672 foreach (iterator_to_array($entry->attributes
) as $attr) {
2674 if (strpos($attr->nodeName
, 'on') === 0) {
2675 $entry->removeAttributeNode($attr);
2678 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2679 $entry->removeAttributeNode($attr);
2688 function check_for_update($link) {
2689 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2690 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2691 "&iid=" . sha1(SELF_URL_PATH
);
2693 $version_data = @fetch_file_contents
($version_url);
2695 if ($version_data) {
2696 $version_data = json_decode($version_data, true);
2697 if ($version_data && $version_data['version']) {
2699 if (version_compare(VERSION
, $version_data['version']) == -1) {
2700 return $version_data;
2708 function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2710 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2711 if (count($ids) == 0) return;
2715 foreach ($ids as $id) {
2716 array_push($tmp_ids, "ref_id = '$id'");
2719 $ids_qpart = join(" OR ", $tmp_ids);
2722 db_query($link, "UPDATE ttrss_user_entries SET
2723 unread = false,last_read = NOW()
2724 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2725 } else if ($cmode == 1) {
2726 db_query($link, "UPDATE ttrss_user_entries SET
2728 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2730 db_query($link, "UPDATE ttrss_user_entries SET
2731 unread = NOT unread,last_read = NOW()
2732 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2737 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2738 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2740 while ($line = db_fetch_assoc($result)) {
2741 ccache_update($link, $line["feed_id"], $owner_uid);
2745 function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2747 $a_id = db_escape_string($id);
2749 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2751 $query = "SELECT DISTINCT tag_name,
2752 owner_uid as owner FROM
2753 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2754 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2756 $obj_id = md5("TAGS:$owner_uid:$id");
2759 /* check cache first */
2761 if ($tag_cache === false) {
2762 $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2763 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2765 $tag_cache = db_fetch_result($result, 0, "tag_cache");
2769 $tags = explode(",", $tag_cache);
2772 /* do it the hard way */
2774 $tmp_result = db_query($link, $query);
2776 while ($tmp_line = db_fetch_assoc($tmp_result)) {
2777 array_push($tags, $tmp_line["tag_name"]);
2780 /* update the cache */
2782 $tags_str = db_escape_string(join(",", $tags));
2784 db_query($link, "UPDATE ttrss_user_entries
2785 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2786 AND owner_uid = $owner_uid");
2792 function trim_array($array) {
2794 array_walk($tmp, 'trim');
2798 function tag_is_valid($tag) {
2799 if ($tag == '') return false;
2800 if (preg_match("/^[0-9]*$/", $tag)) return false;
2801 if (mb_strlen($tag) > 250) return false;
2803 if (function_exists('iconv')) {
2804 $tag = iconv("utf-8", "utf-8", $tag);
2807 if (!$tag) return false;
2812 function render_login_form($link) {
2813 require_once "login_form.php";
2817 // from http://developer.apple.com/internet/safari/faq.html
2818 function no_cache_incantation() {
2819 header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
2820 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
2821 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
2822 header("Cache-Control: post-check=0, pre-check=0", false);
2823 header("Pragma: no-cache"); // HTTP/1.0
2826 function format_warning($msg, $id = "") {
2828 return "<div class=\"warning\" id=\"$id\">
2829 <img src=\"images/sign_excl.svg\">$msg</div>";
2832 function format_notice($msg, $id = "") {
2834 return "<div class=\"notice\" id=\"$id\">
2835 <img src=\"images/sign_info.svg\">$msg</div>";
2838 function format_error($msg, $id = "") {
2840 return "<div class=\"error\" id=\"$id\">
2841 <img src=\"images/sign_excl.svg\">$msg</div>";
2844 function print_notice($msg) {
2845 return print format_notice($msg);
2848 function print_warning($msg) {
2849 return print format_warning($msg);
2852 function print_error($msg) {
2853 return print format_error($msg);
2857 function T_sprintf() {
2858 $args = func_get_args();
2859 return vsprintf(__(array_shift($args)), $args);
2862 function format_inline_player($link, $url, $ctype) {
2866 $url = htmlspecialchars($url);
2868 if (strpos($ctype, "audio/") === 0) {
2870 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2871 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2872 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2874 $id = 'AUDIO-' . uniqid();
2876 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2877 <source type=\"$ctype\" src=\"$url\"></source>
2880 $entry .= "<span onclick=\"player(this)\"
2881 title=\"".__("Click to play")."\" status=\"0\"
2882 class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
2886 $entry .= "<object type=\"application/x-shockwave-flash\"
2887 data=\"lib/button/musicplayer.swf?song_url=$url\"
2888 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
2889 <param name=\"movie\"
2890 value=\"lib/button/musicplayer.swf?song_url=$url\" />
2894 if ($entry) $entry .= " <a target=\"_blank\"
2895 href=\"$url\">" . basename($url) . "</a>";
2903 /* $filename = substr($url, strrpos($url, "/")+1);
2905 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
2906 $filename . " (" . $ctype . ")" . "</a>"; */
2910 function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
2911 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2917 /* we can figure out feed_id from article id anyway, why do we
2918 * pass feed_id here? let's ignore the argument :( */
2920 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
2921 WHERE ref_id = '$id'");
2923 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
2925 $rv['feed_id'] = $feed_id;
2927 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
2929 if ($mark_as_read) {
2930 $result = db_query($link, "UPDATE ttrss_user_entries
2931 SET unread = false,last_read = NOW()
2932 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2934 ccache_update($link, $feed_id, $owner_uid);
2937 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
2938 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
2939 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
2940 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
2947 FROM ttrss_entries,ttrss_user_entries
2948 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
2952 $line = db_fetch_assoc($result);
2954 $tag_cache = $line["tag_cache"];
2956 $line["tags"] = get_article_tags($link, $id, $owner_uid, $line["tag_cache"]);
2957 unset($line["tag_cache"]);
2959 $line["content"] = sanitize($link, $line["content"], false, $owner_uid, $line["site_url"]);
2963 foreach ($pluginhost->get_hooks($pluginhost::HOOK_RENDER_ARTICLE
) as $p) {
2964 $line = $p->hook_render_article($line);
2967 $num_comments = $line["num_comments"];
2968 $entry_comments = "";
2970 if ($num_comments > 0) {
2971 if ($line["comments"]) {
2972 $comments_url = htmlspecialchars($line["comments"]);
2974 $comments_url = htmlspecialchars($line["link"]);
2976 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
2978 if ($line["comments"] && $line["link"] != $line["comments"]) {
2979 $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
2984 header("Content-Type: text/html");
2985 $rv['content'] .= "<html><head>
2986 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
2987 <title>Tiny Tiny RSS - ".$line["title"]."</title>
2988 <link rel=\"stylesheet\" type=\"text/css\" href=\"tt-rss.css\">
2992 $title_escaped = htmlspecialchars($line['title']);
2994 $rv['content'] .= "<div id=\"PTITLE-FULL-$id\" style=\"display : none\">" .
2995 strip_tags($line['title']) . "</div>";
2997 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
2999 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3001 $entry_author = $line["author"];
3003 if ($entry_author) {
3004 $entry_author = __(" - ") . $entry_author;
3007 $parsed_updated = make_local_datetime($link, $line["updated"], true,
3010 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3012 if ($line["link"]) {
3013 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3014 title=\"".htmlspecialchars($line['title'])."\"
3016 htmlspecialchars($line["link"]) . "\">" .
3018 "<span class='author'>$entry_author</span></a></div>";
3020 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3023 $tags_str = format_tags_string($line["tags"], $id);
3024 $tags_str_full = join(", ", $line["tags"]);
3026 if (!$tags_str_full) $tags_str_full = __("no tags");
3028 if (!$entry_comments) $entry_comments = " "; # placeholder
3030 $rv['content'] .= "<div class='postTags' style='float : right'>
3031 <img src='images/tag.png'
3032 class='tagsPic' alt='Tags' title='Tags'> ";
3035 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3036 <a title=\"".__('Edit tags for this article')."\"
3037 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3039 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3040 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3041 position=\"below\">$tags_str_full</div>";
3045 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON
) as $p) {
3046 $rv['content'] .= $p->hook_article_button($line);
3051 $tags_str = strip_tags($tags_str);
3052 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3054 $rv['content'] .= "</div>";
3055 $rv['content'] .= "<div clear='both'>$entry_comments</div>";
3057 if ($line["orig_feed_id"]) {
3059 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3060 WHERE id = ".$line["orig_feed_id"]);
3062 if (db_num_rows($tmp_result) != 0) {
3064 $rv['content'] .= "<div clear='both'>";
3065 $rv['content'] .= __("Originally from:");
3067 $rv['content'] .= " ";
3069 $tmp_line = db_fetch_assoc($tmp_result);
3071 $rv['content'] .= "<a target='_blank'
3072 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3073 $tmp_line['title'] . "</a>";
3075 $rv['content'] .= " ";
3077 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3078 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3080 $rv['content'] .= "</div>";
3084 $rv['content'] .= "</div>";
3086 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3087 if ($line['note']) {
3088 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3090 $rv['content'] .= "</div>";
3092 $rv['content'] .= "<div class=\"postContent\">";
3096 if (DB_TYPE
== "pgsql" and defined('_NGRAM_TITLE_RELATED_THRESHOLD')) {
3098 $ngram_result = db_query($link, "SELECT id,title FROM
3099 ttrss_entries,ttrss_user_entries
3100 WHERE ref_id = id AND updated >= NOW() - INTERVAL '7 day'
3101 AND similarity(title, '$title_escaped') >= "._NGRAM_TITLE_RELATED_THRESHOLD
."
3102 AND title != '$title_escaped'
3103 AND owner_uid = $owner_uid");
3105 if (db_num_rows($ngram_result) > 0) {
3106 $rv['content'] .= "<div dojoType=\"dijit.form.DropDownButton\">".
3107 "<span>" . __('Related')."</span>";
3108 $rv['content'] .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3110 while ($nline = db_fetch_assoc($ngram_result)) {
3111 $rv['content'] .= "<div onclick=\"hlOpenInNewTab(null,".$nline['id'].")\"
3112 dojoType=\"dijit.MenuItem\">".$nline['title']."</div>";
3115 $rv['content'] .= "</div></div><br/";
3119 $rv['content'] .= $line["content"];
3121 $rv['content'] .= format_article_enclosures($link, $id,
3122 $always_display_enclosures, $line["content"], $line["hide_images"]);
3124 $rv['content'] .= "</div>";
3126 $rv['content'] .= "</div>";
3132 <div style=\"text-align : center\">
3133 <button onclick=\"return window.close()\">".
3134 __("Close this window")."</button></div>";
3135 $rv['content'] .= "</body></html>";
3142 function print_checkpoint($n, $s) {
3143 $ts = microtime(true);
3144 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3148 function sanitize_tag($tag) {
3151 $tag = mb_strtolower($tag, 'utf-8');
3153 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3155 // $tag = str_replace('"', "", $tag);
3156 // $tag = str_replace("+", " ", $tag);
3157 $tag = str_replace("technorati tag: ", "", $tag);
3162 function get_self_url_prefix() {
3163 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3164 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3166 return SELF_URL_PATH
;
3171 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3173 * @return string The Mozilla Firefox feed adding URL.
3175 function add_feed_url() {
3176 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3178 $url_path = get_self_url_prefix() .
3179 "/public.php?op=subscribe&feed_url=%s";
3181 } // function add_feed_url
3183 function encrypt_password($pass, $salt = '', $mode2 = false) {
3184 if ($salt && $mode2) {
3185 return "MODE2:" . hash('sha256', $salt . $pass);
3187 return "SHA1X:" . sha1("$salt:$pass");
3189 return "SHA1:" . sha1($pass);
3191 } // function encrypt_password
3193 function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3196 $cat_id = (int)getFeedCategory($link, $feed_id);
3198 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3199 owner_uid = $owner_uid AND enabled = true");
3201 $check_cats = join(",", array_merge(
3202 getParentCategories($link, $cat_id, $owner_uid),
3205 while ($line = db_fetch_assoc($result)) {
3206 $filter_id = $line["id"];
3208 $result2 = db_query($link, "SELECT
3209 r.reg_exp, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3210 FROM ttrss_filters2_rules AS r,
3211 ttrss_filter_types AS t
3213 (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3214 (feed_id IS NULL OR feed_id = '$feed_id') AND
3215 filter_type = t.id AND filter_id = '$filter_id'");
3220 while ($rule_line = db_fetch_assoc($result2)) {
3221 # print_r($rule_line);
3224 $rule["reg_exp"] = $rule_line["reg_exp"];
3225 $rule["type"] = $rule_line["type_name"];
3227 array_push($rules, $rule);
3230 $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3231 FROM ttrss_filters2_actions AS a,
3232 ttrss_filter_actions AS t
3234 action_id = t.id AND filter_id = '$filter_id'");
3236 while ($action_line = db_fetch_assoc($result2)) {
3237 # print_r($action_line);
3240 $action["type"] = $action_line["type_name"];
3241 $action["param"] = $action_line["action_param"];
3243 array_push($actions, $action);
3248 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3249 $filter["rules"] = $rules;
3250 $filter["actions"] = $actions;
3252 if (count($rules) > 0 && count($actions) > 0) {
3253 array_push($filters, $filter);
3260 function get_score_pic($score) {
3262 return "score_high.png";
3263 } else if ($score > 0) {
3264 return "score_half_high.png";
3265 } else if ($score < -100) {
3266 return "score_low.png";
3267 } else if ($score < 0) {
3268 return "score_half_low.png";
3270 return "score_neutral.png";
3274 function feed_has_icon($id) {
3275 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3278 function init_connection($link) {
3281 if (DB_TYPE
== "pgsql") {
3282 pg_query($link, "set client_encoding = 'UTF-8'");
3283 pg_set_client_encoding("UNICODE");
3284 pg_query($link, "set datestyle = 'ISO, european'");
3285 pg_query($link, "set TIME ZONE 0");
3287 db_query($link, "SET time_zone = '+0:0'");
3289 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET
) {
3290 db_query($link, "SET NAMES " . MYSQL_CHARSET
);
3296 $pluginhost = new PluginHost($link);
3297 $pluginhost->load(PLUGINS
, $pluginhost::KIND_ALL
);
3301 print "Unable to connect to database:" . db_last_error();
3306 function format_tags_string($tags, $id) {
3309 $tags_nolinks_str = "";
3315 $formatted_tags = array();
3317 foreach ($tags as $tag) {
3319 $tag_escaped = str_replace("'", "\\'", $tag);
3321 if (mb_strlen($tag) > 30) {
3322 $tag = truncate_string($tag, 30);
3325 $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3327 array_push($formatted_tags, $tag_str);
3329 $tmp_tags_str = implode(", ", $formatted_tags);
3331 if ($num_tags == $tag_limit ||
mb_strlen($tmp_tags_str) > 150) {
3336 $tags_str = implode(", ", $formatted_tags);
3338 if ($num_tags < count($tags)) {
3339 $tags_str .= ", …";
3342 if ($num_tags == 0) {
3343 $tags_str = __("no tags");
3350 function format_article_labels($labels, $id) {
3354 foreach ($labels as $l) {
3355 $labels_str .= sprintf("<span class='hlLabelRef'
3356 style='color : %s; background-color : %s'>%s</span>",
3357 $l[2], $l[3], $l[1]);
3364 function format_article_note($id, $note, $allow_edit = true) {
3366 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3367 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3368 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3374 function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3375 if ($parent_cat_id) {
3376 $parent_qpart = "parent_cat = '$parent_cat_id'";
3377 $parent_insert = "'$parent_cat_id'";
3379 $parent_qpart = "parent_cat IS NULL";
3380 $parent_insert = "NULL";
3383 $result = db_query($link,
3384 "SELECT id FROM ttrss_feed_categories
3385 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3387 if (db_num_rows($result) == 0) {
3390 return db_fetch_result($result, 0, "id");
3394 function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3396 if (!$feed_cat) return false;
3398 db_query($link, "BEGIN");
3400 if ($parent_cat_id) {
3401 $parent_qpart = "parent_cat = '$parent_cat_id'";
3402 $parent_insert = "'$parent_cat_id'";
3404 $parent_qpart = "parent_cat IS NULL";
3405 $parent_insert = "NULL";
3408 $result = db_query($link,
3409 "SELECT id FROM ttrss_feed_categories
3410 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3412 if (db_num_rows($result) == 0) {
3414 $result = db_query($link,
3415 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3416 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3418 db_query($link, "COMMIT");
3426 function getArticleFeed($link, $id) {
3427 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3428 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3430 if (db_num_rows($result) != 0) {
3431 return db_fetch_result($result, 0, "feed_id");
3438 * Fixes incomplete URLs by prepending "http://".
3439 * Also replaces feed:// with http://, and
3440 * prepends a trailing slash if the url is a domain name only.
3442 * @param string $url Possibly incomplete URL
3444 * @return string Fixed URL.
3446 function fix_url($url) {
3447 if (strpos($url, '://') === false) {
3448 $url = 'http://' . $url;
3449 } else if (substr($url, 0, 5) == 'feed:') {
3450 $url = 'http:' . substr($url, 5);
3453 //prepend slash if the URL has no slash in it
3454 // "http://www.example" -> "http://www.example/"
3455 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3459 if ($url != "http:///")
3465 function validate_feed_url($url) {
3466 $parts = parse_url($url);
3468 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3472 function get_article_enclosures($link, $id) {
3474 $query = "SELECT * FROM ttrss_enclosures
3475 WHERE post_id = '$id' AND content_url != ''";
3479 $result = db_query($link, $query);
3481 if (db_num_rows($result) > 0) {
3482 while ($line = db_fetch_assoc($result)) {
3483 array_push($rv, $line);
3490 function save_email_address($link, $email) {
3491 // FIXME: implement persistent storage of emails
3493 if (!$_SESSION['stored_emails'])
3494 $_SESSION['stored_emails'] = array();
3496 if (!in_array($email, $_SESSION['stored_emails']))
3497 array_push($_SESSION['stored_emails'], $email);
3501 function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3503 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3505 $sql_is_cat = bool_to_sql_bool($is_cat);
3507 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3508 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3509 AND owner_uid = " . $owner_uid);
3511 if (db_num_rows($result) == 1) {
3512 return db_fetch_result($result, 0, "access_key");
3514 $key = db_escape_string(sha1(uniqid(rand(), true)));
3516 $result = db_query($link, "INSERT INTO ttrss_access_keys
3517 (access_key, feed_id, is_cat, owner_uid)
3518 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3525 function get_feeds_from_html($url, $content)
3527 $url = fix_url($url);
3528 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3530 libxml_use_internal_errors(true);
3532 $doc = new DOMDocument();
3533 $doc->loadHTML($content);
3534 $xpath = new DOMXPath($doc);
3535 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3536 $feedUrls = array();
3537 foreach ($entries as $entry) {
3538 if ($entry->hasAttribute('href')) {
3539 $title = $entry->getAttribute('title');
3541 $title = $entry->getAttribute('type');
3543 $feedUrl = rewrite_relative_url(
3544 $baseUrl, $entry->getAttribute('href')
3546 $feedUrls[$feedUrl] = $title;
3552 function is_html($content) {
3553 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3556 function url_is_html($url, $login = false, $pass = false) {
3557 return is_html(fetch_file_contents($url, false, $login, $pass));
3560 function print_label_select($link, $name, $value, $attributes = "") {
3562 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3563 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3565 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3566 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3568 while ($line = db_fetch_assoc($result)) {
3570 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3572 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3573 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3577 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3584 function format_article_enclosures($link, $id, $always_display_enclosures,
3585 $article_content, $hide_images = false) {
3587 $result = get_article_enclosures($link, $id);
3590 if (count($result) > 0) {
3592 $entries_html = array();
3594 $entries_inline = array();
3596 foreach ($result as $line) {
3598 $url = $line["content_url"];
3599 $ctype = $line["content_type"];
3601 if (!$ctype) $ctype = __("unknown type");
3603 $filename = substr($url, strrpos($url, "/")+
1);
3605 $player = format_inline_player($link, $url, $ctype);
3607 if ($player) array_push($entries_inline, $player);
3609 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3610 # $filename . " (" . $ctype . ")" . "</a>";
3612 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3613 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3615 array_push($entries_html, $entry);
3619 $entry["type"] = $ctype;
3620 $entry["filename"] = $filename;
3621 $entry["url"] = $url;
3623 array_push($entries, $entry);
3626 if ($_SESSION['uid'] && !get_pref($link, "STRIP_IMAGES")) {
3627 if ($always_display_enclosures ||
3628 !preg_match("/<img/i", $article_content)) {
3630 foreach ($entries as $entry) {
3632 if (preg_match("/image/", $entry["type"]) ||
3633 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3635 if (!$hide_images) {
3637 alt=\"".htmlspecialchars($entry["filename"])."\"
3638 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3640 $rv .= "<p><a target=\"_blank\"
3641 href=\"".htmlspecialchars($entry["url"])."\"
3642 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3650 if (count($entries_inline) > 0) {
3651 $rv .= "<hr clear='both'/>";
3652 foreach ($entries_inline as $entry) { $rv .= $entry; };
3653 $rv .= "<hr clear='both'/>";
3656 $rv .= "<br/><div dojoType=\"dijit.form.DropDownButton\">".
3657 "<span>" . __('Attachments')."</span>";
3658 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3660 foreach ($entries_html as $entry) { $rv .= $entry; };
3662 $rv .= "</div></div>";
3668 function getLastArticleId($link) {
3669 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3670 WHERE owner_uid = " . $_SESSION["uid"]);
3672 if (db_num_rows($result) == 1) {
3673 return db_fetch_result($result, 0, "id");
3679 function build_url($parts) {
3680 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3684 * Converts a (possibly) relative URL to a absolute one.
3686 * @param string $url Base URL (i.e. from where the document is)
3687 * @param string $rel_url Possibly relative URL in the document
3689 * @return string Absolute URL
3691 function rewrite_relative_url($url, $rel_url) {
3692 if (strpos($rel_url, "magnet:") === 0) {
3694 } else if (strpos($rel_url, "://") !== false) {
3696 } else if (strpos($rel_url, "//") === 0) {
3697 # protocol-relative URL (rare but they exist)
3699 } else if (strpos($rel_url, "/") === 0)
3701 $parts = parse_url($url);
3702 $parts['path'] = $rel_url;
3704 return build_url($parts);
3707 $parts = parse_url($url);
3708 if (!isset($parts['path'])) {
3709 $parts['path'] = '/';
3711 $dir = $parts['path'];
3712 if (substr($dir, -1) !== '/') {
3713 $dir = dirname($parts['path']);
3714 $dir !== '/' && $dir .= '/';
3716 $parts['path'] = $dir . $rel_url;
3718 return build_url($parts);
3722 function sphinx_search($query, $offset = 0, $limit = 30) {
3723 require_once 'lib/sphinxapi.php';
3725 $sphinxClient = new SphinxClient();
3727 $sphinxClient->SetServer('localhost', 9312);
3728 $sphinxClient->SetConnectTimeout(1);
3730 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3731 'feed_title' => 20));
3733 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3734 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3735 $sphinxClient->SetLimits($offset, $limit, 1000);
3736 $sphinxClient->SetArrayResult(false);
3737 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3739 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3743 if (is_array($result['matches'])) {
3744 foreach (array_keys($result['matches']) as $int_id) {
3745 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3746 array_push($ids, $ref_id);
3753 function cleanup_tags($link, $days = 14, $limit = 1000) {
3755 if (DB_TYPE
== "pgsql") {
3756 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3757 } else if (DB_TYPE
== "mysql") {
3758 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3763 while ($limit > 0) {
3766 $query = "SELECT ttrss_tags.id AS id
3767 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3768 WHERE post_int_id = int_id AND $interval_query AND
3769 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3771 $result = db_query($link, $query);
3775 while ($line = db_fetch_assoc($result)) {
3776 array_push($ids, $line['id']);
3779 if (count($ids) > 0) {
3780 $ids = join(",", $ids);
3783 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3784 $tags_deleted +
= db_affected_rows($link, $tmp_result);
3789 $limit -= $limit_part;
3794 return $tags_deleted;
3797 function print_user_stylesheet($link) {
3798 $value = get_pref($link, 'USER_STYLESHEET');
3801 print "<style type=\"text/css\">";
3802 print str_replace("<br/>", "\n", $value);
3808 function rewrite_urls($html) {
3809 libxml_use_internal_errors(true);
3811 $charset_hack = '<head>
3812 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3815 $doc = new DOMDocument();
3816 $doc->loadHTML($charset_hack . $html);
3817 $xpath = new DOMXPath($doc);
3819 $entries = $xpath->query('//*/text()');
3821 foreach ($entries as $entry) {
3822 if (strstr($entry->wholeText
, "://") !== false) {
3823 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3824 "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText
);
3826 if ($text != $entry->wholeText
) {
3827 $cdoc = new DOMDocument();
3828 $cdoc->loadHTML($charset_hack . $text);
3831 foreach ($cdoc->childNodes
as $cnode) {
3832 $cnode = $doc->importNode($cnode, true);
3835 $entry->parentNode
->insertBefore($cnode);
3839 $entry->parentNode
->removeChild($entry);
3845 $node = $doc->getElementsByTagName('body')->item(0);
3847 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3849 return $doc->saveXML($node);
3854 function filter_to_sql($link, $filter, $owner_uid) {
3857 if (DB_TYPE
== "pgsql")
3860 $reg_qpart = "REGEXP";
3862 foreach ($filter["rules"] AS $rule) {
3863 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3864 $rule['reg_exp']) !== FALSE;
3866 if ($regexp_valid) {
3868 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
3870 switch ($rule["type"]) {
3872 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3873 $rule['reg_exp'] . "')";
3876 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3877 $rule['reg_exp'] . "')";
3880 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3881 $rule['reg_exp'] . "') OR LOWER(" .
3882 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3885 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3886 $rule['reg_exp'] . "')";
3889 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3890 $rule['reg_exp'] . "')";
3893 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3894 $rule['reg_exp'] . "')";
3898 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3899 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
3902 if (isset($rule["cat_id"])) {
3904 if ($rule["cat_id"] > 0) {
3905 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
3906 array_push($children, $rule["cat_id"]);
3908 $children = join(",", $children);
3910 $cat_qpart = "cat_id IN ($children)";
3912 $cat_qpart = "cat_id IS NULL";
3915 $qpart .= " AND $cat_qpart";
3918 array_push($query, "($qpart)");
3923 if (count($query) > 0) {
3924 return "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
3930 if (!function_exists('gzdecode')) {
3931 function gzdecode($string) { // no support for 2nd argument
3932 return file_get_contents('compress.zlib://data:who/cares;base64,'.
3933 base64_encode($string));
3937 function get_random_bytes($length) {
3938 if (function_exists('openssl_random_pseudo_bytes')) {
3939 return openssl_random_pseudo_bytes($length);
3943 for ($i = 0; $i < $length; $i++
)
3944 $output .= chr(mt_rand(0, 255));
3950 function read_stdin() {
3951 $fp = fopen("php://stdin", "r");
3954 $line = trim(fgets($fp));
3962 function tmpdirname($path, $prefix) {
3963 // Use PHP's tmpfile function to create a temporary
3964 // directory name. Delete the file and keep the name.
3965 $tempname = tempnam($path,$prefix);
3969 if (!unlink($tempname))
3975 function getFeedCategory($link, $feed) {
3976 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
3977 WHERE id = '$feed'");
3979 if (db_num_rows($result) > 0) {
3980 return db_fetch_result($result, 0, "cat_id");
3987 function implements_interface($class, $interface) {
3988 return in_array($interface, class_implements($class));
3991 function geturl($url){
3993 (function_exists('curl_init')) ?
'' : die('cURL Must be installed for geturl function to work. Ask your host to enable it or uncomment extension=php_curl.dll in php.ini');
3995 $curl = curl_init();
3996 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
3997 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
3998 $header[] = "Cache-Control: max-age=0";
3999 $header[] = "Connection: keep-alive";
4000 $header[] = "Keep-Alive: 300";
4001 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4002 $header[] = "Accept-Language: en-us,en;q=0.5";
4003 $header[] = "Pragma: ";
4005 curl_setopt($curl, CURLOPT_URL
, $url);
4006 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4007 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4008 curl_setopt($curl, CURLOPT_HEADER
, true);
4009 curl_setopt($curl, CURLOPT_REFERER
, $url);
4010 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4011 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4012 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4013 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4014 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4016 $html = curl_exec($curl);
4018 $status = curl_getinfo($curl);
4021 if($status['http_code']!=200){
4022 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4023 list($header) = explode("\r\n\r\n", $html, 2);
4025 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4026 $url = trim(str_replace($matches[1],"",$matches[0]));
4027 $url_parsed = parse_url($url);
4028 return (isset($url_parsed))?
geturl($url, $referer):'';
4031 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4032 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4033 # $handle = @fopen('./curl.error.log', 'a');
4034 # fwrite($handle, $line);
4040 function get_minified_js($files) {
4041 require_once 'lib/jshrink/Minifier.php';
4045 foreach ($files as $js) {
4046 if (!isset($_GET['debug'])) {
4047 $cached_file = CACHE_DIR
. "/js/$js.js";
4049 if (file_exists($cached_file) &&
4050 is_readable($cached_file) &&
4051 filemtime($cached_file) >= filemtime("js/$js.js")) {
4053 $rv .= file_get_contents($cached_file);
4056 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4057 file_put_contents($cached_file, $minified);
4061 $rv .= file_get_contents("js/$js.js");
4068 function stylesheet_tag($filename) {
4069 $timestamp = filemtime($filename);
4071 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4074 function javascript_tag($filename) {
4077 if (!(strpos($filename, "?") === FALSE)) {
4078 $query = substr($filename, strpos($filename, "?")+
1);
4079 $filename = substr($filename, 0, strpos($filename, "?"));
4082 $timestamp = filemtime($filename);
4084 if ($query) $timestamp .= "&$query";
4086 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4089 function calculate_dep_timestamp() {
4090 $files = array_merge(glob("js/*.js"), glob("*.css"));
4094 foreach ($files as $file) {
4095 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4101 function get_site_title() {
4102 $original_title = "Tiny Tiny RSS";
4103 if (defined("SITE_TITLE")) {
4106 return $original_title;