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($link, $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 = " .
771 $_SESSION["last_login_update"] = time();
774 if ($_SESSION["uid"] && $_SESSION["language"] && SESSION_COOKIE_LIFETIME
> 0) {
775 setcookie("ttrss_lang", $_SESSION["language"],
776 time() + SESSION_COOKIE_LIFETIME
);
779 if ($_SESSION["uid"]) {
781 load_user_plugins($link, $_SESSION["uid"]);
785 db_query($link, "DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
786 $_SESSION["uid"] . " AND
787 (SELECT COUNT(id) FROM ttrss_feeds WHERE
788 ttrss_feeds.id = feed_id) = 0");
790 db_query($link, "DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
791 $_SESSION["uid"] . " AND
792 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
793 ttrss_feed_categories.id = feed_id) = 0");
800 function truncate_string($str, $max_len, $suffix = '…') {
801 if (mb_strlen($str, "utf-8") > $max_len - 3) {
802 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
808 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
811 $source_tz = new DateTimeZone($source_tz);
812 } catch (Exception
$e) {
813 $source_tz = new DateTimeZone('UTC');
817 $dest_tz = new DateTimeZone($dest_tz);
818 } catch (Exception
$e) {
819 $dest_tz = new DateTimeZone('UTC');
822 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
823 return $dt->format('U') +
$dest_tz->getOffset($dt);
826 function make_local_datetime($link, $timestamp, $long, $owner_uid = false,
827 $no_smart_dt = false) {
829 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
830 if (!$timestamp) $timestamp = '1970-01-01 0:00';
835 # We store date in UTC internally
836 $dt = new DateTime($timestamp, $utc_tz);
838 if ($tz_offset == -1) {
840 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $owner_uid);
843 $user_tz = new DateTimeZone($user_tz_string);
844 } catch (Exception
$e) {
848 $tz_offset = $user_tz->getOffset($dt);
851 $user_timestamp = $dt->format('U') +
$tz_offset;
854 return smart_date_time($link, $user_timestamp,
855 $tz_offset, $owner_uid);
858 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
860 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
862 return date($format, $user_timestamp);
866 function smart_date_time($link, $timestamp, $tz_offset = 0, $owner_uid = false) {
867 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
869 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
870 return date("G:i", $timestamp);
871 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
872 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
873 return date($format, $timestamp);
875 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
876 return date($format, $timestamp);
880 function sql_bool_to_bool($s) {
881 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
888 function bool_to_sql_bool($s) {
896 // Session caching removed due to causing wrong redirects to upgrade
897 // script when get_schema_version() is called on an obsolete session
898 // created on a previous schema version.
899 function get_schema_version($link, $nocache = false) {
900 global $schema_version;
902 if (!$schema_version) {
903 $result = db_query($link, "SELECT schema_version FROM ttrss_version");
904 $version = db_fetch_result($result, 0, "schema_version");
905 $schema_version = $version;
908 return $schema_version;
912 function sanity_check($link) {
913 require_once 'errors.php';
916 $schema_version = get_schema_version($link, true);
918 if ($schema_version != SCHEMA_VERSION
) {
922 if (DB_TYPE
== "mysql") {
923 $result = db_query($link, "SELECT true", false);
924 if (db_num_rows($result) != 1) {
929 if (db_escape_string($link, "testTEST") != "testTEST") {
933 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
936 function file_is_locked($filename) {
937 if (function_exists('flock')) {
938 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
940 if (flock($fp, LOCK_EX | LOCK_NB
)) {
951 return true; // consider the file always locked and skip the test
954 function make_lockfile($filename) {
955 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
957 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
958 if (function_exists('posix_getpid')) {
959 fwrite($fp, posix_getpid() . "\n");
967 function make_stampfile($filename) {
968 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
970 if (flock($fp, LOCK_EX | LOCK_NB
)) {
971 fwrite($fp, time() . "\n");
980 function sql_random_function() {
981 if (DB_TYPE
== "mysql") {
988 function catchup_feed($link, $feed, $cat_view, $owner_uid = false, $max_id = false) {
990 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
992 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
994 $ref_check_qpart = ($max_id &&
995 !get_pref($link, 'REVERSE_HEADLINES')) ?
"ref_id <= '$max_id'" : "true";
997 if (is_numeric($feed)) {
1003 $children = getChildCategories($link, $feed, $owner_uid);
1004 array_push($children, $feed);
1006 $children = join(",", $children);
1008 $cat_qpart = "cat_id IN ($children)";
1010 $cat_qpart = "cat_id IS NULL";
1013 db_query($link, "UPDATE ttrss_user_entries
1014 SET unread = false,last_read = NOW()
1015 WHERE feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart)
1016 AND $ref_check_qpart AND unread = true
1017 AND owner_uid = $owner_uid");
1019 } else if ($feed == -2) {
1021 db_query($link, "UPDATE ttrss_user_entries
1022 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1023 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1024 AND $ref_check_qpart
1025 AND unread = true AND owner_uid = $owner_uid");
1028 } else if ($feed > 0) {
1030 db_query($link, "UPDATE ttrss_user_entries
1031 SET unread = false,last_read = NOW()
1032 WHERE feed_id = '$feed'
1033 AND $ref_check_qpart AND unread = true
1034 AND owner_uid = $owner_uid");
1036 } else if ($feed < 0 && $feed > -10) { // special, like starred
1039 db_query($link, "UPDATE ttrss_user_entries
1040 SET unread = false,last_read = NOW()
1042 AND $ref_check_qpart AND unread = true
1043 AND owner_uid = $owner_uid");
1047 db_query($link, "UPDATE ttrss_user_entries
1048 SET unread = false,last_read = NOW()
1049 WHERE published = true
1050 AND $ref_check_qpart AND unread = true
1051 AND owner_uid = $owner_uid");
1056 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE");
1058 if (DB_TYPE
== "pgsql") {
1059 $match_part = "updated > NOW() - INTERVAL '$intl hour' ";
1061 $match_part = "updated > DATE_SUB(NOW(),
1062 INTERVAL $intl HOUR) ";
1065 $result = db_query($link, "SELECT id FROM ttrss_entries,
1066 ttrss_user_entries WHERE $match_part AND
1068 ttrss_user_entries.ref_id = ttrss_entries.id AND
1069 owner_uid = $owner_uid");
1071 $affected_ids = array();
1073 while ($line = db_fetch_assoc($result)) {
1074 array_push($affected_ids, $line["id"]);
1077 catchupArticlesById($link, $affected_ids, 0);
1081 db_query($link, "UPDATE ttrss_user_entries
1082 SET unread = false,last_read = NOW()
1083 WHERE $ref_check_qpart AND unread = true AND
1084 owner_uid = $owner_uid");
1087 } else if ($feed < -10) { // label
1089 $label_id = -$feed - 11;
1091 db_query($link, "UPDATE ttrss_user_entries, ttrss_user_labels2
1092 SET unread = false, last_read = NOW()
1093 WHERE label_id = '$label_id' AND unread = true
1094 AND $ref_check_qpart
1095 AND owner_uid = '$owner_uid' AND ref_id = article_id");
1099 ccache_update($link, $feed, $owner_uid, $cat_view);
1102 db_query($link, "BEGIN");
1104 $tag_name = db_escape_string($link, $feed);
1106 $result = db_query($link, "SELECT post_int_id FROM ttrss_tags
1107 WHERE tag_name = '$tag_name' AND owner_uid = $owner_uid");
1109 while ($line = db_fetch_assoc($result)) {
1110 db_query($link, "UPDATE ttrss_user_entries SET
1111 unread = false, last_read = NOW()
1112 WHERE $ref_check_qpart AND unread = true
1113 AND int_id = " . $line["post_int_id"]);
1115 db_query($link, "COMMIT");
1119 function getAllCounters($link) {
1120 $data = getGlobalCounters($link);
1122 $data = array_merge($data, getVirtCounters($link));
1123 $data = array_merge($data, getLabelCounters($link));
1124 $data = array_merge($data, getFeedCounters($link, $active_feed));
1125 $data = array_merge($data, getCategoryCounters($link));
1130 function getCategoryTitle($link, $cat_id) {
1132 if ($cat_id == -1) {
1133 return __("Special");
1134 } else if ($cat_id == -2) {
1135 return __("Labels");
1138 $result = db_query($link, "SELECT title FROM ttrss_feed_categories WHERE
1141 if (db_num_rows($result) == 1) {
1142 return db_fetch_result($result, 0, "title");
1144 return __("Uncategorized");
1150 function getCategoryCounters($link) {
1153 /* Labels category */
1155 $cv = array("id" => -2, "kind" => "cat",
1156 "counter" => getCategoryUnread($link, -2));
1158 array_push($ret_arr, $cv);
1160 $result = db_query($link, "SELECT id AS cat_id, value AS unread,
1161 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1162 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1163 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1164 WHERE ttrss_cat_counters_cache.feed_id = id AND
1165 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1166 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1168 while ($line = db_fetch_assoc($result)) {
1169 $line["cat_id"] = (int) $line["cat_id"];
1171 if ($line["num_children"] > 0) {
1172 $child_counter = getCategoryChildrenUnread($link, $line["cat_id"], $_SESSION["uid"]);
1177 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1178 "counter" => $line["unread"] +
$child_counter);
1180 array_push($ret_arr, $cv);
1183 /* Special case: NULL category doesn't actually exist in the DB */
1185 $cv = array("id" => 0, "kind" => "cat",
1186 "counter" => (int) ccache_find($link, 0, $_SESSION["uid"], true));
1188 array_push($ret_arr, $cv);
1193 // only accepts real cats (>= 0)
1194 function getCategoryChildrenUnread($link, $cat, $owner_uid = false) {
1195 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1197 $result = db_query($link, "SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1198 AND owner_uid = $owner_uid");
1202 while ($line = db_fetch_assoc($result)) {
1203 $unread +
= getCategoryUnread($link, $line["id"], $owner_uid);
1204 $unread +
= getCategoryChildrenUnread($link, $line["id"], $owner_uid);
1210 function getCategoryUnread($link, $cat, $owner_uid = false) {
1212 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1217 $cat_query = "cat_id = '$cat'";
1219 $cat_query = "cat_id IS NULL";
1222 $result = db_query($link, "SELECT id FROM ttrss_feeds WHERE $cat_query
1223 AND owner_uid = " . $owner_uid);
1225 $cat_feeds = array();
1226 while ($line = db_fetch_assoc($result)) {
1227 array_push($cat_feeds, "feed_id = " . $line["id"]);
1230 if (count($cat_feeds) == 0) return 0;
1232 $match_part = implode(" OR ", $cat_feeds);
1234 $result = db_query($link, "SELECT COUNT(int_id) AS unread
1235 FROM ttrss_user_entries
1236 WHERE unread = true AND ($match_part)
1237 AND owner_uid = " . $owner_uid);
1241 # this needs to be rewritten
1242 while ($line = db_fetch_assoc($result)) {
1243 $unread +
= $line["unread"];
1247 } else if ($cat == -1) {
1248 return getFeedUnread($link, -1) +
getFeedUnread($link, -2) +
getFeedUnread($link, -3) +
getFeedUnread($link, 0);
1249 } else if ($cat == -2) {
1251 $result = db_query($link, "
1252 SELECT COUNT(unread) AS unread FROM
1253 ttrss_user_entries, ttrss_user_labels2
1254 WHERE article_id = ref_id AND unread = true
1255 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1257 $unread = db_fetch_result($result, 0, "unread");
1264 function getFeedUnread($link, $feed, $is_cat = false) {
1265 return getFeedArticles($link, $feed, $is_cat, true, $_SESSION["uid"]);
1268 function getLabelUnread($link, $label_id, $owner_uid = false) {
1269 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1271 $result = db_query($link, "SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1272 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1274 if (db_num_rows($result) != 0) {
1275 return db_fetch_result($result, 0, "unread");
1281 function getFeedArticles($link, $feed, $is_cat = false, $unread_only = false,
1282 $owner_uid = false) {
1284 $n_feed = (int) $feed;
1285 $need_entries = false;
1287 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1290 $unread_qpart = "unread = true";
1292 $unread_qpart = "true";
1296 return getCategoryUnread($link, $n_feed, $owner_uid);
1297 } else if ($n_feed == -6) {
1299 } else if ($feed != "0" && $n_feed == 0) {
1301 $feed = db_escape_string($link, $feed);
1303 $result = db_query($link, "SELECT SUM((SELECT COUNT(int_id)
1304 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1305 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1306 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1307 return db_fetch_result($result, 0, "count");
1309 } else if ($n_feed == -1) {
1310 $match_part = "marked = true";
1311 } else if ($n_feed == -2) {
1312 $match_part = "published = true";
1313 } else if ($n_feed == -3) {
1314 $match_part = "unread = true AND score >= 0";
1316 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
1318 if (DB_TYPE
== "pgsql") {
1319 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1321 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1324 $need_entries = true;
1326 } else if ($n_feed == -4) {
1327 $match_part = "true";
1328 } else if ($n_feed >= 0) {
1331 $match_part = "feed_id = '$n_feed'";
1333 $match_part = "feed_id IS NULL";
1336 } else if ($feed < -10) {
1338 $label_id = -$feed - 11;
1340 return getLabelUnread($link, $label_id, $owner_uid);
1346 if ($need_entries) {
1347 $from_qpart = "ttrss_user_entries,ttrss_entries";
1348 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1350 $from_qpart = "ttrss_user_entries";
1353 $query = "SELECT count(int_id) AS unread
1354 FROM $from_qpart WHERE
1355 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1357 //echo "[$feed/$query]\n";
1359 $result = db_query($link, $query);
1363 $result = db_query($link, "SELECT COUNT(post_int_id) AS unread
1364 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1365 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1366 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1369 $unread = db_fetch_result($result, 0, "unread");
1374 function getGlobalUnread($link, $user_id = false) {
1377 $user_id = $_SESSION["uid"];
1380 $result = db_query($link, "SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1381 WHERE owner_uid = '$user_id' AND feed_id > 0");
1383 $c_id = db_fetch_result($result, 0, "c_id");
1388 function getGlobalCounters($link, $global_unread = -1) {
1391 if ($global_unread == -1) {
1392 $global_unread = getGlobalUnread($link);
1395 $cv = array("id" => "global-unread",
1396 "counter" => (int) $global_unread);
1398 array_push($ret_arr, $cv);
1400 $result = db_query($link, "SELECT COUNT(id) AS fn FROM
1401 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1403 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1405 $cv = array("id" => "subscribed-feeds",
1406 "counter" => (int) $subscribed_feeds);
1408 array_push($ret_arr, $cv);
1413 function getVirtCounters($link) {
1417 for ($i = 0; $i >= -4; $i--) {
1419 $count = getFeedUnread($link, $i);
1421 $cv = array("id" => $i,
1422 "counter" => (int) $count);
1424 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1425 // $cv["xmsg"] = getFeedArticles($link, $i)." ".__("total");
1427 array_push($ret_arr, $cv);
1433 function getLabelCounters($link, $descriptions = false) {
1437 $owner_uid = $_SESSION["uid"];
1439 $result = db_query($link, "SELECT id,caption,COUNT(unread) AS unread
1440 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1441 (ttrss_labels2.id = label_id)
1442 LEFT JOIN ttrss_user_entries ON (ref_id = article_id AND unread = true)
1443 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1444 ttrss_labels2.caption");
1446 while ($line = db_fetch_assoc($result)) {
1448 $id = -$line["id"] - 11;
1450 $label_name = $line["caption"];
1451 $count = $line["unread"];
1453 $cv = array("id" => $id,
1454 "counter" => (int) $count);
1457 $cv["description"] = $label_name;
1459 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1460 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1462 array_push($ret_arr, $cv);
1468 function getFeedCounters($link, $active_feed = false) {
1472 $query = "SELECT ttrss_feeds.id,
1474 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1475 last_error, value AS count
1476 FROM ttrss_feeds, ttrss_counters_cache
1477 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1478 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1479 AND ttrss_counters_cache.feed_id = id";
1481 $result = db_query($link, $query);
1482 $fctrs_modified = false;
1484 while ($line = db_fetch_assoc($result)) {
1487 $count = $line["count"];
1488 $last_error = htmlspecialchars($line["last_error"]);
1490 $last_updated = make_local_datetime($link, $line['last_updated'], false);
1492 $has_img = feed_has_icon($id);
1494 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1497 $cv = array("id" => $id,
1498 "updated" => $last_updated,
1499 "counter" => (int) $count,
1500 "has_img" => (int) $has_img);
1503 $cv["error"] = $last_error;
1505 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1506 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1508 if ($active_feed && $id == $active_feed)
1509 $cv["title"] = truncate_string($line["title"], 30);
1511 array_push($ret_arr, $cv);
1518 function get_pgsql_version($link) {
1519 $result = db_query($link, "SELECT version() AS version");
1520 $version = explode(" ", db_fetch_result($result, 0, "version"));
1525 * @return array (code => Status code, message => error message if available)
1527 * 0 - OK, Feed already exists
1528 * 1 - OK, Feed added
1530 * 3 - URL content is HTML, no feeds available
1531 * 4 - URL content is HTML which contains multiple feeds.
1532 * Here you should call extractfeedurls in rpc-backend
1533 * to get all possible feeds.
1534 * 5 - Couldn't download the URL content.
1536 function subscribe_to_feed($link, $url, $cat_id = 0,
1537 $auth_login = '', $auth_pass = '', $need_auth = false) {
1539 global $fetch_last_error;
1541 require_once "include/rssfuncs.php";
1543 $url = fix_url($url);
1545 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1547 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1550 return array("code" => 5, "message" => $fetch_last_error);
1553 if (is_html($contents)) {
1554 $feedUrls = get_feeds_from_html($url, $contents);
1556 if (count($feedUrls) == 0) {
1557 return array("code" => 3);
1558 } else if (count($feedUrls) > 1) {
1559 return array("code" => 4, "feeds" => $feedUrls);
1561 //use feed url as new URL
1562 $url = key($feedUrls);
1565 if ($cat_id == "0" ||
!$cat_id) {
1566 $cat_qpart = "NULL";
1568 $cat_qpart = "'$cat_id'";
1571 $result = db_query($link,
1572 "SELECT id FROM ttrss_feeds
1573 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1575 if (db_num_rows($result) == 0) {
1576 $result = db_query($link,
1577 "INSERT INTO ttrss_feeds
1578 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method)
1579 VALUES ('".$_SESSION["uid"]."', '$url',
1580 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0)");
1582 $result = db_query($link,
1583 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1584 AND owner_uid = " . $_SESSION["uid"]);
1586 $feed_id = db_fetch_result($result, 0, "id");
1589 update_rss_feed($link, $feed_id, true);
1592 return array("code" => 1);
1594 return array("code" => 0);
1598 function print_feed_select($link, $id, $default_id = "",
1599 $attributes = "", $include_all_feeds = true,
1600 $root_id = false, $nest_level = 0) {
1603 print "<select id=\"$id\" name=\"$id\" $attributes>";
1604 if ($include_all_feeds) {
1605 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1606 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1610 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1613 $parent_qpart = "parent_cat = '$root_id'";
1615 $parent_qpart = "parent_cat IS NULL";
1617 $result = db_query($link, "SELECT id,title,
1618 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1619 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1620 FROM ttrss_feed_categories
1621 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1623 while ($line = db_fetch_assoc($result)) {
1625 for ($i = 0; $i < $nest_level; $i++
)
1626 $line["title"] = " - " . $line["title"];
1628 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1630 printf("<option $is_selected value='CAT:%d'>%s</option>",
1631 $line["id"], htmlspecialchars($line["title"]));
1633 if ($line["num_children"] > 0)
1634 print_feed_select($link, $id, $default_id, $attributes,
1635 $include_all_feeds, $line["id"], $nest_level+
1);
1637 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1638 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1640 while ($fline = db_fetch_assoc($feed_result)) {
1641 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1643 $fline["title"] = " + " . $fline["title"];
1645 for ($i = 0; $i < $nest_level; $i++
)
1646 $fline["title"] = " - " . $fline["title"];
1648 printf("<option $is_selected value='%d'>%s</option>",
1649 $fline["id"], htmlspecialchars($fline["title"]));
1654 $is_selected = ($default_id == "CAT:0") ?
"selected=\"1\"" : "";
1656 printf("<option $is_selected value='CAT:0'>%s</option>",
1657 __("Uncategorized"));
1659 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1660 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1662 while ($fline = db_fetch_assoc($feed_result)) {
1663 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1665 $fline["title"] = " + " . $fline["title"];
1667 for ($i = 0; $i < $nest_level; $i++
)
1668 $fline["title"] = " - " . $fline["title"];
1670 printf("<option $is_selected value='%d'>%s</option>",
1671 $fline["id"], htmlspecialchars($fline["title"]));
1676 $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1677 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1679 while ($line = db_fetch_assoc($result)) {
1681 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1683 printf("<option $is_selected value='%d'>%s</option>",
1684 $line["id"], htmlspecialchars($line["title"]));
1693 function print_feed_cat_select($link, $id, $default_id,
1694 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1697 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1701 $parent_qpart = "parent_cat = '$root_id'";
1703 $parent_qpart = "parent_cat IS NULL";
1705 $result = db_query($link, "SELECT id,title,
1706 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1707 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1708 FROM ttrss_feed_categories
1709 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1711 while ($line = db_fetch_assoc($result)) {
1712 if ($line["id"] == $default_id) {
1713 $is_selected = "selected=\"1\"";
1718 for ($i = 0; $i < $nest_level; $i++
)
1719 $line["title"] = " - " . $line["title"];
1722 printf("<option $is_selected value='%d'>%s</option>",
1723 $line["id"], htmlspecialchars($line["title"]));
1725 if ($line["num_children"] > 0)
1726 print_feed_cat_select($link, $id, $default_id, $attributes,
1727 $include_all_cats, $line["id"], $nest_level+
1);
1731 if ($include_all_cats) {
1732 if (db_num_rows($result) > 0) {
1733 print "<option disabled=\"1\">--------</option>";
1736 if ($default_id == 0) {
1737 $is_selected = "selected=\"1\"";
1742 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1748 function checkbox_to_sql_bool($val) {
1749 return ($val == "on") ?
"true" : "false";
1752 function getFeedCatTitle($link, $id) {
1754 return __("Special");
1755 } else if ($id < -10) {
1756 return __("Labels");
1757 } else if ($id > 0) {
1758 $result = db_query($link, "SELECT ttrss_feed_categories.title
1759 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1760 cat_id = ttrss_feed_categories.id");
1761 if (db_num_rows($result) == 1) {
1762 return db_fetch_result($result, 0, "title");
1764 return __("Uncategorized");
1767 return "getFeedCatTitle($id) failed";
1772 function getFeedIcon($id) {
1775 return "images/archive.png";
1778 return "images/mark_set.svg";
1781 return "images/pub_set.svg";
1784 return "images/fresh.png";
1787 return "images/tag.png";
1790 return "images/recently_read.png";
1794 return "images/label.png";
1796 if (file_exists(ICONS_DIR
. "/$id.ico"))
1797 return ICONS_URL
. "/$id.ico";
1803 function getFeedTitle($link, $id, $cat = false) {
1805 return getCategoryTitle($link, $id);
1806 } else if ($id == -1) {
1807 return __("Starred articles");
1808 } else if ($id == -2) {
1809 return __("Published articles");
1810 } else if ($id == -3) {
1811 return __("Fresh articles");
1812 } else if ($id == -4) {
1813 return __("All articles");
1814 } else if ($id === 0 ||
$id === "0") {
1815 return __("Archived articles");
1816 } else if ($id == -6) {
1817 return __("Recently read");
1818 } else if ($id < -10) {
1819 $label_id = -$id - 11;
1820 $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1821 if (db_num_rows($result) == 1) {
1822 return db_fetch_result($result, 0, "caption");
1824 return "Unknown label ($label_id)";
1827 } else if (is_numeric($id) && $id > 0) {
1828 $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1829 if (db_num_rows($result) == 1) {
1830 return db_fetch_result($result, 0, "title");
1832 return "Unknown feed ($id)";
1839 function make_init_params($link) {
1842 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1843 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1844 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", "DEFAULT_ARTICLE_LIMIT",
1845 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1847 $params[strtolower($param)] = (int) get_pref($link, $param);
1850 $params["icons_url"] = ICONS_URL
;
1851 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1852 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1853 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1854 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1855 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1857 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1858 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1860 $max_feed_id = db_fetch_result($result, 0, "mid");
1861 $num_feeds = db_fetch_result($result, 0, "nf");
1863 $params["max_feed_id"] = (int) $max_feed_id;
1864 $params["num_feeds"] = (int) $num_feeds;
1866 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1867 $params["hotkeys"] = get_hotkeys_map($link);
1869 $params["csrf_token"] = $_SESSION["csrf_token"];
1870 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1872 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1877 function get_hotkeys_info($link) {
1879 __("Navigation") => array(
1880 "next_feed" => __("Open next feed"),
1881 "prev_feed" => __("Open previous feed"),
1882 "next_article" => __("Open next article"),
1883 "prev_article" => __("Open previous article"),
1884 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1885 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1886 "search_dialog" => __("Show search dialog")),
1887 __("Article") => array(
1888 "toggle_mark" => __("Toggle starred"),
1889 "toggle_publ" => __("Toggle published"),
1890 "toggle_unread" => __("Toggle unread"),
1891 "edit_tags" => __("Edit tags"),
1892 "dismiss_selected" => __("Dismiss selected"),
1893 "dismiss_read" => __("Dismiss read"),
1894 "open_in_new_window" => __("Open in new window"),
1895 "catchup_below" => __("Mark below as read"),
1896 "catchup_above" => __("Mark above as read"),
1897 "article_scroll_down" => __("Scroll down"),
1898 "article_scroll_up" => __("Scroll up"),
1899 "select_article_cursor" => __("Select article under cursor"),
1900 "email_article" => __("Email article"),
1901 "close_article" => __("Close/collapse article"),
1902 "toggle_widescreen" => __("Toggle widescreen mode"),
1903 "toggle_embed_original" => __("Toggle embed original")),
1904 __("Article selection") => array(
1905 "select_all" => __("Select all articles"),
1906 "select_unread" => __("Select unread"),
1907 "select_marked" => __("Select starred"),
1908 "select_published" => __("Select published"),
1909 "select_invert" => __("Invert selection"),
1910 "select_none" => __("Deselect everything")),
1911 __("Feed") => array(
1912 "feed_refresh" => __("Refresh current feed"),
1913 "feed_unhide_read" => __("Un/hide read feeds"),
1914 "feed_subscribe" => __("Subscribe to feed"),
1915 "feed_edit" => __("Edit feed"),
1916 "feed_catchup" => __("Mark as read"),
1917 "feed_reverse" => __("Reverse headlines"),
1918 "feed_debug_update" => __("Debug feed update"),
1919 "catchup_all" => __("Mark all feeds as read"),
1920 "cat_toggle_collapse" => __("Un/collapse current category"),
1921 "toggle_combined_mode" => __("Toggle combined mode")),
1922 __("Go to") => array(
1923 "goto_all" => __("All articles"),
1924 "goto_fresh" => __("Fresh"),
1925 "goto_marked" => __("Starred"),
1926 "goto_published" => __("Published"),
1927 "goto_tagcloud" => __("Tag cloud"),
1928 "goto_prefs" => __("Preferences")),
1929 __("Other") => array(
1930 "create_label" => __("Create label"),
1931 "create_filter" => __("Create filter"),
1932 "collapse_sidebar" => __("Un/collapse sidebar"),
1933 "help_dialog" => __("Show help dialog"))
1939 function get_hotkeys_map($link) {
1941 // "navigation" => array(
1944 "n" => "next_article",
1945 "p" => "prev_article",
1946 "(38)|up" => "prev_article",
1947 "(40)|down" => "next_article",
1948 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1949 // "^(40)|Ctrl-down" => "next_article_noscroll",
1950 "(191)|/" => "search_dialog",
1951 // "article" => array(
1952 "s" => "toggle_mark",
1953 "*s" => "toggle_publ",
1954 "u" => "toggle_unread",
1955 "*t" => "edit_tags",
1956 "*d" => "dismiss_selected",
1957 "*x" => "dismiss_read",
1958 "o" => "open_in_new_window",
1959 "c p" => "catchup_below",
1960 "c n" => "catchup_above",
1961 "*n" => "article_scroll_down",
1962 "*p" => "article_scroll_up",
1963 "*(38)|Shift+up" => "article_scroll_up",
1964 "*(40)|Shift+down" => "article_scroll_down",
1965 "a *w" => "toggle_widescreen",
1966 "a e" => "toggle_embed_original",
1967 "e" => "email_article",
1968 "a q" => "close_article",
1969 // "article_selection" => array(
1970 "a a" => "select_all",
1971 "a u" => "select_unread",
1972 "a *u" => "select_marked",
1973 "a p" => "select_published",
1974 "a i" => "select_invert",
1975 "a n" => "select_none",
1977 "f r" => "feed_refresh",
1978 "f a" => "feed_unhide_read",
1979 "f s" => "feed_subscribe",
1980 "f e" => "feed_edit",
1981 "f q" => "feed_catchup",
1982 "f x" => "feed_reverse",
1983 "f *d" => "feed_debug_update",
1984 "f *c" => "toggle_combined_mode",
1985 "*q" => "catchup_all",
1986 "x" => "cat_toggle_collapse",
1988 "g a" => "goto_all",
1989 "g f" => "goto_fresh",
1990 "g s" => "goto_marked",
1991 "g p" => "goto_published",
1992 "g t" => "goto_tagcloud",
1993 "g *p" => "goto_prefs",
1994 // "other" => array(
1995 "(9)|Tab" => "select_article_cursor", // tab
1996 "c l" => "create_label",
1997 "c f" => "create_filter",
1998 "c s" => "collapse_sidebar",
1999 "^(191)|Ctrl+/" => "help_dialog",
2002 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
2003 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2004 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2008 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP
) as $plugin) {
2009 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2012 $prefixes = array();
2014 foreach (array_keys($hotkeys) as $hotkey) {
2015 $pair = explode(" ", $hotkey, 2);
2017 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2018 array_push($prefixes, $pair[0]);
2022 return array($prefixes, $hotkeys);
2025 function make_runtime_info($link) {
2028 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2029 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2031 $max_feed_id = db_fetch_result($result, 0, "mid");
2032 $num_feeds = db_fetch_result($result, 0, "nf");
2034 $data["max_feed_id"] = (int) $max_feed_id;
2035 $data["num_feeds"] = (int) $num_feeds;
2037 $data['last_article_id'] = getLastArticleId($link);
2038 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2040 $data['dep_ts'] = calculate_dep_timestamp();
2042 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2044 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2046 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2048 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2051 $stamp_delta = time() - $stamp;
2053 if ($stamp_delta > 1800) {
2057 $_SESSION["daemon_stamp_check"] = time();
2060 $data['daemon_stamp_ok'] = $stamp_check;
2062 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2064 $data['daemon_stamp'] = $stamp_fmt;
2069 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2070 $new_version_details = @check_for_update
($link);
2072 $data['new_version_available'] = (int) ($new_version_details != false);
2074 $_SESSION["last_version_check"] = time();
2075 $_SESSION["version_data"] = $new_version_details;
2081 function search_to_sql($link, $search) {
2083 $search_query_part = "";
2085 $keywords = explode(" ", $search);
2086 $query_keywords = array();
2088 foreach ($keywords as $k) {
2089 if (strpos($k, "-") === 0) {
2096 $commandpair = explode(":", mb_strtolower($k), 2);
2098 if ($commandpair[0] == "note" && $commandpair[1]) {
2100 if ($commandpair[1] == "true")
2101 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2103 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2105 } else if ($commandpair[0] == "star" && $commandpair[1]) {
2107 if ($commandpair[1] == "true")
2108 array_push($query_keywords, "($not (marked = true))");
2110 array_push($query_keywords, "($not (marked = false))");
2112 } else if ($commandpair[0] == "pub" && $commandpair[1]) {
2114 if ($commandpair[1] == "true")
2115 array_push($query_keywords, "($not (published = true))");
2117 array_push($query_keywords, "($not (published = false))");
2119 } else if (strpos($k, "@") === 0) {
2121 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2122 $orig_ts = strtotime(substr($k, 1));
2123 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2125 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2127 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2129 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2130 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2134 $search_query_part = implode("AND", $query_keywords);
2136 return $search_query_part;
2139 function getParentCategories($link, $cat, $owner_uid) {
2142 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2143 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2145 while ($line = db_fetch_assoc($result)) {
2146 array_push($rv, $line["parent_cat"]);
2147 $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2153 function getChildCategories($link, $cat, $owner_uid) {
2156 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2157 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2159 while ($line = db_fetch_assoc($result)) {
2160 array_push($rv, $line["id"]);
2161 $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2167 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) {
2169 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2171 $ext_tables_part = "";
2175 if (SPHINX_ENABLED
) {
2176 $ids = join(",", @sphinx_search
($search, 0, 500));
2179 $search_query_part = "ref_id IN ($ids) AND ";
2181 $search_query_part = "ref_id = -1 AND ";
2184 $search_query_part = search_to_sql($link, $search);
2185 $search_query_part .= " AND ";
2189 $search_query_part = "";
2194 if (DB_TYPE
== "pgsql") {
2195 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2197 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2200 $override_order = "updated DESC";
2202 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2204 // Try to check if SQL regexp implementation chokes on a valid regexp
2205 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2206 ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2207 WHERE $filter_query_part LIMIT 1", false);
2210 $test = db_fetch_result($result, 0, "true_val");
2213 $filter_query_part = "false AND";
2215 $filter_query_part .= " AND";
2218 $filter_query_part = "false AND";
2222 $filter_query_part = "";
2226 $since_id_part = "ttrss_entries.id > $since_id AND ";
2228 $since_id_part = "";
2231 $view_query_part = "";
2233 if ($view_mode == "adaptive" ||
$view_query_part == "noscores") {
2235 $view_query_part = " ";
2236 } else if ($feed != -1) {
2237 $unread = getFeedUnread($link, $feed, $cat_view);
2239 if ($cat_view && $feed > 0 && $include_children)
2240 $unread +
= getCategoryChildrenUnread($link, $feed);
2243 $view_query_part = " unread = true AND ";
2248 if ($view_mode == "marked") {
2249 $view_query_part = " marked = true AND ";
2252 if ($view_mode == "published") {
2253 $view_query_part = " published = true AND ";
2256 if ($view_mode == "unread") {
2257 $view_query_part = " unread = true AND ";
2260 if ($view_mode == "updated") {
2261 $view_query_part = " (last_read is null and unread = false) AND ";
2265 $limit_query_part = "LIMIT " . $limit;
2268 $allow_archived = false;
2270 $vfeed_query_part = "";
2272 // override query strategy and enable feed display when searching globally
2273 if ($search && $search_mode == "all_feeds") {
2274 $query_strategy_part = "true";
2275 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2277 } else if (!is_numeric($feed)) {
2278 $query_strategy_part = "true";
2279 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2280 id = feed_id) as feed_title,";
2281 } else if ($search && $search_mode == "this_cat") {
2282 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2285 if ($include_children) {
2286 $subcats = getChildCategories($link, $feed, $owner_uid);
2287 array_push($subcats, $feed);
2288 $cats_qpart = join(",", $subcats);
2290 $cats_qpart = $feed;
2293 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2296 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2299 } else if ($feed > 0) {
2304 if ($include_children) {
2306 $subcats = getChildCategories($link, $feed, $owner_uid);
2308 array_push($subcats, $feed);
2309 $query_strategy_part = "cat_id IN (".
2310 implode(",", $subcats).")";
2313 $query_strategy_part = "cat_id = '$feed'";
2317 $query_strategy_part = "cat_id IS NULL";
2320 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2323 $query_strategy_part = "feed_id = '$feed'";
2325 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2326 $query_strategy_part = "feed_id IS NULL";
2327 $allow_archived = true;
2328 } else if ($feed == 0 && $cat_view) { // uncategorized
2329 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2330 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2331 } else if ($feed == -1) { // starred virtual feed
2332 $query_strategy_part = "marked = true";
2333 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2334 $allow_archived = true;
2336 if (!$override_order) $override_order = "last_marked DESC, updated DESC";
2338 } else if ($feed == -2) { // published virtual feed OR labels category
2341 $query_strategy_part = "published = true";
2342 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2343 $allow_archived = true;
2345 if (!$override_order) $override_order = "last_published DESC, updated DESC";
2347 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2349 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2351 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2352 ttrss_user_labels2.article_id = ref_id";
2355 } else if ($feed == -6) { // recently read
2356 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2357 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2358 $allow_archived = true;
2360 if (!$override_order) $override_order = "last_read DESC";
2361 } else if ($feed == -3) { // fresh virtual feed
2362 $query_strategy_part = "unread = true AND score >= 0";
2364 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2366 if (DB_TYPE
== "pgsql") {
2367 $query_strategy_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
2369 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2372 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2373 } else if ($feed == -4) { // all articles virtual feed
2374 $query_strategy_part = "true";
2375 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2376 } else if ($feed <= -10) { // labels
2377 $label_id = -$feed - 11;
2379 $query_strategy_part = "label_id = '$label_id' AND
2380 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2381 ttrss_user_labels2.article_id = ref_id";
2383 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2384 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2385 $allow_archived = true;
2388 $query_strategy_part = "true";
2391 if (get_pref($link, "SORT_HEADLINES_BY_FEED_DATE", $owner_uid)) {
2392 $date_sort_field = "updated";
2394 $date_sort_field = "date_entered";
2397 if (get_pref($link, 'REVERSE_HEADLINES', $owner_uid)) {
2398 $order_by = "$date_sort_field";
2400 $order_by = "$date_sort_field DESC";
2403 if ($view_mode != "noscores") {
2404 $order_by = "score DESC, $order_by";
2407 if ($override_order) {
2408 $order_by = $override_order;
2414 $feed_title = T_sprintf("Search results: %s", $search);
2417 $feed_title = getCategoryTitle($link, $feed);
2419 if (is_numeric($feed) && $feed > 0) {
2420 $result = db_query($link, "SELECT title,site_url,last_error
2421 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2423 $feed_title = db_fetch_result($result, 0, "title");
2424 $feed_site_url = db_fetch_result($result, 0, "site_url");
2425 $last_error = db_fetch_result($result, 0, "last_error");
2427 $feed_title = getFeedTitle($link, $feed);
2432 $content_query_part = "content as content_preview, cached_content, ";
2434 if (is_numeric($feed)) {
2437 $feed_kind = "Feeds";
2439 $feed_kind = "Labels";
2442 if ($limit_query_part) {
2443 $offset_query_part = "OFFSET $offset";
2446 // proper override_order applied above
2447 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2448 if (!$override_order) {
2449 $order_by = "ttrss_feeds.title, $order_by";
2451 $order_by = "ttrss_feeds.title, $override_order";
2455 if (!$allow_archived) {
2456 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2457 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2460 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2461 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2464 $query = "SELECT DISTINCT
2467 ttrss_entries.id,ttrss_entries.title,
2471 always_display_enclosures,
2478 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2479 last_marked, last_published,
2480 ".SUBSTRING_FOR_DATE
."(last_read,1,19) as last_read_noms,
2483 ".SUBSTRING_FOR_DATE
."(updated,1,19) as updated_noms,
2489 ttrss_user_entries.ref_id = ttrss_entries.id AND
2490 ttrss_user_entries.owner_uid = '$owner_uid' AND
2495 $query_strategy_part ORDER BY $order_by
2496 $limit_query_part $offset_query_part";
2498 if ($_REQUEST["debug"]) print $query;
2500 $result = db_query($link, $query);
2505 $select_qpart = "SELECT DISTINCT " .
2509 "ttrss_entries.id as id," .
2522 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2523 "last_marked, last_published, " .
2524 SUBSTRING_FOR_DATE
. "(last_read,1,19) as last_read_noms," .
2527 $content_query_part .
2528 SUBSTRING_FOR_DATE
. "(updated,1,19) as updated_noms," .
2531 $feed_kind = "Tags";
2532 $all_tags = explode(",", $feed);
2533 if ($search_mode == 'any') {
2534 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2535 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2536 $where_qpart = " WHERE " .
2537 "ref_id = ttrss_entries.id AND " .
2538 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2539 "post_int_id = int_id AND $tag_sql AND " .
2541 $search_query_part .
2542 $query_strategy_part . " ORDER BY $order_by " .
2547 $sub_selects = array();
2548 $sub_ands = array();
2549 foreach ($all_tags as $term) {
2550 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");
2557 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2562 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2563 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2564 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2565 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2567 // error_log("TAG SQL: " . $tag_sql);
2568 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2570 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2571 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2574 return array($result, $feed_title, $feed_site_url, $last_error);
2578 function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2579 if (!$owner) $owner = $_SESSION["uid"];
2581 $res = trim($str); if (!$res) return '';
2583 if (strpos($res, "href=") === false)
2584 $res = rewrite_urls($res);
2586 $charset_hack = '<head>
2587 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2590 $res = trim($res); if (!$res) return '';
2592 libxml_use_internal_errors(true);
2594 $doc = new DOMDocument();
2595 $doc->loadHTML($charset_hack . $res);
2596 $xpath = new DOMXPath($doc);
2598 $entries = $xpath->query('(//a[@href]|//img[@src])');
2600 foreach ($entries as $entry) {
2604 if ($entry->hasAttribute('href'))
2605 $entry->setAttribute('href',
2606 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2608 if ($entry->hasAttribute('src')) {
2609 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2611 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2613 if (file_exists($cached_filename)) {
2614 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2617 $entry->setAttribute('src', $src);
2620 if ($entry->nodeName
== 'img') {
2621 if (($owner && get_pref($link, "STRIP_IMAGES", $owner)) ||
2622 $force_remove_images) {
2624 $p = $doc->createElement('p');
2626 $a = $doc->createElement('a');
2627 $a->setAttribute('href', $entry->getAttribute('src'));
2629 $a->appendChild(new DOMText($entry->getAttribute('src')));
2630 $a->setAttribute('target', '_blank');
2632 $p->appendChild($a);
2634 $entry->parentNode
->replaceChild($p, $entry);
2639 if (strtolower($entry->nodeName
) == "a") {
2640 $entry->setAttribute("target", "_blank");
2644 $entries = $xpath->query('//iframe');
2645 foreach ($entries as $entry) {
2646 $entry->setAttribute('sandbox', 'allow-scripts');
2652 if (isset($pluginhost)) {
2653 foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE
) as $plugin) {
2654 $doc = $plugin->hook_sanitize($doc, $site_url);
2658 $doc->removeChild($doc->firstChild
); //remove doctype
2659 $doc = strip_harmful_tags($doc);
2660 $res = $doc->saveHTML();
2664 function strip_harmful_tags($doc) {
2665 $entries = $doc->getElementsByTagName("*");
2667 $allowed_elements = array('a', 'address', 'audio', 'article',
2668 'b', 'big', 'blockquote', 'body', 'br', 'cite',
2669 'code', 'dd', 'del', 'details', 'div', 'dl', 'font',
2670 'dt', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
2671 'header', 'html', 'i', 'img', 'ins', 'kbd',
2672 'li', 'nav', 'ol', 'p', 'pre', 'q', 's','small',
2673 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2674 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
2675 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2677 if ($_SESSION['hasSandbox']) array_push($allowed_elements, 'iframe');
2679 $disallowed_attributes = array('id', 'style', 'class');
2681 foreach ($entries as $entry) {
2682 if (!in_array($entry->nodeName
, $allowed_elements)) {
2683 $entry->parentNode
->removeChild($entry);
2686 if ($entry->hasAttributes()) {
2687 foreach (iterator_to_array($entry->attributes
) as $attr) {
2689 if (strpos($attr->nodeName
, 'on') === 0) {
2690 $entry->removeAttributeNode($attr);
2693 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2694 $entry->removeAttributeNode($attr);
2703 function check_for_update($link) {
2704 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2705 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2706 "&iid=" . sha1(SELF_URL_PATH
);
2708 $version_data = @fetch_file_contents
($version_url);
2710 if ($version_data) {
2711 $version_data = json_decode($version_data, true);
2712 if ($version_data && $version_data['version']) {
2714 if (version_compare(VERSION
, $version_data['version']) == -1) {
2715 return $version_data;
2723 function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2725 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2726 if (count($ids) == 0) return;
2730 foreach ($ids as $id) {
2731 array_push($tmp_ids, "ref_id = '$id'");
2734 $ids_qpart = join(" OR ", $tmp_ids);
2737 db_query($link, "UPDATE ttrss_user_entries SET
2738 unread = false,last_read = NOW()
2739 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2740 } else if ($cmode == 1) {
2741 db_query($link, "UPDATE ttrss_user_entries SET
2743 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2745 db_query($link, "UPDATE ttrss_user_entries SET
2746 unread = NOT unread,last_read = NOW()
2747 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2752 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2753 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2755 while ($line = db_fetch_assoc($result)) {
2756 ccache_update($link, $line["feed_id"], $owner_uid);
2760 function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2762 $a_id = db_escape_string($link, $id);
2764 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2766 $query = "SELECT DISTINCT tag_name,
2767 owner_uid as owner FROM
2768 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2769 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2771 $obj_id = md5("TAGS:$owner_uid:$id");
2774 /* check cache first */
2776 if ($tag_cache === false) {
2777 $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2778 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2780 $tag_cache = db_fetch_result($result, 0, "tag_cache");
2784 $tags = explode(",", $tag_cache);
2787 /* do it the hard way */
2789 $tmp_result = db_query($link, $query);
2791 while ($tmp_line = db_fetch_assoc($tmp_result)) {
2792 array_push($tags, $tmp_line["tag_name"]);
2795 /* update the cache */
2797 $tags_str = db_escape_string($link, join(",", $tags));
2799 db_query($link, "UPDATE ttrss_user_entries
2800 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2801 AND owner_uid = $owner_uid");
2807 function trim_array($array) {
2809 array_walk($tmp, 'trim');
2813 function tag_is_valid($tag) {
2814 if ($tag == '') return false;
2815 if (preg_match("/^[0-9]*$/", $tag)) return false;
2816 if (mb_strlen($tag) > 250) return false;
2818 if (function_exists('iconv')) {
2819 $tag = iconv("utf-8", "utf-8", $tag);
2822 if (!$tag) return false;
2827 function render_login_form($link) {
2828 require_once "login_form.php";
2832 // from http://developer.apple.com/internet/safari/faq.html
2833 function no_cache_incantation() {
2834 header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
2835 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
2836 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
2837 header("Cache-Control: post-check=0, pre-check=0", false);
2838 header("Pragma: no-cache"); // HTTP/1.0
2841 function format_warning($msg, $id = "") {
2843 return "<div class=\"warning\" id=\"$id\">
2844 <img src=\"images/sign_excl.svg\">$msg</div>";
2847 function format_notice($msg, $id = "") {
2849 return "<div class=\"notice\" id=\"$id\">
2850 <img src=\"images/sign_info.svg\">$msg</div>";
2853 function format_error($msg, $id = "") {
2855 return "<div class=\"error\" id=\"$id\">
2856 <img src=\"images/sign_excl.svg\">$msg</div>";
2859 function print_notice($msg) {
2860 return print format_notice($msg);
2863 function print_warning($msg) {
2864 return print format_warning($msg);
2867 function print_error($msg) {
2868 return print format_error($msg);
2872 function T_sprintf() {
2873 $args = func_get_args();
2874 return vsprintf(__(array_shift($args)), $args);
2877 function format_inline_player($link, $url, $ctype) {
2881 $url = htmlspecialchars($url);
2883 if (strpos($ctype, "audio/") === 0) {
2885 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2886 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2887 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2889 $id = 'AUDIO-' . uniqid();
2891 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2892 <source type=\"$ctype\" src=\"$url\"></source>
2895 $entry .= "<span onclick=\"player(this)\"
2896 title=\"".__("Click to play")."\" status=\"0\"
2897 class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
2901 $entry .= "<object type=\"application/x-shockwave-flash\"
2902 data=\"lib/button/musicplayer.swf?song_url=$url\"
2903 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
2904 <param name=\"movie\"
2905 value=\"lib/button/musicplayer.swf?song_url=$url\" />
2909 if ($entry) $entry .= " <a target=\"_blank\"
2910 href=\"$url\">" . basename($url) . "</a>";
2918 /* $filename = substr($url, strrpos($url, "/")+1);
2920 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
2921 $filename . " (" . $ctype . ")" . "</a>"; */
2925 function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
2926 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2932 /* we can figure out feed_id from article id anyway, why do we
2933 * pass feed_id here? let's ignore the argument :( */
2935 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
2936 WHERE ref_id = '$id'");
2938 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
2940 $rv['feed_id'] = $feed_id;
2942 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
2944 if ($mark_as_read) {
2945 $result = db_query($link, "UPDATE ttrss_user_entries
2946 SET unread = false,last_read = NOW()
2947 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2949 ccache_update($link, $feed_id, $owner_uid);
2952 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
2953 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
2954 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
2955 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
2962 FROM ttrss_entries,ttrss_user_entries
2963 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
2967 $line = db_fetch_assoc($result);
2969 $tag_cache = $line["tag_cache"];
2971 $line["tags"] = get_article_tags($link, $id, $owner_uid, $line["tag_cache"]);
2972 unset($line["tag_cache"]);
2974 $line["content"] = sanitize($link, $line["content"], false, $owner_uid, $line["site_url"]);
2978 foreach ($pluginhost->get_hooks($pluginhost::HOOK_RENDER_ARTICLE
) as $p) {
2979 $line = $p->hook_render_article($line);
2982 $num_comments = $line["num_comments"];
2983 $entry_comments = "";
2985 if ($num_comments > 0) {
2986 if ($line["comments"]) {
2987 $comments_url = htmlspecialchars($line["comments"]);
2989 $comments_url = htmlspecialchars($line["link"]);
2991 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
2993 if ($line["comments"] && $line["link"] != $line["comments"]) {
2994 $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
2999 header("Content-Type: text/html");
3000 $rv['content'] .= "<html><head>
3001 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3002 <title>Tiny Tiny RSS - ".$line["title"]."</title>
3003 <link rel=\"stylesheet\" type=\"text/css\" href=\"tt-rss.css\">
3007 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3009 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3011 $entry_author = $line["author"];
3013 if ($entry_author) {
3014 $entry_author = __(" - ") . $entry_author;
3017 $parsed_updated = make_local_datetime($link, $line["updated"], true,
3020 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3022 if ($line["link"]) {
3023 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3024 title=\"".htmlspecialchars($line['title'])."\"
3026 htmlspecialchars($line["link"]) . "\">" .
3027 $line["title"] . "</a>" .
3028 "<span class='author'>$entry_author</span></div>";
3030 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3033 $tags_str = format_tags_string($line["tags"], $id);
3034 $tags_str_full = join(", ", $line["tags"]);
3036 if (!$tags_str_full) $tags_str_full = __("no tags");
3038 if (!$entry_comments) $entry_comments = " "; # placeholder
3040 $rv['content'] .= "<div class='postTags' style='float : right'>
3041 <img src='images/tag.png'
3042 class='tagsPic' alt='Tags' title='Tags'> ";
3045 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3046 <a title=\"".__('Edit tags for this article')."\"
3047 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3049 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3050 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3051 position=\"below\">$tags_str_full</div>";
3055 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON
) as $p) {
3056 $rv['content'] .= $p->hook_article_button($line);
3061 $tags_str = strip_tags($tags_str);
3062 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3064 $rv['content'] .= "</div>";
3065 $rv['content'] .= "<div clear='both'>$entry_comments</div>";
3067 if ($line["orig_feed_id"]) {
3069 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3070 WHERE id = ".$line["orig_feed_id"]);
3072 if (db_num_rows($tmp_result) != 0) {
3074 $rv['content'] .= "<div clear='both'>";
3075 $rv['content'] .= __("Originally from:");
3077 $rv['content'] .= " ";
3079 $tmp_line = db_fetch_assoc($tmp_result);
3081 $rv['content'] .= "<a target='_blank'
3082 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3083 $tmp_line['title'] . "</a>";
3085 $rv['content'] .= " ";
3087 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3088 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3090 $rv['content'] .= "</div>";
3094 $rv['content'] .= "</div>";
3096 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3097 if ($line['note']) {
3098 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3100 $rv['content'] .= "</div>";
3102 $rv['content'] .= "<div class=\"postContent\">";
3104 $rv['content'] .= $line["content"];
3106 $rv['content'] .= format_article_enclosures($link, $id,
3107 $always_display_enclosures, $line["content"], $line["hide_images"]);
3109 $rv['content'] .= "</div>";
3111 $rv['content'] .= "</div>";
3117 <div style=\"text-align : center\">
3118 <button onclick=\"return window.close()\">".
3119 __("Close this window")."</button></div>";
3120 $rv['content'] .= "</body></html>";
3127 function print_checkpoint($n, $s) {
3128 $ts = microtime(true);
3129 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3133 function sanitize_tag($tag) {
3136 $tag = mb_strtolower($tag, 'utf-8');
3138 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3140 // $tag = str_replace('"', "", $tag);
3141 // $tag = str_replace("+", " ", $tag);
3142 $tag = str_replace("technorati tag: ", "", $tag);
3147 function get_self_url_prefix() {
3148 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3149 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3151 return SELF_URL_PATH
;
3156 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3158 * @return string The Mozilla Firefox feed adding URL.
3160 function add_feed_url() {
3161 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3163 $url_path = get_self_url_prefix() .
3164 "/public.php?op=subscribe&feed_url=%s";
3166 } // function add_feed_url
3168 function encrypt_password($pass, $salt = '', $mode2 = false) {
3169 if ($salt && $mode2) {
3170 return "MODE2:" . hash('sha256', $salt . $pass);
3172 return "SHA1X:" . sha1("$salt:$pass");
3174 return "SHA1:" . sha1($pass);
3176 } // function encrypt_password
3178 function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3181 $cat_id = (int)getFeedCategory($link, $feed_id);
3183 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3184 owner_uid = $owner_uid AND enabled = true");
3186 $check_cats = join(",", array_merge(
3187 getParentCategories($link, $cat_id, $owner_uid),
3190 while ($line = db_fetch_assoc($result)) {
3191 $filter_id = $line["id"];
3193 $result2 = db_query($link, "SELECT
3194 r.reg_exp, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3195 FROM ttrss_filters2_rules AS r,
3196 ttrss_filter_types AS t
3198 (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3199 (feed_id IS NULL OR feed_id = '$feed_id') AND
3200 filter_type = t.id AND filter_id = '$filter_id'");
3205 while ($rule_line = db_fetch_assoc($result2)) {
3206 # print_r($rule_line);
3209 $rule["reg_exp"] = $rule_line["reg_exp"];
3210 $rule["type"] = $rule_line["type_name"];
3212 array_push($rules, $rule);
3215 $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3216 FROM ttrss_filters2_actions AS a,
3217 ttrss_filter_actions AS t
3219 action_id = t.id AND filter_id = '$filter_id'");
3221 while ($action_line = db_fetch_assoc($result2)) {
3222 # print_r($action_line);
3225 $action["type"] = $action_line["type_name"];
3226 $action["param"] = $action_line["action_param"];
3228 array_push($actions, $action);
3233 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3234 $filter["rules"] = $rules;
3235 $filter["actions"] = $actions;
3237 if (count($rules) > 0 && count($actions) > 0) {
3238 array_push($filters, $filter);
3245 function get_score_pic($score) {
3247 return "score_high.png";
3248 } else if ($score > 0) {
3249 return "score_half_high.png";
3250 } else if ($score < -100) {
3251 return "score_low.png";
3252 } else if ($score < 0) {
3253 return "score_half_low.png";
3255 return "score_neutral.png";
3259 function feed_has_icon($id) {
3260 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3263 function init_connection($link) {
3266 if (DB_TYPE
== "pgsql") {
3267 pg_query($link, "set client_encoding = 'UTF-8'");
3268 pg_set_client_encoding("UNICODE");
3269 pg_query($link, "set datestyle = 'ISO, european'");
3270 pg_query($link, "set TIME ZONE 0");
3272 db_query($link, "SET time_zone = '+0:0'");
3274 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET
) {
3275 db_query($link, "SET NAMES " . MYSQL_CHARSET
);
3281 $pluginhost = new PluginHost($link);
3282 $pluginhost->load(PLUGINS
, $pluginhost::KIND_ALL
);
3286 print "Unable to connect to database:" . db_last_error();
3291 function format_tags_string($tags, $id) {
3294 $tags_nolinks_str = "";
3300 $formatted_tags = array();
3302 foreach ($tags as $tag) {
3304 $tag_escaped = str_replace("'", "\\'", $tag);
3306 if (mb_strlen($tag) > 30) {
3307 $tag = truncate_string($tag, 30);
3310 $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3312 array_push($formatted_tags, $tag_str);
3314 $tmp_tags_str = implode(", ", $formatted_tags);
3316 if ($num_tags == $tag_limit ||
mb_strlen($tmp_tags_str) > 150) {
3321 $tags_str = implode(", ", $formatted_tags);
3323 if ($num_tags < count($tags)) {
3324 $tags_str .= ", …";
3327 if ($num_tags == 0) {
3328 $tags_str = __("no tags");
3335 function format_article_labels($labels, $id) {
3339 foreach ($labels as $l) {
3340 $labels_str .= sprintf("<span class='hlLabelRef'
3341 style='color : %s; background-color : %s'>%s</span>",
3342 $l[2], $l[3], $l[1]);
3349 function format_article_note($id, $note, $allow_edit = true) {
3351 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3352 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3353 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3359 function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3360 if ($parent_cat_id) {
3361 $parent_qpart = "parent_cat = '$parent_cat_id'";
3362 $parent_insert = "'$parent_cat_id'";
3364 $parent_qpart = "parent_cat IS NULL";
3365 $parent_insert = "NULL";
3368 $result = db_query($link,
3369 "SELECT id FROM ttrss_feed_categories
3370 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3372 if (db_num_rows($result) == 0) {
3375 return db_fetch_result($result, 0, "id");
3379 function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3381 if (!$feed_cat) return false;
3383 db_query($link, "BEGIN");
3385 if ($parent_cat_id) {
3386 $parent_qpart = "parent_cat = '$parent_cat_id'";
3387 $parent_insert = "'$parent_cat_id'";
3389 $parent_qpart = "parent_cat IS NULL";
3390 $parent_insert = "NULL";
3393 $result = db_query($link,
3394 "SELECT id FROM ttrss_feed_categories
3395 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3397 if (db_num_rows($result) == 0) {
3399 $result = db_query($link,
3400 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3401 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3403 db_query($link, "COMMIT");
3411 function getArticleFeed($link, $id) {
3412 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3413 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3415 if (db_num_rows($result) != 0) {
3416 return db_fetch_result($result, 0, "feed_id");
3423 * Fixes incomplete URLs by prepending "http://".
3424 * Also replaces feed:// with http://, and
3425 * prepends a trailing slash if the url is a domain name only.
3427 * @param string $url Possibly incomplete URL
3429 * @return string Fixed URL.
3431 function fix_url($url) {
3432 if (strpos($url, '://') === false) {
3433 $url = 'http://' . $url;
3434 } else if (substr($url, 0, 5) == 'feed:') {
3435 $url = 'http:' . substr($url, 5);
3438 //prepend slash if the URL has no slash in it
3439 // "http://www.example" -> "http://www.example/"
3440 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3444 if ($url != "http:///")
3450 function validate_feed_url($url) {
3451 $parts = parse_url($url);
3453 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3457 function get_article_enclosures($link, $id) {
3459 $query = "SELECT * FROM ttrss_enclosures
3460 WHERE post_id = '$id' AND content_url != ''";
3464 $result = db_query($link, $query);
3466 if (db_num_rows($result) > 0) {
3467 while ($line = db_fetch_assoc($result)) {
3468 array_push($rv, $line);
3475 function save_email_address($link, $email) {
3476 // FIXME: implement persistent storage of emails
3478 if (!$_SESSION['stored_emails'])
3479 $_SESSION['stored_emails'] = array();
3481 if (!in_array($email, $_SESSION['stored_emails']))
3482 array_push($_SESSION['stored_emails'], $email);
3486 function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3488 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3490 $sql_is_cat = bool_to_sql_bool($is_cat);
3492 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3493 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3494 AND owner_uid = " . $owner_uid);
3496 if (db_num_rows($result) == 1) {
3497 return db_fetch_result($result, 0, "access_key");
3499 $key = db_escape_string($link, sha1(uniqid(rand(), true)));
3501 $result = db_query($link, "INSERT INTO ttrss_access_keys
3502 (access_key, feed_id, is_cat, owner_uid)
3503 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3510 function get_feeds_from_html($url, $content)
3512 $url = fix_url($url);
3513 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3515 libxml_use_internal_errors(true);
3517 $doc = new DOMDocument();
3518 $doc->loadHTML($content);
3519 $xpath = new DOMXPath($doc);
3520 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3521 $feedUrls = array();
3522 foreach ($entries as $entry) {
3523 if ($entry->hasAttribute('href')) {
3524 $title = $entry->getAttribute('title');
3526 $title = $entry->getAttribute('type');
3528 $feedUrl = rewrite_relative_url(
3529 $baseUrl, $entry->getAttribute('href')
3531 $feedUrls[$feedUrl] = $title;
3537 function is_html($content) {
3538 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3541 function url_is_html($url, $login = false, $pass = false) {
3542 return is_html(fetch_file_contents($url, false, $login, $pass));
3545 function print_label_select($link, $name, $value, $attributes = "") {
3547 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3548 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3550 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3551 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3553 while ($line = db_fetch_assoc($result)) {
3555 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3557 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3558 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3562 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3569 function format_article_enclosures($link, $id, $always_display_enclosures,
3570 $article_content, $hide_images = false) {
3572 $result = get_article_enclosures($link, $id);
3575 if (count($result) > 0) {
3577 $entries_html = array();
3579 $entries_inline = array();
3581 foreach ($result as $line) {
3583 $url = $line["content_url"];
3584 $ctype = $line["content_type"];
3586 if (!$ctype) $ctype = __("unknown type");
3588 $filename = substr($url, strrpos($url, "/")+
1);
3590 $player = format_inline_player($link, $url, $ctype);
3592 if ($player) array_push($entries_inline, $player);
3594 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3595 # $filename . " (" . $ctype . ")" . "</a>";
3597 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3598 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3600 array_push($entries_html, $entry);
3604 $entry["type"] = $ctype;
3605 $entry["filename"] = $filename;
3606 $entry["url"] = $url;
3608 array_push($entries, $entry);
3611 if ($_SESSION['uid'] && !get_pref($link, "STRIP_IMAGES")) {
3612 if ($always_display_enclosures ||
3613 !preg_match("/<img/i", $article_content)) {
3615 foreach ($entries as $entry) {
3617 if (preg_match("/image/", $entry["type"]) ||
3618 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3620 if (!$hide_images) {
3622 alt=\"".htmlspecialchars($entry["filename"])."\"
3623 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3625 $rv .= "<p><a target=\"_blank\"
3626 href=\"".htmlspecialchars($entry["url"])."\"
3627 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3635 if (count($entries_inline) > 0) {
3636 $rv .= "<hr clear='both'/>";
3637 foreach ($entries_inline as $entry) { $rv .= $entry; };
3638 $rv .= "<hr clear='both'/>";
3641 $rv .= "<br/><div dojoType=\"dijit.form.DropDownButton\">".
3642 "<span>" . __('Attachments')."</span>";
3643 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3645 foreach ($entries_html as $entry) { $rv .= $entry; };
3647 $rv .= "</div></div>";
3653 function getLastArticleId($link) {
3654 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3655 WHERE owner_uid = " . $_SESSION["uid"]);
3657 if (db_num_rows($result) == 1) {
3658 return db_fetch_result($result, 0, "id");
3664 function build_url($parts) {
3665 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3669 * Converts a (possibly) relative URL to a absolute one.
3671 * @param string $url Base URL (i.e. from where the document is)
3672 * @param string $rel_url Possibly relative URL in the document
3674 * @return string Absolute URL
3676 function rewrite_relative_url($url, $rel_url) {
3677 if (strpos($rel_url, "magnet:") === 0) {
3679 } else if (strpos($rel_url, "://") !== false) {
3681 } else if (strpos($rel_url, "//") === 0) {
3682 # protocol-relative URL (rare but they exist)
3684 } else if (strpos($rel_url, "/") === 0)
3686 $parts = parse_url($url);
3687 $parts['path'] = $rel_url;
3689 return build_url($parts);
3692 $parts = parse_url($url);
3693 if (!isset($parts['path'])) {
3694 $parts['path'] = '/';
3696 $dir = $parts['path'];
3697 if (substr($dir, -1) !== '/') {
3698 $dir = dirname($parts['path']);
3699 $dir !== '/' && $dir .= '/';
3701 $parts['path'] = $dir . $rel_url;
3703 return build_url($parts);
3707 function sphinx_search($query, $offset = 0, $limit = 30) {
3708 require_once 'lib/sphinxapi.php';
3710 $sphinxClient = new SphinxClient();
3712 $sphinxClient->SetServer('localhost', 9312);
3713 $sphinxClient->SetConnectTimeout(1);
3715 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3716 'feed_title' => 20));
3718 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3719 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3720 $sphinxClient->SetLimits($offset, $limit, 1000);
3721 $sphinxClient->SetArrayResult(false);
3722 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3724 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3728 if (is_array($result['matches'])) {
3729 foreach (array_keys($result['matches']) as $int_id) {
3730 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3731 array_push($ids, $ref_id);
3738 function cleanup_tags($link, $days = 14, $limit = 1000) {
3740 if (DB_TYPE
== "pgsql") {
3741 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3742 } else if (DB_TYPE
== "mysql") {
3743 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3748 while ($limit > 0) {
3751 $query = "SELECT ttrss_tags.id AS id
3752 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3753 WHERE post_int_id = int_id AND $interval_query AND
3754 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3756 $result = db_query($link, $query);
3760 while ($line = db_fetch_assoc($result)) {
3761 array_push($ids, $line['id']);
3764 if (count($ids) > 0) {
3765 $ids = join(",", $ids);
3768 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3769 $tags_deleted +
= db_affected_rows($link, $tmp_result);
3774 $limit -= $limit_part;
3779 return $tags_deleted;
3782 function print_user_stylesheet($link) {
3783 $value = get_pref($link, 'USER_STYLESHEET');
3786 print "<style type=\"text/css\">";
3787 print str_replace("<br/>", "\n", $value);
3793 function rewrite_urls($html) {
3794 libxml_use_internal_errors(true);
3796 $charset_hack = '<head>
3797 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3800 $doc = new DOMDocument();
3801 $doc->loadHTML($charset_hack . $html);
3802 $xpath = new DOMXPath($doc);
3804 $entries = $xpath->query('//*/text()');
3806 foreach ($entries as $entry) {
3807 if (strstr($entry->wholeText
, "://") !== false) {
3808 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3809 "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText
);
3811 if ($text != $entry->wholeText
) {
3812 $cdoc = new DOMDocument();
3813 $cdoc->loadHTML($charset_hack . $text);
3816 foreach ($cdoc->childNodes
as $cnode) {
3817 $cnode = $doc->importNode($cnode, true);
3820 $entry->parentNode
->insertBefore($cnode);
3824 $entry->parentNode
->removeChild($entry);
3830 $node = $doc->getElementsByTagName('body')->item(0);
3832 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3834 return $doc->saveXML($node);
3839 function filter_to_sql($link, $filter, $owner_uid) {
3842 if (DB_TYPE
== "pgsql")
3845 $reg_qpart = "REGEXP";
3847 foreach ($filter["rules"] AS $rule) {
3848 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3849 $rule['reg_exp']) !== FALSE;
3851 if ($regexp_valid) {
3853 $rule['reg_exp'] = db_escape_string($link, $rule['reg_exp']);
3855 switch ($rule["type"]) {
3857 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3858 $rule['reg_exp'] . "')";
3861 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3862 $rule['reg_exp'] . "')";
3865 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3866 $rule['reg_exp'] . "') OR LOWER(" .
3867 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3870 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3871 $rule['reg_exp'] . "')";
3874 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3875 $rule['reg_exp'] . "')";
3878 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3879 $rule['reg_exp'] . "')";
3883 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3884 $qpart .= " AND feed_id = " . db_escape_string($link, $rule["feed_id"]);
3887 if (isset($rule["cat_id"])) {
3889 if ($rule["cat_id"] > 0) {
3890 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
3891 array_push($children, $rule["cat_id"]);
3893 $children = join(",", $children);
3895 $cat_qpart = "cat_id IN ($children)";
3897 $cat_qpart = "cat_id IS NULL";
3900 $qpart .= " AND $cat_qpart";
3903 array_push($query, "($qpart)");
3908 if (count($query) > 0) {
3909 return "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
3915 if (!function_exists('gzdecode')) {
3916 function gzdecode($string) { // no support for 2nd argument
3917 return file_get_contents('compress.zlib://data:who/cares;base64,'.
3918 base64_encode($string));
3922 function get_random_bytes($length) {
3923 if (function_exists('openssl_random_pseudo_bytes')) {
3924 return openssl_random_pseudo_bytes($length);
3928 for ($i = 0; $i < $length; $i++
)
3929 $output .= chr(mt_rand(0, 255));
3935 function read_stdin() {
3936 $fp = fopen("php://stdin", "r");
3939 $line = trim(fgets($fp));
3947 function tmpdirname($path, $prefix) {
3948 // Use PHP's tmpfile function to create a temporary
3949 // directory name. Delete the file and keep the name.
3950 $tempname = tempnam($path,$prefix);
3954 if (!unlink($tempname))
3960 function getFeedCategory($link, $feed) {
3961 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
3962 WHERE id = '$feed'");
3964 if (db_num_rows($result) > 0) {
3965 return db_fetch_result($result, 0, "cat_id");
3972 function implements_interface($class, $interface) {
3973 return in_array($interface, class_implements($class));
3976 function geturl($url){
3978 (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');
3980 $curl = curl_init();
3981 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
3982 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
3983 $header[] = "Cache-Control: max-age=0";
3984 $header[] = "Connection: keep-alive";
3985 $header[] = "Keep-Alive: 300";
3986 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
3987 $header[] = "Accept-Language: en-us,en;q=0.5";
3988 $header[] = "Pragma: ";
3990 curl_setopt($curl, CURLOPT_URL
, $url);
3991 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
3992 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
3993 curl_setopt($curl, CURLOPT_HEADER
, true);
3994 curl_setopt($curl, CURLOPT_REFERER
, $url);
3995 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
3996 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
3997 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
3998 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
3999 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4001 $html = curl_exec($curl);
4003 $status = curl_getinfo($curl);
4006 if($status['http_code']!=200){
4007 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4008 list($header) = explode("\r\n\r\n", $html, 2);
4010 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4011 $url = trim(str_replace($matches[1],"",$matches[0]));
4012 $url_parsed = parse_url($url);
4013 return (isset($url_parsed))?
geturl($url, $referer):'';
4016 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4017 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4018 # $handle = @fopen('./curl.error.log', 'a');
4019 # fwrite($handle, $line);
4025 function get_minified_js($files) {
4026 require_once 'lib/jshrink/Minifier.php';
4030 foreach ($files as $js) {
4031 if (!isset($_GET['debug'])) {
4032 $cached_file = CACHE_DIR
. "/js/$js.js";
4034 if (file_exists($cached_file) &&
4035 is_readable($cached_file) &&
4036 filemtime($cached_file) >= filemtime("js/$js.js")) {
4038 $rv .= file_get_contents($cached_file);
4041 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4042 file_put_contents($cached_file, $minified);
4046 $rv .= file_get_contents("js/$js.js");
4053 function stylesheet_tag($filename) {
4054 $timestamp = filemtime($filename);
4056 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4059 function javascript_tag($filename) {
4062 if (!(strpos($filename, "?") === FALSE)) {
4063 $query = substr($filename, strpos($filename, "?")+
1);
4064 $filename = substr($filename, 0, strpos($filename, "?"));
4067 $timestamp = filemtime($filename);
4069 if ($query) $timestamp .= "&$query";
4071 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4074 function calculate_dep_timestamp() {
4075 $files = array_merge(glob("js/*.js"), glob("*.css"));
4079 foreach ($files as $file) {
4080 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4086 function get_site_title() {
4087 if (defined("_SITE_TITLE")) {
4090 return "Tiny Tiny RSS";