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",
50 "fr_FR" => "Français",
51 "hu_HU" => "Magyar (Hungarian)",
52 "it_IT" => "Italiano",
53 "ja_JP" => "日本語 (Japanese)",
54 "lv_LV" => "Latviešu",
55 "nb_NO" => "Norwegian bokmål",
58 "pt_BR" => "Portuguese/Brazil",
59 "zh_CN" => "Simplified Chinese");
64 require_once "lib/accept-to-gettext.php";
65 require_once "lib/gettext/gettext.inc";
68 function startup_gettext() {
70 # Get locale from Accept-Language header
71 $lang = al2gt(array_keys(get_translations()), "text/html");
73 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
74 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
77 /* In login action of mobile version */
78 if ($_POST["language"] && defined('MOBILE_VERSION')) {
79 $lang = $_POST["language"];
80 } else if ($_SESSION["language"] && $_SESSION["language"] != "auto") {
81 $lang = $_SESSION["language"];
85 if (defined('LC_MESSAGES')) {
86 _setlocale(LC_MESSAGES
, $lang);
87 } else if (defined('LC_ALL')) {
88 _setlocale(LC_ALL
, $lang);
91 if (defined('MOBILE_VERSION')) {
92 _bindtextdomain("messages", "../locale");
94 _bindtextdomain("messages", "locale");
97 _textdomain("messages");
98 _bind_textdomain_codeset("messages", "UTF-8");
104 require_once 'db-prefs.php';
105 require_once 'version.php';
106 require_once 'ccache.php';
107 require_once 'labels.php';
109 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
110 ini_set('user_agent', SELF_USER_AGENT
);
112 require_once 'lib/pubsubhubbub/publisher.php';
115 $utc_tz = new DateTimeZone('UTC');
116 $schema_version = false;
119 * Print a timestamped debug message.
121 * @param string $msg The debug message.
124 function _debug($msg) {
125 if (defined('QUIET') && QUIET
) {
128 $ts = strftime("%H:%M:%S", time());
129 if (function_exists('posix_getpid')) {
130 $ts = "$ts/" . posix_getpid();
132 print "[$ts] $msg\n";
136 * Purge a feed old posts.
138 * @param mixed $link A database connection.
139 * @param mixed $feed_id The id of the purged feed.
140 * @param mixed $purge_interval Olderness of purged posts.
141 * @param boolean $debug Set to True to enable the debug. False by default.
145 function purge_feed($link, $feed_id, $purge_interval, $debug = false) {
147 if (!$purge_interval) $purge_interval = feed_purge_interval($link, $feed_id);
151 $result = db_query($link,
152 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
156 if (db_num_rows($result) == 1) {
157 $owner_uid = db_fetch_result($result, 0, "owner_uid");
160 if ($purge_interval == -1 ||
!$purge_interval) {
162 ccache_update($link, $feed_id, $owner_uid);
167 if (!$owner_uid) return;
169 if (FORCE_ARTICLE_PURGE
== 0) {
170 $purge_unread = get_pref($link, "PURGE_UNREAD_ARTICLES",
173 $purge_unread = true;
174 $purge_interval = FORCE_ARTICLE_PURGE
;
177 if (!$purge_unread) $query_limit = " unread = false AND ";
179 if (DB_TYPE
== "pgsql") {
180 $pg_version = get_pgsql_version($link);
182 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
184 $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
185 ttrss_entries.id = ref_id AND
187 feed_id = '$feed_id' AND
189 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
193 $result = db_query($link, "DELETE FROM ttrss_user_entries
195 WHERE ttrss_entries.id = ref_id AND
197 feed_id = '$feed_id' AND
199 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
202 $rows = pg_affected_rows($result);
206 /* $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
207 marked = false AND feed_id = '$feed_id' AND
208 (SELECT date_updated FROM ttrss_entries WHERE
209 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
211 $result = db_query($link, "DELETE FROM ttrss_user_entries
212 USING ttrss_user_entries, ttrss_entries
213 WHERE ttrss_entries.id = ref_id AND
215 feed_id = '$feed_id' AND
217 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
219 $rows = mysql_affected_rows($link);
223 ccache_update($link, $feed_id, $owner_uid);
226 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
230 } // function purge_feed
232 function feed_purge_interval($link, $feed_id) {
234 $result = db_query($link, "SELECT purge_interval, owner_uid FROM ttrss_feeds
235 WHERE id = '$feed_id'");
237 if (db_num_rows($result) == 1) {
238 $purge_interval = db_fetch_result($result, 0, "purge_interval");
239 $owner_uid = db_fetch_result($result, 0, "owner_uid");
241 if ($purge_interval == 0) $purge_interval = get_pref($link,
242 'PURGE_OLD_DAYS', $owner_uid);
244 return $purge_interval;
251 function purge_orphans($link, $do_output = false) {
253 // purge orphaned posts in main content table
254 $result = db_query($link, "DELETE FROM ttrss_entries WHERE
255 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
258 $rows = db_affected_rows($link, $result);
259 _debug("Purged $rows orphaned posts.");
263 function get_feed_update_interval($link, $feed_id) {
264 $result = db_query($link, "SELECT owner_uid, update_interval FROM
265 ttrss_feeds WHERE id = '$feed_id'");
267 if (db_num_rows($result) == 1) {
268 $update_interval = db_fetch_result($result, 0, "update_interval");
269 $owner_uid = db_fetch_result($result, 0, "owner_uid");
271 if ($update_interval != 0) {
272 return $update_interval;
274 return get_pref($link, 'DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
282 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false) {
283 $login = urlencode($login);
284 $pass = urlencode($pass);
286 global $fetch_last_error;
288 if (function_exists('curl_init') && !ini_get("open_basedir")) {
290 if (ini_get("safe_mode")) {
291 $ch = curl_init(geturl($url));
293 $ch = curl_init($url);
296 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : 15);
297 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : 45);
298 //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
299 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
300 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
301 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
302 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
303 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
304 curl_setopt($ch, CURLOPT_USERAGENT
, SELF_USER_AGENT
);
305 curl_setopt($ch, CURLOPT_ENCODING
, "gzip");
306 curl_setopt($ch, CURLOPT_REFERER
, $url);
309 curl_setopt($ch, CURLOPT_POST
, true);
310 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
314 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
316 $contents = @curl_exec
($ch);
318 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
319 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
320 $contents = @curl_exec
($ch);
323 if ($contents === false) {
324 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
329 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
330 $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
332 if ($http_code != 200 ||
$type && strpos($content_type, "$type") === false) {
333 if (curl_errno($ch) != 0) {
334 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
336 $fetch_last_error = "HTTP Code: $http_code";
346 if ($login && $pass ){
347 $url_parts = array();
349 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
351 if ($url_parts[1] && $url_parts[2]) {
352 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
356 $data = @file_get_contents
($url);
358 $gzdecoded = gzdecode($data);
359 if ($gzdecoded) $data = $gzdecoded;
361 if (!$data && function_exists('error_get_last')) {
362 $error = error_get_last();
363 $fetch_last_error = $error["message"];
371 * Try to determine the favicon URL for a feed.
372 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
373 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
375 * @param string $url A feed or page URL
377 * @return mixed The favicon URL, or false if none was found.
379 function get_favicon_url($url) {
381 $favicon_url = false;
383 if ($html = @fetch_file_contents
($url)) {
385 libxml_use_internal_errors(true);
387 $doc = new DOMDocument();
388 $doc->loadHTML($html);
389 $xpath = new DOMXPath($doc);
391 $base = $xpath->query('/html/head/base');
392 foreach ($base as $b) {
393 $url = $b->getAttribute("href");
397 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
398 if (count($entries) > 0) {
399 foreach ($entries as $entry) {
400 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
407 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
410 } // function get_favicon_url
412 function check_feed_favicon($site_url, $feed, $link) {
413 # print "FAVICON [$site_url]: $favicon_url\n";
415 $icon_file = ICONS_DIR
. "/$feed.ico";
417 if (!file_exists($icon_file)) {
418 $favicon_url = get_favicon_url($site_url);
421 // Limiting to "image" type misses those served with text/plain
422 $contents = fetch_file_contents($favicon_url); // , "image");
425 // Crude image type matching.
426 // Patterns gleaned from the file(1) source code.
427 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
428 // 0 string \000\000\001\000 MS Windows icon resource
429 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
431 elseif (preg_match('/^GIF8/', $contents)) {
432 // 0 string GIF8 GIF image data
433 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
435 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
436 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
437 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
439 elseif (preg_match('/^\xff\xd8/', $contents)) {
440 // 0 beshort 0xffd8 JPEG image data
441 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
444 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
450 $fp = @fopen
($icon_file, "w");
453 fwrite($fp, $contents);
455 chmod($icon_file, 0644);
462 function print_select($id, $default, $values, $attributes = "") {
463 print "<select name=\"$id\" id=\"$id\" $attributes>";
464 foreach ($values as $v) {
466 $sel = "selected=\"1\"";
472 print "<option value=\"$v\" $sel>$v</option>";
477 function print_select_hash($id, $default, $values, $attributes = "") {
478 print "<select name=\"$id\" id='$id' $attributes>";
479 foreach (array_keys($values) as $v) {
481 $sel = 'selected="selected"';
487 print "<option $sel value=\"$v\">".$values[$v]."</option>";
493 function print_radio($id, $default, $true_is, $values, $attributes = "") {
494 foreach ($values as $v) {
501 if ($v == $true_is) {
502 $sel .= " value=\"1\"";
504 $sel .= " value=\"0\"";
507 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
508 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
513 function initialize_user_prefs($link, $uid, $profile = false) {
515 $uid = db_escape_string($uid);
519 $profile_qpart = "AND profile IS NULL";
521 $profile_qpart = "AND profile = '$profile'";
524 if (get_schema_version($link) < 63) $profile_qpart = "";
526 db_query($link, "BEGIN");
528 $result = db_query($link, "SELECT pref_name,def_value FROM ttrss_prefs");
530 $u_result = db_query($link, "SELECT pref_name
531 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
533 $active_prefs = array();
535 while ($line = db_fetch_assoc($u_result)) {
536 array_push($active_prefs, $line["pref_name"]);
539 while ($line = db_fetch_assoc($result)) {
540 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
541 // print "adding " . $line["pref_name"] . "<br>";
543 if (get_schema_version($link) < 63) {
544 db_query($link, "INSERT INTO ttrss_user_prefs
545 (owner_uid,pref_name,value) VALUES
546 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
549 db_query($link, "INSERT INTO ttrss_user_prefs
550 (owner_uid,pref_name,value, profile) VALUES
551 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
557 db_query($link, "COMMIT");
561 function get_ssl_certificate_id() {
562 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
563 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
564 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
565 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
566 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
571 function authenticate_user($link, $login, $password, $check_only = false) {
573 if (!SINGLE_USER_MODE
) {
578 foreach ($pluginhost->get_hooks($pluginhost::HOOK_AUTH_USER
) as $plugin) {
580 $user_id = (int) $plugin->authenticate($login, $password);
583 $_SESSION["auth_module"] = strtolower(get_class($plugin));
588 if ($user_id && !$check_only) {
589 $_SESSION["uid"] = $user_id;
591 $result = db_query($link, "SELECT login,access_level,pwd_hash FROM ttrss_users
592 WHERE id = '$user_id'");
594 $_SESSION["name"] = db_fetch_result($result, 0, "login");
595 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
596 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
598 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
601 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
602 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
604 $_SESSION["last_version_check"] = time();
606 initialize_user_prefs($link, $_SESSION["uid"]);
615 $_SESSION["uid"] = 1;
616 $_SESSION["name"] = "admin";
617 $_SESSION["access_level"] = 10;
619 $_SESSION["hide_hello"] = true;
620 $_SESSION["hide_logout"] = true;
622 $_SESSION["auth_module"] = false;
624 if (!$_SESSION["csrf_token"]) {
625 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
628 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
630 initialize_user_prefs($link, $_SESSION["uid"]);
636 function make_password($length = 8) {
639 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
643 while ($i < $length) {
644 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
646 if (!strstr($password, $char)) {
654 // this is called after user is created to initialize default feeds, labels
657 // user preferences are checked on every login, not here
659 function initialize_user($link, $uid) {
661 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
662 values ('$uid', 'Tiny Tiny RSS: New Releases',
663 'http://tt-rss.org/releases.rss')");
665 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
666 values ('$uid', 'Tiny Tiny RSS: Forum',
667 'http://tt-rss.org/forum/rss.php')");
670 function logout_user() {
672 if (isset($_COOKIE[session_name()])) {
673 setcookie(session_name(), '', time()-42000, '/');
677 function validate_csrf($csrf_token) {
678 return $csrf_token == $_SESSION['csrf_token'];
681 function validate_session($link) {
682 if (SINGLE_USER_MODE
) return true;
684 $check_ip = $_SESSION['ip_address'];
686 switch (SESSION_CHECK_ADDRESS
) {
691 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
694 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.'));
695 $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+
1);
699 if ($check_ip && strpos($_SERVER['REMOTE_ADDR'], $check_ip) !== 0) {
700 $_SESSION["login_error_msg"] =
701 __("Session failed to validate (incorrect IP)");
705 if ($_SESSION["ref_schema_version"] != get_schema_version($link, true))
708 if ($_SESSION["uid"]) {
710 $result = db_query($link,
711 "SELECT pwd_hash FROM ttrss_users WHERE id = '".$_SESSION["uid"]."'");
713 $pwd_hash = db_fetch_result($result, 0, "pwd_hash");
715 if ($pwd_hash != $_SESSION["pwd_hash"]) {
720 /* if ($_SESSION["cookie_lifetime"] && $_SESSION["uid"]) {
722 //print_r($_SESSION);
724 if (time() > $_SESSION["cookie_lifetime"]) {
732 function load_user_plugins($link, $owner_uid) {
734 $plugins = get_pref($link, "_ENABLED_PLUGINS", $owner_uid);
737 $pluginhost->load($plugins, $pluginhost::KIND_USER
, $owner_uid);
739 if (get_schema_version($link) > 100) {
740 $pluginhost->load_data();
745 function login_sequence($link, $login_form = 0) {
746 $_SESSION["prefs_cache"] = false;
748 if (SINGLE_USER_MODE
) {
749 authenticate_user($link, "admin", null);
751 load_user_plugins($link, $_SESSION["uid"]);
753 if (!$_SESSION["uid"] ||
!validate_session($link)) {
755 if (AUTH_AUTO_LOGIN
&& authenticate_user($link, null, null)) {
756 $_SESSION["ref_schema_version"] = get_schema_version($link, true);
758 authenticate_user($link, null, null, true);
761 if (!$_SESSION["uid"]) render_login_form($link, $login_form);
764 /* bump login timestamp */
765 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
769 if ($_SESSION["uid"] && $_SESSION["language"] && SESSION_COOKIE_LIFETIME
> 0) {
770 setcookie("ttrss_lang", $_SESSION["language"],
771 time() + SESSION_COOKIE_LIFETIME
);
774 if ($_SESSION["uid"]) {
776 load_user_plugins($link, $_SESSION["uid"]);
781 function truncate_string($str, $max_len, $suffix = '…') {
782 if (mb_strlen($str, "utf-8") > $max_len - 3) {
783 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
789 // Deprecated, TODO: remove
790 function theme_image($link, $filename) {
794 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
797 $source_tz = new DateTimeZone($source_tz);
798 } catch (Exception
$e) {
799 $source_tz = new DateTimeZone('UTC');
803 $dest_tz = new DateTimeZone($dest_tz);
804 } catch (Exception
$e) {
805 $dest_tz = new DateTimeZone('UTC');
808 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
809 return $dt->format('U') +
$dest_tz->getOffset($dt);
812 function make_local_datetime($link, $timestamp, $long, $owner_uid = false,
813 $no_smart_dt = false) {
815 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
816 if (!$timestamp) $timestamp = '1970-01-01 0:00';
821 # We store date in UTC internally
822 $dt = new DateTime($timestamp, $utc_tz);
824 if ($tz_offset == -1) {
826 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $owner_uid);
829 $user_tz = new DateTimeZone($user_tz_string);
830 } catch (Exception
$e) {
834 $tz_offset = $user_tz->getOffset($dt);
837 $user_timestamp = $dt->format('U') +
$tz_offset;
840 return smart_date_time($link, $user_timestamp,
841 $tz_offset, $owner_uid);
844 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
846 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
848 return date($format, $user_timestamp);
852 function smart_date_time($link, $timestamp, $tz_offset = 0, $owner_uid = false) {
853 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
855 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
856 return date("G:i", $timestamp);
857 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
858 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
859 return date($format, $timestamp);
861 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
862 return date($format, $timestamp);
866 function sql_bool_to_bool($s) {
867 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
874 function bool_to_sql_bool($s) {
882 // Session caching removed due to causing wrong redirects to upgrade
883 // script when get_schema_version() is called on an obsolete session
884 // created on a previous schema version.
885 function get_schema_version($link, $nocache = false) {
886 global $schema_version;
888 if (!$schema_version) {
889 $result = db_query($link, "SELECT schema_version FROM ttrss_version");
890 $version = db_fetch_result($result, 0, "schema_version");
891 $schema_version = $version;
894 return $schema_version;
898 function sanity_check($link) {
899 require_once 'errors.php';
902 $schema_version = get_schema_version($link, true);
904 if ($schema_version != SCHEMA_VERSION
) {
908 if (DB_TYPE
== "mysql") {
909 $result = db_query($link, "SELECT true", false);
910 if (db_num_rows($result) != 1) {
915 if (db_escape_string("testTEST") != "testTEST") {
919 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
922 function file_is_locked($filename) {
923 if (function_exists('flock')) {
924 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
926 if (flock($fp, LOCK_EX | LOCK_NB
)) {
937 return true; // consider the file always locked and skip the test
940 function make_lockfile($filename) {
941 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
943 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
944 if (function_exists('posix_getpid')) {
945 fwrite($fp, posix_getpid() . "\n");
953 function make_stampfile($filename) {
954 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
956 if (flock($fp, LOCK_EX | LOCK_NB
)) {
957 fwrite($fp, time() . "\n");
966 function sql_random_function() {
967 if (DB_TYPE
== "mysql") {
974 function catchup_feed($link, $feed, $cat_view, $owner_uid = false, $max_id = false) {
976 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
978 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
980 $ref_check_qpart = ($max_id &&
981 !get_pref($link, 'REVERSE_HEADLINES')) ?
"ref_id <= '$max_id'" : "true";
983 if (is_numeric($feed)) {
989 $children = getChildCategories($link, $feed, $owner_uid);
990 array_push($children, $feed);
992 $children = join(",", $children);
994 $cat_qpart = "cat_id IN ($children)";
996 $cat_qpart = "cat_id IS NULL";
999 db_query($link, "UPDATE ttrss_user_entries
1000 SET unread = false,last_read = NOW()
1001 WHERE feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart)
1002 AND $ref_check_qpart AND unread = true
1003 AND owner_uid = $owner_uid");
1005 } else if ($feed == -2) {
1007 db_query($link, "UPDATE ttrss_user_entries
1008 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1009 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1010 AND $ref_check_qpart
1011 AND unread = true AND owner_uid = $owner_uid");
1014 } else if ($feed > 0) {
1016 db_query($link, "UPDATE ttrss_user_entries
1017 SET unread = false,last_read = NOW()
1018 WHERE feed_id = '$feed'
1019 AND $ref_check_qpart AND unread = true
1020 AND owner_uid = $owner_uid");
1022 } else if ($feed < 0 && $feed > -10) { // special, like starred
1025 db_query($link, "UPDATE ttrss_user_entries
1026 SET unread = false,last_read = NOW()
1028 AND $ref_check_qpart AND unread = true
1029 AND owner_uid = $owner_uid");
1033 db_query($link, "UPDATE ttrss_user_entries
1034 SET unread = false,last_read = NOW()
1035 WHERE published = true
1036 AND $ref_check_qpart AND unread = true
1037 AND owner_uid = $owner_uid");
1042 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE");
1044 if (DB_TYPE
== "pgsql") {
1045 $match_part = "updated > NOW() - INTERVAL '$intl hour' ";
1047 $match_part = "updated > DATE_SUB(NOW(),
1048 INTERVAL $intl HOUR) ";
1051 $result = db_query($link, "SELECT id FROM ttrss_entries,
1052 ttrss_user_entries WHERE $match_part AND
1054 ttrss_user_entries.ref_id = ttrss_entries.id AND
1055 owner_uid = $owner_uid");
1057 $affected_ids = array();
1059 while ($line = db_fetch_assoc($result)) {
1060 array_push($affected_ids, $line["id"]);
1063 catchupArticlesById($link, $affected_ids, 0);
1067 db_query($link, "UPDATE ttrss_user_entries
1068 SET unread = false,last_read = NOW()
1069 WHERE $ref_check_qpart AND unread = true AND
1070 owner_uid = $owner_uid");
1073 } else if ($feed < -10) { // label
1075 $label_id = -$feed - 11;
1077 db_query($link, "UPDATE ttrss_user_entries, ttrss_user_labels2
1078 SET unread = false, last_read = NOW()
1079 WHERE label_id = '$label_id' AND unread = true
1080 AND $ref_check_qpart
1081 AND owner_uid = '$owner_uid' AND ref_id = article_id");
1085 ccache_update($link, $feed, $owner_uid, $cat_view);
1088 db_query($link, "BEGIN");
1090 $tag_name = db_escape_string($feed);
1092 $result = db_query($link, "SELECT post_int_id FROM ttrss_tags
1093 WHERE tag_name = '$tag_name' AND owner_uid = $owner_uid");
1095 while ($line = db_fetch_assoc($result)) {
1096 db_query($link, "UPDATE ttrss_user_entries SET
1097 unread = false, last_read = NOW()
1098 WHERE $ref_check_qpart AND unread = true
1099 AND int_id = " . $line["post_int_id"]);
1101 db_query($link, "COMMIT");
1105 function getAllCounters($link) {
1106 $data = getGlobalCounters($link);
1108 $data = array_merge($data, getVirtCounters($link));
1109 $data = array_merge($data, getLabelCounters($link));
1110 $data = array_merge($data, getFeedCounters($link, $active_feed));
1111 $data = array_merge($data, getCategoryCounters($link));
1116 function getCategoryTitle($link, $cat_id) {
1118 if ($cat_id == -1) {
1119 return __("Special");
1120 } else if ($cat_id == -2) {
1121 return __("Labels");
1124 $result = db_query($link, "SELECT title FROM ttrss_feed_categories WHERE
1127 if (db_num_rows($result) == 1) {
1128 return db_fetch_result($result, 0, "title");
1130 return __("Uncategorized");
1136 function getCategoryCounters($link) {
1139 /* Labels category */
1141 $cv = array("id" => -2, "kind" => "cat",
1142 "counter" => getCategoryUnread($link, -2));
1144 array_push($ret_arr, $cv);
1146 $result = db_query($link, "SELECT id AS cat_id, value AS unread,
1147 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1148 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1149 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1150 WHERE ttrss_cat_counters_cache.feed_id = id AND
1151 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1152 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1154 while ($line = db_fetch_assoc($result)) {
1155 $line["cat_id"] = (int) $line["cat_id"];
1157 if ($line["num_children"] > 0) {
1158 $child_counter = getCategoryChildrenUnread($link, $line["cat_id"], $_SESSION["uid"]);
1163 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1164 "counter" => $line["unread"] +
$child_counter);
1166 array_push($ret_arr, $cv);
1169 /* Special case: NULL category doesn't actually exist in the DB */
1171 $cv = array("id" => 0, "kind" => "cat",
1172 "counter" => (int) ccache_find($link, 0, $_SESSION["uid"], true));
1174 array_push($ret_arr, $cv);
1179 // only accepts real cats (>= 0)
1180 function getCategoryChildrenUnread($link, $cat, $owner_uid = false) {
1181 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1183 $result = db_query($link, "SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1184 AND owner_uid = $owner_uid");
1188 while ($line = db_fetch_assoc($result)) {
1189 $unread +
= getCategoryUnread($link, $line["id"], $owner_uid);
1190 $unread +
= getCategoryChildrenUnread($link, $line["id"], $owner_uid);
1196 function getCategoryUnread($link, $cat, $owner_uid = false) {
1198 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1203 $cat_query = "cat_id = '$cat'";
1205 $cat_query = "cat_id IS NULL";
1208 $result = db_query($link, "SELECT id FROM ttrss_feeds WHERE $cat_query
1209 AND owner_uid = " . $owner_uid);
1211 $cat_feeds = array();
1212 while ($line = db_fetch_assoc($result)) {
1213 array_push($cat_feeds, "feed_id = " . $line["id"]);
1216 if (count($cat_feeds) == 0) return 0;
1218 $match_part = implode(" OR ", $cat_feeds);
1220 $result = db_query($link, "SELECT COUNT(int_id) AS unread
1221 FROM ttrss_user_entries
1222 WHERE unread = true AND ($match_part)
1223 AND owner_uid = " . $owner_uid);
1227 # this needs to be rewritten
1228 while ($line = db_fetch_assoc($result)) {
1229 $unread +
= $line["unread"];
1233 } else if ($cat == -1) {
1234 return getFeedUnread($link, -1) +
getFeedUnread($link, -2) +
getFeedUnread($link, -3) +
getFeedUnread($link, 0);
1235 } else if ($cat == -2) {
1237 $result = db_query($link, "
1238 SELECT COUNT(unread) AS unread FROM
1239 ttrss_user_entries, ttrss_user_labels2
1240 WHERE article_id = ref_id AND unread = true
1241 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1243 $unread = db_fetch_result($result, 0, "unread");
1250 function getFeedUnread($link, $feed, $is_cat = false) {
1251 return getFeedArticles($link, $feed, $is_cat, true, $_SESSION["uid"]);
1254 function getLabelUnread($link, $label_id, $owner_uid = false) {
1255 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1257 $result = db_query($link, "SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1258 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1260 if (db_num_rows($result) != 0) {
1261 return db_fetch_result($result, 0, "unread");
1267 function getFeedArticles($link, $feed, $is_cat = false, $unread_only = false,
1268 $owner_uid = false) {
1270 $n_feed = (int) $feed;
1271 $need_entries = false;
1273 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1276 $unread_qpart = "unread = true";
1278 $unread_qpart = "true";
1282 return getCategoryUnread($link, $n_feed, $owner_uid);
1283 } else if ($n_feed == -6) {
1285 } else if ($feed != "0" && $n_feed == 0) {
1287 $feed = db_escape_string($feed);
1289 $result = db_query($link, "SELECT SUM((SELECT COUNT(int_id)
1290 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1291 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1292 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1293 return db_fetch_result($result, 0, "count");
1295 } else if ($n_feed == -1) {
1296 $match_part = "marked = true";
1297 } else if ($n_feed == -2) {
1298 $match_part = "published = true";
1299 } else if ($n_feed == -3) {
1300 $match_part = "unread = true AND score >= 0";
1302 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
1304 if (DB_TYPE
== "pgsql") {
1305 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1307 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1310 $need_entries = true;
1312 } else if ($n_feed == -4) {
1313 $match_part = "true";
1314 } else if ($n_feed >= 0) {
1317 $match_part = "feed_id = '$n_feed'";
1319 $match_part = "feed_id IS NULL";
1322 } else if ($feed < -10) {
1324 $label_id = -$feed - 11;
1326 return getLabelUnread($link, $label_id, $owner_uid);
1332 if ($need_entries) {
1333 $from_qpart = "ttrss_user_entries,ttrss_entries";
1334 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1336 $from_qpart = "ttrss_user_entries";
1339 $query = "SELECT count(int_id) AS unread
1340 FROM $from_qpart WHERE
1341 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1343 //echo "[$feed/$query]\n";
1345 $result = db_query($link, $query);
1349 $result = db_query($link, "SELECT COUNT(post_int_id) AS unread
1350 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1351 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1352 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1355 $unread = db_fetch_result($result, 0, "unread");
1360 function getGlobalUnread($link, $user_id = false) {
1363 $user_id = $_SESSION["uid"];
1366 $result = db_query($link, "SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1367 WHERE owner_uid = '$user_id' AND feed_id > 0");
1369 $c_id = db_fetch_result($result, 0, "c_id");
1374 function getGlobalCounters($link, $global_unread = -1) {
1377 if ($global_unread == -1) {
1378 $global_unread = getGlobalUnread($link);
1381 $cv = array("id" => "global-unread",
1382 "counter" => (int) $global_unread);
1384 array_push($ret_arr, $cv);
1386 $result = db_query($link, "SELECT COUNT(id) AS fn FROM
1387 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1389 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1391 $cv = array("id" => "subscribed-feeds",
1392 "counter" => (int) $subscribed_feeds);
1394 array_push($ret_arr, $cv);
1399 function getVirtCounters($link) {
1403 for ($i = 0; $i >= -4; $i--) {
1405 $count = getFeedUnread($link, $i);
1407 $cv = array("id" => $i,
1408 "counter" => (int) $count);
1410 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1411 // $cv["xmsg"] = getFeedArticles($link, $i)." ".__("total");
1413 array_push($ret_arr, $cv);
1419 function getLabelCounters($link, $descriptions = false) {
1423 $owner_uid = $_SESSION["uid"];
1425 $result = db_query($link, "SELECT id,caption,COUNT(unread) AS unread
1426 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1427 (ttrss_labels2.id = label_id)
1428 LEFT JOIN ttrss_user_entries ON (ref_id = article_id AND unread = true)
1429 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1430 ttrss_labels2.caption");
1432 while ($line = db_fetch_assoc($result)) {
1434 $id = -$line["id"] - 11;
1436 $label_name = $line["caption"];
1437 $count = $line["unread"];
1439 $cv = array("id" => $id,
1440 "counter" => (int) $count);
1443 $cv["description"] = $label_name;
1445 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1446 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1448 array_push($ret_arr, $cv);
1454 function getFeedCounters($link, $active_feed = false) {
1458 $query = "SELECT ttrss_feeds.id,
1460 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1461 last_error, value AS count
1462 FROM ttrss_feeds, ttrss_counters_cache
1463 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1464 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1465 AND ttrss_counters_cache.feed_id = id";
1467 $result = db_query($link, $query);
1468 $fctrs_modified = false;
1470 while ($line = db_fetch_assoc($result)) {
1473 $count = $line["count"];
1474 $last_error = htmlspecialchars($line["last_error"]);
1476 $last_updated = make_local_datetime($link, $line['last_updated'], false);
1478 $has_img = feed_has_icon($id);
1480 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1483 $cv = array("id" => $id,
1484 "updated" => $last_updated,
1485 "counter" => (int) $count,
1486 "has_img" => (int) $has_img);
1489 $cv["error"] = $last_error;
1491 // if (get_pref($link, 'EXTENDED_FEEDLIST'))
1492 // $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1494 if ($active_feed && $id == $active_feed)
1495 $cv["title"] = truncate_string($line["title"], 30);
1497 array_push($ret_arr, $cv);
1504 function get_pgsql_version($link) {
1505 $result = db_query($link, "SELECT version() AS version");
1506 $version = explode(" ", db_fetch_result($result, 0, "version"));
1511 * @return array (code => Status code, message => error message if available)
1513 * 0 - OK, Feed already exists
1514 * 1 - OK, Feed added
1516 * 3 - URL content is HTML, no feeds available
1517 * 4 - URL content is HTML which contains multiple feeds.
1518 * Here you should call extractfeedurls in rpc-backend
1519 * to get all possible feeds.
1520 * 5 - Couldn't download the URL content.
1522 function subscribe_to_feed($link, $url, $cat_id = 0,
1523 $auth_login = '', $auth_pass = '', $need_auth = false) {
1525 global $fetch_last_error;
1527 require_once "include/rssfuncs.php";
1529 $url = fix_url($url);
1531 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1533 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1536 return array("code" => 5, "message" => $fetch_last_error);
1539 if (is_html($contents)) {
1540 $feedUrls = get_feeds_from_html($url, $contents);
1542 if (count($feedUrls) == 0) {
1543 return array("code" => 3);
1544 } else if (count($feedUrls) > 1) {
1545 return array("code" => 4, "feeds" => $feedUrls);
1547 //use feed url as new URL
1548 $url = key($feedUrls);
1551 if ($cat_id == "0" ||
!$cat_id) {
1552 $cat_qpart = "NULL";
1554 $cat_qpart = "'$cat_id'";
1557 $result = db_query($link,
1558 "SELECT id FROM ttrss_feeds
1559 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1561 if (db_num_rows($result) == 0) {
1562 $result = db_query($link,
1563 "INSERT INTO ttrss_feeds
1564 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method)
1565 VALUES ('".$_SESSION["uid"]."', '$url',
1566 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0)");
1568 $result = db_query($link,
1569 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1570 AND owner_uid = " . $_SESSION["uid"]);
1572 $feed_id = db_fetch_result($result, 0, "id");
1575 update_rss_feed($link, $feed_id, true);
1578 return array("code" => 1);
1580 return array("code" => 0);
1584 function print_feed_select($link, $id, $default_id = "",
1585 $attributes = "", $include_all_feeds = true,
1586 $root_id = false, $nest_level = 0) {
1589 print "<select id=\"$id\" name=\"$id\" $attributes>";
1590 if ($include_all_feeds) {
1591 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1592 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1596 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1599 $parent_qpart = "parent_cat = '$root_id'";
1601 $parent_qpart = "parent_cat IS NULL";
1603 $result = db_query($link, "SELECT id,title,
1604 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1605 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1606 FROM ttrss_feed_categories
1607 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1609 while ($line = db_fetch_assoc($result)) {
1611 for ($i = 0; $i < $nest_level; $i++
)
1612 $line["title"] = " - " . $line["title"];
1614 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1616 printf("<option $is_selected value='CAT:%d'>%s</option>",
1617 $line["id"], htmlspecialchars($line["title"]));
1619 if ($line["num_children"] > 0)
1620 print_feed_select($link, $id, $default_id, $attributes,
1621 $include_all_feeds, $line["id"], $nest_level+
1);
1623 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1624 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1626 while ($fline = db_fetch_assoc($feed_result)) {
1627 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1629 $fline["title"] = " + " . $fline["title"];
1631 for ($i = 0; $i < $nest_level; $i++
)
1632 $fline["title"] = " - " . $fline["title"];
1634 printf("<option $is_selected value='%d'>%s</option>",
1635 $fline["id"], htmlspecialchars($fline["title"]));
1640 $is_selected = ($default_id == "CAT:0") ?
"selected=\"1\"" : "";
1642 printf("<option $is_selected value='CAT:0'>%s</option>",
1643 __("Uncategorized"));
1645 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1646 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1648 while ($fline = db_fetch_assoc($feed_result)) {
1649 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1651 $fline["title"] = " + " . $fline["title"];
1653 for ($i = 0; $i < $nest_level; $i++
)
1654 $fline["title"] = " - " . $fline["title"];
1656 printf("<option $is_selected value='%d'>%s</option>",
1657 $fline["id"], htmlspecialchars($fline["title"]));
1662 $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1663 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1665 while ($line = db_fetch_assoc($result)) {
1667 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1669 printf("<option $is_selected value='%d'>%s</option>",
1670 $line["id"], htmlspecialchars($line["title"]));
1679 function print_feed_cat_select($link, $id, $default_id,
1680 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1683 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1687 $parent_qpart = "parent_cat = '$root_id'";
1689 $parent_qpart = "parent_cat IS NULL";
1691 $result = db_query($link, "SELECT id,title,
1692 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1693 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1694 FROM ttrss_feed_categories
1695 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1697 while ($line = db_fetch_assoc($result)) {
1698 if ($line["id"] == $default_id) {
1699 $is_selected = "selected=\"1\"";
1704 for ($i = 0; $i < $nest_level; $i++
)
1705 $line["title"] = " - " . $line["title"];
1708 printf("<option $is_selected value='%d'>%s</option>",
1709 $line["id"], htmlspecialchars($line["title"]));
1711 if ($line["num_children"] > 0)
1712 print_feed_cat_select($link, $id, $default_id, $attributes,
1713 $include_all_cats, $line["id"], $nest_level+
1);
1717 if ($include_all_cats) {
1718 if (db_num_rows($result) > 0) {
1719 print "<option disabled=\"1\">--------</option>";
1722 if ($default_id == 0) {
1723 $is_selected = "selected=\"1\"";
1728 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1734 function checkbox_to_sql_bool($val) {
1735 return ($val == "on") ?
"true" : "false";
1738 function getFeedCatTitle($link, $id) {
1740 return __("Special");
1741 } else if ($id < -10) {
1742 return __("Labels");
1743 } else if ($id > 0) {
1744 $result = db_query($link, "SELECT ttrss_feed_categories.title
1745 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1746 cat_id = ttrss_feed_categories.id");
1747 if (db_num_rows($result) == 1) {
1748 return db_fetch_result($result, 0, "title");
1750 return __("Uncategorized");
1753 return "getFeedCatTitle($id) failed";
1758 function getFeedIcon($id) {
1761 return "images/archive.png";
1764 return "images/mark_set.svg";
1767 return "images/pub_set.svg";
1770 return "images/fresh.png";
1773 return "images/tag.png";
1776 return "images/recently_read.png";
1780 return "images/label.png";
1782 if (file_exists(ICONS_DIR
. "/$id.ico"))
1783 return ICONS_URL
. "/$id.ico";
1789 function getFeedTitle($link, $id, $cat = false) {
1791 return getCategoryTitle($link, $id);
1792 } else if ($id == -1) {
1793 return __("Starred articles");
1794 } else if ($id == -2) {
1795 return __("Published articles");
1796 } else if ($id == -3) {
1797 return __("Fresh articles");
1798 } else if ($id == -4) {
1799 return __("All articles");
1800 } else if ($id === 0 ||
$id === "0") {
1801 return __("Archived articles");
1802 } else if ($id == -6) {
1803 return __("Recently read");
1804 } else if ($id < -10) {
1805 $label_id = -$id - 11;
1806 $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1807 if (db_num_rows($result) == 1) {
1808 return db_fetch_result($result, 0, "caption");
1810 return "Unknown label ($label_id)";
1813 } else if (is_numeric($id) && $id > 0) {
1814 $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1815 if (db_num_rows($result) == 1) {
1816 return db_fetch_result($result, 0, "title");
1818 return "Unknown feed ($id)";
1825 function make_init_params($link) {
1828 $params["sign_progress"] = theme_image($link, "images/indicator_white.gif");
1829 $params["sign_progress_tiny"] = theme_image($link, "images/indicator_tiny.gif");
1830 $params["sign_excl"] = theme_image($link, "images/sign_excl.svg");
1831 $params["sign_info"] = theme_image($link, "images/sign_info.svg");
1833 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1834 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1835 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", "DEFAULT_ARTICLE_LIMIT",
1836 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1838 $params[strtolower($param)] = (int) get_pref($link, $param);
1841 $params["icons_url"] = ICONS_URL
;
1842 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1843 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1844 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1845 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1846 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1848 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1849 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1851 $max_feed_id = db_fetch_result($result, 0, "mid");
1852 $num_feeds = db_fetch_result($result, 0, "nf");
1854 $params["max_feed_id"] = (int) $max_feed_id;
1855 $params["num_feeds"] = (int) $num_feeds;
1857 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1858 $params["hotkeys"] = get_hotkeys_map($link);
1860 $params["csrf_token"] = $_SESSION["csrf_token"];
1861 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1863 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
1868 function get_hotkeys_info($link) {
1870 __("Navigation") => array(
1871 "next_feed" => __("Open next feed"),
1872 "prev_feed" => __("Open previous feed"),
1873 "next_article" => __("Open next article"),
1874 "prev_article" => __("Open previous article"),
1875 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1876 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1877 "search_dialog" => __("Show search dialog")),
1878 __("Article") => array(
1879 "toggle_mark" => __("Toggle starred"),
1880 "toggle_publ" => __("Toggle published"),
1881 "toggle_unread" => __("Toggle unread"),
1882 "edit_tags" => __("Edit tags"),
1883 "dismiss_selected" => __("Dismiss selected"),
1884 "dismiss_read" => __("Dismiss read"),
1885 "open_in_new_window" => __("Open in new window"),
1886 "catchup_below" => __("Mark below as read"),
1887 "catchup_above" => __("Mark above as read"),
1888 "article_scroll_down" => __("Scroll down"),
1889 "article_scroll_up" => __("Scroll up"),
1890 "select_article_cursor" => __("Select article under cursor"),
1891 "email_article" => __("Email article"),
1892 "close_article" => __("Close article"),
1893 "toggle_widescreen" => __("Toggle widescreen mode")),
1894 __("Article selection") => array(
1895 "select_all" => __("Select all articles"),
1896 "select_unread" => __("Select unread"),
1897 "select_marked" => __("Select starred"),
1898 "select_published" => __("Select published"),
1899 "select_invert" => __("Invert selection"),
1900 "select_none" => __("Deselect everything")),
1901 __("Feed") => array(
1902 "feed_refresh" => __("Refresh current feed"),
1903 "feed_unhide_read" => __("Un/hide read feeds"),
1904 "feed_subscribe" => __("Subscribe to feed"),
1905 "feed_edit" => __("Edit feed"),
1906 "feed_catchup" => __("Mark as read"),
1907 "feed_reverse" => __("Reverse headlines"),
1908 "feed_debug_update" => __("Debug feed update"),
1909 "catchup_all" => __("Mark all feeds as read"),
1910 "cat_toggle_collapse" => __("Un/collapse current category"),
1911 "toggle_combined_mode" => __("Toggle combined mode")),
1912 __("Go to") => array(
1913 "goto_all" => __("All articles"),
1914 "goto_fresh" => __("Fresh"),
1915 "goto_marked" => __("Starred"),
1916 "goto_published" => __("Published"),
1917 "goto_tagcloud" => __("Tag cloud"),
1918 "goto_prefs" => __("Preferences")),
1919 __("Other") => array(
1920 "create_label" => __("Create label"),
1921 "create_filter" => __("Create filter"),
1922 "collapse_sidebar" => __("Un/collapse sidebar"),
1923 "help_dialog" => __("Show help dialog"))
1929 function get_hotkeys_map($link) {
1931 // "navigation" => array(
1934 "n" => "next_article",
1935 "p" => "prev_article",
1936 "(38)|up" => "prev_article",
1937 "(40)|down" => "next_article",
1938 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1939 // "^(40)|Ctrl-down" => "next_article_noscroll",
1940 "(191)|/" => "search_dialog",
1941 // "article" => array(
1942 "s" => "toggle_mark",
1943 "*s" => "toggle_publ",
1944 "u" => "toggle_unread",
1945 "*t" => "edit_tags",
1946 "*d" => "dismiss_selected",
1947 "*x" => "dismiss_read",
1948 "o" => "open_in_new_window",
1949 "c p" => "catchup_below",
1950 "c n" => "catchup_above",
1951 "*n" => "article_scroll_down",
1952 "*p" => "article_scroll_up",
1953 "*(38)|Shift+up" => "article_scroll_up",
1954 "*(40)|Shift+down" => "article_scroll_down",
1955 "a *w" => "toggle_widescreen",
1956 "e" => "email_article",
1957 "a q" => "close_article",
1958 // "article_selection" => array(
1959 "a a" => "select_all",
1960 "a u" => "select_unread",
1961 "a *u" => "select_marked",
1962 "a p" => "select_published",
1963 "a i" => "select_invert",
1964 "a n" => "select_none",
1966 "f r" => "feed_refresh",
1967 "f a" => "feed_unhide_read",
1968 "f s" => "feed_subscribe",
1969 "f e" => "feed_edit",
1970 "f q" => "feed_catchup",
1971 "f x" => "feed_reverse",
1972 "f *d" => "feed_debug_update",
1973 "f *c" => "toggle_combined_mode",
1974 "*q" => "catchup_all",
1975 "x" => "cat_toggle_collapse",
1977 "g a" => "goto_all",
1978 "g f" => "goto_fresh",
1979 "g s" => "goto_marked",
1980 "g p" => "goto_published",
1981 "g t" => "goto_tagcloud",
1982 "g *p" => "goto_prefs",
1983 // "other" => array(
1984 "(9)|Tab" => "select_article_cursor", // tab
1985 "c l" => "create_label",
1986 "c f" => "create_filter",
1987 "c s" => "collapse_sidebar",
1988 "^(191)|Ctrl+/" => "help_dialog",
1991 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
1992 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1993 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1997 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP
) as $plugin) {
1998 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2001 $prefixes = array();
2003 foreach (array_keys($hotkeys) as $hotkey) {
2004 $pair = explode(" ", $hotkey, 2);
2006 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2007 array_push($prefixes, $pair[0]);
2011 return array($prefixes, $hotkeys);
2014 function make_runtime_info($link) {
2017 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2018 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2020 $max_feed_id = db_fetch_result($result, 0, "mid");
2021 $num_feeds = db_fetch_result($result, 0, "nf");
2023 $data["max_feed_id"] = (int) $max_feed_id;
2024 $data["num_feeds"] = (int) $num_feeds;
2026 $data['last_article_id'] = getLastArticleId($link);
2027 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2029 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2031 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2033 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2035 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2038 $stamp_delta = time() - $stamp;
2040 if ($stamp_delta > 1800) {
2044 $_SESSION["daemon_stamp_check"] = time();
2047 $data['daemon_stamp_ok'] = $stamp_check;
2049 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2051 $data['daemon_stamp'] = $stamp_fmt;
2056 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2057 $new_version_details = @check_for_update
($link);
2059 $data['new_version_available'] = (int) ($new_version_details != false);
2061 $_SESSION["last_version_check"] = time();
2062 $_SESSION["version_data"] = $new_version_details;
2068 function search_to_sql($link, $search, $match_on) {
2070 $search_query_part = "";
2072 $keywords = explode(" ", $search);
2073 $query_keywords = array();
2075 foreach ($keywords as $k) {
2076 if (strpos($k, "-") === 0) {
2083 $commandpair = explode(":", mb_strtolower($k), 2);
2085 if ($commandpair[0] == "note" && $commandpair[1]) {
2087 if ($commandpair[1] == "true")
2088 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2090 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2092 } else if ($commandpair[0] == "star" && $commandpair[1]) {
2094 if ($commandpair[1] == "true")
2095 array_push($query_keywords, "($not (marked = true))");
2097 array_push($query_keywords, "($not (marked = false))");
2099 } else if ($commandpair[0] == "pub" && $commandpair[1]) {
2101 if ($commandpair[1] == "true")
2102 array_push($query_keywords, "($not (published = true))");
2104 array_push($query_keywords, "($not (published = false))");
2106 } else if (strpos($k, "@") === 0) {
2108 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2109 $orig_ts = strtotime(substr($k, 1));
2110 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2112 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2114 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2115 } else if ($match_on == "both") {
2116 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2117 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2118 } else if ($match_on == "title") {
2119 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%'))");
2120 } else if ($match_on == "content") {
2121 array_push($query_keywords, "(UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2125 $search_query_part = implode("AND", $query_keywords);
2127 return $search_query_part;
2130 function getParentCategories($link, $cat, $owner_uid) {
2133 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2134 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2136 while ($line = db_fetch_assoc($result)) {
2137 array_push($rv, $line["parent_cat"]);
2138 $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2144 function getChildCategories($link, $cat, $owner_uid) {
2147 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2148 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2150 while ($line = db_fetch_assoc($result)) {
2151 array_push($rv, $line["id"]);
2152 $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2158 function queryFeedHeadlines($link, $feed, $limit, $view_mode, $cat_view, $search, $search_mode, $match_on, $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, $ignore_vfeed_group = false) {
2160 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2162 $ext_tables_part = "";
2166 if (SPHINX_ENABLED
) {
2167 $ids = join(",", @sphinx_search
($search, 0, 500));
2170 $search_query_part = "ref_id IN ($ids) AND ";
2172 $search_query_part = "ref_id = -1 AND ";
2175 $search_query_part = search_to_sql($link, $search, $match_on);
2176 $search_query_part .= " AND ";
2180 $search_query_part = "";
2185 if (DB_TYPE
== "pgsql") {
2186 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2188 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2191 $override_order = "updated DESC";
2193 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2195 // Try to check if SQL regexp implementation chokes on a valid regexp
2196 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2197 ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2198 WHERE $filter_query_part LIMIT 1", false);
2201 $test = db_fetch_result($result, 0, "true_val");
2204 $filter_query_part = "false AND";
2206 $filter_query_part .= " AND";
2209 $filter_query_part = "false AND";
2213 $filter_query_part = "";
2217 $since_id_part = "ttrss_entries.id > $since_id AND ";
2219 $since_id_part = "";
2222 $view_query_part = "";
2224 if ($view_mode == "adaptive" ||
$view_query_part == "noscores") {
2226 $view_query_part = " ";
2227 } else if ($feed != -1) {
2228 $unread = getFeedUnread($link, $feed, $cat_view);
2230 if ($cat_view && $feed > 0 && $include_children)
2231 $unread +
= getCategoryChildrenUnread($link, $feed);
2234 $view_query_part = " unread = true AND ";
2239 if ($view_mode == "marked") {
2240 $view_query_part = " marked = true AND ";
2243 if ($view_mode == "published") {
2244 $view_query_part = " published = true AND ";
2247 if ($view_mode == "unread") {
2248 $view_query_part = " unread = true AND ";
2251 if ($view_mode == "updated") {
2252 $view_query_part = " (last_read is null and unread = false) AND ";
2256 $limit_query_part = "LIMIT " . $limit;
2259 $allow_archived = false;
2261 $vfeed_query_part = "";
2263 // override query strategy and enable feed display when searching globally
2264 if ($search && $search_mode == "all_feeds") {
2265 $query_strategy_part = "true";
2266 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2268 } else if (!is_numeric($feed)) {
2269 $query_strategy_part = "true";
2270 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2271 id = feed_id) as feed_title,";
2272 } else if ($search && $search_mode == "this_cat") {
2273 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2276 if ($include_children) {
2277 $subcats = getChildCategories($link, $feed, $owner_uid);
2278 array_push($subcats, $feed);
2279 $cats_qpart = join(",", $subcats);
2281 $cats_qpart = $feed;
2284 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2287 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2290 } else if ($feed > 0) {
2295 if ($include_children) {
2297 $subcats = getChildCategories($link, $feed, $owner_uid);
2299 array_push($subcats, $feed);
2300 $query_strategy_part = "cat_id IN (".
2301 implode(",", $subcats).")";
2304 $query_strategy_part = "cat_id = '$feed'";
2308 $query_strategy_part = "cat_id IS NULL";
2311 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2314 $query_strategy_part = "feed_id = '$feed'";
2316 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2317 $query_strategy_part = "feed_id IS NULL";
2318 $allow_archived = true;
2319 } else if ($feed == 0 && $cat_view) { // uncategorized
2320 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2321 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2322 } else if ($feed == -1) { // starred virtual feed
2323 $query_strategy_part = "marked = true";
2324 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2325 $allow_archived = true;
2327 if (!$override_order) $override_order = "last_marked DESC, updated DESC";
2329 } else if ($feed == -2) { // published virtual feed OR labels category
2332 $query_strategy_part = "published = true";
2333 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2334 $allow_archived = true;
2336 if (!$override_order) $override_order = "last_published DESC, updated DESC";
2338 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2340 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2342 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2343 ttrss_user_labels2.article_id = ref_id";
2346 } else if ($feed == -6) { // recently read
2347 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2348 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2349 $allow_archived = true;
2351 if (!$override_order) $override_order = "last_read DESC";
2352 } else if ($feed == -3) { // fresh virtual feed
2353 $query_strategy_part = "unread = true AND score >= 0";
2355 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2357 if (DB_TYPE
== "pgsql") {
2358 $query_strategy_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
2360 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2363 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2364 } else if ($feed == -4) { // all articles virtual feed
2365 $query_strategy_part = "true";
2366 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2367 } else if ($feed <= -10) { // labels
2368 $label_id = -$feed - 11;
2370 $query_strategy_part = "label_id = '$label_id' AND
2371 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2372 ttrss_user_labels2.article_id = ref_id";
2374 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2375 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2376 $allow_archived = true;
2379 $query_strategy_part = "true";
2382 if (get_pref($link, "SORT_HEADLINES_BY_FEED_DATE", $owner_uid)) {
2383 $date_sort_field = "updated";
2385 $date_sort_field = "date_entered";
2388 if (get_pref($link, 'REVERSE_HEADLINES', $owner_uid)) {
2389 $order_by = "$date_sort_field";
2391 $order_by = "$date_sort_field DESC";
2394 if ($view_mode != "noscores") {
2395 $order_by = "score DESC, $order_by";
2398 if ($override_order) {
2399 $order_by = $override_order;
2405 $feed_title = T_sprintf("Search results: %s", $search);
2408 $feed_title = getCategoryTitle($link, $feed);
2410 if (is_numeric($feed) && $feed > 0) {
2411 $result = db_query($link, "SELECT title,site_url,last_error
2412 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2414 $feed_title = db_fetch_result($result, 0, "title");
2415 $feed_site_url = db_fetch_result($result, 0, "site_url");
2416 $last_error = db_fetch_result($result, 0, "last_error");
2418 $feed_title = getFeedTitle($link, $feed);
2423 $content_query_part = "content as content_preview, cached_content, ";
2425 if (is_numeric($feed)) {
2428 $feed_kind = "Feeds";
2430 $feed_kind = "Labels";
2433 if ($limit_query_part) {
2434 $offset_query_part = "OFFSET $offset";
2437 // proper override_order applied above
2438 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2439 if (!$override_order) {
2440 $order_by = "ttrss_feeds.title, $order_by";
2442 $order_by = "ttrss_feeds.title, $override_order";
2446 if (!$allow_archived) {
2447 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2448 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2451 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2452 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2455 $query = "SELECT DISTINCT
2458 ttrss_entries.id,ttrss_entries.title,
2462 always_display_enclosures,
2469 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2470 last_marked, last_published,
2471 ".SUBSTRING_FOR_DATE
."(last_read,1,19) as last_read_noms,
2474 ".SUBSTRING_FOR_DATE
."(updated,1,19) as updated_noms,
2480 ttrss_user_entries.ref_id = ttrss_entries.id AND
2481 ttrss_user_entries.owner_uid = '$owner_uid' AND
2486 $query_strategy_part ORDER BY $order_by
2487 $limit_query_part $offset_query_part";
2489 if ($_REQUEST["debug"]) print $query;
2491 $result = db_query($link, $query);
2496 $select_qpart = "SELECT DISTINCT " .
2500 "ttrss_entries.id as id," .
2514 "last_marked, last_published, " .
2515 SUBSTRING_FOR_DATE
. "(last_read,1,19) as last_read_noms," .
2518 $content_query_part .
2519 SUBSTRING_FOR_DATE
. "(updated,1,19) as updated_noms," .
2522 $feed_kind = "Tags";
2523 $all_tags = explode(",", $feed);
2524 if ($search_mode == 'any') {
2525 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2526 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2527 $where_qpart = " WHERE " .
2528 "ref_id = ttrss_entries.id AND " .
2529 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2530 "post_int_id = int_id AND $tag_sql AND " .
2532 $search_query_part .
2533 $query_strategy_part . " ORDER BY $order_by " .
2538 $sub_selects = array();
2539 $sub_ands = array();
2540 foreach ($all_tags as $term) {
2541 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");
2548 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2553 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2554 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2555 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2556 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2558 // error_log("TAG SQL: " . $tag_sql);
2559 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2561 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2562 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2565 return array($result, $feed_title, $feed_site_url, $last_error);
2569 function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2570 if (!$owner) $owner = $_SESSION["uid"];
2572 $res = trim($str); if (!$res) return '';
2574 if (strpos($res, "href=") === false)
2575 $res = rewrite_urls($res);
2577 $charset_hack = '<head>
2578 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2581 $res = trim($res); if (!$res) return '';
2583 libxml_use_internal_errors(true);
2585 $doc = new DOMDocument();
2586 $doc->loadHTML($charset_hack . $res);
2587 $xpath = new DOMXPath($doc);
2589 $entries = $xpath->query('(//a[@href]|//img[@src])');
2591 foreach ($entries as $entry) {
2595 if ($entry->hasAttribute('href'))
2596 $entry->setAttribute('href',
2597 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2599 if ($entry->hasAttribute('src')) {
2600 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2602 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2604 if (file_exists($cached_filename)) {
2605 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2608 $entry->setAttribute('src', $src);
2611 if ($entry->nodeName
== 'img') {
2612 if (get_pref($link, "STRIP_IMAGES", $owner) ||
$force_remove_images) {
2614 $p = $doc->createElement('p');
2616 $a = $doc->createElement('a');
2617 $a->setAttribute('href', $entry->getAttribute('src'));
2619 $a->appendChild(new DOMText($entry->getAttribute('src')));
2620 $a->setAttribute('target', '_blank');
2622 $p->appendChild($a);
2624 $entry->parentNode
->replaceChild($p, $entry);
2629 if (strtolower($entry->nodeName
) == "a") {
2630 $entry->setAttribute("target", "_blank");
2634 $entries = $xpath->query('//iframe');
2635 foreach ($entries as $entry) {
2636 $entry->setAttribute('sandbox', true);
2641 if (isset($pluginhost)) {
2642 foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE
) as $plugin) {
2643 $doc = $plugin->hook_sanitize($doc, $site_url);
2647 $doc->removeChild($doc->firstChild
); //remove doctype
2648 $doc = strip_harmful_tags($doc);
2649 $res = $doc->saveHTML();
2653 function strip_harmful_tags($doc) {
2654 $entries = $doc->getElementsByTagName("*");
2656 $allowed_elements = array('a', 'address', 'audio', 'article',
2657 'b', 'big', 'blockquote', 'body', 'br', 'cite',
2658 'code', 'dd', 'del', 'details', 'div', 'dl',
2659 'dt', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
2660 'header', 'html', 'i', 'img', 'ins', 'kbd',
2661 'li', 'nav', 'ol', 'p', 'pre', 'q', 's','small',
2662 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2663 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
2664 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2666 if ($_SESSION['hasSandbox']) array_push($allowed_elements, 'iframe');
2668 $disallowed_attributes = array('id', 'style', 'class');
2670 foreach ($entries as $entry) {
2671 if (!in_array($entry->nodeName
, $allowed_elements)) {
2672 $entry->parentNode
->removeChild($entry);
2675 if ($entry->hasAttributes()) {
2676 foreach (iterator_to_array($entry->attributes
) as $attr) {
2678 if (strpos($attr->nodeName
, 'on') === 0) {
2679 $entry->removeAttributeNode($attr);
2682 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2683 $entry->removeAttributeNode($attr);
2692 function check_for_update($link) {
2693 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2694 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2695 "&iid=" . sha1(SELF_URL_PATH
);
2697 $version_data = @fetch_file_contents
($version_url);
2699 if ($version_data) {
2700 $version_data = json_decode($version_data, true);
2701 if ($version_data && $version_data['version']) {
2703 if (version_compare(VERSION
, $version_data['version']) == -1) {
2704 return $version_data;
2712 function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2714 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2715 if (count($ids) == 0) return;
2719 foreach ($ids as $id) {
2720 array_push($tmp_ids, "ref_id = '$id'");
2723 $ids_qpart = join(" OR ", $tmp_ids);
2726 db_query($link, "UPDATE ttrss_user_entries SET
2727 unread = false,last_read = NOW()
2728 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2729 } else if ($cmode == 1) {
2730 db_query($link, "UPDATE ttrss_user_entries SET
2732 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2734 db_query($link, "UPDATE ttrss_user_entries SET
2735 unread = NOT unread,last_read = NOW()
2736 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2741 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2742 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2744 while ($line = db_fetch_assoc($result)) {
2745 ccache_update($link, $line["feed_id"], $owner_uid);
2749 function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2751 $a_id = db_escape_string($id);
2753 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2755 $query = "SELECT DISTINCT tag_name,
2756 owner_uid as owner FROM
2757 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2758 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2760 $obj_id = md5("TAGS:$owner_uid:$id");
2763 /* check cache first */
2765 if ($tag_cache === false) {
2766 $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2767 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2769 $tag_cache = db_fetch_result($result, 0, "tag_cache");
2773 $tags = explode(",", $tag_cache);
2776 /* do it the hard way */
2778 $tmp_result = db_query($link, $query);
2780 while ($tmp_line = db_fetch_assoc($tmp_result)) {
2781 array_push($tags, $tmp_line["tag_name"]);
2784 /* update the cache */
2786 $tags_str = db_escape_string(join(",", $tags));
2788 db_query($link, "UPDATE ttrss_user_entries
2789 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2790 AND owner_uid = $owner_uid");
2796 function trim_array($array) {
2798 array_walk($tmp, 'trim');
2802 function tag_is_valid($tag) {
2803 if ($tag == '') return false;
2804 if (preg_match("/^[0-9]*$/", $tag)) return false;
2805 if (mb_strlen($tag) > 250) return false;
2807 if (function_exists('iconv')) {
2808 $tag = iconv("utf-8", "utf-8", $tag);
2811 if (!$tag) return false;
2816 function render_login_form($link, $form_id = 0) {
2819 require_once "login_form.php";
2822 require_once "mobile/login_form.php";
2828 // from http://developer.apple.com/internet/safari/faq.html
2829 function no_cache_incantation() {
2830 header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
2831 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
2832 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
2833 header("Cache-Control: post-check=0, pre-check=0", false);
2834 header("Pragma: no-cache"); // HTTP/1.0
2837 function format_warning($msg, $id = "") {
2839 return "<div class=\"warning\" id=\"$id\">
2840 <img src=\"".theme_image($link, "images/sign_excl.svg")."\">$msg</div>";
2843 function format_notice($msg, $id = "") {
2845 return "<div class=\"notice\" id=\"$id\">
2846 <img src=\"".theme_image($link, "images/sign_info.svg")."\">$msg</div>";
2849 function format_error($msg, $id = "") {
2851 return "<div class=\"error\" id=\"$id\">
2852 <img src=\"".theme_image($link, "images/sign_excl.svg")."\">$msg</div>";
2855 function print_notice($msg) {
2856 return print format_notice($msg);
2859 function print_warning($msg) {
2860 return print format_warning($msg);
2863 function print_error($msg) {
2864 return print format_error($msg);
2868 function T_sprintf() {
2869 $args = func_get_args();
2870 return vsprintf(__(array_shift($args)), $args);
2873 function format_inline_player($link, $url, $ctype) {
2877 $url = htmlspecialchars($url);
2879 if (strpos($ctype, "audio/") === 0) {
2881 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2882 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2883 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2885 $id = 'AUDIO-' . uniqid();
2887 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2888 <source type=\"$ctype\" src=\"$url\"></source>
2891 $entry .= "<span onclick=\"player(this)\"
2892 title=\"".__("Click to play")."\" status=\"0\"
2893 class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
2897 $entry .= "<object type=\"application/x-shockwave-flash\"
2898 data=\"lib/button/musicplayer.swf?song_url=$url\"
2899 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
2900 <param name=\"movie\"
2901 value=\"lib/button/musicplayer.swf?song_url=$url\" />
2905 if ($entry) $entry .= " <a target=\"_blank\"
2906 href=\"$url\">" . basename($url) . "</a>";
2914 /* $filename = substr($url, strrpos($url, "/")+1);
2916 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
2917 $filename . " (" . $ctype . ")" . "</a>"; */
2921 function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
2922 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2928 /* we can figure out feed_id from article id anyway, why do we
2929 * pass feed_id here? let's ignore the argument :( */
2931 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
2932 WHERE ref_id = '$id'");
2934 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
2936 $rv['feed_id'] = $feed_id;
2938 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
2940 if ($mark_as_read) {
2941 $result = db_query($link, "UPDATE ttrss_user_entries
2942 SET unread = false,last_read = NOW()
2943 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2945 ccache_update($link, $feed_id, $owner_uid);
2948 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
2949 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
2950 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
2957 FROM ttrss_entries,ttrss_user_entries
2958 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
2962 $line = db_fetch_assoc($result);
2964 $tag_cache = $line["tag_cache"];
2966 $line["tags"] = get_article_tags($link, $id, $owner_uid, $line["tag_cache"]);
2967 unset($line["tag_cache"]);
2969 $line["content"] = sanitize($link, $line["content"], false, $owner_uid, $line["site_url"]);
2973 foreach ($pluginhost->get_hooks($pluginhost::HOOK_RENDER_ARTICLE
) as $p) {
2974 $line = $p->hook_render_article($line);
2977 $num_comments = $line["num_comments"];
2978 $entry_comments = "";
2980 if ($num_comments > 0) {
2981 if ($line["comments"]) {
2982 $comments_url = htmlspecialchars($line["comments"]);
2984 $comments_url = htmlspecialchars($line["link"]);
2986 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
2988 if ($line["comments"] && $line["link"] != $line["comments"]) {
2989 $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
2994 header("Content-Type: text/html");
2995 $rv['content'] .= "<html><head>
2996 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
2997 <title>Tiny Tiny RSS - ".$line["title"]."</title>
2998 <link rel=\"stylesheet\" type=\"text/css\" href=\"tt-rss.css\">
3002 $title_escaped = htmlspecialchars($line['title']);
3004 $rv['content'] .= "<div id=\"PTITLE-FULL-$id\" style=\"display : none\">" .
3005 strip_tags($line['title']) . "</div>";
3007 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3009 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3011 $entry_author = $line["author"];
3013 if ($entry_author) {
3014 $entry_author = __(" - ") . $entry_author;
3017 $parsed_updated = make_local_datetime($link, $line["updated"], true,
3020 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3022 if ($line["link"]) {
3023 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3024 title=\"".htmlspecialchars($line['title'])."\"
3026 htmlspecialchars($line["link"]) . "\">" .
3028 "<span class='author'>$entry_author</span></a></div>";
3030 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3033 $tags_str = format_tags_string($line["tags"], $id);
3034 $tags_str_full = join(", ", $line["tags"]);
3036 if (!$tags_str_full) $tags_str_full = __("no tags");
3038 if (!$entry_comments) $entry_comments = " "; # placeholder
3040 $rv['content'] .= "<div class='postTags' style='float : right'>
3041 <img src='".theme_image($link, 'images/tag.png')."'
3042 class='tagsPic' alt='Tags' title='Tags'> ";
3045 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3046 <a title=\"".__('Edit tags for this article')."\"
3047 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3049 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3050 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3051 position=\"below\">$tags_str_full</div>";
3055 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON
) as $p) {
3056 $rv['content'] .= $p->hook_article_button($line);
3061 $tags_str = strip_tags($tags_str);
3062 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3064 $rv['content'] .= "</div>";
3065 $rv['content'] .= "<div clear='both'>$entry_comments</div>";
3067 if ($line["orig_feed_id"]) {
3069 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3070 WHERE id = ".$line["orig_feed_id"]);
3072 if (db_num_rows($tmp_result) != 0) {
3074 $rv['content'] .= "<div clear='both'>";
3075 $rv['content'] .= __("Originally from:");
3077 $rv['content'] .= " ";
3079 $tmp_line = db_fetch_assoc($tmp_result);
3081 $rv['content'] .= "<a target='_blank'
3082 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3083 $tmp_line['title'] . "</a>";
3085 $rv['content'] .= " ";
3087 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3088 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3090 $rv['content'] .= "</div>";
3094 $rv['content'] .= "</div>";
3096 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3097 if ($line['note']) {
3098 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3100 $rv['content'] .= "</div>";
3102 $rv['content'] .= "<div class=\"postContent\">";
3106 if (DB_TYPE
== "pgsql" and defined('_NGRAM_TITLE_RELATED_THRESHOLD')) {
3108 $ngram_result = db_query($link, "SELECT id,title FROM
3109 ttrss_entries,ttrss_user_entries
3110 WHERE ref_id = id AND updated >= NOW() - INTERVAL '7 day'
3111 AND similarity(title, '$title_escaped') >= "._NGRAM_TITLE_RELATED_THRESHOLD
."
3112 AND title != '$title_escaped'
3113 AND owner_uid = $owner_uid");
3115 if (db_num_rows($ngram_result) > 0) {
3116 $rv['content'] .= "<div dojoType=\"dijit.form.DropDownButton\">".
3117 "<span>" . __('Related')."</span>";
3118 $rv['content'] .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3120 while ($nline = db_fetch_assoc($ngram_result)) {
3121 $rv['content'] .= "<div onclick=\"hlOpenInNewTab(null,".$nline['id'].")\"
3122 dojoType=\"dijit.MenuItem\">".$nline['title']."</div>";
3125 $rv['content'] .= "</div></div><br/";
3129 $rv['content'] .= $line["content"];
3131 $rv['content'] .= format_article_enclosures($link, $id,
3132 $always_display_enclosures, $line["content"]);
3134 $rv['content'] .= "</div>";
3136 $rv['content'] .= "</div>";
3142 <div style=\"text-align : center\">
3143 <button onclick=\"return window.close()\">".
3144 __("Close this window")."</button></div>";
3145 $rv['content'] .= "</body></html>";
3152 function print_checkpoint($n, $s) {
3153 $ts = microtime(true);
3154 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3158 function sanitize_tag($tag) {
3161 $tag = mb_strtolower($tag, 'utf-8');
3163 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3165 // $tag = str_replace('"', "", $tag);
3166 // $tag = str_replace("+", " ", $tag);
3167 $tag = str_replace("technorati tag: ", "", $tag);
3172 function get_self_url_prefix() {
3173 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3174 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3176 return SELF_URL_PATH
;
3181 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3183 * @return string The Mozilla Firefox feed adding URL.
3185 function add_feed_url() {
3186 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3188 $url_path = get_self_url_prefix() .
3189 "/public.php?op=subscribe&feed_url=%s";
3191 } // function add_feed_url
3193 function encrypt_password($pass, $salt = '', $mode2 = false) {
3194 if ($salt && $mode2) {
3195 return "MODE2:" . hash('sha256', $salt . $pass);
3197 return "SHA1X:" . sha1("$salt:$pass");
3199 return "SHA1:" . sha1($pass);
3201 } // function encrypt_password
3203 function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3206 $cat_id = (int)getFeedCategory($link, $feed_id);
3208 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3209 owner_uid = $owner_uid AND enabled = true");
3211 $check_cats = join(",", array_merge(
3212 getParentCategories($link, $cat_id, $owner_uid),
3215 while ($line = db_fetch_assoc($result)) {
3216 $filter_id = $line["id"];
3218 $result2 = db_query($link, "SELECT
3219 r.reg_exp, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3220 FROM ttrss_filters2_rules AS r,
3221 ttrss_filter_types AS t
3223 (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3224 (feed_id IS NULL OR feed_id = '$feed_id') AND
3225 filter_type = t.id AND filter_id = '$filter_id'");
3230 while ($rule_line = db_fetch_assoc($result2)) {
3231 # print_r($rule_line);
3234 $rule["reg_exp"] = $rule_line["reg_exp"];
3235 $rule["type"] = $rule_line["type_name"];
3237 array_push($rules, $rule);
3240 $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3241 FROM ttrss_filters2_actions AS a,
3242 ttrss_filter_actions AS t
3244 action_id = t.id AND filter_id = '$filter_id'");
3246 while ($action_line = db_fetch_assoc($result2)) {
3247 # print_r($action_line);
3250 $action["type"] = $action_line["type_name"];
3251 $action["param"] = $action_line["action_param"];
3253 array_push($actions, $action);
3258 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3259 $filter["rules"] = $rules;
3260 $filter["actions"] = $actions;
3262 if (count($rules) > 0 && count($actions) > 0) {
3263 array_push($filters, $filter);
3270 function get_score_pic($score) {
3272 return "score_high.png";
3273 } else if ($score > 0) {
3274 return "score_half_high.png";
3275 } else if ($score < -100) {
3276 return "score_low.png";
3277 } else if ($score < 0) {
3278 return "score_half_low.png";
3280 return "score_neutral.png";
3284 function feed_has_icon($id) {
3285 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3288 function init_connection($link) {
3291 if (DB_TYPE
== "pgsql") {
3292 pg_query($link, "set client_encoding = 'UTF-8'");
3293 pg_set_client_encoding("UNICODE");
3294 pg_query($link, "set datestyle = 'ISO, european'");
3295 pg_query($link, "set TIME ZONE 0");
3297 db_query($link, "SET time_zone = '+0:0'");
3299 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET
) {
3300 db_query($link, "SET NAMES " . MYSQL_CHARSET
);
3306 $pluginhost = new PluginHost($link);
3307 $pluginhost->load(PLUGINS
, $pluginhost::KIND_ALL
);
3311 print "Unable to connect to database:" . db_last_error();
3316 function format_tags_string($tags, $id) {
3319 $tags_nolinks_str = "";
3325 $formatted_tags = array();
3327 foreach ($tags as $tag) {
3329 $tag_escaped = str_replace("'", "\\'", $tag);
3331 if (mb_strlen($tag) > 30) {
3332 $tag = truncate_string($tag, 30);
3335 $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3337 array_push($formatted_tags, $tag_str);
3339 $tmp_tags_str = implode(", ", $formatted_tags);
3341 if ($num_tags == $tag_limit ||
mb_strlen($tmp_tags_str) > 150) {
3346 $tags_str = implode(", ", $formatted_tags);
3348 if ($num_tags < count($tags)) {
3349 $tags_str .= ", …";
3352 if ($num_tags == 0) {
3353 $tags_str = __("no tags");
3360 function format_article_labels($labels, $id) {
3364 foreach ($labels as $l) {
3365 $labels_str .= sprintf("<span class='hlLabelRef'
3366 style='color : %s; background-color : %s'>%s</span>",
3367 $l[2], $l[3], $l[1]);
3374 function format_article_note($id, $note, $allow_edit = true) {
3376 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3377 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3378 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3384 function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3385 if ($parent_cat_id) {
3386 $parent_qpart = "parent_cat = '$parent_cat_id'";
3387 $parent_insert = "'$parent_cat_id'";
3389 $parent_qpart = "parent_cat IS NULL";
3390 $parent_insert = "NULL";
3393 $result = db_query($link,
3394 "SELECT id FROM ttrss_feed_categories
3395 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3397 if (db_num_rows($result) == 0) {
3400 return db_fetch_result($result, 0, "id");
3404 function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3406 if (!$feed_cat) return false;
3408 db_query($link, "BEGIN");
3410 if ($parent_cat_id) {
3411 $parent_qpart = "parent_cat = '$parent_cat_id'";
3412 $parent_insert = "'$parent_cat_id'";
3414 $parent_qpart = "parent_cat IS NULL";
3415 $parent_insert = "NULL";
3418 $result = db_query($link,
3419 "SELECT id FROM ttrss_feed_categories
3420 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3422 if (db_num_rows($result) == 0) {
3424 $result = db_query($link,
3425 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3426 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3428 db_query($link, "COMMIT");
3436 function getArticleFeed($link, $id) {
3437 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3438 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3440 if (db_num_rows($result) != 0) {
3441 return db_fetch_result($result, 0, "feed_id");
3448 * Fixes incomplete URLs by prepending "http://".
3449 * Also replaces feed:// with http://, and
3450 * prepends a trailing slash if the url is a domain name only.
3452 * @param string $url Possibly incomplete URL
3454 * @return string Fixed URL.
3456 function fix_url($url) {
3457 if (strpos($url, '://') === false) {
3458 $url = 'http://' . $url;
3459 } else if (substr($url, 0, 5) == 'feed:') {
3460 $url = 'http:' . substr($url, 5);
3463 //prepend slash if the URL has no slash in it
3464 // "http://www.example" -> "http://www.example/"
3465 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3469 if ($url != "http:///")
3475 function validate_feed_url($url) {
3476 $parts = parse_url($url);
3478 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3482 function get_article_enclosures($link, $id) {
3484 $query = "SELECT * FROM ttrss_enclosures
3485 WHERE post_id = '$id' AND content_url != ''";
3489 $result = db_query($link, $query);
3491 if (db_num_rows($result) > 0) {
3492 while ($line = db_fetch_assoc($result)) {
3493 array_push($rv, $line);
3500 function save_email_address($link, $email) {
3501 // FIXME: implement persistent storage of emails
3503 if (!$_SESSION['stored_emails'])
3504 $_SESSION['stored_emails'] = array();
3506 if (!in_array($email, $_SESSION['stored_emails']))
3507 array_push($_SESSION['stored_emails'], $email);
3511 function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3513 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3515 $sql_is_cat = bool_to_sql_bool($is_cat);
3517 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3518 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3519 AND owner_uid = " . $owner_uid);
3521 if (db_num_rows($result) == 1) {
3522 return db_fetch_result($result, 0, "access_key");
3524 $key = db_escape_string(sha1(uniqid(rand(), true)));
3526 $result = db_query($link, "INSERT INTO ttrss_access_keys
3527 (access_key, feed_id, is_cat, owner_uid)
3528 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3535 function get_feeds_from_html($url, $content)
3537 $url = fix_url($url);
3538 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3540 libxml_use_internal_errors(true);
3542 $doc = new DOMDocument();
3543 $doc->loadHTML($content);
3544 $xpath = new DOMXPath($doc);
3545 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3546 $feedUrls = array();
3547 foreach ($entries as $entry) {
3548 if ($entry->hasAttribute('href')) {
3549 $title = $entry->getAttribute('title');
3551 $title = $entry->getAttribute('type');
3553 $feedUrl = rewrite_relative_url(
3554 $baseUrl, $entry->getAttribute('href')
3556 $feedUrls[$feedUrl] = $title;
3562 function is_html($content) {
3563 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3566 function url_is_html($url, $login = false, $pass = false) {
3567 return is_html(fetch_file_contents($url, false, $login, $pass));
3570 function print_label_select($link, $name, $value, $attributes = "") {
3572 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3573 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3575 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3576 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3578 while ($line = db_fetch_assoc($result)) {
3580 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3582 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3583 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3587 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3594 function format_article_enclosures($link, $id, $always_display_enclosures,
3597 $result = get_article_enclosures($link, $id);
3600 if (count($result) > 0) {
3602 $entries_html = array();
3604 $entries_inline = array();
3606 foreach ($result as $line) {
3608 $url = $line["content_url"];
3609 $ctype = $line["content_type"];
3611 if (!$ctype) $ctype = __("unknown type");
3613 $filename = substr($url, strrpos($url, "/")+
1);
3615 $player = format_inline_player($link, $url, $ctype);
3617 if ($player) array_push($entries_inline, $player);
3619 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3620 # $filename . " (" . $ctype . ")" . "</a>";
3622 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3623 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3625 array_push($entries_html, $entry);
3629 $entry["type"] = $ctype;
3630 $entry["filename"] = $filename;
3631 $entry["url"] = $url;
3633 array_push($entries, $entry);
3636 if (!get_pref($link, "STRIP_IMAGES")) {
3637 if ($always_display_enclosures ||
3638 !preg_match("/<img/i", $article_content)) {
3640 foreach ($entries as $entry) {
3642 if (preg_match("/image/", $entry["type"]) ||
3643 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3646 alt=\"".htmlspecialchars($entry["filename"])."\"
3647 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3654 if (count($entries_inline) > 0) {
3655 $rv .= "<hr clear='both'/>";
3656 foreach ($entries_inline as $entry) { $rv .= $entry; };
3657 $rv .= "<hr clear='both'/>";
3660 $rv .= "<br/><div dojoType=\"dijit.form.DropDownButton\">".
3661 "<span>" . __('Attachments')."</span>";
3662 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3664 foreach ($entries_html as $entry) { $rv .= $entry; };
3666 $rv .= "</div></div>";
3672 function getLastArticleId($link) {
3673 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3674 WHERE owner_uid = " . $_SESSION["uid"]);
3676 if (db_num_rows($result) == 1) {
3677 return db_fetch_result($result, 0, "id");
3683 function build_url($parts) {
3684 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3688 * Converts a (possibly) relative URL to a absolute one.
3690 * @param string $url Base URL (i.e. from where the document is)
3691 * @param string $rel_url Possibly relative URL in the document
3693 * @return string Absolute URL
3695 function rewrite_relative_url($url, $rel_url) {
3696 if (strpos($rel_url, "magnet:") === 0) {
3698 } else if (strpos($rel_url, "://") !== false) {
3700 } else if (strpos($rel_url, "//") === 0) {
3701 # protocol-relative URL (rare but they exist)
3703 } else if (strpos($rel_url, "/") === 0)
3705 $parts = parse_url($url);
3706 $parts['path'] = $rel_url;
3708 return build_url($parts);
3711 $parts = parse_url($url);
3712 if (!isset($parts['path'])) {
3713 $parts['path'] = '/';
3715 $dir = $parts['path'];
3716 if (substr($dir, -1) !== '/') {
3717 $dir = dirname($parts['path']);
3718 $dir !== '/' && $dir .= '/';
3720 $parts['path'] = $dir . $rel_url;
3722 return build_url($parts);
3726 function sphinx_search($query, $offset = 0, $limit = 30) {
3727 require_once 'lib/sphinxapi.php';
3729 $sphinxClient = new SphinxClient();
3731 $sphinxClient->SetServer('localhost', 9312);
3732 $sphinxClient->SetConnectTimeout(1);
3734 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3735 'feed_title' => 20));
3737 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3738 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3739 $sphinxClient->SetLimits($offset, $limit, 1000);
3740 $sphinxClient->SetArrayResult(false);
3741 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3743 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3747 if (is_array($result['matches'])) {
3748 foreach (array_keys($result['matches']) as $int_id) {
3749 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3750 array_push($ids, $ref_id);
3757 function cleanup_tags($link, $days = 14, $limit = 1000) {
3759 if (DB_TYPE
== "pgsql") {
3760 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3761 } else if (DB_TYPE
== "mysql") {
3762 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3767 while ($limit > 0) {
3770 $query = "SELECT ttrss_tags.id AS id
3771 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3772 WHERE post_int_id = int_id AND $interval_query AND
3773 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3775 $result = db_query($link, $query);
3779 while ($line = db_fetch_assoc($result)) {
3780 array_push($ids, $line['id']);
3783 if (count($ids) > 0) {
3784 $ids = join(",", $ids);
3787 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3788 $tags_deleted +
= db_affected_rows($link, $tmp_result);
3793 $limit -= $limit_part;
3798 return $tags_deleted;
3801 function print_user_stylesheet($link) {
3802 $value = get_pref($link, 'USER_STYLESHEET');
3805 print "<style type=\"text/css\">";
3806 print str_replace("<br/>", "\n", $value);
3812 function rewrite_urls($html) {
3813 libxml_use_internal_errors(true);
3815 $charset_hack = '<head>
3816 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3819 $doc = new DOMDocument();
3820 $doc->loadHTML($charset_hack . $html);
3821 $xpath = new DOMXPath($doc);
3823 $entries = $xpath->query('//*/text()');
3825 foreach ($entries as $entry) {
3826 if (strstr($entry->wholeText
, "://") !== false) {
3827 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3828 "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText
);
3830 if ($text != $entry->wholeText
) {
3831 $cdoc = new DOMDocument();
3832 $cdoc->loadHTML($charset_hack . $text);
3835 foreach ($cdoc->childNodes
as $cnode) {
3836 $cnode = $doc->importNode($cnode, true);
3839 $entry->parentNode
->insertBefore($cnode);
3843 $entry->parentNode
->removeChild($entry);
3849 $node = $doc->getElementsByTagName('body')->item(0);
3851 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3853 return $doc->saveXML($node);
3858 function filter_to_sql($link, $filter, $owner_uid) {
3861 if (DB_TYPE
== "pgsql")
3864 $reg_qpart = "REGEXP";
3866 foreach ($filter["rules"] AS $rule) {
3867 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3868 $rule['reg_exp']) !== FALSE;
3870 if ($regexp_valid) {
3872 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
3874 switch ($rule["type"]) {
3876 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3877 $rule['reg_exp'] . "')";
3880 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3881 $rule['reg_exp'] . "')";
3884 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3885 $rule['reg_exp'] . "') OR LOWER(" .
3886 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3889 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3890 $rule['reg_exp'] . "')";
3893 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3894 $rule['reg_exp'] . "')";
3897 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3898 $rule['reg_exp'] . "')";
3902 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3903 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
3906 if (isset($rule["cat_id"])) {
3908 if ($rule["cat_id"] > 0) {
3909 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
3910 array_push($children, $rule["cat_id"]);
3912 $children = join(",", $children);
3914 $cat_qpart = "cat_id IN ($children)";
3916 $cat_qpart = "cat_id IS NULL";
3919 $qpart .= " AND $cat_qpart";
3922 array_push($query, "($qpart)");
3927 if (count($query) > 0) {
3928 return "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
3934 if (!function_exists('gzdecode')) {
3935 function gzdecode($string) { // no support for 2nd argument
3936 return file_get_contents('compress.zlib://data:who/cares;base64,'.
3937 base64_encode($string));
3941 function get_random_bytes($length) {
3942 if (function_exists('openssl_random_pseudo_bytes')) {
3943 return openssl_random_pseudo_bytes($length);
3947 for ($i = 0; $i < $length; $i++
)
3948 $output .= chr(mt_rand(0, 255));
3954 function read_stdin() {
3955 $fp = fopen("php://stdin", "r");
3958 $line = trim(fgets($fp));
3966 function tmpdirname($path, $prefix) {
3967 // Use PHP's tmpfile function to create a temporary
3968 // directory name. Delete the file and keep the name.
3969 $tempname = tempnam($path,$prefix);
3973 if (!unlink($tempname))
3979 function getFeedCategory($link, $feed) {
3980 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
3981 WHERE id = '$feed'");
3983 if (db_num_rows($result) > 0) {
3984 return db_fetch_result($result, 0, "cat_id");
3991 function implements_interface($class, $interface) {
3992 return in_array($interface, class_implements($class));
3995 function geturl($url){
3997 (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');
3999 $curl = curl_init();
4000 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4001 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4002 $header[] = "Cache-Control: max-age=0";
4003 $header[] = "Connection: keep-alive";
4004 $header[] = "Keep-Alive: 300";
4005 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4006 $header[] = "Accept-Language: en-us,en;q=0.5";
4007 $header[] = "Pragma: ";
4009 curl_setopt($curl, CURLOPT_URL
, $url);
4010 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4011 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4012 curl_setopt($curl, CURLOPT_HEADER
, true);
4013 curl_setopt($curl, CURLOPT_REFERER
, $url);
4014 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4015 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4016 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4017 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4018 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4020 $html = curl_exec($curl);
4022 $status = curl_getinfo($curl);
4025 if($status['http_code']!=200){
4026 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4027 list($header) = explode("\r\n\r\n", $html, 2);
4029 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4030 $url = trim(str_replace($matches[1],"",$matches[0]));
4031 $url_parsed = parse_url($url);
4032 return (isset($url_parsed))?
geturl($url, $referer):'';
4035 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4036 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4037 # $handle = @fopen('./curl.error.log', 'a');
4038 # fwrite($handle, $line);
4044 function get_minified_js($files) {
4045 require_once 'lib/jshrink/Minifier.php';
4049 foreach ($files as $js) {
4050 if (!isset($_GET['debug'])) {
4051 $cached_file = CACHE_DIR
. "/js/$js.js";
4053 if (file_exists($cached_file) &&
4054 is_readable($cached_file) &&
4055 filemtime($cached_file) >= filemtime("js/$js.js")) {
4057 $rv .= file_get_contents($cached_file);
4060 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4061 file_put_contents($cached_file, $minified);
4065 $rv .= file_get_contents("js/$js.js");