2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 123);
5 define('LABEL_BASE_INDEX', -1024);
6 define('PLUGIN_FEED_BASE_INDEX', -128);
8 define('COOKIE_LIFETIME_LONG', 86400*365);
10 $fetch_last_error = false;
11 $fetch_last_error_code = false;
12 $fetch_last_content_type = false;
13 $fetch_curl_used = false;
14 $suppress_debugging = false;
16 mb_internal_encoding("UTF-8");
17 date_default_timezone_set('UTC');
18 if (defined('E_DEPRECATED')) {
19 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
21 error_reporting(E_ALL & ~E_NOTICE);
24 require_once 'config.php';
27 * Define a constant if not already defined
29 * @param string $name The constant name.
30 * @param mixed $value The constant value.
32 * @return boolean True if defined successfully or not.
34 function define_default($name, $value) {
35 defined($name) or define($name, $value);
38 ///// Some defaults that you can override in config.php //////
40 define_default('FEED_FETCH_TIMEOUT', 45);
41 // How may seconds to wait for response when requesting feed from a site
42 define_default('FEED_FETCH_NO_CACHE_TIMEOUT', 15);
43 // How may seconds to wait for response when requesting feed from a
44 // site when that feed wasn't cached before
45 define_default('FILE_FETCH_TIMEOUT', 45);
46 // Default timeout when fetching files from remote sites
47 define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
48 // How many seconds to wait for initial response from website when
49 // fetching files from remote sites
51 if (DB_TYPE == "pgsql") {
52 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
54 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
58 * Return available translations names.
61 * @return array A array of available translations.
63 function get_translations() {
65 "auto" => "Detect automatically",
71 "fr_FR" => "Français",
72 "hu_HU" => "Magyar (Hungarian)",
73 "it_IT" => "Italiano",
74 "ja_JP" => "日本語 (Japanese)",
75 "lv_LV" => "Latviešu",
76 "nb_NO" => "Norwegian bokmål",
80 "pt_BR" => "Portuguese/Brazil",
81 "zh_CN" => "Simplified Chinese",
88 require_once "lib/accept-to-gettext.php";
89 require_once "lib/gettext/gettext.inc";
91 require_once "lib/languagedetect/LanguageDetect.php";
93 function startup_gettext() {
95 # Get locale from Accept-Language header
96 $lang = al2gt(array_keys(get_translations()), "text/html");
98 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
99 $lang = _TRANSLATION_OVERRIDE_DEFAULT;
102 if ($_SESSION["uid"] && get_schema_version() >= 120) {
103 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
105 if ($pref_lang && $pref_lang != 'auto') {
111 if (defined('LC_MESSAGES')) {
112 _setlocale(LC_MESSAGES, $lang);
113 } else if (defined('LC_ALL')) {
114 _setlocale(LC_ALL, $lang);
117 _bindtextdomain("messages", "locale");
119 _textdomain("messages");
120 _bind_textdomain_codeset("messages", "UTF-8");
124 require_once 'db-prefs.php';
125 require_once 'version.php';
126 require_once 'ccache.php';
127 require_once 'labels.php';
129 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION . ' (http://tt-rss.org/)');
130 ini_set('user_agent', SELF_USER_AGENT);
132 require_once 'lib/pubsubhubbub/publisher.php';
134 $schema_version = false;
136 function _debug_suppress($suppress) {
137 global $suppress_debugging;
139 $suppress_debugging = $suppress;
143 * Print a timestamped debug message.
145 * @param string $msg The debug message.
148 function _debug($msg, $show = true) {
149 global $suppress_debugging;
151 //echo "[$suppress_debugging] $msg $show\n";
153 if ($suppress_debugging) return false;
155 $ts = strftime("%H:%M:%S", time());
156 if (function_exists('posix_getpid')) {
157 $ts = "$ts/" . posix_getpid();
160 if ($show && !(defined('QUIET') && QUIET)) {
161 print "[$ts] $msg\n";
164 if (defined('LOGFILE')) {
165 $fp = fopen(LOGFILE, 'a+');
170 if (function_exists("flock")) {
173 // try to lock logfile for writing
174 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
185 fputs($fp, "[$ts] $msg\n");
187 if (function_exists("flock")) {
198 * Purge a feed old posts.
200 * @param mixed $link A database connection.
201 * @param mixed $feed_id The id of the purged feed.
202 * @param mixed $purge_interval Olderness of purged posts.
203 * @param boolean $debug Set to True to enable the debug. False by default.
207 function purge_feed($feed_id, $purge_interval, $debug = false) {
209 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
214 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
218 if (db_num_rows($result) == 1) {
219 $owner_uid = db_fetch_result($result, 0, "owner_uid");
222 if ($purge_interval == -1 || !$purge_interval) {
224 ccache_update($feed_id, $owner_uid);
229 if (!$owner_uid) return;
231 if (FORCE_ARTICLE_PURGE == 0) {
232 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
235 $purge_unread = true;
236 $purge_interval = FORCE_ARTICLE_PURGE;
239 if (!$purge_unread) $query_limit = " unread = false AND ";
241 if (DB_TYPE == "pgsql") {
242 $pg_version = get_pgsql_version();
244 if (preg_match("/^7\./", $pg_version) || preg_match("/^8\.0/", $pg_version)) {
246 $result = db_query("DELETE FROM ttrss_user_entries WHERE
247 ttrss_entries.id = ref_id AND
249 feed_id = '$feed_id' AND
251 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
255 $result = db_query("DELETE FROM ttrss_user_entries
257 WHERE ttrss_entries.id = ref_id AND
259 feed_id = '$feed_id' AND
261 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
266 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
267 marked = false AND feed_id = '$feed_id' AND
268 (SELECT date_updated FROM ttrss_entries WHERE
269 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
271 $result = db_query("DELETE FROM ttrss_user_entries
272 USING ttrss_user_entries, ttrss_entries
273 WHERE ttrss_entries.id = ref_id AND
275 feed_id = '$feed_id' AND
277 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
280 $rows = db_affected_rows($result);
282 ccache_update($feed_id, $owner_uid);
285 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
289 } // function purge_feed
291 function feed_purge_interval($feed_id) {
293 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
294 WHERE id = '$feed_id'");
296 if (db_num_rows($result) == 1) {
297 $purge_interval = db_fetch_result($result, 0, "purge_interval");
298 $owner_uid = db_fetch_result($result, 0, "owner_uid");
300 if ($purge_interval == 0) $purge_interval = get_pref(
301 'PURGE_OLD_DAYS', $owner_uid);
303 return $purge_interval;
310 function purge_orphans($do_output = false) {
312 // purge orphaned posts in main content table
313 $result = db_query("DELETE FROM ttrss_entries WHERE
314 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
317 $rows = db_affected_rows($result);
318 _debug("Purged $rows orphaned posts.");
322 function get_feed_update_interval($feed_id) {
323 $result = db_query("SELECT owner_uid, update_interval FROM
324 ttrss_feeds WHERE id = '$feed_id'");
326 if (db_num_rows($result) == 1) {
327 $update_interval = db_fetch_result($result, 0, "update_interval");
328 $owner_uid = db_fetch_result($result, 0, "owner_uid");
330 if ($update_interval != 0) {
331 return $update_interval;
333 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
341 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0) {
343 global $fetch_last_error;
344 global $fetch_last_error_code;
345 global $fetch_last_content_type;
346 global $fetch_curl_used;
348 $url = str_replace(' ', '%20', $url);
350 if (!defined('NO_CURL') && function_exists('curl_init')) {
352 $fetch_curl_used = true;
354 if (ini_get("safe_mode") || ini_get("open_basedir")) {
355 $new_url = geturl($url);
357 // geturl has already populated $fetch_last_error
360 $ch = curl_init($new_url);
362 $ch = curl_init($url);
365 if ($timestamp && !$post_query) {
366 curl_setopt($ch, CURLOPT_HTTPHEADER,
367 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
370 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
371 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : FILE_FETCH_TIMEOUT);
372 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("safe_mode") && !ini_get("open_basedir"));
373 curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
374 curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
375 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
376 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
377 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
378 curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT);
379 curl_setopt($ch, CURLOPT_ENCODING, "");
380 curl_setopt($ch, CURLOPT_REFERER, $url);
382 if (!ini_get("safe_mode") && !ini_get("open_basedir")) {
383 curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
386 if (defined('_CURL_HTTP_PROXY')) {
387 curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY);
391 curl_setopt($ch, CURLOPT_POST, true);
392 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
395 if ((OPENSSL_VERSION_NUMBER >= 0x0090808f) && (OPENSSL_VERSION_NUMBER < 0x10000000)) {
396 curl_setopt($ch, CURLOPT_SSLVERSION, 3);
400 curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
402 $contents = @curl_exec($ch);
404 if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
405 curl_setopt($ch, CURLOPT_ENCODING, 'none');
406 $contents = @curl_exec($ch);
409 if ($contents === false) {
410 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
415 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
416 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
418 $fetch_last_error_code = $http_code;
420 if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) {
421 if (curl_errno($ch) != 0) {
422 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
424 $fetch_last_error = "HTTP Code: $http_code";
435 $fetch_curl_used = false;
437 if ($login && $pass){
438 $url_parts = array();
440 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
442 $pass = urlencode($pass);
444 if ($url_parts[1] && $url_parts[2]) {
445 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
449 if (!$post_query && $timestamp) {
450 $context = stream_context_create(array(
453 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
459 $old_error = error_get_last();
461 $data = @file_get_contents($url, false, $context);
463 $fetch_last_content_type = false; // reset if no type was sent from server
464 if (isset($http_response_header) && is_array($http_response_header)) {
465 foreach ($http_response_header as $h) {
466 if (substr(strtolower($h), 0, 13) == 'content-type:') {
467 $fetch_last_content_type = substr($h, 14);
468 // don't abort here b/c there might be more than one
469 // e.g. if we were being redirected -- last one is the right one
472 if (substr(strtolower($h), 0, 7) == 'http/1.') {
473 $fetch_last_error_code = (int) substr($h, 9, 3);
479 $error = error_get_last();
481 if ($error['message'] != $old_error['message']) {
482 $fetch_last_error = $error["message"];
484 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
493 * Try to determine the favicon URL for a feed.
494 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
495 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
497 * @param string $url A feed or page URL
499 * @return mixed The favicon URL, or false if none was found.
501 function get_favicon_url($url) {
503 $favicon_url = false;
505 if ($html = @fetch_file_contents($url)) {
507 libxml_use_internal_errors(true);
509 $doc = new DOMDocument();
510 $doc->loadHTML($html);
511 $xpath = new DOMXPath($doc);
513 $base = $xpath->query('/html/head/base');
514 foreach ($base as $b) {
515 $url = $b->getAttribute("href");
519 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
520 if (count($entries) > 0) {
521 foreach ($entries as $entry) {
522 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
529 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
532 } // function get_favicon_url
534 function check_feed_favicon($site_url, $feed) {
535 # print "FAVICON [$site_url]: $favicon_url\n";
537 $icon_file = ICONS_DIR . "/$feed.ico";
539 if (!file_exists($icon_file)) {
540 $favicon_url = get_favicon_url($site_url);
543 // Limiting to "image" type misses those served with text/plain
544 $contents = fetch_file_contents($favicon_url); // , "image");
547 // Crude image type matching.
548 // Patterns gleaned from the file(1) source code.
549 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
550 // 0 string \000\000\001\000 MS Windows icon resource
551 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
553 elseif (preg_match('/^GIF8/', $contents)) {
554 // 0 string GIF8 GIF image data
555 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
557 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
558 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
559 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
561 elseif (preg_match('/^\xff\xd8/', $contents)) {
562 // 0 beshort 0xffd8 JPEG image data
563 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
566 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
572 $fp = @fopen($icon_file, "w");
575 fwrite($fp, $contents);
577 chmod($icon_file, 0644);
585 function print_select($id, $default, $values, $attributes = "") {
586 print "<select name=\"$id\" id=\"$id\" $attributes>";
587 foreach ($values as $v) {
589 $sel = "selected=\"1\"";
595 print "<option value=\"$v\" $sel>$v</option>";
600 function print_select_hash($id, $default, $values, $attributes = "") {
601 print "<select name=\"$id\" id='$id' $attributes>";
602 foreach (array_keys($values) as $v) {
604 $sel = 'selected="selected"';
610 print "<option $sel value=\"$v\">".$values[$v]."</option>";
616 function print_radio($id, $default, $true_is, $values, $attributes = "") {
617 foreach ($values as $v) {
624 if ($v == $true_is) {
625 $sel .= " value=\"1\"";
627 $sel .= " value=\"0\"";
630 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
631 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
636 function initialize_user_prefs($uid, $profile = false) {
638 $uid = db_escape_string($uid);
642 $profile_qpart = "AND profile IS NULL";
644 $profile_qpart = "AND profile = '$profile'";
647 if (get_schema_version() < 63) $profile_qpart = "";
651 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
653 $u_result = db_query("SELECT pref_name
654 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
656 $active_prefs = array();
658 while ($line = db_fetch_assoc($u_result)) {
659 array_push($active_prefs, $line["pref_name"]);
662 while ($line = db_fetch_assoc($result)) {
663 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
664 // print "adding " . $line["pref_name"] . "<br>";
666 $line["def_value"] = db_escape_string($line["def_value"]);
667 $line["pref_name"] = db_escape_string($line["pref_name"]);
669 if (get_schema_version() < 63) {
670 db_query("INSERT INTO ttrss_user_prefs
671 (owner_uid,pref_name,value) VALUES
672 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
675 db_query("INSERT INTO ttrss_user_prefs
676 (owner_uid,pref_name,value, profile) VALUES
677 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
687 function get_ssl_certificate_id() {
688 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
689 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
690 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
691 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
692 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
697 function authenticate_user($login, $password, $check_only = false) {
699 if (!SINGLE_USER_MODE) {
702 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
704 $user_id = (int) $plugin->authenticate($login, $password);
707 $_SESSION["auth_module"] = strtolower(get_class($plugin));
712 if ($user_id && !$check_only) {
715 $_SESSION["uid"] = $user_id;
716 $_SESSION["version"] = VERSION_STATIC;
718 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
719 WHERE id = '$user_id'");
721 $_SESSION["name"] = db_fetch_result($result, 0, "login");
722 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
723 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
725 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
728 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
729 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
730 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
732 $_SESSION["last_version_check"] = time();
734 initialize_user_prefs($_SESSION["uid"]);
743 $_SESSION["uid"] = 1;
744 $_SESSION["name"] = "admin";
745 $_SESSION["access_level"] = 10;
747 $_SESSION["hide_hello"] = true;
748 $_SESSION["hide_logout"] = true;
750 $_SESSION["auth_module"] = false;
752 if (!$_SESSION["csrf_token"]) {
753 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
756 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
758 initialize_user_prefs($_SESSION["uid"]);
764 function make_password($length = 8) {
767 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
771 while ($i < $length) {
772 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
774 if (!strstr($password, $char)) {
782 // this is called after user is created to initialize default feeds, labels
785 // user preferences are checked on every login, not here
787 function initialize_user($uid) {
789 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
790 values ('$uid', 'Tiny Tiny RSS: New Releases',
791 'http://tt-rss.org/releases.rss')");
793 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
794 values ('$uid', 'Tiny Tiny RSS: Forum',
795 'http://tt-rss.org/forum/rss.php')");
798 function logout_user() {
800 if (isset($_COOKIE[session_name()])) {
801 setcookie(session_name(), '', time()-42000, '/');
805 function validate_csrf($csrf_token) {
806 return $csrf_token == $_SESSION['csrf_token'];
809 function load_user_plugins($owner_uid) {
810 if ($owner_uid && SCHEMA_VERSION >= 100) {
811 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
813 PluginHost::getInstance()->load($plugins, PluginHost::KIND_USER, $owner_uid);
815 if (get_schema_version() > 100) {
816 PluginHost::getInstance()->load_data();
821 function login_sequence() {
822 if (SINGLE_USER_MODE) {
824 authenticate_user("admin", null);
826 load_user_plugins($_SESSION["uid"]);
828 if (!validate_session()) $_SESSION["uid"] = false;
830 if (!$_SESSION["uid"]) {
832 if (AUTH_AUTO_LOGIN && authenticate_user(null, null)) {
833 $_SESSION["ref_schema_version"] = get_schema_version(true);
835 authenticate_user(null, null, true);
838 if (!$_SESSION["uid"]) {
840 setcookie(session_name(), '', time()-42000, '/');
847 /* bump login timestamp */
848 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
850 $_SESSION["last_login_update"] = time();
853 if ($_SESSION["uid"]) {
855 load_user_plugins($_SESSION["uid"]);
859 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
860 $_SESSION["uid"] . " AND
861 (SELECT COUNT(id) FROM ttrss_feeds WHERE
862 ttrss_feeds.id = feed_id) = 0");
864 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
865 $_SESSION["uid"] . " AND
866 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
867 ttrss_feed_categories.id = feed_id) = 0");
874 function truncate_string($str, $max_len, $suffix = '…') {
875 if (mb_strlen($str, "utf-8") > $max_len - 3) {
876 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
882 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
885 $source_tz = new DateTimeZone($source_tz);
886 } catch (Exception $e) {
887 $source_tz = new DateTimeZone('UTC');
891 $dest_tz = new DateTimeZone($dest_tz);
892 } catch (Exception $e) {
893 $dest_tz = new DateTimeZone('UTC');
896 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
897 return $dt->format('U') + $dest_tz->getOffset($dt);
900 function make_local_datetime($timestamp, $long, $owner_uid = false,
901 $no_smart_dt = false) {
903 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
904 if (!$timestamp) $timestamp = '1970-01-01 0:00';
909 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
911 $timestamp = substr($timestamp, 0, 19);
913 # We store date in UTC internally
914 $dt = new DateTime($timestamp, $utc_tz);
916 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
918 if ($user_tz_string != 'Automatic') {
921 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
922 } catch (Exception $e) {
926 $tz_offset = $user_tz->getOffset($dt);
928 $tz_offset = (int) -$_SESSION["clientTzOffset"];
931 $user_timestamp = $dt->format('U') + $tz_offset;
934 return smart_date_time($user_timestamp,
935 $tz_offset, $owner_uid);
938 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
940 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
942 return date($format, $user_timestamp);
946 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false) {
947 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
949 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() + $tz_offset)) {
950 return date("G:i", $timestamp);
951 } else if (date("Y", $timestamp) == date("Y", time() + $tz_offset)) {
952 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
953 return date($format, $timestamp);
955 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
956 return date($format, $timestamp);
960 function sql_bool_to_bool($s) {
961 if ($s == "t" || $s == "1" || strtolower($s) == "true") {
968 function bool_to_sql_bool($s) {
976 // Session caching removed due to causing wrong redirects to upgrade
977 // script when get_schema_version() is called on an obsolete session
978 // created on a previous schema version.
979 function get_schema_version($nocache = false) {
980 global $schema_version;
982 if (!$schema_version && !$nocache) {
983 $result = db_query("SELECT schema_version FROM ttrss_version");
984 $version = db_fetch_result($result, 0, "schema_version");
985 $schema_version = $version;
988 return $schema_version;
992 function sanity_check() {
993 require_once 'errors.php';
996 $schema_version = get_schema_version(true);
998 if ($schema_version != SCHEMA_VERSION) {
1002 if (DB_TYPE == "mysql") {
1003 $result = db_query("SELECT true", false);
1004 if (db_num_rows($result) != 1) {
1009 if (db_escape_string("testTEST") != "testTEST") {
1013 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
1016 function file_is_locked($filename) {
1017 if (file_exists(LOCK_DIRECTORY . "/$filename")) {
1018 if (function_exists('flock')) {
1019 $fp = @fopen(LOCK_DIRECTORY . "/$filename", "r");
1021 if (flock($fp, LOCK_EX | LOCK_NB)) {
1022 flock($fp, LOCK_UN);
1032 return true; // consider the file always locked and skip the test
1039 function make_lockfile($filename) {
1040 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
1042 if ($fp && flock($fp, LOCK_EX | LOCK_NB)) {
1043 $stat_h = fstat($fp);
1044 $stat_f = stat(LOCK_DIRECTORY . "/$filename");
1046 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1047 if ($stat_h["ino"] != $stat_f["ino"] ||
1048 $stat_h["dev"] != $stat_f["dev"]) {
1054 if (function_exists('posix_getpid')) {
1055 fwrite($fp, posix_getpid() . "\n");
1063 function make_stampfile($filename) {
1064 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
1066 if (flock($fp, LOCK_EX | LOCK_NB)) {
1067 fwrite($fp, time() . "\n");
1068 flock($fp, LOCK_UN);
1076 function sql_random_function() {
1077 if (DB_TYPE == "mysql") {
1084 function catchup_feed($feed, $cat_view, $owner_uid = false, $max_id = false, $mode = 'all') {
1086 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
1088 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
1090 // Todo: all this interval stuff needs some generic generator function
1092 $date_qpart = "false";
1096 if (DB_TYPE == "pgsql") {
1097 $date_qpart = "date_entered < NOW() - INTERVAL '1 day' ";
1099 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) ";
1103 if (DB_TYPE == "pgsql") {
1104 $date_qpart = "date_entered < NOW() - INTERVAL '1 week' ";
1106 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) ";
1110 if (DB_TYPE == "pgsql") {
1111 $date_qpart = "date_entered < NOW() - INTERVAL '2 week' ";
1113 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) ";
1117 $date_qpart = "true";
1120 if (is_numeric($feed)) {
1126 $children = getChildCategories($feed, $owner_uid);
1127 array_push($children, $feed);
1129 $children = join(",", $children);
1131 $cat_qpart = "cat_id IN ($children)";
1133 $cat_qpart = "cat_id IS NULL";
1136 db_query("UPDATE ttrss_user_entries
1137 SET unread = false, last_read = NOW() WHERE ref_id IN
1139 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1140 AND owner_uid = $owner_uid AND unread = true AND feed_id IN
1141 (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart) as tmp)");
1143 } else if ($feed == -2) {
1145 db_query("UPDATE ttrss_user_entries
1146 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1147 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1148 AND unread = true AND $date_qpart AND owner_uid = $owner_uid");
1151 } else if ($feed > 0) {
1153 db_query("UPDATE ttrss_user_entries
1154 SET unread = false, last_read = NOW() WHERE ref_id IN
1156 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1157 AND owner_uid = $owner_uid AND unread = true AND feed_id = $feed AND $date_qpart) as tmp)");
1159 } else if ($feed < 0 && $feed > LABEL_BASE_INDEX) { // special, like starred
1162 db_query("UPDATE ttrss_user_entries
1163 SET unread = false, last_read = NOW() WHERE ref_id IN
1165 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1166 AND owner_uid = $owner_uid AND unread = true AND marked = true AND $date_qpart) as tmp)");
1170 db_query("UPDATE ttrss_user_entries
1171 SET unread = false, last_read = NOW() WHERE ref_id IN
1173 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1174 AND owner_uid = $owner_uid AND unread = true AND published = true AND $date_qpart) as tmp)");
1179 $intl = get_pref("FRESH_ARTICLE_MAX_AGE");
1181 if (DB_TYPE == "pgsql") {
1182 $match_part = "date_entered > NOW() - INTERVAL '$intl hour' ";
1184 $match_part = "date_entered > DATE_SUB(NOW(),
1185 INTERVAL $intl HOUR) ";
1188 db_query("UPDATE ttrss_user_entries
1189 SET unread = false, last_read = NOW() WHERE ref_id IN
1191 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1192 AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $match_part) as tmp)");
1196 db_query("UPDATE ttrss_user_entries
1197 SET unread = false, last_read = NOW() WHERE ref_id IN
1199 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1200 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1203 } else if ($feed < LABEL_BASE_INDEX) { // label
1205 $label_id = feed_to_label_id($feed);
1207 db_query("UPDATE ttrss_user_entries
1208 SET unread = false, last_read = NOW() WHERE ref_id IN
1210 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id
1211 AND label_id = '$label_id' AND ref_id = article_id
1212 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1216 ccache_update($feed, $owner_uid, $cat_view);
1219 db_query("UPDATE ttrss_user_entries
1220 SET unread = false, last_read = NOW() WHERE ref_id IN
1222 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id
1223 AND post_int_id = int_id AND tag_name = '$feed'
1224 AND ttrss_user_entries.owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1229 function getAllCounters() {
1230 $data = getGlobalCounters();
1232 $data = array_merge($data, getVirtCounters());
1233 $data = array_merge($data, getLabelCounters());
1234 $data = array_merge($data, getFeedCounters());
1235 $data = array_merge($data, getCategoryCounters());
1240 function getCategoryTitle($cat_id) {
1242 if ($cat_id == -1) {
1243 return __("Special");
1244 } else if ($cat_id == -2) {
1245 return __("Labels");
1248 $result = db_query("SELECT title FROM ttrss_feed_categories WHERE
1251 if (db_num_rows($result) == 1) {
1252 return db_fetch_result($result, 0, "title");
1254 return __("Uncategorized");
1260 function getCategoryCounters() {
1263 /* Labels category */
1265 $cv = array("id" => -2, "kind" => "cat",
1266 "counter" => getCategoryUnread(-2));
1268 array_push($ret_arr, $cv);
1270 $result = db_query("SELECT id AS cat_id, value AS unread,
1271 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1272 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1273 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1274 WHERE ttrss_cat_counters_cache.feed_id = id AND
1275 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1276 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1278 while ($line = db_fetch_assoc($result)) {
1279 $line["cat_id"] = (int) $line["cat_id"];
1281 if ($line["num_children"] > 0) {
1282 $child_counter = getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
1287 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1288 "counter" => $line["unread"] + $child_counter);
1290 array_push($ret_arr, $cv);
1293 /* Special case: NULL category doesn't actually exist in the DB */
1295 $cv = array("id" => 0, "kind" => "cat",
1296 "counter" => (int) ccache_find(0, $_SESSION["uid"], true));
1298 array_push($ret_arr, $cv);
1303 // only accepts real cats (>= 0)
1304 function getCategoryChildrenUnread($cat, $owner_uid = false) {
1305 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1307 $result = db_query("SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1308 AND owner_uid = $owner_uid");
1312 while ($line = db_fetch_assoc($result)) {
1313 $unread += getCategoryUnread($line["id"], $owner_uid);
1314 $unread += getCategoryChildrenUnread($line["id"], $owner_uid);
1320 function getCategoryUnread($cat, $owner_uid = false) {
1322 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1327 $cat_query = "cat_id = '$cat'";
1329 $cat_query = "cat_id IS NULL";
1332 $result = db_query("SELECT id FROM ttrss_feeds WHERE $cat_query
1333 AND owner_uid = " . $owner_uid);
1335 $cat_feeds = array();
1336 while ($line = db_fetch_assoc($result)) {
1337 array_push($cat_feeds, "feed_id = " . $line["id"]);
1340 if (count($cat_feeds) == 0) return 0;
1342 $match_part = implode(" OR ", $cat_feeds);
1344 $result = db_query("SELECT COUNT(int_id) AS unread
1345 FROM ttrss_user_entries
1346 WHERE unread = true AND ($match_part)
1347 AND owner_uid = " . $owner_uid);
1351 # this needs to be rewritten
1352 while ($line = db_fetch_assoc($result)) {
1353 $unread += $line["unread"];
1357 } else if ($cat == -1) {
1358 return getFeedUnread(-1) + getFeedUnread(-2) + getFeedUnread(-3) + getFeedUnread(0);
1359 } else if ($cat == -2) {
1361 $result = db_query("
1362 SELECT COUNT(unread) AS unread FROM
1363 ttrss_user_entries, ttrss_user_labels2
1364 WHERE article_id = ref_id AND unread = true
1365 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1367 $unread = db_fetch_result($result, 0, "unread");
1374 function getFeedUnread($feed, $is_cat = false) {
1375 return getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1378 function getLabelUnread($label_id, $owner_uid = false) {
1379 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1381 $result = db_query("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1382 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1384 if (db_num_rows($result) != 0) {
1385 return db_fetch_result($result, 0, "unread");
1391 function getFeedArticles($feed, $is_cat = false, $unread_only = false,
1392 $owner_uid = false) {
1394 $n_feed = (int) $feed;
1395 $need_entries = false;
1397 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1400 $unread_qpart = "unread = true";
1402 $unread_qpart = "true";
1406 return getCategoryUnread($n_feed, $owner_uid);
1407 } else if ($n_feed == -6) {
1409 } else if ($feed != "0" && $n_feed == 0) {
1411 $feed = db_escape_string($feed);
1413 $result = db_query("SELECT SUM((SELECT COUNT(int_id)
1414 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1415 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1416 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1417 return db_fetch_result($result, 0, "count");
1419 } else if ($n_feed == -1) {
1420 $match_part = "marked = true";
1421 } else if ($n_feed == -2) {
1422 $match_part = "published = true";
1423 } else if ($n_feed == -3) {
1424 $match_part = "unread = true AND score >= 0";
1426 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
1428 if (DB_TYPE == "pgsql") {
1429 $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
1431 $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1434 $need_entries = true;
1436 } else if ($n_feed == -4) {
1437 $match_part = "true";
1438 } else if ($n_feed >= 0) {
1441 $match_part = "feed_id = '$n_feed'";
1443 $match_part = "feed_id IS NULL";
1446 } else if ($feed < LABEL_BASE_INDEX) {
1448 $label_id = feed_to_label_id($feed);
1450 return getLabelUnread($label_id, $owner_uid);
1456 if ($need_entries) {
1457 $from_qpart = "ttrss_user_entries,ttrss_entries";
1458 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1460 $from_qpart = "ttrss_user_entries";
1463 $query = "SELECT count(int_id) AS unread
1464 FROM $from_qpart WHERE
1465 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1467 //echo "[$feed/$query]\n";
1469 $result = db_query($query);
1473 $result = db_query("SELECT COUNT(post_int_id) AS unread
1474 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1475 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1476 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1479 $unread = db_fetch_result($result, 0, "unread");
1484 function getGlobalUnread($user_id = false) {
1487 $user_id = $_SESSION["uid"];
1490 $result = db_query("SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1491 WHERE owner_uid = '$user_id' AND feed_id > 0");
1493 $c_id = db_fetch_result($result, 0, "c_id");
1498 function getGlobalCounters($global_unread = -1) {
1501 if ($global_unread == -1) {
1502 $global_unread = getGlobalUnread();
1505 $cv = array("id" => "global-unread",
1506 "counter" => (int) $global_unread);
1508 array_push($ret_arr, $cv);
1510 $result = db_query("SELECT COUNT(id) AS fn FROM
1511 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1513 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1515 $cv = array("id" => "subscribed-feeds",
1516 "counter" => (int) $subscribed_feeds);
1518 array_push($ret_arr, $cv);
1523 function getVirtCounters() {
1527 for ($i = 0; $i >= -4; $i--) {
1529 $count = getFeedUnread($i);
1531 if ($i == 0 || $i == -1 || $i == -2)
1532 $auxctr = getFeedArticles($i, false);
1536 $cv = array("id" => $i,
1537 "counter" => (int) $count,
1538 "auxcounter" => $auxctr);
1540 // if (get_pref('EXTENDED_FEEDLIST'))
1541 // $cv["xmsg"] = getFeedArticles($i)." ".__("total");
1543 array_push($ret_arr, $cv);
1546 $feeds = PluginHost::getInstance()->get_feeds(-1);
1548 if (is_array($feeds)) {
1549 foreach ($feeds as $feed) {
1550 $cv = array("id" => PluginHost::pfeed_to_feed_id($feed['id']),
1551 "counter" => $feed['sender']->get_unread($feed['id']));
1553 if (method_exists($feed['sender'], 'get_total'))
1554 $cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
1556 array_push($ret_arr, $cv);
1563 function getLabelCounters($descriptions = false) {
1567 $owner_uid = $_SESSION["uid"];
1569 $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total
1570 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1571 (ttrss_labels2.id = label_id)
1572 LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id
1573 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1574 ttrss_labels2.caption");
1576 while ($line = db_fetch_assoc($result)) {
1578 $id = label_to_feed_id($line["id"]);
1580 $cv = array("id" => $id,
1581 "counter" => (int) $line["unread"],
1582 "auxcounter" => (int) $line["total"]);
1585 $cv["description"] = $line["caption"];
1587 array_push($ret_arr, $cv);
1593 function getFeedCounters($active_feed = false) {
1597 $query = "SELECT ttrss_feeds.id,
1599 ".SUBSTRING_FOR_DATE."(ttrss_feeds.last_updated,1,19) AS last_updated,
1600 last_error, value AS count
1601 FROM ttrss_feeds, ttrss_counters_cache
1602 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1603 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1604 AND ttrss_counters_cache.feed_id = id";
1606 $result = db_query($query);
1607 $fctrs_modified = false;
1609 while ($line = db_fetch_assoc($result)) {
1612 $count = $line["count"];
1613 $last_error = htmlspecialchars($line["last_error"]);
1615 $last_updated = make_local_datetime($line['last_updated'], false);
1617 $has_img = feed_has_icon($id);
1619 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1622 $cv = array("id" => $id,
1623 "updated" => $last_updated,
1624 "counter" => (int) $count,
1625 "has_img" => (int) $has_img);
1628 $cv["error"] = $last_error;
1630 // if (get_pref('EXTENDED_FEEDLIST'))
1631 // $cv["xmsg"] = getFeedArticles($id)." ".__("total");
1633 if ($active_feed && $id == $active_feed)
1634 $cv["title"] = truncate_string($line["title"], 30);
1636 array_push($ret_arr, $cv);
1643 function get_pgsql_version() {
1644 $result = db_query("SELECT version() AS version");
1645 $version = explode(" ", db_fetch_result($result, 0, "version"));
1650 * @return array (code => Status code, message => error message if available)
1652 * 0 - OK, Feed already exists
1653 * 1 - OK, Feed added
1655 * 3 - URL content is HTML, no feeds available
1656 * 4 - URL content is HTML which contains multiple feeds.
1657 * Here you should call extractfeedurls in rpc-backend
1658 * to get all possible feeds.
1659 * 5 - Couldn't download the URL content.
1660 * 6 - Content is an invalid XML.
1662 function subscribe_to_feed($url, $cat_id = 0,
1663 $auth_login = '', $auth_pass = '') {
1665 global $fetch_last_error;
1667 require_once "include/rssfuncs.php";
1669 $url = fix_url($url);
1671 if (!$url || !validate_feed_url($url)) return array("code" => 2);
1673 $contents = @fetch_file_contents($url, false, $auth_login, $auth_pass);
1676 return array("code" => 5, "message" => $fetch_last_error);
1679 if (is_html($contents)) {
1680 $feedUrls = get_feeds_from_html($url, $contents);
1682 if (count($feedUrls) == 0) {
1683 return array("code" => 3);
1684 } else if (count($feedUrls) > 1) {
1685 return array("code" => 4, "feeds" => $feedUrls);
1687 //use feed url as new URL
1688 $url = key($feedUrls);
1691 /* libxml_use_internal_errors(true);
1692 $doc = new DOMDocument();
1693 $doc->loadXML($contents);
1694 $error = libxml_get_last_error();
1695 libxml_clear_errors();
1698 $error_message = format_libxml_error($error);
1700 return array("code" => 6, "message" => $error_message);
1703 if ($cat_id == "0" || !$cat_id) {
1704 $cat_qpart = "NULL";
1706 $cat_qpart = "'$cat_id'";
1710 "SELECT id FROM ttrss_feeds
1711 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1713 if (strlen(FEED_CRYPT_KEY) > 0) {
1714 require_once "crypt.php";
1715 $auth_pass = substr(encrypt_string($auth_pass), 0, 250);
1716 $auth_pass_encrypted = 'true';
1718 $auth_pass_encrypted = 'false';
1721 $auth_pass = db_escape_string($auth_pass);
1723 if (db_num_rows($result) == 0) {
1725 "INSERT INTO ttrss_feeds
1726 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted)
1727 VALUES ('".$_SESSION["uid"]."', '$url',
1728 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, $auth_pass_encrypted)");
1731 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1732 AND owner_uid = " . $_SESSION["uid"]);
1734 $feed_id = db_fetch_result($result, 0, "id");
1737 update_rss_feed($feed_id, true);
1740 return array("code" => 1);
1742 return array("code" => 0);
1746 function print_feed_select($id, $default_id = "",
1747 $attributes = "", $include_all_feeds = true,
1748 $root_id = false, $nest_level = 0) {
1751 print "<select id=\"$id\" name=\"$id\" $attributes>";
1752 if ($include_all_feeds) {
1753 $is_selected = ("0" == $default_id) ? "selected=\"1\"" : "";
1754 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1758 if (get_pref('ENABLE_FEED_CATS')) {
1761 $parent_qpart = "parent_cat = '$root_id'";
1763 $parent_qpart = "parent_cat IS NULL";
1765 $result = db_query("SELECT id,title,
1766 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1767 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1768 FROM ttrss_feed_categories
1769 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1771 while ($line = db_fetch_assoc($result)) {
1773 for ($i = 0; $i < $nest_level; $i++)
1774 $line["title"] = " - " . $line["title"];
1776 $is_selected = ("CAT:".$line["id"] == $default_id) ? "selected=\"1\"" : "";
1778 printf("<option $is_selected value='CAT:%d'>%s</option>",
1779 $line["id"], htmlspecialchars($line["title"]));
1781 if ($line["num_children"] > 0)
1782 print_feed_select($id, $default_id, $attributes,
1783 $include_all_feeds, $line["id"], $nest_level+1);
1785 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1786 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1788 while ($fline = db_fetch_assoc($feed_result)) {
1789 $is_selected = ($fline["id"] == $default_id) ? "selected=\"1\"" : "";
1791 $fline["title"] = " + " . $fline["title"];
1793 for ($i = 0; $i < $nest_level; $i++)
1794 $fline["title"] = " - " . $fline["title"];
1796 printf("<option $is_selected value='%d'>%s</option>",
1797 $fline["id"], htmlspecialchars($fline["title"]));
1802 $default_is_cat = ($default_id == "CAT:0");
1803 $is_selected = $default_is_cat ? "selected=\"1\"" : "";
1805 printf("<option $is_selected value='CAT:0'>%s</option>",
1806 __("Uncategorized"));
1808 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1809 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1811 while ($fline = db_fetch_assoc($feed_result)) {
1812 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ? "selected=\"1\"" : "";
1814 $fline["title"] = " + " . $fline["title"];
1816 for ($i = 0; $i < $nest_level; $i++)
1817 $fline["title"] = " - " . $fline["title"];
1819 printf("<option $is_selected value='%d'>%s</option>",
1820 $fline["id"], htmlspecialchars($fline["title"]));
1825 $result = db_query("SELECT id,title FROM ttrss_feeds
1826 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1828 while ($line = db_fetch_assoc($result)) {
1830 $is_selected = ($line["id"] == $default_id) ? "selected=\"1\"" : "";
1832 printf("<option $is_selected value='%d'>%s</option>",
1833 $line["id"], htmlspecialchars($line["title"]));
1842 function print_feed_cat_select($id, $default_id,
1843 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1846 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1850 $parent_qpart = "parent_cat = '$root_id'";
1852 $parent_qpart = "parent_cat IS NULL";
1854 $result = db_query("SELECT id,title,
1855 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1856 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1857 FROM ttrss_feed_categories
1858 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1860 while ($line = db_fetch_assoc($result)) {
1861 if ($line["id"] == $default_id) {
1862 $is_selected = "selected=\"1\"";
1867 for ($i = 0; $i < $nest_level; $i++)
1868 $line["title"] = " - " . $line["title"];
1871 printf("<option $is_selected value='%d'>%s</option>",
1872 $line["id"], htmlspecialchars($line["title"]));
1874 if ($line["num_children"] > 0)
1875 print_feed_cat_select($id, $default_id, $attributes,
1876 $include_all_cats, $line["id"], $nest_level+1);
1880 if ($include_all_cats) {
1881 if (db_num_rows($result) > 0) {
1882 print "<option disabled=\"1\">--------</option>";
1885 if ($default_id == 0) {
1886 $is_selected = "selected=\"1\"";
1891 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1897 function checkbox_to_sql_bool($val) {
1898 return ($val == "on") ? "true" : "false";
1901 function getFeedCatTitle($id) {
1903 return __("Special");
1904 } else if ($id < LABEL_BASE_INDEX) {
1905 return __("Labels");
1906 } else if ($id > 0) {
1907 $result = db_query("SELECT ttrss_feed_categories.title
1908 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1909 cat_id = ttrss_feed_categories.id");
1910 if (db_num_rows($result) == 1) {
1911 return db_fetch_result($result, 0, "title");
1913 return __("Uncategorized");
1916 return "getFeedCatTitle($id) failed";
1921 function getFeedIcon($id) {
1924 return "images/archive.png";
1927 return "images/star.png";
1930 return "images/feed.png";
1933 return "images/fresh.png";
1936 return "images/folder.png";
1939 return "images/time.png";
1942 if ($id < LABEL_BASE_INDEX) {
1943 return "images/label.png";
1945 if (file_exists(ICONS_DIR . "/$id.ico"))
1946 return ICONS_URL . "/$id.ico";
1954 function getFeedTitle($id, $cat = false) {
1956 return getCategoryTitle($id);
1957 } else if ($id == -1) {
1958 return __("Starred articles");
1959 } else if ($id == -2) {
1960 return __("Published articles");
1961 } else if ($id == -3) {
1962 return __("Fresh articles");
1963 } else if ($id == -4) {
1964 return __("All articles");
1965 } else if ($id === 0 || $id === "0") {
1966 return __("Archived articles");
1967 } else if ($id == -6) {
1968 return __("Recently read");
1969 } else if ($id < LABEL_BASE_INDEX) {
1970 $label_id = feed_to_label_id($id);
1971 $result = db_query("SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1972 if (db_num_rows($result) == 1) {
1973 return db_fetch_result($result, 0, "caption");
1975 return "Unknown label ($label_id)";
1978 } else if (is_numeric($id) && $id > 0) {
1979 $result = db_query("SELECT title FROM ttrss_feeds WHERE id = '$id'");
1980 if (db_num_rows($result) == 1) {
1981 return db_fetch_result($result, 0, "title");
1983 return "Unknown feed ($id)";
1990 function make_init_params() {
1993 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1994 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1995 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1996 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1998 $params[strtolower($param)] = (int) get_pref($param);
2001 $params["icons_url"] = ICONS_URL;
2002 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
2003 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
2004 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
2005 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
2006 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
2007 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
2009 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2010 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2012 $max_feed_id = db_fetch_result($result, 0, "mid");
2013 $num_feeds = db_fetch_result($result, 0, "nf");
2015 $params["max_feed_id"] = (int) $max_feed_id;
2016 $params["num_feeds"] = (int) $num_feeds;
2018 $params["hotkeys"] = get_hotkeys_map();
2020 $params["csrf_token"] = $_SESSION["csrf_token"];
2021 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
2023 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
2028 function get_hotkeys_info() {
2030 __("Navigation") => array(
2031 "next_feed" => __("Open next feed"),
2032 "prev_feed" => __("Open previous feed"),
2033 "next_article" => __("Open next article"),
2034 "prev_article" => __("Open previous article"),
2035 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
2036 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
2037 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
2038 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
2039 "search_dialog" => __("Show search dialog")),
2040 __("Article") => array(
2041 "toggle_mark" => __("Toggle starred"),
2042 "toggle_publ" => __("Toggle published"),
2043 "toggle_unread" => __("Toggle unread"),
2044 "edit_tags" => __("Edit tags"),
2045 "dismiss_selected" => __("Dismiss selected"),
2046 "dismiss_read" => __("Dismiss read"),
2047 "open_in_new_window" => __("Open in new window"),
2048 "catchup_below" => __("Mark below as read"),
2049 "catchup_above" => __("Mark above as read"),
2050 "article_scroll_down" => __("Scroll down"),
2051 "article_scroll_up" => __("Scroll up"),
2052 "select_article_cursor" => __("Select article under cursor"),
2053 "email_article" => __("Email article"),
2054 "close_article" => __("Close/collapse article"),
2055 "toggle_expand" => __("Toggle article expansion (combined mode)"),
2056 "toggle_widescreen" => __("Toggle widescreen mode"),
2057 "toggle_embed_original" => __("Toggle embed original")),
2058 __("Article selection") => array(
2059 "select_all" => __("Select all articles"),
2060 "select_unread" => __("Select unread"),
2061 "select_marked" => __("Select starred"),
2062 "select_published" => __("Select published"),
2063 "select_invert" => __("Invert selection"),
2064 "select_none" => __("Deselect everything")),
2065 __("Feed") => array(
2066 "feed_refresh" => __("Refresh current feed"),
2067 "feed_unhide_read" => __("Un/hide read feeds"),
2068 "feed_subscribe" => __("Subscribe to feed"),
2069 "feed_edit" => __("Edit feed"),
2070 "feed_catchup" => __("Mark as read"),
2071 "feed_reverse" => __("Reverse headlines"),
2072 "feed_debug_update" => __("Debug feed update"),
2073 "catchup_all" => __("Mark all feeds as read"),
2074 "cat_toggle_collapse" => __("Un/collapse current category"),
2075 "toggle_combined_mode" => __("Toggle combined mode"),
2076 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
2077 __("Go to") => array(
2078 "goto_all" => __("All articles"),
2079 "goto_fresh" => __("Fresh"),
2080 "goto_marked" => __("Starred"),
2081 "goto_published" => __("Published"),
2082 "goto_tagcloud" => __("Tag cloud"),
2083 "goto_prefs" => __("Preferences")),
2084 __("Other") => array(
2085 "create_label" => __("Create label"),
2086 "create_filter" => __("Create filter"),
2087 "collapse_sidebar" => __("Un/collapse sidebar"),
2088 "help_dialog" => __("Show help dialog"))
2091 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
2092 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
2098 function get_hotkeys_map() {
2100 // "navigation" => array(
2103 "n" => "next_article",
2104 "p" => "prev_article",
2105 "(38)|up" => "prev_article",
2106 "(40)|down" => "next_article",
2107 // "^(38)|Ctrl-up" => "prev_article_noscroll",
2108 // "^(40)|Ctrl-down" => "next_article_noscroll",
2109 "(191)|/" => "search_dialog",
2110 // "article" => array(
2111 "s" => "toggle_mark",
2112 "*s" => "toggle_publ",
2113 "u" => "toggle_unread",
2114 "*t" => "edit_tags",
2115 "*d" => "dismiss_selected",
2116 "*x" => "dismiss_read",
2117 "o" => "open_in_new_window",
2118 "c p" => "catchup_below",
2119 "c n" => "catchup_above",
2120 "*n" => "article_scroll_down",
2121 "*p" => "article_scroll_up",
2122 "*(38)|Shift+up" => "article_scroll_up",
2123 "*(40)|Shift+down" => "article_scroll_down",
2124 "a *w" => "toggle_widescreen",
2125 "a e" => "toggle_embed_original",
2126 "e" => "email_article",
2127 "a q" => "close_article",
2128 // "article_selection" => array(
2129 "a a" => "select_all",
2130 "a u" => "select_unread",
2131 "a *u" => "select_marked",
2132 "a p" => "select_published",
2133 "a i" => "select_invert",
2134 "a n" => "select_none",
2136 "f r" => "feed_refresh",
2137 "f a" => "feed_unhide_read",
2138 "f s" => "feed_subscribe",
2139 "f e" => "feed_edit",
2140 "f q" => "feed_catchup",
2141 "f x" => "feed_reverse",
2142 "f *d" => "feed_debug_update",
2143 "f *c" => "toggle_combined_mode",
2144 "f c" => "toggle_cdm_expanded",
2145 "*q" => "catchup_all",
2146 "x" => "cat_toggle_collapse",
2148 "g a" => "goto_all",
2149 "g f" => "goto_fresh",
2150 "g s" => "goto_marked",
2151 "g p" => "goto_published",
2152 "g t" => "goto_tagcloud",
2153 "g *p" => "goto_prefs",
2154 // "other" => array(
2155 "(9)|Tab" => "select_article_cursor", // tab
2156 "c l" => "create_label",
2157 "c f" => "create_filter",
2158 "c s" => "collapse_sidebar",
2159 "^(191)|Ctrl+/" => "help_dialog",
2162 if (get_pref('COMBINED_DISPLAY_MODE')) {
2163 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2164 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2167 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
2168 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2171 $prefixes = array();
2173 foreach (array_keys($hotkeys) as $hotkey) {
2174 $pair = explode(" ", $hotkey, 2);
2176 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2177 array_push($prefixes, $pair[0]);
2181 return array($prefixes, $hotkeys);
2184 function make_runtime_info() {
2187 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2188 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2190 $max_feed_id = db_fetch_result($result, 0, "mid");
2191 $num_feeds = db_fetch_result($result, 0, "nf");
2193 $data["max_feed_id"] = (int) $max_feed_id;
2194 $data["num_feeds"] = (int) $num_feeds;
2196 $data['last_article_id'] = getLastArticleId();
2197 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
2199 $data['dep_ts'] = calculate_dep_timestamp();
2200 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
2202 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
2204 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2206 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2208 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
2211 $stamp_delta = time() - $stamp;
2213 if ($stamp_delta > 1800) {
2217 $_SESSION["daemon_stamp_check"] = time();
2220 $data['daemon_stamp_ok'] = $stamp_check;
2222 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2224 $data['daemon_stamp'] = $stamp_fmt;
2229 if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
2230 $new_version_details = @check_for_update();
2232 $data['new_version_available'] = (int) ($new_version_details != false);
2234 $_SESSION["last_version_check"] = time();
2235 $_SESSION["version_data"] = $new_version_details;
2241 function search_to_sql($search) {
2243 $search_query_part = "";
2245 $keywords = explode(" ", $search);
2246 $query_keywords = array();
2247 $search_words = array();
2249 foreach ($keywords as $k) {
2250 if (strpos($k, "-") === 0) {
2257 $commandpair = explode(":", mb_strtolower($k), 2);
2259 switch ($commandpair[0]) {
2261 if ($commandpair[1]) {
2262 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
2263 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2265 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2266 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2267 array_push($search_words, $k);
2271 if ($commandpair[1]) {
2272 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
2273 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2275 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2276 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2277 array_push($search_words, $k);
2281 if ($commandpair[1]) {
2282 if ($commandpair[1] == "true")
2283 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2284 else if ($commandpair[1] == "false")
2285 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2287 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
2288 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2290 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2291 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2292 if (!$not) array_push($search_words, $k);
2297 if ($commandpair[1]) {
2298 if ($commandpair[1] == "true")
2299 array_push($query_keywords, "($not (marked = true))");
2301 array_push($query_keywords, "($not (marked = false))");
2303 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2304 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2305 if (!$not) array_push($search_words, $k);
2309 if ($commandpair[1]) {
2310 if ($commandpair[1] == "true")
2311 array_push($query_keywords, "($not (published = true))");
2313 array_push($query_keywords, "($not (published = false))");
2316 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2317 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2318 if (!$not) array_push($search_words, $k);
2322 if (strpos($k, "@") === 0) {
2324 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
2325 $orig_ts = strtotime(substr($k, 1));
2326 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2328 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2330 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
2332 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2333 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2335 if (!$not) array_push($search_words, $k);
2340 $search_query_part = implode("AND", $query_keywords);
2342 return array($search_query_part, $search_words);
2345 function getParentCategories($cat, $owner_uid) {
2348 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
2349 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2351 while ($line = db_fetch_assoc($result)) {
2352 array_push($rv, $line["parent_cat"]);
2353 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
2359 function getChildCategories($cat, $owner_uid) {
2362 $result = db_query("SELECT id FROM ttrss_feed_categories
2363 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2365 while ($line = db_fetch_assoc($result)) {
2366 array_push($rv, $line["id"]);
2367 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
2373 function queryFeedHeadlines($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, $override_strategy = false, $override_vfeed = false) {
2375 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2377 $ext_tables_part = "";
2378 $search_words = array();
2382 if (SPHINX_ENABLED) {
2383 $ids = join(",", @sphinx_search($search, 0, 500));
2386 $search_query_part = "ref_id IN ($ids) AND ";
2388 $search_query_part = "ref_id = -1 AND ";
2391 list($search_query_part, $search_words) = search_to_sql($search);
2392 $search_query_part .= " AND ";
2396 $search_query_part = "";
2401 if (DB_TYPE == "pgsql") {
2402 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2404 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2407 $override_order = "updated DESC";
2409 $filter_query_part = filter_to_sql($filter, $owner_uid);
2411 // Try to check if SQL regexp implementation chokes on a valid regexp
2414 $result = db_query("SELECT true AS true_val FROM ttrss_entries,
2415 ttrss_user_entries, ttrss_feeds
2416 WHERE $filter_query_part LIMIT 1", false);
2419 $test = db_fetch_result($result, 0, "true_val");
2422 $filter_query_part = "false AND";
2424 $filter_query_part .= " AND";
2427 $filter_query_part = "false AND";
2431 $filter_query_part = "";
2435 $since_id_part = "ttrss_entries.id > $since_id AND ";
2437 $since_id_part = "";
2440 $view_query_part = "";
2442 if ($view_mode == "adaptive") {
2444 $view_query_part = " ";
2445 } else if ($feed != -1) {
2447 $unread = getFeedUnread($feed, $cat_view);
2449 if ($cat_view && $feed > 0 && $include_children)
2450 $unread += getCategoryChildrenUnread($feed);
2453 $view_query_part = " unread = true AND ";
2458 if ($view_mode == "marked") {
2459 $view_query_part = " marked = true AND ";
2462 if ($view_mode == "has_note") {
2463 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
2466 if ($view_mode == "published") {
2467 $view_query_part = " published = true AND ";
2470 if ($view_mode == "unread" && $feed != -6) {
2471 $view_query_part = " unread = true AND ";
2475 $limit_query_part = "LIMIT " . $limit;
2478 $allow_archived = false;
2480 $vfeed_query_part = "";
2482 // override query strategy and enable feed display when searching globally
2483 if ($search && $search_mode == "all_feeds") {
2484 $query_strategy_part = "true";
2485 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2487 } else if (!is_numeric($feed)) {
2488 $query_strategy_part = "true";
2489 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2490 id = feed_id) as feed_title,";
2491 } else if ($search && $search_mode == "this_cat") {
2492 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2495 if ($include_children) {
2496 $subcats = getChildCategories($feed, $owner_uid);
2497 array_push($subcats, $feed);
2498 $cats_qpart = join(",", $subcats);
2500 $cats_qpart = $feed;
2503 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2506 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2509 } else if ($feed > 0) {
2514 if ($include_children) {
2516 $subcats = getChildCategories($feed, $owner_uid);
2518 array_push($subcats, $feed);
2519 $query_strategy_part = "cat_id IN (".
2520 implode(",", $subcats).")";
2523 $query_strategy_part = "cat_id = '$feed'";
2527 $query_strategy_part = "cat_id IS NULL";
2530 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2533 $query_strategy_part = "feed_id = '$feed'";
2535 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2536 $query_strategy_part = "feed_id IS NULL";
2537 $allow_archived = true;
2538 } else if ($feed == 0 && $cat_view) { // uncategorized
2539 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2540 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2541 } else if ($feed == -1) { // starred virtual feed
2542 $query_strategy_part = "marked = true";
2543 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2544 $allow_archived = true;
2546 if (!$override_order) {
2547 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
2550 } else if ($feed == -2) { // published virtual feed OR labels category
2553 $query_strategy_part = "published = true";
2554 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2555 $allow_archived = true;
2557 if (!$override_order) {
2558 $override_order = "last_published DESC, date_entered DESC, updated DESC";
2562 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2564 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2566 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2567 ttrss_user_labels2.article_id = ref_id";
2570 } else if ($feed == -6) { // recently read
2571 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2572 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2573 $allow_archived = true;
2575 if (!$override_order) $override_order = "last_read DESC";
2577 /* } else if ($feed == -7) { // shared
2578 $query_strategy_part = "uuid != ''";
2579 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2580 $allow_archived = true; */
2581 } else if ($feed == -3) { // fresh virtual feed
2582 $query_strategy_part = "unread = true AND score >= 0";
2584 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
2586 if (DB_TYPE == "pgsql") {
2587 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
2589 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2592 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2593 } else if ($feed == -4) { // all articles virtual feed
2594 $allow_archived = true;
2595 $query_strategy_part = "true";
2596 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2597 } else if ($feed <= LABEL_BASE_INDEX) { // labels
2598 $label_id = feed_to_label_id($feed);
2600 $query_strategy_part = "label_id = '$label_id' AND
2601 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2602 ttrss_user_labels2.article_id = ref_id";
2604 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2605 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2606 $allow_archived = true;
2609 $query_strategy_part = "true";
2612 $order_by = "score DESC, date_entered DESC, updated DESC";
2614 if ($view_mode == "unread_first") {
2615 $order_by = "unread DESC, $order_by";
2618 if ($override_order) {
2619 $order_by = $override_order;
2622 if ($override_strategy) {
2623 $query_strategy_part = $override_strategy;
2626 if ($override_vfeed) {
2627 $vfeed_query_part = $override_vfeed;
2633 $feed_title = T_sprintf("Search results: %s", $search);
2636 $feed_title = getCategoryTitle($feed);
2638 if (is_numeric($feed) && $feed > 0) {
2639 $result = db_query("SELECT title,site_url,last_error,last_updated
2640 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2642 $feed_title = db_fetch_result($result, 0, "title");
2643 $feed_site_url = db_fetch_result($result, 0, "site_url");
2644 $last_error = db_fetch_result($result, 0, "last_error");
2645 $last_updated = db_fetch_result($result, 0, "last_updated");
2647 $feed_title = getFeedTitle($feed);
2653 $content_query_part = "content, content AS content_preview, ";
2656 if (is_numeric($feed)) {
2659 $feed_kind = "Feeds";
2661 $feed_kind = "Labels";
2664 if ($limit_query_part) {
2665 $offset_query_part = "OFFSET $offset";
2668 // proper override_order applied above
2669 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
2670 if (!$override_order) {
2671 $order_by = "ttrss_feeds.title, $order_by";
2673 $order_by = "ttrss_feeds.title, $override_order";
2677 if (!$allow_archived) {
2678 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2679 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2682 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2683 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2686 if ($vfeed_query_part)
2687 $vfeed_query_part .= "favicon_avg_color,";
2689 $query = "SELECT DISTINCT
2692 ttrss_entries.id,ttrss_entries.title,
2696 always_display_enclosures,
2705 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2706 last_marked, last_published,
2714 ttrss_user_entries.ref_id = ttrss_entries.id AND
2715 ttrss_user_entries.owner_uid = '$owner_uid' AND
2720 $query_strategy_part ORDER BY $order_by
2721 $limit_query_part $offset_query_part";
2723 if ($_REQUEST["debug"]) print $query;
2725 $result = db_query($query);
2730 $select_qpart = "SELECT DISTINCT " .
2734 "ttrss_entries.id as id," .
2749 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2750 "last_marked, last_published, " .
2753 $content_query_part .
2756 $feed_kind = "Tags";
2757 $all_tags = explode(",", $feed);
2758 if ($search_mode == 'any') {
2759 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2760 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2761 $where_qpart = " WHERE " .
2762 "ref_id = ttrss_entries.id AND " .
2763 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2764 "post_int_id = int_id AND $tag_sql AND " .
2766 $search_query_part .
2767 $query_strategy_part . " ORDER BY $order_by " .
2772 $sub_selects = array();
2773 $sub_ands = array();
2774 foreach ($all_tags as $term) {
2775 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");
2782 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2787 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2788 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2789 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2790 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2792 // error_log("TAG SQL: " . $tag_sql);
2793 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2795 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2796 $result = db_query($select_qpart . $from_qpart . $where_qpart);
2799 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
2803 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
2804 if (!$owner) $owner = $_SESSION["uid"];
2806 $res = trim($str); if (!$res) return '';
2808 $charset_hack = '<head>
2809 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2812 $res = trim($res); if (!$res) return '';
2814 libxml_use_internal_errors(true);
2816 $doc = new DOMDocument();
2817 $doc->loadHTML($charset_hack . $res);
2818 $xpath = new DOMXPath($doc);
2820 $entries = $xpath->query('(//a[@href]|//img[@src])');
2822 foreach ($entries as $entry) {
2826 if ($entry->hasAttribute('href'))
2827 $entry->setAttribute('href',
2828 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2830 if ($entry->hasAttribute('src')) {
2831 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2833 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
2835 if (file_exists($cached_filename)) {
2836 $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src);
2839 $entry->setAttribute('src', $src);
2842 if ($entry->nodeName == 'img') {
2843 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
2844 $force_remove_images || $_SESSION["bw_limit"]) {
2846 $p = $doc->createElement('p');
2848 $a = $doc->createElement('a');
2849 $a->setAttribute('href', $entry->getAttribute('src'));
2851 $a->appendChild(new DOMText($entry->getAttribute('src')));
2852 $a->setAttribute('target', '_blank');
2854 $p->appendChild($a);
2856 $entry->parentNode->replaceChild($p, $entry);
2861 if (strtolower($entry->nodeName) == "a") {
2862 $entry->setAttribute("target", "_blank");
2866 $entries = $xpath->query('//iframe');
2867 foreach ($entries as $entry) {
2868 $entry->setAttribute('sandbox', 'allow-scripts');
2872 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
2873 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
2874 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
2875 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
2876 'dt', 'em', 'footer', 'figure', 'figcaption',
2877 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
2878 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
2879 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
2880 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2881 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
2882 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2884 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
2886 $disallowed_attributes = array('id', 'style', 'class');
2888 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
2889 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
2890 if (is_array($retval)) {
2892 $allowed_elements = $retval[1];
2893 $disallowed_attributes = $retval[2];
2899 $doc->removeChild($doc->firstChild); //remove doctype
2900 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
2902 if ($highlight_words) {
2903 foreach ($highlight_words as $word) {
2905 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
2907 $elements = $xpath->query("//*/text()");
2909 foreach ($elements as $child) {
2911 $fragment = $doc->createDocumentFragment();
2912 $text = $child->textContent;
2915 while (($pos = mb_stripos($text, $word)) !== false) {
2916 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
2917 $word = mb_substr($text, $pos, mb_strlen($word));
2918 $highlight = $doc->createElement('span');
2919 $highlight->appendChild(new DomText($word));
2920 $highlight->setAttribute('class', 'highlight');
2921 $fragment->appendChild($highlight);
2922 $text = mb_substr($text, $pos + mb_strlen($word));
2925 if (!empty($text)) $fragment->appendChild(new DomText($text));
2927 $child->parentNode->replaceChild($fragment, $child);
2932 $res = $doc->saveHTML();
2937 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
2938 $xpath = new DOMXPath($doc);
2939 $entries = $xpath->query('//*');
2941 foreach ($entries as $entry) {
2942 if (!in_array($entry->nodeName, $allowed_elements)) {
2943 $entry->parentNode->removeChild($entry);
2946 if ($entry->hasAttributes()) {
2947 $attrs_to_remove = array();
2949 foreach ($entry->attributes as $attr) {
2951 if (strpos($attr->nodeName, 'on') === 0) {
2952 array_push($attrs_to_remove, $attr);
2955 if (in_array($attr->nodeName, $disallowed_attributes)) {
2956 array_push($attrs_to_remove, $attr);
2960 foreach ($attrs_to_remove as $attr) {
2961 $entry->removeAttributeNode($attr);
2969 function check_for_update() {
2970 if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) {
2971 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION .
2972 "&iid=" . sha1(SELF_URL_PATH);
2974 $version_data = @fetch_file_contents($version_url);
2976 if ($version_data) {
2977 $version_data = json_decode($version_data, true);
2978 if ($version_data && $version_data['version']) {
2979 if (version_compare(VERSION_STATIC, $version_data['version']) == -1) {
2980 return $version_data;
2988 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
2990 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2991 if (count($ids) == 0) return;
2995 foreach ($ids as $id) {
2996 array_push($tmp_ids, "ref_id = '$id'");
2999 $ids_qpart = join(" OR ", $tmp_ids);
3002 db_query("UPDATE ttrss_user_entries SET
3003 unread = false,last_read = NOW()
3004 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3005 } else if ($cmode == 1) {
3006 db_query("UPDATE ttrss_user_entries SET
3008 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3010 db_query("UPDATE ttrss_user_entries SET
3011 unread = NOT unread,last_read = NOW()
3012 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3017 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
3018 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3020 while ($line = db_fetch_assoc($result)) {
3021 ccache_update($line["feed_id"], $owner_uid);
3025 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
3027 $a_id = db_escape_string($id);
3029 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3031 $query = "SELECT DISTINCT tag_name,
3032 owner_uid as owner FROM
3033 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
3034 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
3038 /* check cache first */
3040 if ($tag_cache === false) {
3041 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
3042 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3044 $tag_cache = db_fetch_result($result, 0, "tag_cache");
3048 $tags = explode(",", $tag_cache);
3051 /* do it the hard way */
3053 $tmp_result = db_query($query);
3055 while ($tmp_line = db_fetch_assoc($tmp_result)) {
3056 array_push($tags, $tmp_line["tag_name"]);
3059 /* update the cache */
3061 $tags_str = db_escape_string(join(",", $tags));
3063 db_query("UPDATE ttrss_user_entries
3064 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
3065 AND owner_uid = $owner_uid");
3071 function trim_array($array) {
3073 array_walk($tmp, 'trim');
3077 function tag_is_valid($tag) {
3078 if ($tag == '') return false;
3079 if (preg_match("/^[0-9]*$/", $tag)) return false;
3080 if (mb_strlen($tag) > 250) return false;
3082 if (!$tag) return false;
3087 function render_login_form() {
3088 header('Cache-Control: public');
3090 require_once "login_form.php";
3094 function format_warning($msg, $id = "") {
3096 return "<div class=\"warning\" id=\"$id\">
3097 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3100 function format_notice($msg, $id = "") {
3102 return "<div class=\"notice\" id=\"$id\">
3103 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
3106 function format_error($msg, $id = "") {
3108 return "<div class=\"error\" id=\"$id\">
3109 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3112 function print_notice($msg) {
3113 return print format_notice($msg);
3116 function print_warning($msg) {
3117 return print format_warning($msg);
3120 function print_error($msg) {
3121 return print format_error($msg);
3125 function T_sprintf() {
3126 $args = func_get_args();
3127 return vsprintf(__(array_shift($args)), $args);
3130 function format_inline_player($url, $ctype) {
3134 $url = htmlspecialchars($url);
3136 if (strpos($ctype, "audio/") === 0) {
3138 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
3139 $_SESSION["hasMp3"])) {
3141 $entry .= "<audio preload=\"none\" controls>
3142 <source type=\"$ctype\" src=\"$url\"></source>
3147 $entry .= "<object type=\"application/x-shockwave-flash\"
3148 data=\"lib/button/musicplayer.swf?song_url=$url\"
3149 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
3150 <param name=\"movie\"
3151 value=\"lib/button/musicplayer.swf?song_url=$url\" />
3155 if ($entry) $entry .= " <a target=\"_blank\"
3156 href=\"$url\">" . basename($url) . "</a>";
3164 /* $filename = substr($url, strrpos($url, "/")+1);
3166 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3167 $filename . " (" . $ctype . ")" . "</a>"; */
3171 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
3172 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3178 /* we can figure out feed_id from article id anyway, why do we
3179 * pass feed_id here? let's ignore the argument :(*/
3181 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3182 WHERE ref_id = '$id'");
3184 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
3186 $rv['feed_id'] = $feed_id;
3188 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
3190 if ($mark_as_read) {
3191 $result = db_query("UPDATE ttrss_user_entries
3192 SET unread = false,last_read = NOW()
3193 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3195 ccache_update($feed_id, $owner_uid);
3198 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
3199 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
3200 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
3201 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
3202 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
3203 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
3209 FROM ttrss_entries,ttrss_user_entries
3210 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
3214 $line = db_fetch_assoc($result);
3216 $tag_cache = $line["tag_cache"];
3218 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
3219 unset($line["tag_cache"]);
3221 $line["content"] = sanitize($line["content"],
3222 sql_bool_to_bool($line['hide_images']),
3223 $owner_uid, $line["site_url"], false, $line["id"]);
3225 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
3226 $line = $p->hook_render_article($line);
3229 $num_comments = $line["num_comments"];
3230 $entry_comments = "";
3232 if ($num_comments > 0) {
3233 if ($line["comments"]) {
3234 $comments_url = htmlspecialchars($line["comments"]);
3236 $comments_url = htmlspecialchars($line["link"]);
3238 $entry_comments = "<a class=\"postComments\"
3239 target='_blank' href=\"$comments_url\">$num_comments ".
3240 _ngettext("comment", "comments", $num_comments)."</a>";
3243 if ($line["comments"] && $line["link"] != $line["comments"]) {
3244 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
3249 header("Content-Type: text/html");
3250 $rv['content'] .= "<html><head>
3251 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3252 <title>Tiny Tiny RSS - ".$line["title"]."</title>
3253 <link rel=\"stylesheet\" type=\"text/css\" href=\"css/tt-rss.css\">
3254 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
3255 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
3257 <script type=\"text/javascript\">
3258 function openSelectedAttachment(elem) {
3260 var url = elem[elem.selectedIndex].value;
3264 elem.selectedIndex = 0;
3268 exception_error(\"openSelectedAttachment\", e);
3272 </head><body id=\"ttrssZoom\">";
3275 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3277 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3279 $entry_author = $line["author"];
3281 if ($entry_author) {
3282 $entry_author = __(" - ") . $entry_author;
3285 $parsed_updated = make_local_datetime($line["updated"], true,
3289 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3291 if ($line["link"]) {
3292 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3293 title=\"".htmlspecialchars($line['title'])."\"
3295 htmlspecialchars($line["link"]) . "\">" .
3296 $line["title"] . "</a>" .
3297 "<span class='author'>$entry_author</span></div>";
3299 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3303 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
3304 "\" target=\"_blank\">".
3305 htmlspecialchars($line["feed_title"])."</a>";
3307 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
3309 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3312 $tags_str = format_tags_string($line["tags"], $id);
3313 $tags_str_full = join(", ", $line["tags"]);
3315 if (!$tags_str_full) $tags_str_full = __("no tags");
3317 if (!$entry_comments) $entry_comments = " "; # placeholder
3319 $rv['content'] .= "<div class='postTags' style='float : right'>
3320 <img src='images/tag.png'
3321 class='tagsPic' alt='Tags' title='Tags'> ";
3324 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3325 <a title=\"".__('Edit tags for this article')."\"
3326 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3328 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3329 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3330 position=\"below\">$tags_str_full</div>";
3332 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
3333 $rv['content'] .= $p->hook_article_button($line);
3337 $tags_str = strip_tags($tags_str);
3338 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3340 $rv['content'] .= "</div>";
3341 $rv['content'] .= "<div clear='both'>";
3343 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
3344 $rv['content'] .= $p->hook_article_left_button($line);
3347 $rv['content'] .= "$entry_comments</div>";
3349 if ($line["orig_feed_id"]) {
3351 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
3352 WHERE id = ".$line["orig_feed_id"]);
3354 if (db_num_rows($tmp_result) != 0) {
3356 $rv['content'] .= "<div clear='both'>";
3357 $rv['content'] .= __("Originally from:");
3359 $rv['content'] .= " ";
3361 $tmp_line = db_fetch_assoc($tmp_result);
3363 $rv['content'] .= "<a target='_blank'
3364 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3365 $tmp_line['title'] . "</a>";
3367 $rv['content'] .= " ";
3369 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3370 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3372 $rv['content'] .= "</div>";
3376 $rv['content'] .= "</div>";
3378 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3379 if ($line['note']) {
3380 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3382 $rv['content'] .= "</div>";
3384 if (!$line['lang']) $line['lang'] = 'en';
3386 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
3388 $rv['content'] .= $line["content"];
3389 $rv['content'] .= format_article_enclosures($id,
3390 sql_bool_to_bool($line["always_display_enclosures"]),
3392 sql_bool_to_bool($line["hide_images"]));
3394 $rv['content'] .= "</div>";
3396 $rv['content'] .= "</div>";
3402 <div class='footer'>
3403 <button onclick=\"return window.close()\">".
3404 __("Close this window")."</button></div>";
3405 $rv['content'] .= "</body></html>";
3412 function print_checkpoint($n, $s) {
3413 $ts = microtime(true);
3414 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
3418 function sanitize_tag($tag) {
3421 $tag = mb_strtolower($tag, 'utf-8');
3423 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3425 // $tag = str_replace('"', "", $tag);
3426 // $tag = str_replace("+", " ", $tag);
3427 $tag = str_replace("technorati tag: ", "", $tag);
3432 function get_self_url_prefix() {
3433 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
3434 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
3436 return SELF_URL_PATH;
3441 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3443 * @return string The Mozilla Firefox feed adding URL.
3445 function add_feed_url() {
3446 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3448 $url_path = get_self_url_prefix() .
3449 "/public.php?op=subscribe&feed_url=%s";
3451 } // function add_feed_url
3453 function encrypt_password($pass, $salt = '', $mode2 = false) {
3454 if ($salt && $mode2) {
3455 return "MODE2:" . hash('sha256', $salt . $pass);
3457 return "SHA1X:" . sha1("$salt:$pass");
3459 return "SHA1:" . sha1($pass);
3461 } // function encrypt_password
3463 function load_filters($feed_id, $owner_uid, $action_id = false) {
3466 $cat_id = (int)getFeedCategory($feed_id);
3469 $null_cat_qpart = "cat_id IS NULL OR";
3471 $null_cat_qpart = "";
3473 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
3474 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
3476 $check_cats = join(",", array_merge(
3477 getParentCategories($cat_id, $owner_uid),
3480 while ($line = db_fetch_assoc($result)) {
3481 $filter_id = $line["id"];
3483 $result2 = db_query("SELECT
3484 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3485 FROM ttrss_filters2_rules AS r,
3486 ttrss_filter_types AS t
3488 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
3489 (feed_id IS NULL OR feed_id = '$feed_id') AND
3490 filter_type = t.id AND filter_id = '$filter_id'");
3495 while ($rule_line = db_fetch_assoc($result2)) {
3496 # print_r($rule_line);
3499 $rule["reg_exp"] = $rule_line["reg_exp"];
3500 $rule["type"] = $rule_line["type_name"];
3501 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
3503 array_push($rules, $rule);
3506 $result2 = db_query("SELECT a.action_param,t.name AS type_name
3507 FROM ttrss_filters2_actions AS a,
3508 ttrss_filter_actions AS t
3510 action_id = t.id AND filter_id = '$filter_id'");
3512 while ($action_line = db_fetch_assoc($result2)) {
3513 # print_r($action_line);
3516 $action["type"] = $action_line["type_name"];
3517 $action["param"] = $action_line["action_param"];
3519 array_push($actions, $action);
3524 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3525 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
3526 $filter["rules"] = $rules;
3527 $filter["actions"] = $actions;
3529 if (count($rules) > 0 && count($actions) > 0) {
3530 array_push($filters, $filter);
3537 function get_score_pic($score) {
3539 return "score_high.png";
3540 } else if ($score > 0) {
3541 return "score_half_high.png";
3542 } else if ($score < -100) {
3543 return "score_low.png";
3544 } else if ($score < 0) {
3545 return "score_half_low.png";
3547 return "score_neutral.png";
3551 function feed_has_icon($id) {
3552 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
3555 function init_plugins() {
3556 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
3561 function format_tags_string($tags, $id) {
3562 if (!is_array($tags) || count($tags) == 0) {
3563 return __("no tags");
3565 $maxtags = min(5, count($tags));
3567 for ($i = 0; $i < $maxtags; $i++) {
3568 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
3571 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
3573 if (count($tags) > $maxtags)
3574 $tags_str .= ", …";
3580 function format_article_labels($labels, $id) {
3582 if (!is_array($labels)) return '';
3586 foreach ($labels as $l) {
3587 $labels_str .= sprintf("<span class='hlLabelRef'
3588 style='color : %s; background-color : %s'>%s</span>",
3589 $l[2], $l[3], $l[1]);
3596 function format_article_note($id, $note, $allow_edit = true) {
3598 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3599 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3600 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
3606 function get_feed_category($feed_cat, $parent_cat_id = false) {
3607 if ($parent_cat_id) {
3608 $parent_qpart = "parent_cat = '$parent_cat_id'";
3609 $parent_insert = "'$parent_cat_id'";
3611 $parent_qpart = "parent_cat IS NULL";
3612 $parent_insert = "NULL";
3616 "SELECT id FROM ttrss_feed_categories
3617 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3619 if (db_num_rows($result) == 0) {
3622 return db_fetch_result($result, 0, "id");
3626 function add_feed_category($feed_cat, $parent_cat_id = false) {
3628 if (!$feed_cat) return false;
3632 if ($parent_cat_id) {
3633 $parent_qpart = "parent_cat = '$parent_cat_id'";
3634 $parent_insert = "'$parent_cat_id'";
3636 $parent_qpart = "parent_cat IS NULL";
3637 $parent_insert = "NULL";
3640 $feed_cat = mb_substr($feed_cat, 0, 250);
3643 "SELECT id FROM ttrss_feed_categories
3644 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3646 if (db_num_rows($result) == 0) {
3649 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3650 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3660 function getArticleFeed($id) {
3661 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3662 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3664 if (db_num_rows($result) != 0) {
3665 return db_fetch_result($result, 0, "feed_id");
3672 * Fixes incomplete URLs by prepending "http://".
3673 * Also replaces feed:// with http://, and
3674 * prepends a trailing slash if the url is a domain name only.
3676 * @param string $url Possibly incomplete URL
3678 * @return string Fixed URL.
3680 function fix_url($url) {
3681 if (strpos($url, '://') === false) {
3682 $url = 'http://' . $url;
3683 } else if (substr($url, 0, 5) == 'feed:') {
3684 $url = 'http:' . substr($url, 5);
3687 //prepend slash if the URL has no slash in it
3688 // "http://www.example" -> "http://www.example/"
3689 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
3693 if ($url != "http:///")
3699 function validate_feed_url($url) {
3700 $parts = parse_url($url);
3702 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
3706 function get_article_enclosures($id) {
3708 $query = "SELECT * FROM ttrss_enclosures
3709 WHERE post_id = '$id' AND content_url != ''";
3713 $result = db_query($query);
3715 if (db_num_rows($result) > 0) {
3716 while ($line = db_fetch_assoc($result)) {
3717 array_push($rv, $line);
3724 function save_email_address($email) {
3725 // FIXME: implement persistent storage of emails
3727 if (!$_SESSION['stored_emails'])
3728 $_SESSION['stored_emails'] = array();
3730 if (!in_array($email, $_SESSION['stored_emails']))
3731 array_push($_SESSION['stored_emails'], $email);
3735 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
3737 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3739 $sql_is_cat = bool_to_sql_bool($is_cat);
3741 $result = db_query("SELECT access_key FROM ttrss_access_keys
3742 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3743 AND owner_uid = " . $owner_uid);
3745 if (db_num_rows($result) == 1) {
3746 return db_fetch_result($result, 0, "access_key");
3748 $key = db_escape_string(sha1(uniqid(rand(), true)));
3750 $result = db_query("INSERT INTO ttrss_access_keys
3751 (access_key, feed_id, is_cat, owner_uid)
3752 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3759 function get_feeds_from_html($url, $content)
3761 $url = fix_url($url);
3762 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
3764 libxml_use_internal_errors(true);
3766 $doc = new DOMDocument();
3767 $doc->loadHTML($content);
3768 $xpath = new DOMXPath($doc);
3769 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3770 $feedUrls = array();
3771 foreach ($entries as $entry) {
3772 if ($entry->hasAttribute('href')) {
3773 $title = $entry->getAttribute('title');
3775 $title = $entry->getAttribute('type');
3777 $feedUrl = rewrite_relative_url(
3778 $baseUrl, $entry->getAttribute('href')
3780 $feedUrls[$feedUrl] = $title;
3786 function is_html($content) {
3787 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3790 function url_is_html($url, $login = false, $pass = false) {
3791 return is_html(fetch_file_contents($url, false, $login, $pass));
3794 function print_label_select($name, $value, $attributes = "") {
3796 $result = db_query("SELECT caption FROM ttrss_labels2
3797 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3799 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3800 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3802 while ($line = db_fetch_assoc($result)) {
3804 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
3806 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3807 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3811 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3818 function format_article_enclosures($id, $always_display_enclosures,
3819 $article_content, $hide_images = false) {
3821 $result = get_article_enclosures($id);
3824 if (count($result) > 0) {
3826 $entries_html = array();
3828 $entries_inline = array();
3830 foreach ($result as $line) {
3832 $url = $line["content_url"];
3833 $ctype = $line["content_type"];
3834 $title = $line["title"];
3836 if (!$ctype) $ctype = __("unknown type");
3838 $filename = substr($url, strrpos($url, "/")+1);
3840 $player = format_inline_player($url, $ctype);
3842 if ($player) array_push($entries_inline, $player);
3844 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3845 # $filename . " (" . $ctype . ")" . "</a>";
3847 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3848 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3850 array_push($entries_html, $entry);
3854 $entry["type"] = $ctype;
3855 $entry["filename"] = $filename;
3856 $entry["url"] = $url;
3857 $entry["title"] = $title;
3859 array_push($entries, $entry);
3862 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
3863 if ($always_display_enclosures ||
3864 !preg_match("/<img/i", $article_content)) {
3866 foreach ($entries as $entry) {
3868 if (preg_match("/image/", $entry["type"]) ||
3869 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3871 if (!$hide_images) {
3873 alt=\"".htmlspecialchars($entry["filename"])."\"
3874 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3876 $rv .= "<p><a target=\"_blank\"
3877 href=\"".htmlspecialchars($entry["url"])."\"
3878 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3881 if ($entry['title']) {
3882 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
3889 if (count($entries_inline) > 0) {
3890 $rv .= "<hr clear='both'/>";
3891 foreach ($entries_inline as $entry) { $rv .= $entry; };
3892 $rv .= "<hr clear='both'/>";
3895 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
3896 "<option value=''>" . __('Attachments')."</option>";
3898 foreach ($entries as $entry) {
3899 if ($entry["title"])
3900 $title = "— " . truncate_string($entry["title"], 30);
3904 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
3914 function getLastArticleId() {
3915 $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3916 WHERE owner_uid = " . $_SESSION["uid"]);
3918 if (db_num_rows($result) == 1) {
3919 return db_fetch_result($result, 0, "id");
3925 function build_url($parts) {
3926 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3930 * Converts a (possibly) relative URL to a absolute one.
3932 * @param string $url Base URL (i.e. from where the document is)
3933 * @param string $rel_url Possibly relative URL in the document
3935 * @return string Absolute URL
3937 function rewrite_relative_url($url, $rel_url) {
3938 if (strpos($rel_url, ":") !== false) {
3940 } else if (strpos($rel_url, "://") !== false) {
3942 } else if (strpos($rel_url, "//") === 0) {
3943 # protocol-relative URL (rare but they exist)
3945 } else if (strpos($rel_url, "/") === 0)
3947 $parts = parse_url($url);
3948 $parts['path'] = $rel_url;
3950 return build_url($parts);
3953 $parts = parse_url($url);
3954 if (!isset($parts['path'])) {
3955 $parts['path'] = '/';
3957 $dir = $parts['path'];
3958 if (substr($dir, -1) !== '/') {
3959 $dir = dirname($parts['path']);
3960 $dir !== '/' && $dir .= '/';
3962 $parts['path'] = $dir . $rel_url;
3964 return build_url($parts);
3968 function sphinx_search($query, $offset = 0, $limit = 30) {
3969 require_once 'lib/sphinxapi.php';
3971 $sphinxClient = new SphinxClient();
3973 $sphinxpair = explode(":", SPHINX_SERVER, 2);
3975 $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
3976 $sphinxClient->SetConnectTimeout(1);
3978 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3979 'feed_title' => 20));
3981 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
3982 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
3983 $sphinxClient->SetLimits($offset, $limit, 1000);
3984 $sphinxClient->SetArrayResult(false);
3985 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3987 $result = $sphinxClient->Query($query, SPHINX_INDEX);
3991 if (is_array($result['matches'])) {
3992 foreach (array_keys($result['matches']) as $int_id) {
3993 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3994 array_push($ids, $ref_id);
4001 function cleanup_tags($days = 14, $limit = 1000) {
4003 if (DB_TYPE == "pgsql") {
4004 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
4005 } else if (DB_TYPE == "mysql") {
4006 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
4011 while ($limit > 0) {
4014 $query = "SELECT ttrss_tags.id AS id
4015 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
4016 WHERE post_int_id = int_id AND $interval_query AND
4017 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
4019 $result = db_query($query);
4023 while ($line = db_fetch_assoc($result)) {
4024 array_push($ids, $line['id']);
4027 if (count($ids) > 0) {
4028 $ids = join(",", $ids);
4030 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
4031 $tags_deleted += db_affected_rows($tmp_result);
4036 $limit -= $limit_part;
4039 return $tags_deleted;
4042 function print_user_stylesheet() {
4043 $value = get_pref('USER_STYLESHEET');
4046 print "<style type=\"text/css\">";
4047 print str_replace("<br/>", "\n", $value);
4053 function filter_to_sql($filter, $owner_uid) {
4056 if (DB_TYPE == "pgsql")
4059 $reg_qpart = "REGEXP";
4061 foreach ($filter["rules"] AS $rule) {
4062 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
4063 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
4064 $rule['reg_exp']) !== FALSE;
4066 if ($regexp_valid) {
4068 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
4070 switch ($rule["type"]) {
4072 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4073 $rule['reg_exp'] . "')";
4076 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
4077 $rule['reg_exp'] . "')";
4080 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4081 $rule['reg_exp'] . "') OR LOWER(" .
4082 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
4085 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
4086 $rule['reg_exp'] . "')";
4089 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
4090 $rule['reg_exp'] . "')";
4093 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
4094 $rule['reg_exp'] . "')";
4098 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
4100 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
4101 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
4104 if (isset($rule["cat_id"])) {
4106 if ($rule["cat_id"] > 0) {
4107 $children = getChildCategories($rule["cat_id"], $owner_uid);
4108 array_push($children, $rule["cat_id"]);
4110 $children = join(",", $children);
4112 $cat_qpart = "cat_id IN ($children)";
4114 $cat_qpart = "cat_id IS NULL";
4117 $qpart .= " AND $cat_qpart";
4120 $qpart .= " AND feed_id IS NOT NULL";
4122 array_push($query, "($qpart)");
4127 if (count($query) > 0) {
4128 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
4130 $fullquery = "(false)";
4133 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
4138 if (!function_exists('gzdecode')) {
4139 function gzdecode($string) { // no support for 2nd argument
4140 return file_get_contents('compress.zlib://data:who/cares;base64,'.
4141 base64_encode($string));
4145 function get_random_bytes($length) {
4146 if (function_exists('openssl_random_pseudo_bytes')) {
4147 return openssl_random_pseudo_bytes($length);
4151 for ($i = 0; $i < $length; $i++)
4152 $output .= chr(mt_rand(0, 255));
4158 function read_stdin() {
4159 $fp = fopen("php://stdin", "r");
4162 $line = trim(fgets($fp));
4170 function tmpdirname($path, $prefix) {
4171 // Use PHP's tmpfile function to create a temporary
4172 // directory name. Delete the file and keep the name.
4173 $tempname = tempnam($path,$prefix);
4177 if (!unlink($tempname))
4183 function getFeedCategory($feed) {
4184 $result = db_query("SELECT cat_id FROM ttrss_feeds
4185 WHERE id = '$feed'");
4187 if (db_num_rows($result) > 0) {
4188 return db_fetch_result($result, 0, "cat_id");
4195 function implements_interface($class, $interface) {
4196 return in_array($interface, class_implements($class));
4199 function geturl($url, $depth = 0){
4201 if ($depth == 20) return $url;
4203 if (!function_exists('curl_init'))
4204 return user_error('CURL Must be installed for geturl function to work. Ask your host to enable it or uncomment extension=php_curl.dll in php.ini', E_USER_ERROR);
4206 $curl = curl_init();
4207 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4208 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4209 $header[] = "Cache-Control: max-age=0";
4210 $header[] = "Connection: keep-alive";
4211 $header[] = "Keep-Alive: 300";
4212 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4213 $header[] = "Accept-Language: en-us,en;q=0.5";
4214 $header[] = "Pragma: ";
4216 curl_setopt($curl, CURLOPT_URL, $url);
4217 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4218 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
4219 curl_setopt($curl, CURLOPT_HEADER, true);
4220 curl_setopt($curl, CURLOPT_REFERER, $url);
4221 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
4222 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
4223 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
4224 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4225 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
4226 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
4228 if (defined('_CURL_HTTP_PROXY')) {
4229 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
4232 if ((OPENSSL_VERSION_NUMBER >= 0x0090808f) && (OPENSSL_VERSION_NUMBER < 0x10000000)) {
4233 curl_setopt($curl, CURLOPT_SSLVERSION, 3);
4236 $html = curl_exec($curl);
4238 $status = curl_getinfo($curl);
4240 if($status['http_code']!=200){
4241 if($status['http_code'] == 301 || $status['http_code'] == 302) {
4243 list($header) = explode("\r\n\r\n", $html, 2);
4245 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4246 $url = trim(str_replace($matches[1],"",$matches[0]));
4247 $url_parsed = parse_url($url);
4248 return (isset($url_parsed))? geturl($url, $depth + 1):'';
4251 global $fetch_last_error;
4253 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
4257 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4258 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4259 # $handle = @fopen('./curl.error.log', 'a');
4260 # fwrite($handle, $line);
4267 function get_minified_js($files) {
4268 require_once 'lib/jshrink/Minifier.php';
4272 foreach ($files as $js) {
4273 if (!isset($_GET['debug'])) {
4274 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
4276 if (file_exists($cached_file) &&
4277 is_readable($cached_file) &&
4278 filemtime($cached_file) >= filemtime("js/$js.js")) {
4280 $rv .= file_get_contents($cached_file);
4283 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
4284 file_put_contents($cached_file, $minified);
4288 $rv .= file_get_contents("js/$js.js");
4295 function stylesheet_tag($filename) {
4296 $timestamp = filemtime($filename);
4298 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4301 function javascript_tag($filename) {
4304 if (!(strpos($filename, "?") === FALSE)) {
4305 $query = substr($filename, strpos($filename, "?")+1);
4306 $filename = substr($filename, 0, strpos($filename, "?"));
4309 $timestamp = filemtime($filename);
4311 if ($query) $timestamp .= "&$query";
4313 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4316 function calculate_dep_timestamp() {
4317 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
4321 foreach ($files as $file) {
4322 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4328 function T_js_decl($s1, $s2) {
4330 $s1 = preg_replace("/\n/", "", $s1);
4331 $s2 = preg_replace("/\n/", "", $s2);
4333 $s1 = preg_replace("/\"/", "\\\"", $s1);
4334 $s2 = preg_replace("/\"/", "\\\"", $s2);
4336 return "T_messages[\"$s1\"] = \"$s2\";\n";
4340 function init_js_translations() {
4342 print 'var T_messages = new Object();
4345 if (T_messages[msg]) {
4346 return T_messages[msg];
4352 function ngettext(msg1, msg2, n) {
4353 return __((parseInt(n) > 1) ? msg2 : msg1);
4356 $l10n = _get_reader();
4358 for ($i = 0; $i < $l10n->total; $i++) {
4359 $orig = $l10n->get_original_string($i);
4360 if(strpos($orig, "\000") !== FALSE) { // Plural forms
4361 $key = explode(chr(0), $orig);
4362 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
4363 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
4365 $translation = __($orig);
4366 print T_js_decl($orig, $translation);
4371 function label_to_feed_id($label) {
4372 return LABEL_BASE_INDEX - 1 - abs($label);
4375 function feed_to_label_id($feed) {
4376 return LABEL_BASE_INDEX - 1 + abs($feed);
4379 function format_libxml_error($error) {
4380 return T_sprintf("LibXML error %s at line %d (column %d): %s",
4381 $error->code, $error->line, $error->column,