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