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",
60 "pt_BR" => "Portuguese/Brazil",
61 "zh_CN" => "Simplified Chinese");
66 require_once "lib/accept-to-gettext.php";
67 require_once "lib/gettext/gettext.inc";
70 function startup_gettext() {
72 # Get locale from Accept-Language header
73 $lang = al2gt(array_keys(get_translations()), "text/html");
75 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
76 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
79 /* In login action of mobile version */
80 if ($_POST["language"] && defined('MOBILE_VERSION')) {
81 $lang = $_POST["language"];
82 } else if ($_SESSION["language"] && $_SESSION["language"] != "auto") {
83 $lang = $_SESSION["language"];
87 if (defined('LC_MESSAGES')) {
88 _setlocale(LC_MESSAGES
, $lang);
89 } else if (defined('LC_ALL')) {
90 _setlocale(LC_ALL
, $lang);
93 if (defined('MOBILE_VERSION')) {
94 _bindtextdomain("messages", "../locale");
96 _bindtextdomain("messages", "locale");
99 _textdomain("messages");
100 _bind_textdomain_codeset("messages", "UTF-8");
106 require_once 'db-prefs.php';
107 require_once 'version.php';
108 require_once 'ccache.php';
109 require_once 'labels.php';
111 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
112 ini_set('user_agent', SELF_USER_AGENT
);
114 require_once 'lib/pubsubhubbub/publisher.php';
117 $utc_tz = new DateTimeZone('UTC');
118 $schema_version = false;
121 * Print a timestamped debug message.
123 * @param string $msg The debug message.
126 function _debug($msg) {
127 $ts = strftime("%H:%M:%S", time());
128 if (function_exists('posix_getpid')) {
129 $ts = "$ts/" . posix_getpid();
132 if (!(defined('QUIET') && QUIET
)) {
133 print "[$ts] $msg\n";
136 if (defined('LOGFILE')) {
137 $fp = fopen(LOGFILE
, 'a+');
140 fputs($fp, "[$ts] $msg\n");
148 * Purge a feed old posts.
150 * @param mixed $link A database connection.
151 * @param mixed $feed_id The id of the purged feed.
152 * @param mixed $purge_interval Olderness of purged posts.
153 * @param boolean $debug Set to True to enable the debug. False by default.
157 function purge_feed($link, $feed_id, $purge_interval, $debug = false) {
159 if (!$purge_interval) $purge_interval = feed_purge_interval($link, $feed_id);
163 $result = db_query($link,
164 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
168 if (db_num_rows($result) == 1) {
169 $owner_uid = db_fetch_result($result, 0, "owner_uid");
172 if ($purge_interval == -1 ||
!$purge_interval) {
174 ccache_update($link, $feed_id, $owner_uid);
179 if (!$owner_uid) return;
181 if (FORCE_ARTICLE_PURGE
== 0) {
182 $purge_unread = get_pref($link, "PURGE_UNREAD_ARTICLES",
185 $purge_unread = true;
186 $purge_interval = FORCE_ARTICLE_PURGE
;
189 if (!$purge_unread) $query_limit = " unread = false AND ";
191 if (DB_TYPE
== "pgsql") {
192 $pg_version = get_pgsql_version($link);
194 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
196 $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
197 ttrss_entries.id = ref_id AND
199 feed_id = '$feed_id' AND
201 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
205 $result = db_query($link, "DELETE FROM ttrss_user_entries
207 WHERE ttrss_entries.id = ref_id AND
209 feed_id = '$feed_id' AND
211 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
214 $rows = pg_affected_rows($result);
218 /* $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
219 marked = false AND feed_id = '$feed_id' AND
220 (SELECT date_updated FROM ttrss_entries WHERE
221 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
223 $result = db_query($link, "DELETE FROM ttrss_user_entries
224 USING ttrss_user_entries, ttrss_entries
225 WHERE ttrss_entries.id = ref_id AND
227 feed_id = '$feed_id' AND
229 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
231 $rows = mysql_affected_rows($link);
235 ccache_update($link, $feed_id, $owner_uid);
238 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
242 } // function purge_feed
244 function feed_purge_interval($link, $feed_id) {
246 $result = db_query($link, "SELECT purge_interval, owner_uid FROM ttrss_feeds
247 WHERE id = '$feed_id'");
249 if (db_num_rows($result) == 1) {
250 $purge_interval = db_fetch_result($result, 0, "purge_interval");
251 $owner_uid = db_fetch_result($result, 0, "owner_uid");
253 if ($purge_interval == 0) $purge_interval = get_pref($link,
254 'PURGE_OLD_DAYS', $owner_uid);
256 return $purge_interval;
263 function purge_orphans($link, $do_output = false) {
265 // purge orphaned posts in main content table
266 $result = db_query($link, "DELETE FROM ttrss_entries WHERE
267 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
270 $rows = db_affected_rows($link, $result);
271 _debug("Purged $rows orphaned posts.");
275 function get_feed_update_interval($link, $feed_id) {
276 $result = db_query($link, "SELECT owner_uid, update_interval FROM
277 ttrss_feeds WHERE id = '$feed_id'");
279 if (db_num_rows($result) == 1) {
280 $update_interval = db_fetch_result($result, 0, "update_interval");
281 $owner_uid = db_fetch_result($result, 0, "owner_uid");
283 if ($update_interval != 0) {
284 return $update_interval;
286 return get_pref($link, 'DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
294 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false) {
295 $login = urlencode($login);
296 $pass = urlencode($pass);
298 global $fetch_last_error;
300 if (function_exists('curl_init') && !ini_get("open_basedir")) {
302 if (ini_get("safe_mode")) {
303 $ch = curl_init(geturl($url));
305 $ch = curl_init($url);
308 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : 15);
309 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : 45);
310 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("safe_mode"));
311 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
312 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
313 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
314 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
315 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
316 curl_setopt($ch, CURLOPT_USERAGENT
, SELF_USER_AGENT
);
317 curl_setopt($ch, CURLOPT_ENCODING
, "gzip");
318 curl_setopt($ch, CURLOPT_REFERER
, $url);
321 curl_setopt($ch, CURLOPT_POST
, true);
322 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
326 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
328 $contents = @curl_exec
($ch);
330 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
331 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
332 $contents = @curl_exec
($ch);
335 if ($contents === false) {
336 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
341 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
342 $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
344 if ($http_code != 200 ||
$type && strpos($content_type, "$type") === false) {
345 if (curl_errno($ch) != 0) {
346 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
348 $fetch_last_error = "HTTP Code: $http_code";
358 if ($login && $pass ){
359 $url_parts = array();
361 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
363 if ($url_parts[1] && $url_parts[2]) {
364 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
368 $data = @file_get_contents
($url);
370 $gzdecoded = gzdecode($data);
371 if ($gzdecoded) $data = $gzdecoded;
373 if (!$data && function_exists('error_get_last')) {
374 $error = error_get_last();
375 $fetch_last_error = $error["message"];
383 * Try to determine the favicon URL for a feed.
384 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
385 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
387 * @param string $url A feed or page URL
389 * @return mixed The favicon URL, or false if none was found.
391 function get_favicon_url($url) {
393 $favicon_url = false;
395 if ($html = @fetch_file_contents
($url)) {
397 libxml_use_internal_errors(true);
399 $doc = new DOMDocument();
400 $doc->loadHTML($html);
401 $xpath = new DOMXPath($doc);
403 $base = $xpath->query('/html/head/base');
404 foreach ($base as $b) {
405 $url = $b->getAttribute("href");
409 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
410 if (count($entries) > 0) {
411 foreach ($entries as $entry) {
412 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
419 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
422 } // function get_favicon_url
424 function check_feed_favicon($site_url, $feed, $link) {
425 # print "FAVICON [$site_url]: $favicon_url\n";
427 $icon_file = ICONS_DIR
. "/$feed.ico";
429 if (!file_exists($icon_file)) {
430 $favicon_url = get_favicon_url($site_url);
433 // Limiting to "image" type misses those served with text/plain
434 $contents = fetch_file_contents($favicon_url); // , "image");
437 // Crude image type matching.
438 // Patterns gleaned from the file(1) source code.
439 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
440 // 0 string \000\000\001\000 MS Windows icon resource
441 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
443 elseif (preg_match('/^GIF8/', $contents)) {
444 // 0 string GIF8 GIF image data
445 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
447 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
448 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
449 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
451 elseif (preg_match('/^\xff\xd8/', $contents)) {
452 // 0 beshort 0xffd8 JPEG image data
453 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
456 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
462 $fp = @fopen
($icon_file, "w");
465 fwrite($fp, $contents);
467 chmod($icon_file, 0644);
474 function print_select($id, $default, $values, $attributes = "") {
475 print "<select name=\"$id\" id=\"$id\" $attributes>";
476 foreach ($values as $v) {
478 $sel = "selected=\"1\"";
484 print "<option value=\"$v\" $sel>$v</option>";
489 function print_select_hash($id, $default, $values, $attributes = "") {
490 print "<select name=\"$id\" id='$id' $attributes>";
491 foreach (array_keys($values) as $v) {
493 $sel = 'selected="selected"';
499 print "<option $sel value=\"$v\">".$values[$v]."</option>";
505 function print_radio($id, $default, $true_is, $values, $attributes = "") {
506 foreach ($values as $v) {
513 if ($v == $true_is) {
514 $sel .= " value=\"1\"";
516 $sel .= " value=\"0\"";
519 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
520 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
525 function initialize_user_prefs($link, $uid, $profile = false) {
527 $uid = db_escape_string($uid);
531 $profile_qpart = "AND profile IS NULL";
533 $profile_qpart = "AND profile = '$profile'";
536 if (get_schema_version($link) < 63) $profile_qpart = "";
538 db_query($link, "BEGIN");
540 $result = db_query($link, "SELECT pref_name,def_value FROM ttrss_prefs");
542 $u_result = db_query($link, "SELECT pref_name
543 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
545 $active_prefs = array();
547 while ($line = db_fetch_assoc($u_result)) {
548 array_push($active_prefs, $line["pref_name"]);
551 while ($line = db_fetch_assoc($result)) {
552 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
553 // print "adding " . $line["pref_name"] . "<br>";
555 if (get_schema_version($link) < 63) {
556 db_query($link, "INSERT INTO ttrss_user_prefs
557 (owner_uid,pref_name,value) VALUES
558 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
561 db_query($link, "INSERT INTO ttrss_user_prefs
562 (owner_uid,pref_name,value, profile) VALUES
563 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
569 db_query($link, "COMMIT");
573 function get_ssl_certificate_id() {
574 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
575 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
576 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
577 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
578 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
583 function authenticate_user($link, $login, $password, $check_only = false) {
585 if (!SINGLE_USER_MODE
) {
590 foreach ($pluginhost->get_hooks($pluginhost::HOOK_AUTH_USER
) as $plugin) {
592 $user_id = (int) $plugin->authenticate($login, $password);
595 $_SESSION["auth_module"] = strtolower(get_class($plugin));
600 if ($user_id && !$check_only) {
601 $_SESSION["uid"] = $user_id;
603 $result = db_query($link, "SELECT login,access_level,pwd_hash FROM ttrss_users
604 WHERE id = '$user_id'");
606 $_SESSION["name"] = db_fetch_result($result, 0, "login");
607 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
608 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
610 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
613 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
614 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
616 $_SESSION["last_version_check"] = time();
618 initialize_user_prefs($link, $_SESSION["uid"]);
627 $_SESSION["uid"] = 1;
628 $_SESSION["name"] = "admin";
629 $_SESSION["access_level"] = 10;
631 $_SESSION["hide_hello"] = true;
632 $_SESSION["hide_logout"] = true;
634 $_SESSION["auth_module"] = false;
636 if (!$_SESSION["csrf_token"]) {
637 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
640 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
642 initialize_user_prefs($link, $_SESSION["uid"]);
648 function make_password($length = 8) {
651 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
655 while ($i < $length) {
656 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
658 if (!strstr($password, $char)) {
666 // this is called after user is created to initialize default feeds, labels
669 // user preferences are checked on every login, not here
671 function initialize_user($link, $uid) {
673 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
674 values ('$uid', 'Tiny Tiny RSS: New Releases',
675 'http://tt-rss.org/releases.rss')");
677 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
678 values ('$uid', 'Tiny Tiny RSS: Forum',
679 'http://tt-rss.org/forum/rss.php')");
682 function logout_user() {
684 if (isset($_COOKIE[session_name()])) {
685 setcookie(session_name(), '', time()-42000, '/');
689 function validate_csrf($csrf_token) {
690 return $csrf_token == $_SESSION['csrf_token'];
693 function validate_session($link) {
694 if (SINGLE_USER_MODE
) return true;
696 $check_ip = $_SESSION['ip_address'];
698 switch (SESSION_CHECK_ADDRESS
) {
703 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
706 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.'));
707 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
711 if ($check_ip && strpos($_SERVER['REMOTE_ADDR'], $check_ip) !== 0) {
712 $_SESSION["login_error_msg"] =
713 __("Session failed to validate (incorrect IP)");
717 if ($_SESSION["ref_schema_version"] != get_schema_version($link, true))
720 if ($_SESSION["uid"]) {
722 $result = db_query($link,
723 "SELECT pwd_hash FROM ttrss_users WHERE id = '".$_SESSION["uid"]."'");
725 $pwd_hash = db_fetch_result($result, 0, "pwd_hash");
727 if ($pwd_hash != $_SESSION["pwd_hash"]) {
732 /* if ($_SESSION["cookie_lifetime"] && $_SESSION["uid"]) {
734 //print_r($_SESSION);
736 if (time() > $_SESSION["cookie_lifetime"]) {
744 function load_user_plugins($link, $owner_uid) {
746 $plugins = get_pref($link, "_ENABLED_PLUGINS", $owner_uid);
749 $pluginhost->load($plugins, $pluginhost::KIND_USER
, $owner_uid);
751 if (get_schema_version($link) > 100) {
752 $pluginhost->load_data();
757 function login_sequence($link, $login_form = 0) {
758 $_SESSION["prefs_cache"] = false;
760 if (SINGLE_USER_MODE
) {
761 authenticate_user($link, "admin", null);
763 load_user_plugins($link, $_SESSION["uid"]);
765 if (!$_SESSION["uid"] ||
!validate_session($link)) {
767 if (AUTH_AUTO_LOGIN
&& authenticate_user($link, null, null)) {
768 $_SESSION["ref_schema_version"] = get_schema_version($link, true);
770 authenticate_user($link, null, null, true);
773 if (!$_SESSION["uid"]) render_login_form($link, $login_form);
776 /* bump login timestamp */
777 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
781 if ($_SESSION["uid"] && $_SESSION["language"] && SESSION_COOKIE_LIFETIME
> 0) {
782 setcookie("ttrss_lang", $_SESSION["language"],
783 time() + SESSION_COOKIE_LIFETIME
);
786 if ($_SESSION["uid"]) {
788 load_user_plugins($link, $_SESSION["uid"]);
793 function truncate_string($str, $max_len, $suffix = '…') {
794 if (mb_strlen($str, "utf-8") > $max_len - 3) {
795 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
801 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
804 $source_tz = new DateTimeZone($source_tz);
805 } catch (Exception
$e) {
806 $source_tz = new DateTimeZone('UTC');
810 $dest_tz = new DateTimeZone($dest_tz);
811 } catch (Exception
$e) {
812 $dest_tz = new DateTimeZone('UTC');
815 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
816 return $dt->format('U') +
$dest_tz->getOffset($dt);
819 function make_local_datetime($link, $timestamp, $long, $owner_uid = false,
820 $no_smart_dt = false) {
822 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
823 if (!$timestamp) $timestamp = '1970-01-01 0:00';
828 # We store date in UTC internally
829 $dt = new DateTime($timestamp, $utc_tz);
831 if ($tz_offset == -1) {
833 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $owner_uid);
836 $user_tz = new DateTimeZone($user_tz_string);
837 } catch (Exception
$e) {
841 $tz_offset = $user_tz->getOffset($dt);
844 $user_timestamp = $dt->format('U') +
$tz_offset;
847 return smart_date_time($link, $user_timestamp,
848 $tz_offset, $owner_uid);
851 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
853 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
855 return date($format, $user_timestamp);
859 function smart_date_time($link, $timestamp, $tz_offset = 0, $owner_uid = false) {
860 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
862 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
863 return date("G:i", $timestamp);
864 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
865 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
866 return date($format, $timestamp);
868 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
869 return date($format, $timestamp);
873 function sql_bool_to_bool($s) {
874 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
881 function bool_to_sql_bool($s) {
889 // Session caching removed due to causing wrong redirects to upgrade
890 // script when get_schema_version() is called on an obsolete session
891 // created on a previous schema version.
892 function get_schema_version($link, $nocache = false) {
893 global $schema_version;
895 if (!$schema_version) {
896 $result = db_query($link, "SELECT schema_version FROM ttrss_version");
897 $version = db_fetch_result($result, 0, "schema_version");
898 $schema_version = $version;
901 return $schema_version;
905 function sanity_check($link) {
906 require_once 'errors.php';
909 $schema_version = get_schema_version($link, true);
911 if ($schema_version != SCHEMA_VERSION
) {
915 if (DB_TYPE
== "mysql") {
916 $result = db_query($link, "SELECT true", false);
917 if (db_num_rows($result) != 1) {
922 if (db_escape_string("testTEST") != "testTEST") {
926 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
929 function file_is_locked($filename) {
930 if (function_exists('flock')) {
931 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
933 if (flock($fp, LOCK_EX | LOCK_NB
)) {
944 return true; // consider the file always locked and skip the test
947 function make_lockfile($filename) {
948 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
950 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
951 if (function_exists('posix_getpid')) {
952 fwrite($fp, posix_getpid() . "\n");
960 function make_stampfile($filename) {
961 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
963 if (flock($fp, LOCK_EX | LOCK_NB
)) {
964 fwrite($fp, time() . "\n");
973 function sql_random_function() {
974 if (DB_TYPE
== "mysql") {
981 function catchup_feed($link, $feed, $cat_view, $owner_uid = false, $max_id = false) {
983 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
985 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
987 $ref_check_qpart = ($max_id &&
988 !get_pref($link, 'REVERSE_HEADLINES')) ?
"ref_id <= '$max_id'" : "true";
990 if (is_numeric($feed)) {
996 $children = getChildCategories($link, $feed, $owner_uid);
997 array_push($children, $feed);
999 $children = join(",", $children);
1001 $cat_qpart = "cat_id IN ($children)";
1003 $cat_qpart = "cat_id IS NULL";
1006 db_query($link, "UPDATE ttrss_user_entries
1007 SET unread = false,last_read = NOW()
1008 WHERE feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart)
1009 AND $ref_check_qpart AND unread = true
1010 AND owner_uid = $owner_uid");
1012 } else if ($feed == -2) {
1014 db_query($link, "UPDATE ttrss_user_entries
1015 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1016 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1017 AND $ref_check_qpart
1018 AND unread = true AND owner_uid = $owner_uid");
1021 } else if ($feed > 0) {
1023 db_query($link, "UPDATE ttrss_user_entries
1024 SET unread = false,last_read = NOW()
1025 WHERE feed_id = '$feed'
1026 AND $ref_check_qpart AND unread = true
1027 AND owner_uid = $owner_uid");
1029 } else if ($feed < 0 && $feed > -10) { // special, like starred
1032 db_query($link, "UPDATE ttrss_user_entries
1033 SET unread = false,last_read = NOW()
1035 AND $ref_check_qpart AND unread = true
1036 AND owner_uid = $owner_uid");
1040 db_query($link, "UPDATE ttrss_user_entries
1041 SET unread = false,last_read = NOW()
1042 WHERE published = true
1043 AND $ref_check_qpart AND unread = true
1044 AND owner_uid = $owner_uid");
1049 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE");
1051 if (DB_TYPE
== "pgsql") {
1052 $match_part = "updated > NOW() - INTERVAL '$intl hour' ";
1054 $match_part = "updated > DATE_SUB(NOW(),
1055 INTERVAL $intl HOUR) ";
1058 $result = db_query($link, "SELECT id FROM ttrss_entries,
1059 ttrss_user_entries WHERE $match_part AND
1061 ttrss_user_entries.ref_id = ttrss_entries.id AND
1062 owner_uid = $owner_uid");
1064 $affected_ids = array();
1066 while ($line = db_fetch_assoc($result)) {
1067 array_push($affected_ids, $line["id"]);
1070 catchupArticlesById($link, $affected_ids, 0);
1074 db_query($link, "UPDATE ttrss_user_entries
1075 SET unread = false,last_read = NOW()
1076 WHERE $ref_check_qpart AND unread = true AND
1077 owner_uid = $owner_uid");
1080 } else if ($feed < -10) { // label
1082 $label_id = -$feed - 11;
1084 db_query($link, "UPDATE ttrss_user_entries, ttrss_user_labels2
1085 SET unread = false, last_read = NOW()
1086 WHERE label_id = '$label_id' AND unread = true
1087 AND $ref_check_qpart
1088 AND owner_uid = '$owner_uid' AND ref_id = article_id");
1092 ccache_update($link, $feed, $owner_uid, $cat_view);
1095 db_query($link, "BEGIN");
1097 $tag_name = db_escape_string($feed);
1099 $result = db_query($link, "SELECT post_int_id FROM ttrss_tags
1100 WHERE tag_name = '$tag_name' AND owner_uid = $owner_uid");
1102 while ($line = db_fetch_assoc($result)) {
1103 db_query($link, "UPDATE ttrss_user_entries SET
1104 unread = false, last_read = NOW()
1105 WHERE $ref_check_qpart AND unread = true
1106 AND int_id = " . $line["post_int_id"]);
1108 db_query($link, "COMMIT");
1112 function getAllCounters($link) {
1113 $data = getGlobalCounters($link);
1115 $data = array_merge($data, getVirtCounters($link));
1116 $data = array_merge($data, getLabelCounters($link));
1117 $data = array_merge($data, getFeedCounters($link, $active_feed));
1118 $data = array_merge($data, getCategoryCounters($link));
1123 function getCategoryTitle($link, $cat_id) {
1125 if ($cat_id == -1) {
1126 return __("Special");
1127 } else if ($cat_id == -2) {
1128 return __("Labels");
1131 $result = db_query($link, "SELECT title FROM ttrss_feed_categories WHERE
1134 if (db_num_rows($result) == 1) {
1135 return db_fetch_result($result, 0, "title");
1137 return __("Uncategorized");
1143 function getCategoryCounters($link) {
1146 /* Labels category */
1148 $cv = array("id" => -2, "kind" => "cat",
1149 "counter" => getCategoryUnread($link, -2));
1151 array_push($ret_arr, $cv);
1153 $result = db_query($link, "SELECT id AS cat_id, value AS unread,
1154 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1155 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1156 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1157 WHERE ttrss_cat_counters_cache.feed_id = id AND
1158 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1159 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1161 while ($line = db_fetch_assoc($result)) {
1162 $line["cat_id"] = (int) $line["cat_id"];
1164 if ($line["num_children"] > 0) {
1165 $child_counter = getCategoryChildrenUnread($link, $line["cat_id"], $_SESSION["uid"]);
1170 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1171 "counter" => $line["unread"] +
$child_counter);
1173 array_push($ret_arr, $cv);
1176 /* Special case: NULL category doesn't actually exist in the DB */
1178 $cv = array("id" => 0, "kind" => "cat",
1179 "counter" => (int) ccache_find($link, 0, $_SESSION["uid"], true));
1181 array_push($ret_arr, $cv);
1186 // only accepts real cats (>= 0)
1187 function getCategoryChildrenUnread($link, $cat, $owner_uid = false) {
1188 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1190 $result = db_query($link, "SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1191 AND owner_uid = $owner_uid");
1195 while ($line = db_fetch_assoc($result)) {
1196 $unread +
= getCategoryUnread($link, $line["id"], $owner_uid);
1197 $unread +
= getCategoryChildrenUnread($link, $line["id"], $owner_uid);
1203 function getCategoryUnread($link, $cat, $owner_uid = false) {
1205 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1210 $cat_query = "cat_id = '$cat'";
1212 $cat_query = "cat_id IS NULL";
1215 $result = db_query($link, "SELECT id FROM ttrss_feeds WHERE $cat_query
1216 AND owner_uid = " . $owner_uid);
1218 $cat_feeds = array();
1219 while ($line = db_fetch_assoc($result)) {
1220 array_push($cat_feeds, "feed_id = " . $line["id"]);
1223 if (count($cat_feeds) == 0) return 0;
1225 $match_part = implode(" OR ", $cat_feeds);
1227 $result = db_query($link, "SELECT COUNT(int_id) AS unread
1228 FROM ttrss_user_entries
1229 WHERE unread = true AND ($match_part)
1230 AND owner_uid = " . $owner_uid);
1234 # this needs to be rewritten
1235 while ($line = db_fetch_assoc($result)) {
1236 $unread +
= $line["unread"];
1240 } else if ($cat == -1) {
1241 return getFeedUnread($link, -1) +
getFeedUnread($link, -2) +
getFeedUnread($link, -3) +
getFeedUnread($link, 0);
1242 } else if ($cat == -2) {
1244 $result = db_query($link, "
1245 SELECT COUNT(unread) AS unread FROM
1246 ttrss_user_entries, ttrss_user_labels2
1247 WHERE article_id = ref_id AND unread = true
1248 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1250 $unread = db_fetch_result($result, 0, "unread");
1257 function getFeedUnread($link, $feed, $is_cat = false) {
1258 return getFeedArticles($link, $feed, $is_cat, true, $_SESSION["uid"]);
1261 function getLabelUnread($link, $label_id, $owner_uid = false) {
1262 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1264 $result = db_query($link, "SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1265 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1267 if (db_num_rows($result) != 0) {
1268 return db_fetch_result($result, 0, "unread");
1274 function getFeedArticles($link, $feed, $is_cat = false, $unread_only = false,
1275 $owner_uid = false) {
1277 $n_feed = (int) $feed;
1278 $need_entries = false;
1280 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1283 $unread_qpart = "unread = true";
1285 $unread_qpart = "true";
1289 return getCategoryUnread($link, $n_feed, $owner_uid);
1290 } else if ($n_feed == -6) {
1292 } else if ($feed != "0" && $n_feed == 0) {
1294 $feed = db_escape_string($feed);
1296 $result = db_query($link, "SELECT SUM((SELECT COUNT(int_id)
1297 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1298 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1299 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1300 return db_fetch_result($result, 0, "count");
1302 } else if ($n_feed == -1) {
1303 $match_part = "marked = true";
1304 } else if ($n_feed == -2) {
1305 $match_part = "published = true";
1306 } else if ($n_feed == -3) {
1307 $match_part = "unread = true AND score >= 0";
1309 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
1311 if (DB_TYPE
== "pgsql") {
1312 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1314 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1317 $need_entries = true;
1319 } else if ($n_feed == -4) {
1320 $match_part = "true";
1321 } else if ($n_feed >= 0) {
1324 $match_part = "feed_id = '$n_feed'";
1326 $match_part = "feed_id IS NULL";
1329 } else if ($feed < -10) {
1331 $label_id = -$feed - 11;
1333 return getLabelUnread($link, $label_id, $owner_uid);
1339 if ($need_entries) {
1340 $from_qpart = "ttrss_user_entries,ttrss_entries";
1341 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1343 $from_qpart = "ttrss_user_entries";
1346 $query = "SELECT count(int_id) AS unread
1347 FROM $from_qpart WHERE
1348 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1350 //echo "[$feed/$query]\n";
1352 $result = db_query($link, $query);
1356 $result = db_query($link, "SELECT COUNT(post_int_id) AS unread
1357 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1358 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1359 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1362 $unread = db_fetch_result($result, 0, "unread");
1367 function getGlobalUnread($link, $user_id = false) {
1370 $user_id = $_SESSION["uid"];
1373 $result = db_query($link, "SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1374 WHERE owner_uid = '$user_id' AND feed_id > 0");
1376 $c_id = db_fetch_result($result, 0, "c_id");
1381 function getGlobalCounters($link, $global_unread = -1) {
1384 if ($global_unread == -1) {
1385 $global_unread = getGlobalUnread($link);
1388 $cv = array("id" => "global-unread",
1389 "counter" => (int) $global_unread);
1391 array_push($ret_arr, $cv);
1393 $result = db_query($link, "SELECT COUNT(id) AS fn FROM
1394 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1396 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1398 $cv = array("id" => "subscribed-feeds",
1399 "counter" => (int) $subscribed_feeds);
1401 array_push($ret_arr, $cv);
1406 function getVirtCounters($link) {
1410 for ($i = 0; $i >= -4; $i--) {
1412 $count = getFeedUnread($link, $i);
1414 $cv = array("id" => $i,
1415 "counter" => (int) $count);
1417 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1418 // $cv["xmsg"] = getFeedArticles($link, $i)." ".__("total");
1420 array_push($ret_arr, $cv);
1426 function getLabelCounters($link, $descriptions = false) {
1430 $owner_uid = $_SESSION["uid"];
1432 $result = db_query($link, "SELECT id,caption,COUNT(unread) AS unread
1433 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1434 (ttrss_labels2.id = label_id)
1435 LEFT JOIN ttrss_user_entries ON (ref_id = article_id AND unread = true)
1436 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1437 ttrss_labels2.caption");
1439 while ($line = db_fetch_assoc($result)) {
1441 $id = -$line["id"] - 11;
1443 $label_name = $line["caption"];
1444 $count = $line["unread"];
1446 $cv = array("id" => $id,
1447 "counter" => (int) $count);
1450 $cv["description"] = $label_name;
1452 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1453 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1455 array_push($ret_arr, $cv);
1461 function getFeedCounters($link, $active_feed = false) {
1465 $query = "SELECT ttrss_feeds.id,
1467 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1468 last_error, value AS count
1469 FROM ttrss_feeds, ttrss_counters_cache
1470 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1471 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1472 AND ttrss_counters_cache.feed_id = id";
1474 $result = db_query($link, $query);
1475 $fctrs_modified = false;
1477 while ($line = db_fetch_assoc($result)) {
1480 $count = $line["count"];
1481 $last_error = htmlspecialchars($line["last_error"]);
1483 $last_updated = make_local_datetime($link, $line['last_updated'], false);
1485 $has_img = feed_has_icon($id);
1487 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1490 $cv = array("id" => $id,
1491 "updated" => $last_updated,
1492 "counter" => (int) $count,
1493 "has_img" => (int) $has_img);
1496 $cv["error"] = $last_error;
1498 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1499 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1501 if ($active_feed && $id == $active_feed)
1502 $cv["title"] = truncate_string($line["title"], 30);
1504 array_push($ret_arr, $cv);
1511 function get_pgsql_version($link) {
1512 $result = db_query($link, "SELECT version() AS version");
1513 $version = explode(" ", db_fetch_result($result, 0, "version"));
1518 * @return array (code => Status code, message => error message if available)
1520 * 0 - OK, Feed already exists
1521 * 1 - OK, Feed added
1523 * 3 - URL content is HTML, no feeds available
1524 * 4 - URL content is HTML which contains multiple feeds.
1525 * Here you should call extractfeedurls in rpc-backend
1526 * to get all possible feeds.
1527 * 5 - Couldn't download the URL content.
1529 function subscribe_to_feed($link, $url, $cat_id = 0,
1530 $auth_login = '', $auth_pass = '', $need_auth = false) {
1532 global $fetch_last_error;
1534 require_once "include/rssfuncs.php";
1536 $url = fix_url($url);
1538 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1540 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1543 return array("code" => 5, "message" => $fetch_last_error);
1546 if (is_html($contents)) {
1547 $feedUrls = get_feeds_from_html($url, $contents);
1549 if (count($feedUrls) == 0) {
1550 return array("code" => 3);
1551 } else if (count($feedUrls) > 1) {
1552 return array("code" => 4, "feeds" => $feedUrls);
1554 //use feed url as new URL
1555 $url = key($feedUrls);
1558 if ($cat_id == "0" ||
!$cat_id) {
1559 $cat_qpart = "NULL";
1561 $cat_qpart = "'$cat_id'";
1564 $result = db_query($link,
1565 "SELECT id FROM ttrss_feeds
1566 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1568 if (db_num_rows($result) == 0) {
1569 $result = db_query($link,
1570 "INSERT INTO ttrss_feeds
1571 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method)
1572 VALUES ('".$_SESSION["uid"]."', '$url',
1573 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0)");
1575 $result = db_query($link,
1576 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1577 AND owner_uid = " . $_SESSION["uid"]);
1579 $feed_id = db_fetch_result($result, 0, "id");
1582 update_rss_feed($link, $feed_id, true);
1585 return array("code" => 1);
1587 return array("code" => 0);
1591 function print_feed_select($link, $id, $default_id = "",
1592 $attributes = "", $include_all_feeds = true,
1593 $root_id = false, $nest_level = 0) {
1596 print "<select id=\"$id\" name=\"$id\" $attributes>";
1597 if ($include_all_feeds) {
1598 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1599 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1603 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1606 $parent_qpart = "parent_cat = '$root_id'";
1608 $parent_qpart = "parent_cat IS NULL";
1610 $result = db_query($link, "SELECT id,title,
1611 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1612 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1613 FROM ttrss_feed_categories
1614 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1616 while ($line = db_fetch_assoc($result)) {
1618 for ($i = 0; $i < $nest_level; $i++
)
1619 $line["title"] = " - " . $line["title"];
1621 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1623 printf("<option $is_selected value='CAT:%d'>%s</option>",
1624 $line["id"], htmlspecialchars($line["title"]));
1626 if ($line["num_children"] > 0)
1627 print_feed_select($link, $id, $default_id, $attributes,
1628 $include_all_feeds, $line["id"], $nest_level+
1);
1630 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1631 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1633 while ($fline = db_fetch_assoc($feed_result)) {
1634 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1636 $fline["title"] = " + " . $fline["title"];
1638 for ($i = 0; $i < $nest_level; $i++
)
1639 $fline["title"] = " - " . $fline["title"];
1641 printf("<option $is_selected value='%d'>%s</option>",
1642 $fline["id"], htmlspecialchars($fline["title"]));
1647 $is_selected = ($default_id == "CAT:0") ?
"selected=\"1\"" : "";
1649 printf("<option $is_selected value='CAT:0'>%s</option>",
1650 __("Uncategorized"));
1652 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1653 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1655 while ($fline = db_fetch_assoc($feed_result)) {
1656 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1658 $fline["title"] = " + " . $fline["title"];
1660 for ($i = 0; $i < $nest_level; $i++
)
1661 $fline["title"] = " - " . $fline["title"];
1663 printf("<option $is_selected value='%d'>%s</option>",
1664 $fline["id"], htmlspecialchars($fline["title"]));
1669 $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1670 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1672 while ($line = db_fetch_assoc($result)) {
1674 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1676 printf("<option $is_selected value='%d'>%s</option>",
1677 $line["id"], htmlspecialchars($line["title"]));
1686 function print_feed_cat_select($link, $id, $default_id,
1687 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1690 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1694 $parent_qpart = "parent_cat = '$root_id'";
1696 $parent_qpart = "parent_cat IS NULL";
1698 $result = db_query($link, "SELECT id,title,
1699 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1700 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1701 FROM ttrss_feed_categories
1702 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1704 while ($line = db_fetch_assoc($result)) {
1705 if ($line["id"] == $default_id) {
1706 $is_selected = "selected=\"1\"";
1711 for ($i = 0; $i < $nest_level; $i++
)
1712 $line["title"] = " - " . $line["title"];
1715 printf("<option $is_selected value='%d'>%s</option>",
1716 $line["id"], htmlspecialchars($line["title"]));
1718 if ($line["num_children"] > 0)
1719 print_feed_cat_select($link, $id, $default_id, $attributes,
1720 $include_all_cats, $line["id"], $nest_level+
1);
1724 if ($include_all_cats) {
1725 if (db_num_rows($result) > 0) {
1726 print "<option disabled=\"1\">--------</option>";
1729 if ($default_id == 0) {
1730 $is_selected = "selected=\"1\"";
1735 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1741 function checkbox_to_sql_bool($val) {
1742 return ($val == "on") ?
"true" : "false";
1745 function getFeedCatTitle($link, $id) {
1747 return __("Special");
1748 } else if ($id < -10) {
1749 return __("Labels");
1750 } else if ($id > 0) {
1751 $result = db_query($link, "SELECT ttrss_feed_categories.title
1752 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1753 cat_id = ttrss_feed_categories.id");
1754 if (db_num_rows($result) == 1) {
1755 return db_fetch_result($result, 0, "title");
1757 return __("Uncategorized");
1760 return "getFeedCatTitle($id) failed";
1765 function getFeedIcon($id) {
1768 return "images/archive.png";
1771 return "images/mark_set.svg";
1774 return "images/pub_set.svg";
1777 return "images/fresh.png";
1780 return "images/tag.png";
1783 return "images/recently_read.png";
1787 return "images/label.png";
1789 if (file_exists(ICONS_DIR
. "/$id.ico"))
1790 return ICONS_URL
. "/$id.ico";
1796 function getFeedTitle($link, $id, $cat = false) {
1798 return getCategoryTitle($link, $id);
1799 } else if ($id == -1) {
1800 return __("Starred articles");
1801 } else if ($id == -2) {
1802 return __("Published articles");
1803 } else if ($id == -3) {
1804 return __("Fresh articles");
1805 } else if ($id == -4) {
1806 return __("All articles");
1807 } else if ($id === 0 ||
$id === "0") {
1808 return __("Archived articles");
1809 } else if ($id == -6) {
1810 return __("Recently read");
1811 } else if ($id < -10) {
1812 $label_id = -$id - 11;
1813 $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1814 if (db_num_rows($result) == 1) {
1815 return db_fetch_result($result, 0, "caption");
1817 return "Unknown label ($label_id)";
1820 } else if (is_numeric($id) && $id > 0) {
1821 $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1822 if (db_num_rows($result) == 1) {
1823 return db_fetch_result($result, 0, "title");
1825 return "Unknown feed ($id)";
1832 function make_init_params($link) {
1835 $params["sign_progress"] = "images/indicator_white.gif";
1836 $params["sign_progress_tiny"] = "images/indicator_tiny.gif";
1837 $params["sign_excl"] = "images/sign_excl.svg";
1838 $params["sign_info"] = "images/sign_info.svg";
1840 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1841 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1842 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", "DEFAULT_ARTICLE_LIMIT",
1843 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1845 $params[strtolower($param)] = (int) get_pref($link, $param);
1848 $params["icons_url"] = ICONS_URL
;
1849 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1850 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1851 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1852 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1853 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1855 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1856 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1858 $max_feed_id = db_fetch_result($result, 0, "mid");
1859 $num_feeds = db_fetch_result($result, 0, "nf");
1861 $params["max_feed_id"] = (int) $max_feed_id;
1862 $params["num_feeds"] = (int) $num_feeds;
1864 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1865 $params["hotkeys"] = get_hotkeys_map($link);
1867 $params["csrf_token"] = $_SESSION["csrf_token"];
1868 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1870 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1875 function get_hotkeys_info($link) {
1877 __("Navigation") => array(
1878 "next_feed" => __("Open next feed"),
1879 "prev_feed" => __("Open previous feed"),
1880 "next_article" => __("Open next article"),
1881 "prev_article" => __("Open previous article"),
1882 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1883 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1884 "search_dialog" => __("Show search dialog")),
1885 __("Article") => array(
1886 "toggle_mark" => __("Toggle starred"),
1887 "toggle_publ" => __("Toggle published"),
1888 "toggle_unread" => __("Toggle unread"),
1889 "edit_tags" => __("Edit tags"),
1890 "dismiss_selected" => __("Dismiss selected"),
1891 "dismiss_read" => __("Dismiss read"),
1892 "open_in_new_window" => __("Open in new window"),
1893 "catchup_below" => __("Mark below as read"),
1894 "catchup_above" => __("Mark above as read"),
1895 "article_scroll_down" => __("Scroll down"),
1896 "article_scroll_up" => __("Scroll up"),
1897 "select_article_cursor" => __("Select article under cursor"),
1898 "email_article" => __("Email article"),
1899 "close_article" => __("Close/collapse article"),
1900 "toggle_widescreen" => __("Toggle widescreen mode")),
1901 __("Article selection") => array(
1902 "select_all" => __("Select all articles"),
1903 "select_unread" => __("Select unread"),
1904 "select_marked" => __("Select starred"),
1905 "select_published" => __("Select published"),
1906 "select_invert" => __("Invert selection"),
1907 "select_none" => __("Deselect everything")),
1908 __("Feed") => array(
1909 "feed_refresh" => __("Refresh current feed"),
1910 "feed_unhide_read" => __("Un/hide read feeds"),
1911 "feed_subscribe" => __("Subscribe to feed"),
1912 "feed_edit" => __("Edit feed"),
1913 "feed_catchup" => __("Mark as read"),
1914 "feed_reverse" => __("Reverse headlines"),
1915 "feed_debug_update" => __("Debug feed update"),
1916 "catchup_all" => __("Mark all feeds as read"),
1917 "cat_toggle_collapse" => __("Un/collapse current category"),
1918 "toggle_combined_mode" => __("Toggle combined mode")),
1919 __("Go to") => array(
1920 "goto_all" => __("All articles"),
1921 "goto_fresh" => __("Fresh"),
1922 "goto_marked" => __("Starred"),
1923 "goto_published" => __("Published"),
1924 "goto_tagcloud" => __("Tag cloud"),
1925 "goto_prefs" => __("Preferences")),
1926 __("Other") => array(
1927 "create_label" => __("Create label"),
1928 "create_filter" => __("Create filter"),
1929 "collapse_sidebar" => __("Un/collapse sidebar"),
1930 "help_dialog" => __("Show help dialog"))
1936 function get_hotkeys_map($link) {
1938 // "navigation" => array(
1941 "n" => "next_article",
1942 "p" => "prev_article",
1943 "(38)|up" => "prev_article",
1944 "(40)|down" => "next_article",
1945 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1946 // "^(40)|Ctrl-down" => "next_article_noscroll",
1947 "(191)|/" => "search_dialog",
1948 // "article" => array(
1949 "s" => "toggle_mark",
1950 "*s" => "toggle_publ",
1951 "u" => "toggle_unread",
1952 "*t" => "edit_tags",
1953 "*d" => "dismiss_selected",
1954 "*x" => "dismiss_read",
1955 "o" => "open_in_new_window",
1956 "c p" => "catchup_below",
1957 "c n" => "catchup_above",
1958 "*n" => "article_scroll_down",
1959 "*p" => "article_scroll_up",
1960 "*(38)|Shift+up" => "article_scroll_up",
1961 "*(40)|Shift+down" => "article_scroll_down",
1962 "a *w" => "toggle_widescreen",
1963 "e" => "email_article",
1964 "a q" => "close_article",
1965 // "article_selection" => array(
1966 "a a" => "select_all",
1967 "a u" => "select_unread",
1968 "a *u" => "select_marked",
1969 "a p" => "select_published",
1970 "a i" => "select_invert",
1971 "a n" => "select_none",
1973 "f r" => "feed_refresh",
1974 "f a" => "feed_unhide_read",
1975 "f s" => "feed_subscribe",
1976 "f e" => "feed_edit",
1977 "f q" => "feed_catchup",
1978 "f x" => "feed_reverse",
1979 "f *d" => "feed_debug_update",
1980 "f *c" => "toggle_combined_mode",
1981 "*q" => "catchup_all",
1982 "x" => "cat_toggle_collapse",
1984 "g a" => "goto_all",
1985 "g f" => "goto_fresh",
1986 "g s" => "goto_marked",
1987 "g p" => "goto_published",
1988 "g t" => "goto_tagcloud",
1989 "g *p" => "goto_prefs",
1990 // "other" => array(
1991 "(9)|Tab" => "select_article_cursor", // tab
1992 "c l" => "create_label",
1993 "c f" => "create_filter",
1994 "c s" => "collapse_sidebar",
1995 "^(191)|Ctrl+/" => "help_dialog",
1998 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
1999 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2000 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2004 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP
) as $plugin) {
2005 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2008 $prefixes = array();
2010 foreach (array_keys($hotkeys) as $hotkey) {
2011 $pair = explode(" ", $hotkey, 2);
2013 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2014 array_push($prefixes, $pair[0]);
2018 return array($prefixes, $hotkeys);
2021 function make_runtime_info($link) {
2024 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2025 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2027 $max_feed_id = db_fetch_result($result, 0, "mid");
2028 $num_feeds = db_fetch_result($result, 0, "nf");
2030 $data["max_feed_id"] = (int) $max_feed_id;
2031 $data["num_feeds"] = (int) $num_feeds;
2033 $data['last_article_id'] = getLastArticleId($link);
2034 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2036 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2038 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2040 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2042 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2045 $stamp_delta = time() - $stamp;
2047 if ($stamp_delta > 1800) {
2051 $_SESSION["daemon_stamp_check"] = time();
2054 $data['daemon_stamp_ok'] = $stamp_check;
2056 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2058 $data['daemon_stamp'] = $stamp_fmt;
2063 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2064 $new_version_details = @check_for_update
($link);
2066 $data['new_version_available'] = (int) ($new_version_details != false);
2068 $_SESSION["last_version_check"] = time();
2069 $_SESSION["version_data"] = $new_version_details;
2075 function search_to_sql($link, $search) {
2077 $search_query_part = "";
2079 $keywords = explode(" ", $search);
2080 $query_keywords = array();
2082 foreach ($keywords as $k) {
2083 if (strpos($k, "-") === 0) {
2090 $commandpair = explode(":", mb_strtolower($k), 2);
2092 if ($commandpair[0] == "note" && $commandpair[1]) {
2094 if ($commandpair[1] == "true")
2095 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2097 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2099 } else if ($commandpair[0] == "star" && $commandpair[1]) {
2101 if ($commandpair[1] == "true")
2102 array_push($query_keywords, "($not (marked = true))");
2104 array_push($query_keywords, "($not (marked = false))");
2106 } else if ($commandpair[0] == "pub" && $commandpair[1]) {
2108 if ($commandpair[1] == "true")
2109 array_push($query_keywords, "($not (published = true))");
2111 array_push($query_keywords, "($not (published = false))");
2113 } else if (strpos($k, "@") === 0) {
2115 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2116 $orig_ts = strtotime(substr($k, 1));
2117 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2119 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2121 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2123 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2124 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2128 $search_query_part = implode("AND", $query_keywords);
2130 return $search_query_part;
2133 function getParentCategories($link, $cat, $owner_uid) {
2136 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2137 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2139 while ($line = db_fetch_assoc($result)) {
2140 array_push($rv, $line["parent_cat"]);
2141 $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2147 function getChildCategories($link, $cat, $owner_uid) {
2150 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2151 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2153 while ($line = db_fetch_assoc($result)) {
2154 array_push($rv, $line["id"]);
2155 $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2161 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) {
2163 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2165 $ext_tables_part = "";
2169 if (SPHINX_ENABLED
) {
2170 $ids = join(",", @sphinx_search
($search, 0, 500));
2173 $search_query_part = "ref_id IN ($ids) AND ";
2175 $search_query_part = "ref_id = -1 AND ";
2178 $search_query_part = search_to_sql($link, $search);
2179 $search_query_part .= " AND ";
2183 $search_query_part = "";
2188 if (DB_TYPE
== "pgsql") {
2189 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2191 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2194 $override_order = "updated DESC";
2196 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2198 // Try to check if SQL regexp implementation chokes on a valid regexp
2199 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2200 ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2201 WHERE $filter_query_part LIMIT 1", false);
2204 $test = db_fetch_result($result, 0, "true_val");
2207 $filter_query_part = "false AND";
2209 $filter_query_part .= " AND";
2212 $filter_query_part = "false AND";
2216 $filter_query_part = "";
2220 $since_id_part = "ttrss_entries.id > $since_id AND ";
2222 $since_id_part = "";
2225 $view_query_part = "";
2227 if ($view_mode == "adaptive" ||
$view_query_part == "noscores") {
2229 $view_query_part = " ";
2230 } else if ($feed != -1) {
2231 $unread = getFeedUnread($link, $feed, $cat_view);
2233 if ($cat_view && $feed > 0 && $include_children)
2234 $unread +
= getCategoryChildrenUnread($link, $feed);
2237 $view_query_part = " unread = true AND ";
2242 if ($view_mode == "marked") {
2243 $view_query_part = " marked = true AND ";
2246 if ($view_mode == "published") {
2247 $view_query_part = " published = true AND ";
2250 if ($view_mode == "unread") {
2251 $view_query_part = " unread = true AND ";
2254 if ($view_mode == "updated") {
2255 $view_query_part = " (last_read is null and unread = false) AND ";
2259 $limit_query_part = "LIMIT " . $limit;
2262 $allow_archived = false;
2264 $vfeed_query_part = "";
2266 // override query strategy and enable feed display when searching globally
2267 if ($search && $search_mode == "all_feeds") {
2268 $query_strategy_part = "true";
2269 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2271 } else if (!is_numeric($feed)) {
2272 $query_strategy_part = "true";
2273 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2274 id = feed_id) as feed_title,";
2275 } else if ($search && $search_mode == "this_cat") {
2276 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2279 if ($include_children) {
2280 $subcats = getChildCategories($link, $feed, $owner_uid);
2281 array_push($subcats, $feed);
2282 $cats_qpart = join(",", $subcats);
2284 $cats_qpart = $feed;
2287 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2290 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2293 } else if ($feed > 0) {
2298 if ($include_children) {
2300 $subcats = getChildCategories($link, $feed, $owner_uid);
2302 array_push($subcats, $feed);
2303 $query_strategy_part = "cat_id IN (".
2304 implode(",", $subcats).")";
2307 $query_strategy_part = "cat_id = '$feed'";
2311 $query_strategy_part = "cat_id IS NULL";
2314 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2317 $query_strategy_part = "feed_id = '$feed'";
2319 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2320 $query_strategy_part = "feed_id IS NULL";
2321 $allow_archived = true;
2322 } else if ($feed == 0 && $cat_view) { // uncategorized
2323 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2324 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2325 } else if ($feed == -1) { // starred virtual feed
2326 $query_strategy_part = "marked = true";
2327 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2328 $allow_archived = true;
2330 if (!$override_order) $override_order = "last_marked DESC, updated DESC";
2332 } else if ($feed == -2) { // published virtual feed OR labels category
2335 $query_strategy_part = "published = true";
2336 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2337 $allow_archived = true;
2339 if (!$override_order) $override_order = "last_published DESC, updated DESC";
2341 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2343 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2345 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2346 ttrss_user_labels2.article_id = ref_id";
2349 } else if ($feed == -6) { // recently read
2350 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2351 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2352 $allow_archived = true;
2354 if (!$override_order) $override_order = "last_read DESC";
2355 } else if ($feed == -3) { // fresh virtual feed
2356 $query_strategy_part = "unread = true AND score >= 0";
2358 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2360 if (DB_TYPE
== "pgsql") {
2361 $query_strategy_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
2363 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2366 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2367 } else if ($feed == -4) { // all articles virtual feed
2368 $query_strategy_part = "true";
2369 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2370 } else if ($feed <= -10) { // labels
2371 $label_id = -$feed - 11;
2373 $query_strategy_part = "label_id = '$label_id' AND
2374 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2375 ttrss_user_labels2.article_id = ref_id";
2377 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2378 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2379 $allow_archived = true;
2382 $query_strategy_part = "true";
2385 if (get_pref($link, "SORT_HEADLINES_BY_FEED_DATE", $owner_uid)) {
2386 $date_sort_field = "updated";
2388 $date_sort_field = "date_entered";
2391 if (get_pref($link, 'REVERSE_HEADLINES', $owner_uid)) {
2392 $order_by = "$date_sort_field";
2394 $order_by = "$date_sort_field DESC";
2397 if ($view_mode != "noscores") {
2398 $order_by = "score DESC, $order_by";
2401 if ($override_order) {
2402 $order_by = $override_order;
2408 $feed_title = T_sprintf("Search results: %s", $search);
2411 $feed_title = getCategoryTitle($link, $feed);
2413 if (is_numeric($feed) && $feed > 0) {
2414 $result = db_query($link, "SELECT title,site_url,last_error
2415 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2417 $feed_title = db_fetch_result($result, 0, "title");
2418 $feed_site_url = db_fetch_result($result, 0, "site_url");
2419 $last_error = db_fetch_result($result, 0, "last_error");
2421 $feed_title = getFeedTitle($link, $feed);
2426 $content_query_part = "content as content_preview, cached_content, ";
2428 if (is_numeric($feed)) {
2431 $feed_kind = "Feeds";
2433 $feed_kind = "Labels";
2436 if ($limit_query_part) {
2437 $offset_query_part = "OFFSET $offset";
2440 // proper override_order applied above
2441 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2442 if (!$override_order) {
2443 $order_by = "ttrss_feeds.title, $order_by";
2445 $order_by = "ttrss_feeds.title, $override_order";
2449 if (!$allow_archived) {
2450 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2451 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2454 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2455 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2458 $query = "SELECT DISTINCT
2461 ttrss_entries.id,ttrss_entries.title,
2465 always_display_enclosures,
2472 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2473 last_marked, last_published,
2474 ".SUBSTRING_FOR_DATE
."(last_read,1,19) as last_read_noms,
2477 ".SUBSTRING_FOR_DATE
."(updated,1,19) as updated_noms,
2483 ttrss_user_entries.ref_id = ttrss_entries.id AND
2484 ttrss_user_entries.owner_uid = '$owner_uid' AND
2489 $query_strategy_part ORDER BY $order_by
2490 $limit_query_part $offset_query_part";
2492 if ($_REQUEST["debug"]) print $query;
2494 $result = db_query($link, $query);
2499 $select_qpart = "SELECT DISTINCT " .
2503 "ttrss_entries.id as id," .
2516 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2517 "last_marked, last_published, " .
2518 SUBSTRING_FOR_DATE
. "(last_read,1,19) as last_read_noms," .
2521 $content_query_part .
2522 SUBSTRING_FOR_DATE
. "(updated,1,19) as updated_noms," .
2525 $feed_kind = "Tags";
2526 $all_tags = explode(",", $feed);
2527 if ($search_mode == 'any') {
2528 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2529 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2530 $where_qpart = " WHERE " .
2531 "ref_id = ttrss_entries.id AND " .
2532 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2533 "post_int_id = int_id AND $tag_sql AND " .
2535 $search_query_part .
2536 $query_strategy_part . " ORDER BY $order_by " .
2541 $sub_selects = array();
2542 $sub_ands = array();
2543 foreach ($all_tags as $term) {
2544 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");
2551 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2556 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2557 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2558 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2559 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2561 // error_log("TAG SQL: " . $tag_sql);
2562 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2564 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2565 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2568 return array($result, $feed_title, $feed_site_url, $last_error);
2572 function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2573 if (!$owner) $owner = $_SESSION["uid"];
2575 $res = trim($str); if (!$res) return '';
2577 if (strpos($res, "href=") === false)
2578 $res = rewrite_urls($res);
2580 $charset_hack = '<head>
2581 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2584 $res = trim($res); if (!$res) return '';
2586 libxml_use_internal_errors(true);
2588 $doc = new DOMDocument();
2589 $doc->loadHTML($charset_hack . $res);
2590 $xpath = new DOMXPath($doc);
2592 $entries = $xpath->query('(//a[@href]|//img[@src])');
2594 foreach ($entries as $entry) {
2598 if ($entry->hasAttribute('href'))
2599 $entry->setAttribute('href',
2600 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2602 if ($entry->hasAttribute('src')) {
2603 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2605 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2607 if (file_exists($cached_filename)) {
2608 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2611 $entry->setAttribute('src', $src);
2614 if ($entry->nodeName
== 'img') {
2615 if (($owner && get_pref($link, "STRIP_IMAGES", $owner)) ||
2616 $force_remove_images) {
2618 $p = $doc->createElement('p');
2620 $a = $doc->createElement('a');
2621 $a->setAttribute('href', $entry->getAttribute('src'));
2623 $a->appendChild(new DOMText($entry->getAttribute('src')));
2624 $a->setAttribute('target', '_blank');
2626 $p->appendChild($a);
2628 $entry->parentNode
->replaceChild($p, $entry);
2633 if (strtolower($entry->nodeName
) == "a") {
2634 $entry->setAttribute("target", "_blank");
2638 $entries = $xpath->query('//iframe');
2639 foreach ($entries as $entry) {
2640 $entry->setAttribute('sandbox', 'allow-scripts');
2646 if (isset($pluginhost)) {
2647 foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE
) as $plugin) {
2648 $doc = $plugin->hook_sanitize($doc, $site_url);
2652 $doc->removeChild($doc->firstChild
); //remove doctype
2653 $doc = strip_harmful_tags($doc);
2654 $res = $doc->saveHTML();
2658 function strip_harmful_tags($doc) {
2659 $entries = $doc->getElementsByTagName("*");
2661 $allowed_elements = array('a', 'address', 'audio', 'article',
2662 'b', 'big', 'blockquote', 'body', 'br', 'cite',
2663 'code', 'dd', 'del', 'details', 'div', 'dl', 'font',
2664 'dt', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
2665 'header', 'html', 'i', 'img', 'ins', 'kbd',
2666 'li', 'nav', 'ol', 'p', 'pre', 'q', 's','small',
2667 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2668 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
2669 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2671 if ($_SESSION['hasSandbox']) array_push($allowed_elements, 'iframe');
2673 $disallowed_attributes = array('id', 'style', 'class');
2675 foreach ($entries as $entry) {
2676 if (!in_array($entry->nodeName
, $allowed_elements)) {
2677 $entry->parentNode
->removeChild($entry);
2680 if ($entry->hasAttributes()) {
2681 foreach (iterator_to_array($entry->attributes
) as $attr) {
2683 if (strpos($attr->nodeName
, 'on') === 0) {
2684 $entry->removeAttributeNode($attr);
2687 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2688 $entry->removeAttributeNode($attr);
2697 function check_for_update($link) {
2698 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2699 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2700 "&iid=" . sha1(SELF_URL_PATH
);
2702 $version_data = @fetch_file_contents
($version_url);
2704 if ($version_data) {
2705 $version_data = json_decode($version_data, true);
2706 if ($version_data && $version_data['version']) {
2708 if (version_compare(VERSION
, $version_data['version']) == -1) {
2709 return $version_data;
2717 function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2719 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2720 if (count($ids) == 0) return;
2724 foreach ($ids as $id) {
2725 array_push($tmp_ids, "ref_id = '$id'");
2728 $ids_qpart = join(" OR ", $tmp_ids);
2731 db_query($link, "UPDATE ttrss_user_entries SET
2732 unread = false,last_read = NOW()
2733 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2734 } else if ($cmode == 1) {
2735 db_query($link, "UPDATE ttrss_user_entries SET
2737 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2739 db_query($link, "UPDATE ttrss_user_entries SET
2740 unread = NOT unread,last_read = NOW()
2741 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2746 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2747 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2749 while ($line = db_fetch_assoc($result)) {
2750 ccache_update($link, $line["feed_id"], $owner_uid);
2754 function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2756 $a_id = db_escape_string($id);
2758 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2760 $query = "SELECT DISTINCT tag_name,
2761 owner_uid as owner FROM
2762 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2763 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2765 $obj_id = md5("TAGS:$owner_uid:$id");
2768 /* check cache first */
2770 if ($tag_cache === false) {
2771 $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2772 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2774 $tag_cache = db_fetch_result($result, 0, "tag_cache");
2778 $tags = explode(",", $tag_cache);
2781 /* do it the hard way */
2783 $tmp_result = db_query($link, $query);
2785 while ($tmp_line = db_fetch_assoc($tmp_result)) {
2786 array_push($tags, $tmp_line["tag_name"]);
2789 /* update the cache */
2791 $tags_str = db_escape_string(join(",", $tags));
2793 db_query($link, "UPDATE ttrss_user_entries
2794 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2795 AND owner_uid = $owner_uid");
2801 function trim_array($array) {
2803 array_walk($tmp, 'trim');
2807 function tag_is_valid($tag) {
2808 if ($tag == '') return false;
2809 if (preg_match("/^[0-9]*$/", $tag)) return false;
2810 if (mb_strlen($tag) > 250) return false;
2812 if (function_exists('iconv')) {
2813 $tag = iconv("utf-8", "utf-8", $tag);
2816 if (!$tag) return false;
2821 function render_login_form($link, $form_id = 0) {
2824 require_once "login_form.php";
2827 require_once "mobile/login_form.php";
2833 // from http://developer.apple.com/internet/safari/faq.html
2834 function no_cache_incantation() {
2835 header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
2836 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
2837 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
2838 header("Cache-Control: post-check=0, pre-check=0", false);
2839 header("Pragma: no-cache"); // HTTP/1.0
2842 function format_warning($msg, $id = "") {
2844 return "<div class=\"warning\" id=\"$id\">
2845 <img src=\"images/sign_excl.svg\">$msg</div>";
2848 function format_notice($msg, $id = "") {
2850 return "<div class=\"notice\" id=\"$id\">
2851 <img src=\"images/sign_info.svg\">$msg</div>";
2854 function format_error($msg, $id = "") {
2856 return "<div class=\"error\" id=\"$id\">
2857 <img src=\"images/sign_excl.svg\">$msg</div>";
2860 function print_notice($msg) {
2861 return print format_notice($msg);
2864 function print_warning($msg) {
2865 return print format_warning($msg);
2868 function print_error($msg) {
2869 return print format_error($msg);
2873 function T_sprintf() {
2874 $args = func_get_args();
2875 return vsprintf(__(array_shift($args)), $args);
2878 function format_inline_player($link, $url, $ctype) {
2882 $url = htmlspecialchars($url);
2884 if (strpos($ctype, "audio/") === 0) {
2886 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2887 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2888 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2890 $id = 'AUDIO-' . uniqid();
2892 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2893 <source type=\"$ctype\" src=\"$url\"></source>
2896 $entry .= "<span onclick=\"player(this)\"
2897 title=\"".__("Click to play")."\" status=\"0\"
2898 class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
2902 $entry .= "<object type=\"application/x-shockwave-flash\"
2903 data=\"lib/button/musicplayer.swf?song_url=$url\"
2904 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
2905 <param name=\"movie\"
2906 value=\"lib/button/musicplayer.swf?song_url=$url\" />
2910 if ($entry) $entry .= " <a target=\"_blank\"
2911 href=\"$url\">" . basename($url) . "</a>";
2919 /* $filename = substr($url, strrpos($url, "/")+1);
2921 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
2922 $filename . " (" . $ctype . ")" . "</a>"; */
2926 function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
2927 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2933 /* we can figure out feed_id from article id anyway, why do we
2934 * pass feed_id here? let's ignore the argument :( */
2936 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
2937 WHERE ref_id = '$id'");
2939 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
2941 $rv['feed_id'] = $feed_id;
2943 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
2945 if ($mark_as_read) {
2946 $result = db_query($link, "UPDATE ttrss_user_entries
2947 SET unread = false,last_read = NOW()
2948 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2950 ccache_update($link, $feed_id, $owner_uid);
2953 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
2954 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
2955 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
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 $title_escaped = htmlspecialchars($line['title']);
3009 $rv['content'] .= "<div id=\"PTITLE-FULL-$id\" style=\"display : none\">" .
3010 strip_tags($line['title']) . "</div>";
3012 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3014 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3016 $entry_author = $line["author"];
3018 if ($entry_author) {
3019 $entry_author = __(" - ") . $entry_author;
3022 $parsed_updated = make_local_datetime($link, $line["updated"], true,
3025 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3027 if ($line["link"]) {
3028 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3029 title=\"".htmlspecialchars($line['title'])."\"
3031 htmlspecialchars($line["link"]) . "\">" .
3033 "<span class='author'>$entry_author</span></a></div>";
3035 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3038 $tags_str = format_tags_string($line["tags"], $id);
3039 $tags_str_full = join(", ", $line["tags"]);
3041 if (!$tags_str_full) $tags_str_full = __("no tags");
3043 if (!$entry_comments) $entry_comments = " "; # placeholder
3045 $rv['content'] .= "<div class='postTags' style='float : right'>
3046 <img src='images/tag.png'
3047 class='tagsPic' alt='Tags' title='Tags'> ";
3050 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3051 <a title=\"".__('Edit tags for this article')."\"
3052 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3054 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3055 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3056 position=\"below\">$tags_str_full</div>";
3060 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON
) as $p) {
3061 $rv['content'] .= $p->hook_article_button($line);
3066 $tags_str = strip_tags($tags_str);
3067 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3069 $rv['content'] .= "</div>";
3070 $rv['content'] .= "<div clear='both'>$entry_comments</div>";
3072 if ($line["orig_feed_id"]) {
3074 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3075 WHERE id = ".$line["orig_feed_id"]);
3077 if (db_num_rows($tmp_result) != 0) {
3079 $rv['content'] .= "<div clear='both'>";
3080 $rv['content'] .= __("Originally from:");
3082 $rv['content'] .= " ";
3084 $tmp_line = db_fetch_assoc($tmp_result);
3086 $rv['content'] .= "<a target='_blank'
3087 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3088 $tmp_line['title'] . "</a>";
3090 $rv['content'] .= " ";
3092 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3093 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3095 $rv['content'] .= "</div>";
3099 $rv['content'] .= "</div>";
3101 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3102 if ($line['note']) {
3103 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3105 $rv['content'] .= "</div>";
3107 $rv['content'] .= "<div class=\"postContent\">";
3111 if (DB_TYPE
== "pgsql" and defined('_NGRAM_TITLE_RELATED_THRESHOLD')) {
3113 $ngram_result = db_query($link, "SELECT id,title FROM
3114 ttrss_entries,ttrss_user_entries
3115 WHERE ref_id = id AND updated >= NOW() - INTERVAL '7 day'
3116 AND similarity(title, '$title_escaped') >= "._NGRAM_TITLE_RELATED_THRESHOLD
."
3117 AND title != '$title_escaped'
3118 AND owner_uid = $owner_uid");
3120 if (db_num_rows($ngram_result) > 0) {
3121 $rv['content'] .= "<div dojoType=\"dijit.form.DropDownButton\">".
3122 "<span>" . __('Related')."</span>";
3123 $rv['content'] .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3125 while ($nline = db_fetch_assoc($ngram_result)) {
3126 $rv['content'] .= "<div onclick=\"hlOpenInNewTab(null,".$nline['id'].")\"
3127 dojoType=\"dijit.MenuItem\">".$nline['title']."</div>";
3130 $rv['content'] .= "</div></div><br/";
3134 $rv['content'] .= $line["content"];
3136 $rv['content'] .= format_article_enclosures($link, $id,
3137 $always_display_enclosures, $line["content"]);
3139 $rv['content'] .= "</div>";
3141 $rv['content'] .= "</div>";
3147 <div style=\"text-align : center\">
3148 <button onclick=\"return window.close()\">".
3149 __("Close this window")."</button></div>";
3150 $rv['content'] .= "</body></html>";
3157 function print_checkpoint($n, $s) {
3158 $ts = microtime(true);
3159 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3163 function sanitize_tag($tag) {
3166 $tag = mb_strtolower($tag, 'utf-8');
3168 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3170 // $tag = str_replace('"', "", $tag);
3171 // $tag = str_replace("+", " ", $tag);
3172 $tag = str_replace("technorati tag: ", "", $tag);
3177 function get_self_url_prefix() {
3178 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3179 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3181 return SELF_URL_PATH
;
3186 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3188 * @return string The Mozilla Firefox feed adding URL.
3190 function add_feed_url() {
3191 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3193 $url_path = get_self_url_prefix() .
3194 "/public.php?op=subscribe&feed_url=%s";
3196 } // function add_feed_url
3198 function encrypt_password($pass, $salt = '', $mode2 = false) {
3199 if ($salt && $mode2) {
3200 return "MODE2:" . hash('sha256', $salt . $pass);
3202 return "SHA1X:" . sha1("$salt:$pass");
3204 return "SHA1:" . sha1($pass);
3206 } // function encrypt_password
3208 function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3211 $cat_id = (int)getFeedCategory($link, $feed_id);
3213 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3214 owner_uid = $owner_uid AND enabled = true");
3216 $check_cats = join(",", array_merge(
3217 getParentCategories($link, $cat_id, $owner_uid),
3220 while ($line = db_fetch_assoc($result)) {
3221 $filter_id = $line["id"];
3223 $result2 = db_query($link, "SELECT
3224 r.reg_exp, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3225 FROM ttrss_filters2_rules AS r,
3226 ttrss_filter_types AS t
3228 (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3229 (feed_id IS NULL OR feed_id = '$feed_id') AND
3230 filter_type = t.id AND filter_id = '$filter_id'");
3235 while ($rule_line = db_fetch_assoc($result2)) {
3236 # print_r($rule_line);
3239 $rule["reg_exp"] = $rule_line["reg_exp"];
3240 $rule["type"] = $rule_line["type_name"];
3242 array_push($rules, $rule);
3245 $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3246 FROM ttrss_filters2_actions AS a,
3247 ttrss_filter_actions AS t
3249 action_id = t.id AND filter_id = '$filter_id'");
3251 while ($action_line = db_fetch_assoc($result2)) {
3252 # print_r($action_line);
3255 $action["type"] = $action_line["type_name"];
3256 $action["param"] = $action_line["action_param"];
3258 array_push($actions, $action);
3263 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3264 $filter["rules"] = $rules;
3265 $filter["actions"] = $actions;
3267 if (count($rules) > 0 && count($actions) > 0) {
3268 array_push($filters, $filter);
3275 function get_score_pic($score) {
3277 return "score_high.png";
3278 } else if ($score > 0) {
3279 return "score_half_high.png";
3280 } else if ($score < -100) {
3281 return "score_low.png";
3282 } else if ($score < 0) {
3283 return "score_half_low.png";
3285 return "score_neutral.png";
3289 function feed_has_icon($id) {
3290 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3293 function init_connection($link) {
3296 if (DB_TYPE
== "pgsql") {
3297 pg_query($link, "set client_encoding = 'UTF-8'");
3298 pg_set_client_encoding("UNICODE");
3299 pg_query($link, "set datestyle = 'ISO, european'");
3300 pg_query($link, "set TIME ZONE 0");
3302 db_query($link, "SET time_zone = '+0:0'");
3304 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET
) {
3305 db_query($link, "SET NAMES " . MYSQL_CHARSET
);
3311 $pluginhost = new PluginHost($link);
3312 $pluginhost->load(PLUGINS
, $pluginhost::KIND_ALL
);
3316 print "Unable to connect to database:" . db_last_error();
3321 function format_tags_string($tags, $id) {
3324 $tags_nolinks_str = "";
3330 $formatted_tags = array();
3332 foreach ($tags as $tag) {
3334 $tag_escaped = str_replace("'", "\\'", $tag);
3336 if (mb_strlen($tag) > 30) {
3337 $tag = truncate_string($tag, 30);
3340 $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3342 array_push($formatted_tags, $tag_str);
3344 $tmp_tags_str = implode(", ", $formatted_tags);
3346 if ($num_tags == $tag_limit ||
mb_strlen($tmp_tags_str) > 150) {
3351 $tags_str = implode(", ", $formatted_tags);
3353 if ($num_tags < count($tags)) {
3354 $tags_str .= ", …";
3357 if ($num_tags == 0) {
3358 $tags_str = __("no tags");
3365 function format_article_labels($labels, $id) {
3369 foreach ($labels as $l) {
3370 $labels_str .= sprintf("<span class='hlLabelRef'
3371 style='color : %s; background-color : %s'>%s</span>",
3372 $l[2], $l[3], $l[1]);
3379 function format_article_note($id, $note, $allow_edit = true) {
3381 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3382 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3383 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3389 function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3390 if ($parent_cat_id) {
3391 $parent_qpart = "parent_cat = '$parent_cat_id'";
3392 $parent_insert = "'$parent_cat_id'";
3394 $parent_qpart = "parent_cat IS NULL";
3395 $parent_insert = "NULL";
3398 $result = db_query($link,
3399 "SELECT id FROM ttrss_feed_categories
3400 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3402 if (db_num_rows($result) == 0) {
3405 return db_fetch_result($result, 0, "id");
3409 function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3411 if (!$feed_cat) return false;
3413 db_query($link, "BEGIN");
3415 if ($parent_cat_id) {
3416 $parent_qpart = "parent_cat = '$parent_cat_id'";
3417 $parent_insert = "'$parent_cat_id'";
3419 $parent_qpart = "parent_cat IS NULL";
3420 $parent_insert = "NULL";
3423 $result = db_query($link,
3424 "SELECT id FROM ttrss_feed_categories
3425 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3427 if (db_num_rows($result) == 0) {
3429 $result = db_query($link,
3430 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3431 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3433 db_query($link, "COMMIT");
3441 function getArticleFeed($link, $id) {
3442 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3443 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3445 if (db_num_rows($result) != 0) {
3446 return db_fetch_result($result, 0, "feed_id");
3453 * Fixes incomplete URLs by prepending "http://".
3454 * Also replaces feed:// with http://, and
3455 * prepends a trailing slash if the url is a domain name only.
3457 * @param string $url Possibly incomplete URL
3459 * @return string Fixed URL.
3461 function fix_url($url) {
3462 if (strpos($url, '://') === false) {
3463 $url = 'http://' . $url;
3464 } else if (substr($url, 0, 5) == 'feed:') {
3465 $url = 'http:' . substr($url, 5);
3468 //prepend slash if the URL has no slash in it
3469 // "http://www.example" -> "http://www.example/"
3470 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3474 if ($url != "http:///")
3480 function validate_feed_url($url) {
3481 $parts = parse_url($url);
3483 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3487 function get_article_enclosures($link, $id) {
3489 $query = "SELECT * FROM ttrss_enclosures
3490 WHERE post_id = '$id' AND content_url != ''";
3494 $result = db_query($link, $query);
3496 if (db_num_rows($result) > 0) {
3497 while ($line = db_fetch_assoc($result)) {
3498 array_push($rv, $line);
3505 function save_email_address($link, $email) {
3506 // FIXME: implement persistent storage of emails
3508 if (!$_SESSION['stored_emails'])
3509 $_SESSION['stored_emails'] = array();
3511 if (!in_array($email, $_SESSION['stored_emails']))
3512 array_push($_SESSION['stored_emails'], $email);
3516 function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3518 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3520 $sql_is_cat = bool_to_sql_bool($is_cat);
3522 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3523 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3524 AND owner_uid = " . $owner_uid);
3526 if (db_num_rows($result) == 1) {
3527 return db_fetch_result($result, 0, "access_key");
3529 $key = db_escape_string(sha1(uniqid(rand(), true)));
3531 $result = db_query($link, "INSERT INTO ttrss_access_keys
3532 (access_key, feed_id, is_cat, owner_uid)
3533 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3540 function get_feeds_from_html($url, $content)
3542 $url = fix_url($url);
3543 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3545 libxml_use_internal_errors(true);
3547 $doc = new DOMDocument();
3548 $doc->loadHTML($content);
3549 $xpath = new DOMXPath($doc);
3550 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3551 $feedUrls = array();
3552 foreach ($entries as $entry) {
3553 if ($entry->hasAttribute('href')) {
3554 $title = $entry->getAttribute('title');
3556 $title = $entry->getAttribute('type');
3558 $feedUrl = rewrite_relative_url(
3559 $baseUrl, $entry->getAttribute('href')
3561 $feedUrls[$feedUrl] = $title;
3567 function is_html($content) {
3568 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3571 function url_is_html($url, $login = false, $pass = false) {
3572 return is_html(fetch_file_contents($url, false, $login, $pass));
3575 function print_label_select($link, $name, $value, $attributes = "") {
3577 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3578 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3580 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3581 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3583 while ($line = db_fetch_assoc($result)) {
3585 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3587 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3588 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3592 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3599 function format_article_enclosures($link, $id, $always_display_enclosures,
3602 $result = get_article_enclosures($link, $id);
3605 if (count($result) > 0) {
3607 $entries_html = array();
3609 $entries_inline = array();
3611 foreach ($result as $line) {
3613 $url = $line["content_url"];
3614 $ctype = $line["content_type"];
3616 if (!$ctype) $ctype = __("unknown type");
3618 $filename = substr($url, strrpos($url, "/")+
1);
3620 $player = format_inline_player($link, $url, $ctype);
3622 if ($player) array_push($entries_inline, $player);
3624 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3625 # $filename . " (" . $ctype . ")" . "</a>";
3627 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3628 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3630 array_push($entries_html, $entry);
3634 $entry["type"] = $ctype;
3635 $entry["filename"] = $filename;
3636 $entry["url"] = $url;
3638 array_push($entries, $entry);
3641 if ($_SESSION['uid'] && !get_pref($link, "STRIP_IMAGES")) {
3642 if ($always_display_enclosures ||
3643 !preg_match("/<img/i", $article_content)) {
3645 foreach ($entries as $entry) {
3647 if (preg_match("/image/", $entry["type"]) ||
3648 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3651 alt=\"".htmlspecialchars($entry["filename"])."\"
3652 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3659 if (count($entries_inline) > 0) {
3660 $rv .= "<hr clear='both'/>";
3661 foreach ($entries_inline as $entry) { $rv .= $entry; };
3662 $rv .= "<hr clear='both'/>";
3665 $rv .= "<br/><div dojoType=\"dijit.form.DropDownButton\">".
3666 "<span>" . __('Attachments')."</span>";
3667 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3669 foreach ($entries_html as $entry) { $rv .= $entry; };
3671 $rv .= "</div></div>";
3677 function getLastArticleId($link) {
3678 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3679 WHERE owner_uid = " . $_SESSION["uid"]);
3681 if (db_num_rows($result) == 1) {
3682 return db_fetch_result($result, 0, "id");
3688 function build_url($parts) {
3689 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3693 * Converts a (possibly) relative URL to a absolute one.
3695 * @param string $url Base URL (i.e. from where the document is)
3696 * @param string $rel_url Possibly relative URL in the document
3698 * @return string Absolute URL
3700 function rewrite_relative_url($url, $rel_url) {
3701 if (strpos($rel_url, "magnet:") === 0) {
3703 } else if (strpos($rel_url, "://") !== false) {
3705 } else if (strpos($rel_url, "//") === 0) {
3706 # protocol-relative URL (rare but they exist)
3708 } else if (strpos($rel_url, "/") === 0)
3710 $parts = parse_url($url);
3711 $parts['path'] = $rel_url;
3713 return build_url($parts);
3716 $parts = parse_url($url);
3717 if (!isset($parts['path'])) {
3718 $parts['path'] = '/';
3720 $dir = $parts['path'];
3721 if (substr($dir, -1) !== '/') {
3722 $dir = dirname($parts['path']);
3723 $dir !== '/' && $dir .= '/';
3725 $parts['path'] = $dir . $rel_url;
3727 return build_url($parts);
3731 function sphinx_search($query, $offset = 0, $limit = 30) {
3732 require_once 'lib/sphinxapi.php';
3734 $sphinxClient = new SphinxClient();
3736 $sphinxClient->SetServer('localhost', 9312);
3737 $sphinxClient->SetConnectTimeout(1);
3739 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3740 'feed_title' => 20));
3742 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3743 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3744 $sphinxClient->SetLimits($offset, $limit, 1000);
3745 $sphinxClient->SetArrayResult(false);
3746 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3748 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3752 if (is_array($result['matches'])) {
3753 foreach (array_keys($result['matches']) as $int_id) {
3754 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3755 array_push($ids, $ref_id);
3762 function cleanup_tags($link, $days = 14, $limit = 1000) {
3764 if (DB_TYPE
== "pgsql") {
3765 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3766 } else if (DB_TYPE
== "mysql") {
3767 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3772 while ($limit > 0) {
3775 $query = "SELECT ttrss_tags.id AS id
3776 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3777 WHERE post_int_id = int_id AND $interval_query AND
3778 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3780 $result = db_query($link, $query);
3784 while ($line = db_fetch_assoc($result)) {
3785 array_push($ids, $line['id']);
3788 if (count($ids) > 0) {
3789 $ids = join(",", $ids);
3792 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3793 $tags_deleted +
= db_affected_rows($link, $tmp_result);
3798 $limit -= $limit_part;
3803 return $tags_deleted;
3806 function print_user_stylesheet($link) {
3807 $value = get_pref($link, 'USER_STYLESHEET');
3810 print "<style type=\"text/css\">";
3811 print str_replace("<br/>", "\n", $value);
3817 function rewrite_urls($html) {
3818 libxml_use_internal_errors(true);
3820 $charset_hack = '<head>
3821 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3824 $doc = new DOMDocument();
3825 $doc->loadHTML($charset_hack . $html);
3826 $xpath = new DOMXPath($doc);
3828 $entries = $xpath->query('//*/text()');
3830 foreach ($entries as $entry) {
3831 if (strstr($entry->wholeText
, "://") !== false) {
3832 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3833 "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText
);
3835 if ($text != $entry->wholeText
) {
3836 $cdoc = new DOMDocument();
3837 $cdoc->loadHTML($charset_hack . $text);
3840 foreach ($cdoc->childNodes
as $cnode) {
3841 $cnode = $doc->importNode($cnode, true);
3844 $entry->parentNode
->insertBefore($cnode);
3848 $entry->parentNode
->removeChild($entry);
3854 $node = $doc->getElementsByTagName('body')->item(0);
3856 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3858 return $doc->saveXML($node);
3863 function filter_to_sql($link, $filter, $owner_uid) {
3866 if (DB_TYPE
== "pgsql")
3869 $reg_qpart = "REGEXP";
3871 foreach ($filter["rules"] AS $rule) {
3872 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3873 $rule['reg_exp']) !== FALSE;
3875 if ($regexp_valid) {
3877 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
3879 switch ($rule["type"]) {
3881 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3882 $rule['reg_exp'] . "')";
3885 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3886 $rule['reg_exp'] . "')";
3889 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3890 $rule['reg_exp'] . "') OR LOWER(" .
3891 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3894 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3895 $rule['reg_exp'] . "')";
3898 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3899 $rule['reg_exp'] . "')";
3902 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3903 $rule['reg_exp'] . "')";
3907 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3908 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
3911 if (isset($rule["cat_id"])) {
3913 if ($rule["cat_id"] > 0) {
3914 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
3915 array_push($children, $rule["cat_id"]);
3917 $children = join(",", $children);
3919 $cat_qpart = "cat_id IN ($children)";
3921 $cat_qpart = "cat_id IS NULL";
3924 $qpart .= " AND $cat_qpart";
3927 array_push($query, "($qpart)");
3932 if (count($query) > 0) {
3933 return "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
3939 if (!function_exists('gzdecode')) {
3940 function gzdecode($string) { // no support for 2nd argument
3941 return file_get_contents('compress.zlib://data:who/cares;base64,'.
3942 base64_encode($string));
3946 function get_random_bytes($length) {
3947 if (function_exists('openssl_random_pseudo_bytes')) {
3948 return openssl_random_pseudo_bytes($length);
3952 for ($i = 0; $i < $length; $i++
)
3953 $output .= chr(mt_rand(0, 255));
3959 function read_stdin() {
3960 $fp = fopen("php://stdin", "r");
3963 $line = trim(fgets($fp));
3971 function tmpdirname($path, $prefix) {
3972 // Use PHP's tmpfile function to create a temporary
3973 // directory name. Delete the file and keep the name.
3974 $tempname = tempnam($path,$prefix);
3978 if (!unlink($tempname))
3984 function getFeedCategory($link, $feed) {
3985 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
3986 WHERE id = '$feed'");
3988 if (db_num_rows($result) > 0) {
3989 return db_fetch_result($result, 0, "cat_id");
3996 function implements_interface($class, $interface) {
3997 return in_array($interface, class_implements($class));
4000 function geturl($url){
4002 (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');
4004 $curl = curl_init();
4005 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4006 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4007 $header[] = "Cache-Control: max-age=0";
4008 $header[] = "Connection: keep-alive";
4009 $header[] = "Keep-Alive: 300";
4010 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4011 $header[] = "Accept-Language: en-us,en;q=0.5";
4012 $header[] = "Pragma: ";
4014 curl_setopt($curl, CURLOPT_URL
, $url);
4015 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4016 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4017 curl_setopt($curl, CURLOPT_HEADER
, true);
4018 curl_setopt($curl, CURLOPT_REFERER
, $url);
4019 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4020 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4021 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4022 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4023 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4025 $html = curl_exec($curl);
4027 $status = curl_getinfo($curl);
4030 if($status['http_code']!=200){
4031 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4032 list($header) = explode("\r\n\r\n", $html, 2);
4034 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4035 $url = trim(str_replace($matches[1],"",$matches[0]));
4036 $url_parsed = parse_url($url);
4037 return (isset($url_parsed))?
geturl($url, $referer):'';
4040 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4041 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4042 # $handle = @fopen('./curl.error.log', 'a');
4043 # fwrite($handle, $line);
4049 function get_minified_js($files) {
4050 require_once 'lib/jshrink/Minifier.php';
4054 foreach ($files as $js) {
4055 if (!isset($_GET['debug'])) {
4056 $cached_file = CACHE_DIR
. "/js/$js.js";
4058 if (file_exists($cached_file) &&
4059 is_readable($cached_file) &&
4060 filemtime($cached_file) >= filemtime("js/$js.js")) {
4062 $rv .= file_get_contents($cached_file);
4065 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4066 file_put_contents($cached_file, $minified);
4070 $rv .= file_get_contents("js/$js.js");
4077 function stylesheet_tag($filename) {
4078 $timestamp = filemtime($filename);
4080 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4083 function javascript_tag($filename) {
4086 if (!(strpos($filename, "?") === FALSE)) {
4087 $query = substr($filename, strpos($filename, "?")+
1);
4088 $filename = substr($filename, 0, strpos($filename, "?"));
4091 $timestamp = filemtime($filename);
4093 if ($query) $timestamp .= "&$query";
4095 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";