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",
89 require_once "lib/accept-to-gettext.php";
90 require_once "lib/gettext/gettext.inc";
92 require_once "lib/languagedetect/LanguageDetect.php";
94 function startup_gettext() {
96 # Get locale from Accept-Language header
97 $lang = al2gt(array_keys(get_translations()), "text/html");
99 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
100 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
103 if ($_SESSION["uid"] && get_schema_version() >= 120) {
104 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
106 if ($pref_lang && $pref_lang != 'auto') {
112 if (defined('LC_MESSAGES')) {
113 _setlocale(LC_MESSAGES
, $lang);
114 } else if (defined('LC_ALL')) {
115 _setlocale(LC_ALL
, $lang);
118 _bindtextdomain("messages", "locale");
120 _textdomain("messages");
121 _bind_textdomain_codeset("messages", "UTF-8");
125 require_once 'db-prefs.php';
126 require_once 'version.php';
127 require_once 'ccache.php';
128 require_once 'labels.php';
130 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
131 ini_set('user_agent', SELF_USER_AGENT
);
133 require_once 'lib/pubsubhubbub/publisher.php';
135 $schema_version = false;
137 function _debug_suppress($suppress) {
138 global $suppress_debugging;
140 $suppress_debugging = $suppress;
144 * Print a timestamped debug message.
146 * @param string $msg The debug message.
149 function _debug($msg, $show = true) {
150 global $suppress_debugging;
152 //echo "[$suppress_debugging] $msg $show\n";
154 if ($suppress_debugging) return false;
156 $ts = strftime("%H:%M:%S", time());
157 if (function_exists('posix_getpid')) {
158 $ts = "$ts/" . posix_getpid();
161 if ($show && !(defined('QUIET') && QUIET
)) {
162 print "[$ts] $msg\n";
165 if (defined('LOGFILE')) {
166 $fp = fopen(LOGFILE
, 'a+');
171 if (function_exists("flock")) {
174 // try to lock logfile for writing
175 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
186 fputs($fp, "[$ts] $msg\n");
188 if (function_exists("flock")) {
199 * Purge a feed old posts.
201 * @param mixed $link A database connection.
202 * @param mixed $feed_id The id of the purged feed.
203 * @param mixed $purge_interval Olderness of purged posts.
204 * @param boolean $debug Set to True to enable the debug. False by default.
208 function purge_feed($feed_id, $purge_interval, $debug = false) {
210 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
215 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
219 if (db_num_rows($result) == 1) {
220 $owner_uid = db_fetch_result($result, 0, "owner_uid");
223 if ($purge_interval == -1 ||
!$purge_interval) {
225 ccache_update($feed_id, $owner_uid);
230 if (!$owner_uid) return;
232 if (FORCE_ARTICLE_PURGE
== 0) {
233 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
236 $purge_unread = true;
237 $purge_interval = FORCE_ARTICLE_PURGE
;
240 if (!$purge_unread) $query_limit = " unread = false AND ";
242 if (DB_TYPE
== "pgsql") {
243 $pg_version = get_pgsql_version();
245 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
247 $result = db_query("DELETE FROM ttrss_user_entries WHERE
248 ttrss_entries.id = ref_id AND
250 feed_id = '$feed_id' AND
252 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
256 $result = db_query("DELETE FROM ttrss_user_entries
258 WHERE ttrss_entries.id = ref_id AND
260 feed_id = '$feed_id' AND
262 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
267 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
268 marked = false AND feed_id = '$feed_id' AND
269 (SELECT date_updated FROM ttrss_entries WHERE
270 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
272 $result = db_query("DELETE FROM ttrss_user_entries
273 USING ttrss_user_entries, ttrss_entries
274 WHERE ttrss_entries.id = ref_id AND
276 feed_id = '$feed_id' AND
278 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
281 $rows = db_affected_rows($result);
283 ccache_update($feed_id, $owner_uid);
286 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
290 } // function purge_feed
292 function feed_purge_interval($feed_id) {
294 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
295 WHERE id = '$feed_id'");
297 if (db_num_rows($result) == 1) {
298 $purge_interval = db_fetch_result($result, 0, "purge_interval");
299 $owner_uid = db_fetch_result($result, 0, "owner_uid");
301 if ($purge_interval == 0) $purge_interval = get_pref(
302 'PURGE_OLD_DAYS', $owner_uid);
304 return $purge_interval;
311 function purge_orphans($do_output = false) {
313 // purge orphaned posts in main content table
314 $result = db_query("DELETE FROM ttrss_entries WHERE
315 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
318 $rows = db_affected_rows($result);
319 _debug("Purged $rows orphaned posts.");
323 function get_feed_update_interval($feed_id) {
324 $result = db_query("SELECT owner_uid, update_interval FROM
325 ttrss_feeds WHERE id = '$feed_id'");
327 if (db_num_rows($result) == 1) {
328 $update_interval = db_fetch_result($result, 0, "update_interval");
329 $owner_uid = db_fetch_result($result, 0, "owner_uid");
331 if ($update_interval != 0) {
332 return $update_interval;
334 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
342 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0) {
344 global $fetch_last_error;
345 global $fetch_last_error_code;
346 global $fetch_last_content_type;
347 global $fetch_curl_used;
349 $url = str_replace(' ', '%20', $url);
351 if (!defined('NO_CURL') && function_exists('curl_init')) {
353 $fetch_curl_used = true;
355 if (ini_get("safe_mode") ||
ini_get("open_basedir")) {
356 $new_url = geturl($url);
358 // geturl has already populated $fetch_last_error
361 $ch = curl_init($new_url);
363 $ch = curl_init($url);
366 if ($timestamp && !$post_query) {
367 curl_setopt($ch, CURLOPT_HTTPHEADER
,
368 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
371 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
372 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
373 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("safe_mode") && !ini_get("open_basedir"));
374 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
375 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
376 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
377 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
378 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
379 curl_setopt($ch, CURLOPT_USERAGENT
, SELF_USER_AGENT
);
380 curl_setopt($ch, CURLOPT_ENCODING
, "");
381 curl_setopt($ch, CURLOPT_REFERER
, $url);
383 if (!ini_get("safe_mode") && !ini_get("open_basedir")) {
384 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
387 if (defined('_CURL_HTTP_PROXY')) {
388 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
392 curl_setopt($ch, CURLOPT_POST
, true);
393 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
396 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
397 curl_setopt($ch, CURLOPT_SSLVERSION
, 3);
401 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
403 $contents = @curl_exec
($ch);
405 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
406 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
407 $contents = @curl_exec
($ch);
410 if ($contents === false) {
411 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
416 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
417 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
419 $fetch_last_error_code = $http_code;
421 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
422 if (curl_errno($ch) != 0) {
423 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
425 $fetch_last_error = "HTTP Code: $http_code";
436 $fetch_curl_used = false;
438 if ($login && $pass){
439 $url_parts = array();
441 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
443 $pass = urlencode($pass);
445 if ($url_parts[1] && $url_parts[2]) {
446 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
450 if (!$post_query && $timestamp) {
451 $context = stream_context_create(array(
454 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
460 $old_error = error_get_last();
462 $data = @file_get_contents
($url, false, $context);
464 $fetch_last_content_type = false; // reset if no type was sent from server
465 if (isset($http_response_header) && is_array($http_response_header)) {
466 foreach ($http_response_header as $h) {
467 if (substr(strtolower($h), 0, 13) == 'content-type:') {
468 $fetch_last_content_type = substr($h, 14);
469 // don't abort here b/c there might be more than one
470 // e.g. if we were being redirected -- last one is the right one
473 if (substr(strtolower($h), 0, 7) == 'http/1.') {
474 $fetch_last_error_code = (int) substr($h, 9, 3);
480 $error = error_get_last();
482 if ($error['message'] != $old_error['message']) {
483 $fetch_last_error = $error["message"];
485 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
494 * Try to determine the favicon URL for a feed.
495 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
496 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
498 * @param string $url A feed or page URL
500 * @return mixed The favicon URL, or false if none was found.
502 function get_favicon_url($url) {
504 $favicon_url = false;
506 if ($html = @fetch_file_contents
($url)) {
508 libxml_use_internal_errors(true);
510 $doc = new DOMDocument();
511 $doc->loadHTML($html);
512 $xpath = new DOMXPath($doc);
514 $base = $xpath->query('/html/head/base');
515 foreach ($base as $b) {
516 $url = $b->getAttribute("href");
520 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
521 if (count($entries) > 0) {
522 foreach ($entries as $entry) {
523 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
530 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
533 } // function get_favicon_url
535 function check_feed_favicon($site_url, $feed) {
536 # print "FAVICON [$site_url]: $favicon_url\n";
538 $icon_file = ICONS_DIR
. "/$feed.ico";
540 if (!file_exists($icon_file)) {
541 $favicon_url = get_favicon_url($site_url);
544 // Limiting to "image" type misses those served with text/plain
545 $contents = fetch_file_contents($favicon_url); // , "image");
548 // Crude image type matching.
549 // Patterns gleaned from the file(1) source code.
550 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
551 // 0 string \000\000\001\000 MS Windows icon resource
552 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
554 elseif (preg_match('/^GIF8/', $contents)) {
555 // 0 string GIF8 GIF image data
556 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
558 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
559 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
560 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
562 elseif (preg_match('/^\xff\xd8/', $contents)) {
563 // 0 beshort 0xffd8 JPEG image data
564 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
567 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
573 $fp = @fopen
($icon_file, "w");
576 fwrite($fp, $contents);
578 chmod($icon_file, 0644);
586 function print_select($id, $default, $values, $attributes = "") {
587 print "<select name=\"$id\" id=\"$id\" $attributes>";
588 foreach ($values as $v) {
590 $sel = "selected=\"1\"";
596 print "<option value=\"$v\" $sel>$v</option>";
601 function print_select_hash($id, $default, $values, $attributes = "") {
602 print "<select name=\"$id\" id='$id' $attributes>";
603 foreach (array_keys($values) as $v) {
605 $sel = 'selected="selected"';
611 print "<option $sel value=\"$v\">".$values[$v]."</option>";
617 function print_radio($id, $default, $true_is, $values, $attributes = "") {
618 foreach ($values as $v) {
625 if ($v == $true_is) {
626 $sel .= " value=\"1\"";
628 $sel .= " value=\"0\"";
631 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
632 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
637 function initialize_user_prefs($uid, $profile = false) {
639 $uid = db_escape_string($uid);
643 $profile_qpart = "AND profile IS NULL";
645 $profile_qpart = "AND profile = '$profile'";
648 if (get_schema_version() < 63) $profile_qpart = "";
652 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
654 $u_result = db_query("SELECT pref_name
655 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
657 $active_prefs = array();
659 while ($line = db_fetch_assoc($u_result)) {
660 array_push($active_prefs, $line["pref_name"]);
663 while ($line = db_fetch_assoc($result)) {
664 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
665 // print "adding " . $line["pref_name"] . "<br>";
667 $line["def_value"] = db_escape_string($line["def_value"]);
668 $line["pref_name"] = db_escape_string($line["pref_name"]);
670 if (get_schema_version() < 63) {
671 db_query("INSERT INTO ttrss_user_prefs
672 (owner_uid,pref_name,value) VALUES
673 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
676 db_query("INSERT INTO ttrss_user_prefs
677 (owner_uid,pref_name,value, profile) VALUES
678 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
688 function get_ssl_certificate_id() {
689 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
690 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
691 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
692 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
693 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
698 function authenticate_user($login, $password, $check_only = false) {
700 if (!SINGLE_USER_MODE
) {
703 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
705 $user_id = (int) $plugin->authenticate($login, $password);
708 $_SESSION["auth_module"] = strtolower(get_class($plugin));
713 if ($user_id && !$check_only) {
716 $_SESSION["uid"] = $user_id;
717 $_SESSION["version"] = VERSION_STATIC
;
719 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
720 WHERE id = '$user_id'");
722 $_SESSION["name"] = db_fetch_result($result, 0, "login");
723 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
724 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
726 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
729 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
730 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
731 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
733 $_SESSION["last_version_check"] = time();
735 initialize_user_prefs($_SESSION["uid"]);
744 $_SESSION["uid"] = 1;
745 $_SESSION["name"] = "admin";
746 $_SESSION["access_level"] = 10;
748 $_SESSION["hide_hello"] = true;
749 $_SESSION["hide_logout"] = true;
751 $_SESSION["auth_module"] = false;
753 if (!$_SESSION["csrf_token"]) {
754 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
757 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
759 initialize_user_prefs($_SESSION["uid"]);
765 function make_password($length = 8) {
768 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
772 while ($i < $length) {
773 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
775 if (!strstr($password, $char)) {
783 // this is called after user is created to initialize default feeds, labels
786 // user preferences are checked on every login, not here
788 function initialize_user($uid) {
790 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
791 values ('$uid', 'Tiny Tiny RSS: New Releases',
792 'http://tt-rss.org/releases.rss')");
794 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
795 values ('$uid', 'Tiny Tiny RSS: Forum',
796 'http://tt-rss.org/forum/rss.php')");
799 function logout_user() {
801 if (isset($_COOKIE[session_name()])) {
802 setcookie(session_name(), '', time()-42000, '/');
806 function validate_csrf($csrf_token) {
807 return $csrf_token == $_SESSION['csrf_token'];
810 function load_user_plugins($owner_uid) {
811 if ($owner_uid && SCHEMA_VERSION
>= 100) {
812 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
814 PluginHost
::getInstance()->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
816 if (get_schema_version() > 100) {
817 PluginHost
::getInstance()->load_data();
822 function login_sequence() {
823 if (SINGLE_USER_MODE
) {
825 authenticate_user("admin", null);
827 load_user_plugins($_SESSION["uid"]);
829 if (!validate_session()) $_SESSION["uid"] = false;
831 if (!$_SESSION["uid"]) {
833 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
834 $_SESSION["ref_schema_version"] = get_schema_version(true);
836 authenticate_user(null, null, true);
839 if (!$_SESSION["uid"]) {
841 setcookie(session_name(), '', time()-42000, '/');
848 /* bump login timestamp */
849 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
851 $_SESSION["last_login_update"] = time();
854 if ($_SESSION["uid"]) {
856 load_user_plugins($_SESSION["uid"]);
860 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
861 $_SESSION["uid"] . " AND
862 (SELECT COUNT(id) FROM ttrss_feeds WHERE
863 ttrss_feeds.id = feed_id) = 0");
865 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
866 $_SESSION["uid"] . " AND
867 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
868 ttrss_feed_categories.id = feed_id) = 0");
875 function truncate_string($str, $max_len, $suffix = '…') {
876 if (mb_strlen($str, "utf-8") > $max_len - 3) {
877 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
883 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
886 $source_tz = new DateTimeZone($source_tz);
887 } catch (Exception
$e) {
888 $source_tz = new DateTimeZone('UTC');
892 $dest_tz = new DateTimeZone($dest_tz);
893 } catch (Exception
$e) {
894 $dest_tz = new DateTimeZone('UTC');
897 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
898 return $dt->format('U') +
$dest_tz->getOffset($dt);
901 function make_local_datetime($timestamp, $long, $owner_uid = false,
902 $no_smart_dt = false) {
904 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
905 if (!$timestamp) $timestamp = '1970-01-01 0:00';
910 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
912 $timestamp = substr($timestamp, 0, 19);
914 # We store date in UTC internally
915 $dt = new DateTime($timestamp, $utc_tz);
917 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
919 if ($user_tz_string != 'Automatic') {
922 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
923 } catch (Exception
$e) {
927 $tz_offset = $user_tz->getOffset($dt);
929 $tz_offset = (int) -$_SESSION["clientTzOffset"];
932 $user_timestamp = $dt->format('U') +
$tz_offset;
935 return smart_date_time($user_timestamp,
936 $tz_offset, $owner_uid);
939 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
941 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
943 return date($format, $user_timestamp);
947 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false) {
948 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
950 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
951 return date("G:i", $timestamp);
952 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
953 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
954 return date($format, $timestamp);
956 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
957 return date($format, $timestamp);
961 function sql_bool_to_bool($s) {
962 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
969 function bool_to_sql_bool($s) {
977 // Session caching removed due to causing wrong redirects to upgrade
978 // script when get_schema_version() is called on an obsolete session
979 // created on a previous schema version.
980 function get_schema_version($nocache = false) {
981 global $schema_version;
983 if (!$schema_version && !$nocache) {
984 $result = db_query("SELECT schema_version FROM ttrss_version");
985 $version = db_fetch_result($result, 0, "schema_version");
986 $schema_version = $version;
989 return $schema_version;
993 function sanity_check() {
994 require_once 'errors.php';
997 $schema_version = get_schema_version(true);
999 if ($schema_version != SCHEMA_VERSION
) {
1003 if (DB_TYPE
== "mysql") {
1004 $result = db_query("SELECT true", false);
1005 if (db_num_rows($result) != 1) {
1010 if (db_escape_string("testTEST") != "testTEST") {
1014 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
1017 function file_is_locked($filename) {
1018 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
1019 if (function_exists('flock')) {
1020 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
1022 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1023 flock($fp, LOCK_UN
);
1033 return true; // consider the file always locked and skip the test
1040 function make_lockfile($filename) {
1041 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1043 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
1044 $stat_h = fstat($fp);
1045 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
1047 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
1048 if ($stat_h["ino"] != $stat_f["ino"] ||
1049 $stat_h["dev"] != $stat_f["dev"]) {
1055 if (function_exists('posix_getpid')) {
1056 fwrite($fp, posix_getpid() . "\n");
1064 function make_stampfile($filename) {
1065 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1067 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1068 fwrite($fp, time() . "\n");
1069 flock($fp, LOCK_UN
);
1077 function sql_random_function() {
1078 if (DB_TYPE
== "mysql") {
1085 function catchup_feed($feed, $cat_view, $owner_uid = false, $max_id = false, $mode = 'all') {
1087 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
1089 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
1091 // Todo: all this interval stuff needs some generic generator function
1093 $date_qpart = "false";
1097 if (DB_TYPE
== "pgsql") {
1098 $date_qpart = "date_entered < NOW() - INTERVAL '1 day' ";
1100 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) ";
1104 if (DB_TYPE
== "pgsql") {
1105 $date_qpart = "date_entered < NOW() - INTERVAL '1 week' ";
1107 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) ";
1111 if (DB_TYPE
== "pgsql") {
1112 $date_qpart = "date_entered < NOW() - INTERVAL '2 week' ";
1114 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) ";
1118 $date_qpart = "true";
1121 if (is_numeric($feed)) {
1127 $children = getChildCategories($feed, $owner_uid);
1128 array_push($children, $feed);
1130 $children = join(",", $children);
1132 $cat_qpart = "cat_id IN ($children)";
1134 $cat_qpart = "cat_id IS NULL";
1137 db_query("UPDATE ttrss_user_entries
1138 SET unread = false, last_read = NOW() WHERE ref_id IN
1140 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1141 AND owner_uid = $owner_uid AND unread = true AND feed_id IN
1142 (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart) as tmp)");
1144 } else if ($feed == -2) {
1146 db_query("UPDATE ttrss_user_entries
1147 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1148 FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart) > 0
1149 AND unread = true AND owner_uid = $owner_uid");
1152 } else if ($feed > 0) {
1154 db_query("UPDATE ttrss_user_entries
1155 SET unread = false, last_read = NOW() WHERE ref_id IN
1157 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1158 AND owner_uid = $owner_uid AND unread = true AND feed_id = $feed AND $date_qpart) as tmp)");
1160 } else if ($feed < 0 && $feed > LABEL_BASE_INDEX
) { // special, like starred
1163 db_query("UPDATE ttrss_user_entries
1164 SET unread = false, last_read = NOW() WHERE ref_id IN
1166 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1167 AND owner_uid = $owner_uid AND unread = true AND marked = true AND $date_qpart) as tmp)");
1171 db_query("UPDATE ttrss_user_entries
1172 SET unread = false, last_read = NOW() WHERE ref_id IN
1174 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1175 AND owner_uid = $owner_uid AND unread = true AND published = true AND $date_qpart) as tmp)");
1180 $intl = get_pref("FRESH_ARTICLE_MAX_AGE");
1182 if (DB_TYPE
== "pgsql") {
1183 $match_part = "date_entered > NOW() - INTERVAL '$intl hour' ";
1185 $match_part = "date_entered > DATE_SUB(NOW(),
1186 INTERVAL $intl HOUR) ";
1189 db_query("UPDATE ttrss_user_entries
1190 SET unread = false, last_read = NOW() WHERE ref_id IN
1192 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1193 AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $match_part) as tmp)");
1197 db_query("UPDATE ttrss_user_entries
1198 SET unread = false, last_read = NOW() WHERE ref_id IN
1200 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1201 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1204 } else if ($feed < LABEL_BASE_INDEX
) { // label
1206 $label_id = feed_to_label_id($feed);
1208 db_query("UPDATE ttrss_user_entries
1209 SET unread = false, last_read = NOW() WHERE ref_id IN
1211 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id
1212 AND label_id = '$label_id' AND ref_id = article_id
1213 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1217 ccache_update($feed, $owner_uid, $cat_view);
1220 db_query("UPDATE ttrss_user_entries
1221 SET unread = false, last_read = NOW() WHERE ref_id IN
1223 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id
1224 AND post_int_id = int_id AND tag_name = '$feed'
1225 AND ttrss_user_entries.owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1230 function getAllCounters() {
1231 $data = getGlobalCounters();
1233 $data = array_merge($data, getVirtCounters());
1234 $data = array_merge($data, getLabelCounters());
1235 $data = array_merge($data, getFeedCounters());
1236 $data = array_merge($data, getCategoryCounters());
1241 function getCategoryTitle($cat_id) {
1243 if ($cat_id == -1) {
1244 return __("Special");
1245 } else if ($cat_id == -2) {
1246 return __("Labels");
1249 $result = db_query("SELECT title FROM ttrss_feed_categories WHERE
1252 if (db_num_rows($result) == 1) {
1253 return db_fetch_result($result, 0, "title");
1255 return __("Uncategorized");
1261 function getCategoryCounters() {
1264 /* Labels category */
1266 $cv = array("id" => -2, "kind" => "cat",
1267 "counter" => getCategoryUnread(-2));
1269 array_push($ret_arr, $cv);
1271 $result = db_query("SELECT id AS cat_id, value AS unread,
1272 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1273 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1274 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1275 WHERE ttrss_cat_counters_cache.feed_id = id AND
1276 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1277 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1279 while ($line = db_fetch_assoc($result)) {
1280 $line["cat_id"] = (int) $line["cat_id"];
1282 if ($line["num_children"] > 0) {
1283 $child_counter = getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
1288 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1289 "counter" => $line["unread"] +
$child_counter);
1291 array_push($ret_arr, $cv);
1294 /* Special case: NULL category doesn't actually exist in the DB */
1296 $cv = array("id" => 0, "kind" => "cat",
1297 "counter" => (int) ccache_find(0, $_SESSION["uid"], true));
1299 array_push($ret_arr, $cv);
1304 // only accepts real cats (>= 0)
1305 function getCategoryChildrenUnread($cat, $owner_uid = false) {
1306 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1308 $result = db_query("SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1309 AND owner_uid = $owner_uid");
1313 while ($line = db_fetch_assoc($result)) {
1314 $unread +
= getCategoryUnread($line["id"], $owner_uid);
1315 $unread +
= getCategoryChildrenUnread($line["id"], $owner_uid);
1321 function getCategoryUnread($cat, $owner_uid = false) {
1323 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1328 $cat_query = "cat_id = '$cat'";
1330 $cat_query = "cat_id IS NULL";
1333 $result = db_query("SELECT id FROM ttrss_feeds WHERE $cat_query
1334 AND owner_uid = " . $owner_uid);
1336 $cat_feeds = array();
1337 while ($line = db_fetch_assoc($result)) {
1338 array_push($cat_feeds, "feed_id = " . $line["id"]);
1341 if (count($cat_feeds) == 0) return 0;
1343 $match_part = implode(" OR ", $cat_feeds);
1345 $result = db_query("SELECT COUNT(int_id) AS unread
1346 FROM ttrss_user_entries
1347 WHERE unread = true AND ($match_part)
1348 AND owner_uid = " . $owner_uid);
1352 # this needs to be rewritten
1353 while ($line = db_fetch_assoc($result)) {
1354 $unread +
= $line["unread"];
1358 } else if ($cat == -1) {
1359 return getFeedUnread(-1) +
getFeedUnread(-2) +
getFeedUnread(-3) +
getFeedUnread(0);
1360 } else if ($cat == -2) {
1362 $result = db_query("
1363 SELECT COUNT(unread) AS unread FROM
1364 ttrss_user_entries, ttrss_user_labels2
1365 WHERE article_id = ref_id AND unread = true
1366 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1368 $unread = db_fetch_result($result, 0, "unread");
1375 function getFeedUnread($feed, $is_cat = false) {
1376 return getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1379 function getLabelUnread($label_id, $owner_uid = false) {
1380 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1382 $result = db_query("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1383 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1385 if (db_num_rows($result) != 0) {
1386 return db_fetch_result($result, 0, "unread");
1392 function getFeedArticles($feed, $is_cat = false, $unread_only = false,
1393 $owner_uid = false) {
1395 $n_feed = (int) $feed;
1396 $need_entries = false;
1398 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1401 $unread_qpart = "unread = true";
1403 $unread_qpart = "true";
1407 return getCategoryUnread($n_feed, $owner_uid);
1408 } else if ($n_feed == -6) {
1410 } else if ($feed != "0" && $n_feed == 0) {
1412 $feed = db_escape_string($feed);
1414 $result = db_query("SELECT SUM((SELECT COUNT(int_id)
1415 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1416 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1417 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1418 return db_fetch_result($result, 0, "count");
1420 } else if ($n_feed == -1) {
1421 $match_part = "marked = true";
1422 } else if ($n_feed == -2) {
1423 $match_part = "published = true";
1424 } else if ($n_feed == -3) {
1425 $match_part = "unread = true AND score >= 0";
1427 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
1429 if (DB_TYPE
== "pgsql") {
1430 $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
1432 $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1435 $need_entries = true;
1437 } else if ($n_feed == -4) {
1438 $match_part = "true";
1439 } else if ($n_feed >= 0) {
1442 $match_part = "feed_id = '$n_feed'";
1444 $match_part = "feed_id IS NULL";
1447 } else if ($feed < LABEL_BASE_INDEX
) {
1449 $label_id = feed_to_label_id($feed);
1451 return getLabelUnread($label_id, $owner_uid);
1457 if ($need_entries) {
1458 $from_qpart = "ttrss_user_entries,ttrss_entries";
1459 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1461 $from_qpart = "ttrss_user_entries";
1465 $query = "SELECT count(int_id) AS unread
1466 FROM $from_qpart WHERE
1467 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1469 //echo "[$feed/$query]\n";
1471 $result = db_query($query);
1475 $result = db_query("SELECT COUNT(post_int_id) AS unread
1476 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1477 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1478 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1481 $unread = db_fetch_result($result, 0, "unread");
1486 function getGlobalUnread($user_id = false) {
1489 $user_id = $_SESSION["uid"];
1492 $result = db_query("SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1493 WHERE owner_uid = '$user_id' AND feed_id > 0");
1495 $c_id = db_fetch_result($result, 0, "c_id");
1500 function getGlobalCounters($global_unread = -1) {
1503 if ($global_unread == -1) {
1504 $global_unread = getGlobalUnread();
1507 $cv = array("id" => "global-unread",
1508 "counter" => (int) $global_unread);
1510 array_push($ret_arr, $cv);
1512 $result = db_query("SELECT COUNT(id) AS fn FROM
1513 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1515 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1517 $cv = array("id" => "subscribed-feeds",
1518 "counter" => (int) $subscribed_feeds);
1520 array_push($ret_arr, $cv);
1525 function getVirtCounters() {
1529 for ($i = 0; $i >= -4; $i--) {
1531 $count = getFeedUnread($i);
1533 if ($i == 0 ||
$i == -1 ||
$i == -2)
1534 $auxctr = getFeedArticles($i, false);
1538 $cv = array("id" => $i,
1539 "counter" => (int) $count,
1540 "auxcounter" => $auxctr);
1542 // if (get_pref('EXTENDED_FEEDLIST'))
1543 // $cv["xmsg"] = getFeedArticles($i)." ".__("total");
1545 array_push($ret_arr, $cv);
1548 $feeds = PluginHost
::getInstance()->get_feeds(-1);
1550 if (is_array($feeds)) {
1551 foreach ($feeds as $feed) {
1552 $cv = array("id" => PluginHost
::pfeed_to_feed_id($feed['id']),
1553 "counter" => $feed['sender']->get_unread($feed['id']));
1555 if (method_exists($feed['sender'], 'get_total'))
1556 $cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
1558 array_push($ret_arr, $cv);
1565 function getLabelCounters($descriptions = false) {
1569 $owner_uid = $_SESSION["uid"];
1571 $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total
1572 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1573 (ttrss_labels2.id = label_id)
1574 LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id
1575 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1576 ttrss_labels2.caption");
1578 while ($line = db_fetch_assoc($result)) {
1580 $id = label_to_feed_id($line["id"]);
1582 $cv = array("id" => $id,
1583 "counter" => (int) $line["unread"],
1584 "auxcounter" => (int) $line["total"]);
1587 $cv["description"] = $line["caption"];
1589 array_push($ret_arr, $cv);
1595 function getFeedCounters($active_feed = false) {
1599 $query = "SELECT ttrss_feeds.id,
1601 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1602 last_error, value AS count
1603 FROM ttrss_feeds, ttrss_counters_cache
1604 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1605 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1606 AND ttrss_counters_cache.feed_id = id";
1608 $result = db_query($query);
1609 $fctrs_modified = false;
1611 while ($line = db_fetch_assoc($result)) {
1614 $count = $line["count"];
1615 $last_error = htmlspecialchars($line["last_error"]);
1617 $last_updated = make_local_datetime($line['last_updated'], false);
1619 $has_img = feed_has_icon($id);
1621 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1624 $cv = array("id" => $id,
1625 "updated" => $last_updated,
1626 "counter" => (int) $count,
1627 "has_img" => (int) $has_img);
1630 $cv["error"] = $last_error;
1632 // if (get_pref('EXTENDED_FEEDLIST'))
1633 // $cv["xmsg"] = getFeedArticles($id)." ".__("total");
1635 if ($active_feed && $id == $active_feed)
1636 $cv["title"] = truncate_string($line["title"], 30);
1638 array_push($ret_arr, $cv);
1645 function get_pgsql_version() {
1646 $result = db_query("SELECT version() AS version");
1647 $version = explode(" ", db_fetch_result($result, 0, "version"));
1652 * @return array (code => Status code, message => error message if available)
1654 * 0 - OK, Feed already exists
1655 * 1 - OK, Feed added
1657 * 3 - URL content is HTML, no feeds available
1658 * 4 - URL content is HTML which contains multiple feeds.
1659 * Here you should call extractfeedurls in rpc-backend
1660 * to get all possible feeds.
1661 * 5 - Couldn't download the URL content.
1662 * 6 - Content is an invalid XML.
1664 function subscribe_to_feed($url, $cat_id = 0,
1665 $auth_login = '', $auth_pass = '') {
1667 global $fetch_last_error;
1669 require_once "include/rssfuncs.php";
1671 $url = fix_url($url);
1673 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1675 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1678 return array("code" => 5, "message" => $fetch_last_error);
1681 if (is_html($contents)) {
1682 $feedUrls = get_feeds_from_html($url, $contents);
1684 if (count($feedUrls) == 0) {
1685 return array("code" => 3);
1686 } else if (count($feedUrls) > 1) {
1687 return array("code" => 4, "feeds" => $feedUrls);
1689 //use feed url as new URL
1690 $url = key($feedUrls);
1693 /* libxml_use_internal_errors(true);
1694 $doc = new DOMDocument();
1695 $doc->loadXML($contents);
1696 $error = libxml_get_last_error();
1697 libxml_clear_errors();
1700 $error_message = format_libxml_error($error);
1702 return array("code" => 6, "message" => $error_message);
1705 if ($cat_id == "0" ||
!$cat_id) {
1706 $cat_qpart = "NULL";
1708 $cat_qpart = "'$cat_id'";
1712 "SELECT id FROM ttrss_feeds
1713 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1715 if (strlen(FEED_CRYPT_KEY
) > 0) {
1716 require_once "crypt.php";
1717 $auth_pass = substr(encrypt_string($auth_pass), 0, 250);
1718 $auth_pass_encrypted = 'true';
1720 $auth_pass_encrypted = 'false';
1723 $auth_pass = db_escape_string($auth_pass);
1725 if (db_num_rows($result) == 0) {
1727 "INSERT INTO ttrss_feeds
1728 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted)
1729 VALUES ('".$_SESSION["uid"]."', '$url',
1730 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, $auth_pass_encrypted)");
1733 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1734 AND owner_uid = " . $_SESSION["uid"]);
1736 $feed_id = db_fetch_result($result, 0, "id");
1739 update_rss_feed($feed_id, true);
1742 return array("code" => 1);
1744 return array("code" => 0);
1748 function print_feed_select($id, $default_id = "",
1749 $attributes = "", $include_all_feeds = true,
1750 $root_id = false, $nest_level = 0) {
1753 print "<select id=\"$id\" name=\"$id\" $attributes>";
1754 if ($include_all_feeds) {
1755 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1756 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1760 if (get_pref('ENABLE_FEED_CATS')) {
1763 $parent_qpart = "parent_cat = '$root_id'";
1765 $parent_qpart = "parent_cat IS NULL";
1767 $result = db_query("SELECT id,title,
1768 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1769 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1770 FROM ttrss_feed_categories
1771 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1773 while ($line = db_fetch_assoc($result)) {
1775 for ($i = 0; $i < $nest_level; $i++
)
1776 $line["title"] = " - " . $line["title"];
1778 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1780 printf("<option $is_selected value='CAT:%d'>%s</option>",
1781 $line["id"], htmlspecialchars($line["title"]));
1783 if ($line["num_children"] > 0)
1784 print_feed_select($id, $default_id, $attributes,
1785 $include_all_feeds, $line["id"], $nest_level+
1);
1787 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1788 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1790 while ($fline = db_fetch_assoc($feed_result)) {
1791 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1793 $fline["title"] = " + " . $fline["title"];
1795 for ($i = 0; $i < $nest_level; $i++
)
1796 $fline["title"] = " - " . $fline["title"];
1798 printf("<option $is_selected value='%d'>%s</option>",
1799 $fline["id"], htmlspecialchars($fline["title"]));
1804 $default_is_cat = ($default_id == "CAT:0");
1805 $is_selected = $default_is_cat ?
"selected=\"1\"" : "";
1807 printf("<option $is_selected value='CAT:0'>%s</option>",
1808 __("Uncategorized"));
1810 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1811 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1813 while ($fline = db_fetch_assoc($feed_result)) {
1814 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1816 $fline["title"] = " + " . $fline["title"];
1818 for ($i = 0; $i < $nest_level; $i++
)
1819 $fline["title"] = " - " . $fline["title"];
1821 printf("<option $is_selected value='%d'>%s</option>",
1822 $fline["id"], htmlspecialchars($fline["title"]));
1827 $result = db_query("SELECT id,title FROM ttrss_feeds
1828 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1830 while ($line = db_fetch_assoc($result)) {
1832 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1834 printf("<option $is_selected value='%d'>%s</option>",
1835 $line["id"], htmlspecialchars($line["title"]));
1844 function print_feed_cat_select($id, $default_id,
1845 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1848 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1852 $parent_qpart = "parent_cat = '$root_id'";
1854 $parent_qpart = "parent_cat IS NULL";
1856 $result = db_query("SELECT id,title,
1857 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1858 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1859 FROM ttrss_feed_categories
1860 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1862 while ($line = db_fetch_assoc($result)) {
1863 if ($line["id"] == $default_id) {
1864 $is_selected = "selected=\"1\"";
1869 for ($i = 0; $i < $nest_level; $i++
)
1870 $line["title"] = " - " . $line["title"];
1873 printf("<option $is_selected value='%d'>%s</option>",
1874 $line["id"], htmlspecialchars($line["title"]));
1876 if ($line["num_children"] > 0)
1877 print_feed_cat_select($id, $default_id, $attributes,
1878 $include_all_cats, $line["id"], $nest_level+
1);
1882 if ($include_all_cats) {
1883 if (db_num_rows($result) > 0) {
1884 print "<option disabled=\"1\">--------</option>";
1887 if ($default_id == 0) {
1888 $is_selected = "selected=\"1\"";
1893 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1899 function checkbox_to_sql_bool($val) {
1900 return ($val == "on") ?
"true" : "false";
1903 function getFeedCatTitle($id) {
1905 return __("Special");
1906 } else if ($id < LABEL_BASE_INDEX
) {
1907 return __("Labels");
1908 } else if ($id > 0) {
1909 $result = db_query("SELECT ttrss_feed_categories.title
1910 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1911 cat_id = ttrss_feed_categories.id");
1912 if (db_num_rows($result) == 1) {
1913 return db_fetch_result($result, 0, "title");
1915 return __("Uncategorized");
1918 return "getFeedCatTitle($id) failed";
1923 function getFeedIcon($id) {
1926 return "images/archive.png";
1929 return "images/star.png";
1932 return "images/feed.png";
1935 return "images/fresh.png";
1938 return "images/folder.png";
1941 return "images/time.png";
1944 if ($id < LABEL_BASE_INDEX
) {
1945 return "images/label.png";
1947 if (file_exists(ICONS_DIR
. "/$id.ico"))
1948 return ICONS_URL
. "/$id.ico";
1956 function getFeedTitle($id, $cat = false) {
1958 return getCategoryTitle($id);
1959 } else if ($id == -1) {
1960 return __("Starred articles");
1961 } else if ($id == -2) {
1962 return __("Published articles");
1963 } else if ($id == -3) {
1964 return __("Fresh articles");
1965 } else if ($id == -4) {
1966 return __("All articles");
1967 } else if ($id === 0 ||
$id === "0") {
1968 return __("Archived articles");
1969 } else if ($id == -6) {
1970 return __("Recently read");
1971 } else if ($id < LABEL_BASE_INDEX
) {
1972 $label_id = feed_to_label_id($id);
1973 $result = db_query("SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1974 if (db_num_rows($result) == 1) {
1975 return db_fetch_result($result, 0, "caption");
1977 return "Unknown label ($label_id)";
1980 } else if (is_numeric($id) && $id > 0) {
1981 $result = db_query("SELECT title FROM ttrss_feeds WHERE id = '$id'");
1982 if (db_num_rows($result) == 1) {
1983 return db_fetch_result($result, 0, "title");
1985 return "Unknown feed ($id)";
1992 function make_init_params() {
1995 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1996 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1997 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1998 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
2000 $params[strtolower($param)] = (int) get_pref($param);
2003 $params["icons_url"] = ICONS_URL
;
2004 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
2005 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
2006 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
2007 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
2008 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
2009 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
2011 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2012 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2014 $max_feed_id = db_fetch_result($result, 0, "mid");
2015 $num_feeds = db_fetch_result($result, 0, "nf");
2017 $params["max_feed_id"] = (int) $max_feed_id;
2018 $params["num_feeds"] = (int) $num_feeds;
2020 $params["hotkeys"] = get_hotkeys_map();
2022 $params["csrf_token"] = $_SESSION["csrf_token"];
2023 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
2025 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
2030 function get_hotkeys_info() {
2032 __("Navigation") => array(
2033 "next_feed" => __("Open next feed"),
2034 "prev_feed" => __("Open previous feed"),
2035 "next_article" => __("Open next article"),
2036 "prev_article" => __("Open previous article"),
2037 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
2038 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
2039 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
2040 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
2041 "search_dialog" => __("Show search dialog")),
2042 __("Article") => array(
2043 "toggle_mark" => __("Toggle starred"),
2044 "toggle_publ" => __("Toggle published"),
2045 "toggle_unread" => __("Toggle unread"),
2046 "edit_tags" => __("Edit tags"),
2047 "dismiss_selected" => __("Dismiss selected"),
2048 "dismiss_read" => __("Dismiss read"),
2049 "open_in_new_window" => __("Open in new window"),
2050 "catchup_below" => __("Mark below as read"),
2051 "catchup_above" => __("Mark above as read"),
2052 "article_scroll_down" => __("Scroll down"),
2053 "article_scroll_up" => __("Scroll up"),
2054 "select_article_cursor" => __("Select article under cursor"),
2055 "email_article" => __("Email article"),
2056 "close_article" => __("Close/collapse article"),
2057 "toggle_expand" => __("Toggle article expansion (combined mode)"),
2058 "toggle_widescreen" => __("Toggle widescreen mode"),
2059 "toggle_embed_original" => __("Toggle embed original")),
2060 __("Article selection") => array(
2061 "select_all" => __("Select all articles"),
2062 "select_unread" => __("Select unread"),
2063 "select_marked" => __("Select starred"),
2064 "select_published" => __("Select published"),
2065 "select_invert" => __("Invert selection"),
2066 "select_none" => __("Deselect everything")),
2067 __("Feed") => array(
2068 "feed_refresh" => __("Refresh current feed"),
2069 "feed_unhide_read" => __("Un/hide read feeds"),
2070 "feed_subscribe" => __("Subscribe to feed"),
2071 "feed_edit" => __("Edit feed"),
2072 "feed_catchup" => __("Mark as read"),
2073 "feed_reverse" => __("Reverse headlines"),
2074 "feed_debug_update" => __("Debug feed update"),
2075 "catchup_all" => __("Mark all feeds as read"),
2076 "cat_toggle_collapse" => __("Un/collapse current category"),
2077 "toggle_combined_mode" => __("Toggle combined mode"),
2078 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
2079 __("Go to") => array(
2080 "goto_all" => __("All articles"),
2081 "goto_fresh" => __("Fresh"),
2082 "goto_marked" => __("Starred"),
2083 "goto_published" => __("Published"),
2084 "goto_tagcloud" => __("Tag cloud"),
2085 "goto_prefs" => __("Preferences")),
2086 __("Other") => array(
2087 "create_label" => __("Create label"),
2088 "create_filter" => __("Create filter"),
2089 "collapse_sidebar" => __("Un/collapse sidebar"),
2090 "help_dialog" => __("Show help dialog"))
2093 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
2094 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
2100 function get_hotkeys_map() {
2102 // "navigation" => array(
2105 "n" => "next_article",
2106 "p" => "prev_article",
2107 "(38)|up" => "prev_article",
2108 "(40)|down" => "next_article",
2109 // "^(38)|Ctrl-up" => "prev_article_noscroll",
2110 // "^(40)|Ctrl-down" => "next_article_noscroll",
2111 "(191)|/" => "search_dialog",
2112 // "article" => array(
2113 "s" => "toggle_mark",
2114 "*s" => "toggle_publ",
2115 "u" => "toggle_unread",
2116 "*t" => "edit_tags",
2117 "*d" => "dismiss_selected",
2118 "*x" => "dismiss_read",
2119 "o" => "open_in_new_window",
2120 "c p" => "catchup_below",
2121 "c n" => "catchup_above",
2122 "*n" => "article_scroll_down",
2123 "*p" => "article_scroll_up",
2124 "*(38)|Shift+up" => "article_scroll_up",
2125 "*(40)|Shift+down" => "article_scroll_down",
2126 "a *w" => "toggle_widescreen",
2127 "a e" => "toggle_embed_original",
2128 "e" => "email_article",
2129 "a q" => "close_article",
2130 // "article_selection" => array(
2131 "a a" => "select_all",
2132 "a u" => "select_unread",
2133 "a *u" => "select_marked",
2134 "a p" => "select_published",
2135 "a i" => "select_invert",
2136 "a n" => "select_none",
2138 "f r" => "feed_refresh",
2139 "f a" => "feed_unhide_read",
2140 "f s" => "feed_subscribe",
2141 "f e" => "feed_edit",
2142 "f q" => "feed_catchup",
2143 "f x" => "feed_reverse",
2144 "f *d" => "feed_debug_update",
2145 "f *c" => "toggle_combined_mode",
2146 "f c" => "toggle_cdm_expanded",
2147 "*q" => "catchup_all",
2148 "x" => "cat_toggle_collapse",
2150 "g a" => "goto_all",
2151 "g f" => "goto_fresh",
2152 "g s" => "goto_marked",
2153 "g p" => "goto_published",
2154 "g t" => "goto_tagcloud",
2155 "g *p" => "goto_prefs",
2156 // "other" => array(
2157 "(9)|Tab" => "select_article_cursor", // tab
2158 "c l" => "create_label",
2159 "c f" => "create_filter",
2160 "c s" => "collapse_sidebar",
2161 "^(191)|Ctrl+/" => "help_dialog",
2164 if (get_pref('COMBINED_DISPLAY_MODE')) {
2165 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2166 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2169 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
2170 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2173 $prefixes = array();
2175 foreach (array_keys($hotkeys) as $hotkey) {
2176 $pair = explode(" ", $hotkey, 2);
2178 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2179 array_push($prefixes, $pair[0]);
2183 return array($prefixes, $hotkeys);
2186 function make_runtime_info() {
2189 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2190 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2192 $max_feed_id = db_fetch_result($result, 0, "mid");
2193 $num_feeds = db_fetch_result($result, 0, "nf");
2195 $data["max_feed_id"] = (int) $max_feed_id;
2196 $data["num_feeds"] = (int) $num_feeds;
2198 $data['last_article_id'] = getLastArticleId();
2199 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
2201 $data['dep_ts'] = calculate_dep_timestamp();
2202 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
2204 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2206 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2208 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2210 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2213 $stamp_delta = time() - $stamp;
2215 if ($stamp_delta > 1800) {
2219 $_SESSION["daemon_stamp_check"] = time();
2222 $data['daemon_stamp_ok'] = $stamp_check;
2224 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2226 $data['daemon_stamp'] = $stamp_fmt;
2231 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2232 $new_version_details = @check_for_update
();
2234 $data['new_version_available'] = (int) ($new_version_details != false);
2236 $_SESSION["last_version_check"] = time();
2237 $_SESSION["version_data"] = $new_version_details;
2243 function search_to_sql($search) {
2245 $search_query_part = "";
2247 $keywords = str_getcsv($search, " ");
2248 $query_keywords = array();
2249 $search_words = array();
2251 foreach ($keywords as $k) {
2252 if (strpos($k, "-") === 0) {
2259 $commandpair = explode(":", mb_strtolower($k), 2);
2261 switch ($commandpair[0]) {
2263 if ($commandpair[1]) {
2264 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
2265 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2267 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2268 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2269 array_push($search_words, $k);
2273 if ($commandpair[1]) {
2274 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
2275 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2277 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2278 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2279 array_push($search_words, $k);
2283 if ($commandpair[1]) {
2284 if ($commandpair[1] == "true")
2285 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2286 else if ($commandpair[1] == "false")
2287 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2289 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
2290 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2292 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2293 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2294 if (!$not) array_push($search_words, $k);
2299 if ($commandpair[1]) {
2300 if ($commandpair[1] == "true")
2301 array_push($query_keywords, "($not (marked = true))");
2303 array_push($query_keywords, "($not (marked = false))");
2305 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2306 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2307 if (!$not) array_push($search_words, $k);
2311 if ($commandpair[1]) {
2312 if ($commandpair[1] == "true")
2313 array_push($query_keywords, "($not (published = true))");
2315 array_push($query_keywords, "($not (published = false))");
2318 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2319 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2320 if (!$not) array_push($search_words, $k);
2324 if (strpos($k, "@") === 0) {
2326 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
2327 $orig_ts = strtotime(substr($k, 1));
2328 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2330 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2332 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2334 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2335 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2337 if (!$not) array_push($search_words, $k);
2342 $search_query_part = implode("AND", $query_keywords);
2344 return array($search_query_part, $search_words);
2347 function getParentCategories($cat, $owner_uid) {
2350 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
2351 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2353 while ($line = db_fetch_assoc($result)) {
2354 array_push($rv, $line["parent_cat"]);
2355 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
2361 function getChildCategories($cat, $owner_uid) {
2364 $result = db_query("SELECT id FROM ttrss_feed_categories
2365 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2367 while ($line = db_fetch_assoc($result)) {
2368 array_push($rv, $line["id"]);
2369 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
2375 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) {
2377 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2379 $ext_tables_part = "";
2380 $search_words = array();
2384 if (SPHINX_ENABLED
) {
2385 $ids = join(",", @sphinx_search
($search, 0, 500));
2388 $search_query_part = "ref_id IN ($ids) AND ";
2390 $search_query_part = "ref_id = -1 AND ";
2393 list($search_query_part, $search_words) = search_to_sql($search);
2394 $search_query_part .= " AND ";
2398 $search_query_part = "";
2403 if (DB_TYPE
== "pgsql") {
2404 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2406 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2409 $override_order = "updated DESC";
2411 $filter_query_part = filter_to_sql($filter, $owner_uid);
2413 // Try to check if SQL regexp implementation chokes on a valid regexp
2416 $result = db_query("SELECT true AS true_val FROM ttrss_entries,
2417 ttrss_user_entries, ttrss_feeds
2418 WHERE $filter_query_part LIMIT 1", false);
2421 $test = db_fetch_result($result, 0, "true_val");
2424 $filter_query_part = "false AND";
2426 $filter_query_part .= " AND";
2429 $filter_query_part = "false AND";
2433 $filter_query_part = "";
2437 $since_id_part = "ttrss_entries.id > $since_id AND ";
2439 $since_id_part = "";
2442 $view_query_part = "";
2444 if ($view_mode == "adaptive") {
2446 $view_query_part = " ";
2447 } else if ($feed != -1) {
2449 $unread = getFeedUnread($feed, $cat_view);
2451 if ($cat_view && $feed > 0 && $include_children)
2452 $unread +
= getCategoryChildrenUnread($feed);
2455 $view_query_part = " unread = true AND ";
2460 if ($view_mode == "marked") {
2461 $view_query_part = " marked = true AND ";
2464 if ($view_mode == "has_note") {
2465 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
2468 if ($view_mode == "published") {
2469 $view_query_part = " published = true AND ";
2472 if ($view_mode == "unread" && $feed != -6) {
2473 $view_query_part = " unread = true AND ";
2477 $limit_query_part = "LIMIT " . $limit;
2480 $allow_archived = false;
2482 $vfeed_query_part = "";
2484 // override query strategy and enable feed display when searching globally
2485 if ($search && $search_mode == "all_feeds") {
2486 $query_strategy_part = "true";
2487 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2489 } else if (!is_numeric($feed)) {
2490 $query_strategy_part = "true";
2491 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2492 id = feed_id) as feed_title,";
2493 } else if ($search && $search_mode == "this_cat") {
2494 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2497 if ($include_children) {
2498 $subcats = getChildCategories($feed, $owner_uid);
2499 array_push($subcats, $feed);
2500 $cats_qpart = join(",", $subcats);
2502 $cats_qpart = $feed;
2505 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2508 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2511 } else if ($feed > 0) {
2516 if ($include_children) {
2518 $subcats = getChildCategories($feed, $owner_uid);
2520 array_push($subcats, $feed);
2521 $query_strategy_part = "cat_id IN (".
2522 implode(",", $subcats).")";
2525 $query_strategy_part = "cat_id = '$feed'";
2529 $query_strategy_part = "cat_id IS NULL";
2532 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2535 $query_strategy_part = "feed_id = '$feed'";
2537 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2538 $query_strategy_part = "feed_id IS NULL";
2539 $allow_archived = true;
2540 } else if ($feed == 0 && $cat_view) { // uncategorized
2541 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2542 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2543 } else if ($feed == -1) { // starred virtual feed
2544 $query_strategy_part = "marked = true";
2545 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2546 $allow_archived = true;
2548 if (!$override_order) {
2549 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
2552 } else if ($feed == -2) { // published virtual feed OR labels category
2555 $query_strategy_part = "published = true";
2556 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2557 $allow_archived = true;
2559 if (!$override_order) {
2560 $override_order = "last_published DESC, date_entered DESC, updated DESC";
2564 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2566 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2568 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2569 ttrss_user_labels2.article_id = ref_id";
2572 } else if ($feed == -6) { // recently read
2573 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2574 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2575 $allow_archived = true;
2577 if (!$override_order) $override_order = "last_read DESC";
2579 /* } else if ($feed == -7) { // shared
2580 $query_strategy_part = "uuid != ''";
2581 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2582 $allow_archived = true; */
2583 } else if ($feed == -3) { // fresh virtual feed
2584 $query_strategy_part = "unread = true AND score >= 0";
2586 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
2588 if (DB_TYPE
== "pgsql") {
2589 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
2591 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2594 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2595 } else if ($feed == -4) { // all articles virtual feed
2596 $allow_archived = true;
2597 $query_strategy_part = "true";
2598 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2599 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
2600 $label_id = feed_to_label_id($feed);
2602 $query_strategy_part = "label_id = '$label_id' AND
2603 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2604 ttrss_user_labels2.article_id = ref_id";
2606 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2607 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2608 $allow_archived = true;
2611 $query_strategy_part = "true";
2614 $order_by = "score DESC, date_entered DESC, updated DESC";
2616 if ($view_mode == "unread_first") {
2617 $order_by = "unread DESC, $order_by";
2620 if ($override_order) {
2621 $order_by = $override_order;
2624 if ($override_strategy) {
2625 $query_strategy_part = $override_strategy;
2628 if ($override_vfeed) {
2629 $vfeed_query_part = $override_vfeed;
2635 $feed_title = T_sprintf("Search results: %s", $search);
2638 $feed_title = getCategoryTitle($feed);
2640 if (is_numeric($feed) && $feed > 0) {
2641 $result = db_query("SELECT title,site_url,last_error,last_updated
2642 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2644 $feed_title = db_fetch_result($result, 0, "title");
2645 $feed_site_url = db_fetch_result($result, 0, "site_url");
2646 $last_error = db_fetch_result($result, 0, "last_error");
2647 $last_updated = db_fetch_result($result, 0, "last_updated");
2649 $feed_title = getFeedTitle($feed);
2655 $content_query_part = "content, ";
2658 if (is_numeric($feed)) {
2661 $feed_kind = "Feeds";
2663 $feed_kind = "Labels";
2666 if ($limit_query_part) {
2667 $offset_query_part = "OFFSET $offset";
2670 // proper override_order applied above
2671 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
2672 if (!$override_order) {
2673 $order_by = "ttrss_feeds.title, $order_by";
2675 $order_by = "ttrss_feeds.title, $override_order";
2679 if (!$allow_archived) {
2680 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2681 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2684 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2685 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2688 if ($vfeed_query_part)
2689 $vfeed_query_part .= "favicon_avg_color,";
2691 $query = "SELECT DISTINCT
2694 ttrss_entries.id,ttrss_entries.title,
2698 always_display_enclosures,
2707 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2708 last_marked, last_published,
2716 ttrss_user_entries.ref_id = ttrss_entries.id AND
2717 ttrss_user_entries.owner_uid = '$owner_uid' AND
2722 $query_strategy_part ORDER BY $order_by
2723 $limit_query_part $offset_query_part";
2725 if ($_REQUEST["debug"]) print $query;
2727 $result = db_query($query);
2732 $select_qpart = "SELECT DISTINCT " .
2736 "ttrss_entries.id as id," .
2751 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2752 "last_marked, last_published, " .
2755 $content_query_part .
2758 $feed_kind = "Tags";
2759 $all_tags = explode(",", $feed);
2760 if ($search_mode == 'any') {
2761 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2762 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2763 $where_qpart = " WHERE " .
2764 "ref_id = ttrss_entries.id AND " .
2765 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2766 "post_int_id = int_id AND $tag_sql AND " .
2768 $search_query_part .
2769 $query_strategy_part . " ORDER BY $order_by " .
2774 $sub_selects = array();
2775 $sub_ands = array();
2776 foreach ($all_tags as $term) {
2777 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");
2784 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2789 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2790 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2791 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2792 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2794 // error_log("TAG SQL: " . $tag_sql);
2795 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2797 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2798 $result = db_query($select_qpart . $from_qpart . $where_qpart);
2801 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
2805 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
2806 if (!$owner) $owner = $_SESSION["uid"];
2808 $res = trim($str); if (!$res) return '';
2810 $charset_hack = '<head>
2811 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2814 $res = trim($res); if (!$res) return '';
2816 libxml_use_internal_errors(true);
2818 $doc = new DOMDocument();
2819 $doc->loadHTML($charset_hack . $res);
2820 $xpath = new DOMXPath($doc);
2822 $entries = $xpath->query('(//a[@href]|//img[@src])');
2824 foreach ($entries as $entry) {
2828 if ($entry->hasAttribute('href'))
2829 $entry->setAttribute('href',
2830 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2832 if ($entry->hasAttribute('src')) {
2833 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2835 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2837 if (file_exists($cached_filename)) {
2838 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2841 $entry->setAttribute('src', $src);
2844 if ($entry->nodeName
== 'img') {
2845 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
2846 $force_remove_images ||
$_SESSION["bw_limit"]) {
2848 $p = $doc->createElement('p');
2850 $a = $doc->createElement('a');
2851 $a->setAttribute('href', $entry->getAttribute('src'));
2853 $a->appendChild(new DOMText($entry->getAttribute('src')));
2854 $a->setAttribute('target', '_blank');
2856 $p->appendChild($a);
2858 $entry->parentNode
->replaceChild($p, $entry);
2863 if (strtolower($entry->nodeName
) == "a") {
2864 $entry->setAttribute("target", "_blank");
2868 $entries = $xpath->query('//iframe');
2869 foreach ($entries as $entry) {
2870 $entry->setAttribute('sandbox', 'allow-scripts');
2874 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
2875 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
2876 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
2877 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
2878 'dt', 'em', 'footer', 'figure', 'figcaption',
2879 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
2880 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
2881 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
2882 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2883 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
2884 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2886 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
2888 $disallowed_attributes = array('id', 'style', 'class');
2890 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
2891 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
2892 if (is_array($retval)) {
2894 $allowed_elements = $retval[1];
2895 $disallowed_attributes = $retval[2];
2901 $doc->removeChild($doc->firstChild
); //remove doctype
2902 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
2904 if ($highlight_words) {
2905 foreach ($highlight_words as $word) {
2907 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
2909 $elements = $xpath->query("//*/text()");
2911 foreach ($elements as $child) {
2913 $fragment = $doc->createDocumentFragment();
2914 $text = $child->textContent
;
2917 while (($pos = mb_stripos($text, $word)) !== false) {
2918 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
2919 $word = mb_substr($text, $pos, mb_strlen($word));
2920 $highlight = $doc->createElement('span');
2921 $highlight->appendChild(new DomText($word));
2922 $highlight->setAttribute('class', 'highlight');
2923 $fragment->appendChild($highlight);
2924 $text = mb_substr($text, $pos +
mb_strlen($word));
2927 if (!empty($text)) $fragment->appendChild(new DomText($text));
2929 $child->parentNode
->replaceChild($fragment, $child);
2934 $res = $doc->saveHTML();
2939 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
2940 $xpath = new DOMXPath($doc);
2941 $entries = $xpath->query('//*');
2943 foreach ($entries as $entry) {
2944 if (!in_array($entry->nodeName
, $allowed_elements)) {
2945 $entry->parentNode
->removeChild($entry);
2948 if ($entry->hasAttributes()) {
2949 $attrs_to_remove = array();
2951 foreach ($entry->attributes
as $attr) {
2953 if (strpos($attr->nodeName
, 'on') === 0) {
2954 array_push($attrs_to_remove, $attr);
2957 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2958 array_push($attrs_to_remove, $attr);
2962 foreach ($attrs_to_remove as $attr) {
2963 $entry->removeAttributeNode($attr);
2971 function check_for_update() {
2972 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2973 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2974 "&iid=" . sha1(SELF_URL_PATH
);
2976 $version_data = @fetch_file_contents
($version_url);
2978 if ($version_data) {
2979 $version_data = json_decode($version_data, true);
2980 if ($version_data && $version_data['version']) {
2981 if (version_compare(VERSION_STATIC
, $version_data['version']) == -1) {
2982 return $version_data;
2990 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
2992 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2993 if (count($ids) == 0) return;
2997 foreach ($ids as $id) {
2998 array_push($tmp_ids, "ref_id = '$id'");
3001 $ids_qpart = join(" OR ", $tmp_ids);
3004 db_query("UPDATE ttrss_user_entries SET
3005 unread = false,last_read = NOW()
3006 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3007 } else if ($cmode == 1) {
3008 db_query("UPDATE ttrss_user_entries SET
3010 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3012 db_query("UPDATE ttrss_user_entries SET
3013 unread = NOT unread,last_read = NOW()
3014 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3019 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
3020 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3022 while ($line = db_fetch_assoc($result)) {
3023 ccache_update($line["feed_id"], $owner_uid);
3027 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
3029 $a_id = db_escape_string($id);
3031 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3033 $query = "SELECT DISTINCT tag_name,
3034 owner_uid as owner FROM
3035 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
3036 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
3040 /* check cache first */
3042 if ($tag_cache === false) {
3043 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
3044 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3046 $tag_cache = db_fetch_result($result, 0, "tag_cache");
3050 $tags = explode(",", $tag_cache);
3053 /* do it the hard way */
3055 $tmp_result = db_query($query);
3057 while ($tmp_line = db_fetch_assoc($tmp_result)) {
3058 array_push($tags, $tmp_line["tag_name"]);
3061 /* update the cache */
3063 $tags_str = db_escape_string(join(",", $tags));
3065 db_query("UPDATE ttrss_user_entries
3066 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
3067 AND owner_uid = $owner_uid");
3073 function trim_array($array) {
3075 array_walk($tmp, 'trim');
3079 function tag_is_valid($tag) {
3080 if ($tag == '') return false;
3081 if (preg_match("/^[0-9]*$/", $tag)) return false;
3082 if (mb_strlen($tag) > 250) return false;
3084 if (!$tag) return false;
3089 function render_login_form() {
3090 header('Cache-Control: public');
3092 require_once "login_form.php";
3096 function format_warning($msg, $id = "") {
3098 return "<div class=\"warning\" id=\"$id\">
3099 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3102 function format_notice($msg, $id = "") {
3104 return "<div class=\"notice\" id=\"$id\">
3105 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
3108 function format_error($msg, $id = "") {
3110 return "<div class=\"error\" id=\"$id\">
3111 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3114 function print_notice($msg) {
3115 return print format_notice($msg);
3118 function print_warning($msg) {
3119 return print format_warning($msg);
3122 function print_error($msg) {
3123 return print format_error($msg);
3127 function T_sprintf() {
3128 $args = func_get_args();
3129 return vsprintf(__(array_shift($args)), $args);
3132 function format_inline_player($url, $ctype) {
3136 $url = htmlspecialchars($url);
3138 if (strpos($ctype, "audio/") === 0) {
3140 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
3141 $_SESSION["hasMp3"])) {
3143 $entry .= "<audio preload=\"none\" controls>
3144 <source type=\"$ctype\" src=\"$url\"></source>
3149 $entry .= "<object type=\"application/x-shockwave-flash\"
3150 data=\"lib/button/musicplayer.swf?song_url=$url\"
3151 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
3152 <param name=\"movie\"
3153 value=\"lib/button/musicplayer.swf?song_url=$url\" />
3157 if ($entry) $entry .= " <a target=\"_blank\"
3158 href=\"$url\">" . basename($url) . "</a>";
3166 /* $filename = substr($url, strrpos($url, "/")+1);
3168 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3169 $filename . " (" . $ctype . ")" . "</a>"; */
3173 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
3174 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3180 /* we can figure out feed_id from article id anyway, why do we
3181 * pass feed_id here? let's ignore the argument :(*/
3183 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3184 WHERE ref_id = '$id'");
3186 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
3188 $rv['feed_id'] = $feed_id;
3190 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
3192 if ($mark_as_read) {
3193 $result = db_query("UPDATE ttrss_user_entries
3194 SET unread = false,last_read = NOW()
3195 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3197 ccache_update($feed_id, $owner_uid);
3200 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
3201 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
3202 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
3203 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
3204 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
3205 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
3211 FROM ttrss_entries,ttrss_user_entries
3212 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
3216 $line = db_fetch_assoc($result);
3218 $tag_cache = $line["tag_cache"];
3220 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
3221 unset($line["tag_cache"]);
3223 $line["content"] = sanitize($line["content"],
3224 sql_bool_to_bool($line['hide_images']),
3225 $owner_uid, $line["site_url"], false, $line["id"]);
3227 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
3228 $line = $p->hook_render_article($line);
3231 $num_comments = $line["num_comments"];
3232 $entry_comments = "";
3234 if ($num_comments > 0) {
3235 if ($line["comments"]) {
3236 $comments_url = htmlspecialchars($line["comments"]);
3238 $comments_url = htmlspecialchars($line["link"]);
3240 $entry_comments = "<a class=\"postComments\"
3241 target='_blank' href=\"$comments_url\">$num_comments ".
3242 _ngettext("comment", "comments", $num_comments)."</a>";
3245 if ($line["comments"] && $line["link"] != $line["comments"]) {
3246 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
3251 header("Content-Type: text/html");
3252 $rv['content'] .= "<html><head>
3253 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3254 <title>Tiny Tiny RSS - ".$line["title"]."</title>
3255 <link rel=\"stylesheet\" type=\"text/css\" href=\"css/tt-rss.css\">
3256 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
3257 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
3259 <script type=\"text/javascript\">
3260 function openSelectedAttachment(elem) {
3262 var url = elem[elem.selectedIndex].value;
3266 elem.selectedIndex = 0;
3270 exception_error(\"openSelectedAttachment\", e);
3274 </head><body id=\"ttrssZoom\">";
3277 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3279 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3281 $entry_author = $line["author"];
3283 if ($entry_author) {
3284 $entry_author = __(" - ") . $entry_author;
3287 $parsed_updated = make_local_datetime($line["updated"], true,
3291 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3293 if ($line["link"]) {
3294 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3295 title=\"".htmlspecialchars($line['title'])."\"
3297 htmlspecialchars($line["link"]) . "\">" .
3298 $line["title"] . "</a>" .
3299 "<span class='author'>$entry_author</span></div>";
3301 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3305 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
3306 "\" target=\"_blank\">".
3307 htmlspecialchars($line["feed_title"])."</a>";
3309 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
3311 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3314 $tags_str = format_tags_string($line["tags"], $id);
3315 $tags_str_full = join(", ", $line["tags"]);
3317 if (!$tags_str_full) $tags_str_full = __("no tags");
3319 if (!$entry_comments) $entry_comments = " "; # placeholder
3321 $rv['content'] .= "<div class='postTags' style='float : right'>
3322 <img src='images/tag.png'
3323 class='tagsPic' alt='Tags' title='Tags'> ";
3326 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3327 <a title=\"".__('Edit tags for this article')."\"
3328 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3330 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3331 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3332 position=\"below\">$tags_str_full</div>";
3334 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
3335 $rv['content'] .= $p->hook_article_button($line);
3339 $tags_str = strip_tags($tags_str);
3340 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3342 $rv['content'] .= "</div>";
3343 $rv['content'] .= "<div clear='both'>";
3345 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
3346 $rv['content'] .= $p->hook_article_left_button($line);
3349 $rv['content'] .= "$entry_comments</div>";
3351 if ($line["orig_feed_id"]) {
3353 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
3354 WHERE id = ".$line["orig_feed_id"]);
3356 if (db_num_rows($tmp_result) != 0) {
3358 $rv['content'] .= "<div clear='both'>";
3359 $rv['content'] .= __("Originally from:");
3361 $rv['content'] .= " ";
3363 $tmp_line = db_fetch_assoc($tmp_result);
3365 $rv['content'] .= "<a target='_blank'
3366 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3367 $tmp_line['title'] . "</a>";
3369 $rv['content'] .= " ";
3371 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3372 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3374 $rv['content'] .= "</div>";
3378 $rv['content'] .= "</div>";
3380 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3381 if ($line['note']) {
3382 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3384 $rv['content'] .= "</div>";
3386 if (!$line['lang']) $line['lang'] = 'en';
3388 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
3390 $rv['content'] .= $line["content"];
3391 $rv['content'] .= format_article_enclosures($id,
3392 sql_bool_to_bool($line["always_display_enclosures"]),
3394 sql_bool_to_bool($line["hide_images"]));
3396 $rv['content'] .= "</div>";
3398 $rv['content'] .= "</div>";
3404 <div class='footer'>
3405 <button onclick=\"return window.close()\">".
3406 __("Close this window")."</button></div>";
3407 $rv['content'] .= "</body></html>";
3414 function print_checkpoint($n, $s) {
3415 $ts = microtime(true);
3416 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
3420 function sanitize_tag($tag) {
3423 $tag = mb_strtolower($tag, 'utf-8');
3425 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3427 // $tag = str_replace('"', "", $tag);
3428 // $tag = str_replace("+", " ", $tag);
3429 $tag = str_replace("technorati tag: ", "", $tag);
3434 function get_self_url_prefix() {
3435 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3436 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3438 return SELF_URL_PATH
;
3443 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3445 * @return string The Mozilla Firefox feed adding URL.
3447 function add_feed_url() {
3448 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3450 $url_path = get_self_url_prefix() .
3451 "/public.php?op=subscribe&feed_url=%s";
3453 } // function add_feed_url
3455 function encrypt_password($pass, $salt = '', $mode2 = false) {
3456 if ($salt && $mode2) {
3457 return "MODE2:" . hash('sha256', $salt . $pass);
3459 return "SHA1X:" . sha1("$salt:$pass");
3461 return "SHA1:" . sha1($pass);
3463 } // function encrypt_password
3465 function load_filters($feed_id, $owner_uid, $action_id = false) {
3468 $cat_id = (int)getFeedCategory($feed_id);
3471 $null_cat_qpart = "cat_id IS NULL OR";
3473 $null_cat_qpart = "";
3475 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
3476 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
3478 $check_cats = join(",", array_merge(
3479 getParentCategories($cat_id, $owner_uid),
3482 while ($line = db_fetch_assoc($result)) {
3483 $filter_id = $line["id"];
3485 $result2 = db_query("SELECT
3486 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3487 FROM ttrss_filters2_rules AS r,
3488 ttrss_filter_types AS t
3490 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
3491 (feed_id IS NULL OR feed_id = '$feed_id') AND
3492 filter_type = t.id AND filter_id = '$filter_id'");
3497 while ($rule_line = db_fetch_assoc($result2)) {
3498 # print_r($rule_line);
3501 $rule["reg_exp"] = $rule_line["reg_exp"];
3502 $rule["type"] = $rule_line["type_name"];
3503 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
3505 array_push($rules, $rule);
3508 $result2 = db_query("SELECT a.action_param,t.name AS type_name
3509 FROM ttrss_filters2_actions AS a,
3510 ttrss_filter_actions AS t
3512 action_id = t.id AND filter_id = '$filter_id'");
3514 while ($action_line = db_fetch_assoc($result2)) {
3515 # print_r($action_line);
3518 $action["type"] = $action_line["type_name"];
3519 $action["param"] = $action_line["action_param"];
3521 array_push($actions, $action);
3526 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3527 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
3528 $filter["rules"] = $rules;
3529 $filter["actions"] = $actions;
3531 if (count($rules) > 0 && count($actions) > 0) {
3532 array_push($filters, $filter);
3539 function get_score_pic($score) {
3541 return "score_high.png";
3542 } else if ($score > 0) {
3543 return "score_half_high.png";
3544 } else if ($score < -100) {
3545 return "score_low.png";
3546 } else if ($score < 0) {
3547 return "score_half_low.png";
3549 return "score_neutral.png";
3553 function feed_has_icon($id) {
3554 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3557 function init_plugins() {
3558 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
3563 function format_tags_string($tags, $id) {
3564 if (!is_array($tags) ||
count($tags) == 0) {
3565 return __("no tags");
3567 $maxtags = min(5, count($tags));
3569 for ($i = 0; $i < $maxtags; $i++
) {
3570 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
3573 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
3575 if (count($tags) > $maxtags)
3576 $tags_str .= ", …";
3582 function format_article_labels($labels, $id) {
3584 if (!is_array($labels)) return '';
3588 foreach ($labels as $l) {
3589 $labels_str .= sprintf("<span class='hlLabelRef'
3590 style='color : %s; background-color : %s'>%s</span>",
3591 $l[2], $l[3], $l[1]);
3598 function format_article_note($id, $note, $allow_edit = true) {
3600 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3601 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3602 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3608 function get_feed_category($feed_cat, $parent_cat_id = false) {
3609 if ($parent_cat_id) {
3610 $parent_qpart = "parent_cat = '$parent_cat_id'";
3611 $parent_insert = "'$parent_cat_id'";
3613 $parent_qpart = "parent_cat IS NULL";
3614 $parent_insert = "NULL";
3618 "SELECT id FROM ttrss_feed_categories
3619 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3621 if (db_num_rows($result) == 0) {
3624 return db_fetch_result($result, 0, "id");
3628 function add_feed_category($feed_cat, $parent_cat_id = false) {
3630 if (!$feed_cat) return false;
3634 if ($parent_cat_id) {
3635 $parent_qpart = "parent_cat = '$parent_cat_id'";
3636 $parent_insert = "'$parent_cat_id'";
3638 $parent_qpart = "parent_cat IS NULL";
3639 $parent_insert = "NULL";
3642 $feed_cat = mb_substr($feed_cat, 0, 250);
3645 "SELECT id FROM ttrss_feed_categories
3646 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3648 if (db_num_rows($result) == 0) {
3651 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3652 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3662 function getArticleFeed($id) {
3663 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3664 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3666 if (db_num_rows($result) != 0) {
3667 return db_fetch_result($result, 0, "feed_id");
3674 * Fixes incomplete URLs by prepending "http://".
3675 * Also replaces feed:// with http://, and
3676 * prepends a trailing slash if the url is a domain name only.
3678 * @param string $url Possibly incomplete URL
3680 * @return string Fixed URL.
3682 function fix_url($url) {
3683 if (strpos($url, '://') === false) {
3684 $url = 'http://' . $url;
3685 } else if (substr($url, 0, 5) == 'feed:') {
3686 $url = 'http:' . substr($url, 5);
3689 //prepend slash if the URL has no slash in it
3690 // "http://www.example" -> "http://www.example/"
3691 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3695 if ($url != "http:///")
3701 function validate_feed_url($url) {
3702 $parts = parse_url($url);
3704 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3708 function get_article_enclosures($id) {
3710 $query = "SELECT * FROM ttrss_enclosures
3711 WHERE post_id = '$id' AND content_url != ''";
3715 $result = db_query($query);
3717 if (db_num_rows($result) > 0) {
3718 while ($line = db_fetch_assoc($result)) {
3719 array_push($rv, $line);
3726 function save_email_address($email) {
3727 // FIXME: implement persistent storage of emails
3729 if (!$_SESSION['stored_emails'])
3730 $_SESSION['stored_emails'] = array();
3732 if (!in_array($email, $_SESSION['stored_emails']))
3733 array_push($_SESSION['stored_emails'], $email);
3737 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
3739 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3741 $sql_is_cat = bool_to_sql_bool($is_cat);
3743 $result = db_query("SELECT access_key FROM ttrss_access_keys
3744 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3745 AND owner_uid = " . $owner_uid);
3747 if (db_num_rows($result) == 1) {
3748 return db_fetch_result($result, 0, "access_key");
3750 $key = db_escape_string(sha1(uniqid(rand(), true)));
3752 $result = db_query("INSERT INTO ttrss_access_keys
3753 (access_key, feed_id, is_cat, owner_uid)
3754 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3761 function get_feeds_from_html($url, $content)
3763 $url = fix_url($url);
3764 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3766 libxml_use_internal_errors(true);
3768 $doc = new DOMDocument();
3769 $doc->loadHTML($content);
3770 $xpath = new DOMXPath($doc);
3771 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3772 $feedUrls = array();
3773 foreach ($entries as $entry) {
3774 if ($entry->hasAttribute('href')) {
3775 $title = $entry->getAttribute('title');
3777 $title = $entry->getAttribute('type');
3779 $feedUrl = rewrite_relative_url(
3780 $baseUrl, $entry->getAttribute('href')
3782 $feedUrls[$feedUrl] = $title;
3788 function is_html($content) {
3789 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3792 function url_is_html($url, $login = false, $pass = false) {
3793 return is_html(fetch_file_contents($url, false, $login, $pass));
3796 function print_label_select($name, $value, $attributes = "") {
3798 $result = db_query("SELECT caption FROM ttrss_labels2
3799 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3801 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3802 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3804 while ($line = db_fetch_assoc($result)) {
3806 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3808 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3809 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3813 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3820 function format_article_enclosures($id, $always_display_enclosures,
3821 $article_content, $hide_images = false) {
3823 $result = get_article_enclosures($id);
3826 if (count($result) > 0) {
3828 $entries_html = array();
3830 $entries_inline = array();
3832 foreach ($result as $line) {
3834 $url = $line["content_url"];
3835 $ctype = $line["content_type"];
3836 $title = $line["title"];
3838 if (!$ctype) $ctype = __("unknown type");
3840 $filename = substr($url, strrpos($url, "/")+
1);
3842 $player = format_inline_player($url, $ctype);
3844 if ($player) array_push($entries_inline, $player);
3846 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3847 # $filename . " (" . $ctype . ")" . "</a>";
3849 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3850 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3852 array_push($entries_html, $entry);
3856 $entry["type"] = $ctype;
3857 $entry["filename"] = $filename;
3858 $entry["url"] = $url;
3859 $entry["title"] = $title;
3861 array_push($entries, $entry);
3864 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
3865 if ($always_display_enclosures ||
3866 !preg_match("/<img/i", $article_content)) {
3868 foreach ($entries as $entry) {
3870 if (preg_match("/image/", $entry["type"]) ||
3871 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3873 if (!$hide_images) {
3875 alt=\"".htmlspecialchars($entry["filename"])."\"
3876 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3878 $rv .= "<p><a target=\"_blank\"
3879 href=\"".htmlspecialchars($entry["url"])."\"
3880 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3883 if ($entry['title']) {
3884 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
3891 if (count($entries_inline) > 0) {
3892 $rv .= "<hr clear='both'/>";
3893 foreach ($entries_inline as $entry) { $rv .= $entry; };
3894 $rv .= "<hr clear='both'/>";
3897 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
3898 "<option value=''>" . __('Attachments')."</option>";
3900 foreach ($entries as $entry) {
3901 if ($entry["title"])
3902 $title = "— " . truncate_string($entry["title"], 30);
3906 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
3916 function getLastArticleId() {
3917 $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3918 WHERE owner_uid = " . $_SESSION["uid"]);
3920 if (db_num_rows($result) == 1) {
3921 return db_fetch_result($result, 0, "id");
3927 function build_url($parts) {
3928 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3932 * Converts a (possibly) relative URL to a absolute one.
3934 * @param string $url Base URL (i.e. from where the document is)
3935 * @param string $rel_url Possibly relative URL in the document
3937 * @return string Absolute URL
3939 function rewrite_relative_url($url, $rel_url) {
3940 if (strpos($rel_url, ":") !== false) {
3942 } else if (strpos($rel_url, "://") !== false) {
3944 } else if (strpos($rel_url, "//") === 0) {
3945 # protocol-relative URL (rare but they exist)
3947 } else if (strpos($rel_url, "/") === 0)
3949 $parts = parse_url($url);
3950 $parts['path'] = $rel_url;
3952 return build_url($parts);
3955 $parts = parse_url($url);
3956 if (!isset($parts['path'])) {
3957 $parts['path'] = '/';
3959 $dir = $parts['path'];
3960 if (substr($dir, -1) !== '/') {
3961 $dir = dirname($parts['path']);
3962 $dir !== '/' && $dir .= '/';
3964 $parts['path'] = $dir . $rel_url;
3966 return build_url($parts);
3970 function sphinx_search($query, $offset = 0, $limit = 30) {
3971 require_once 'lib/sphinxapi.php';
3973 $sphinxClient = new SphinxClient();
3975 $sphinxpair = explode(":", SPHINX_SERVER
, 2);
3977 $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
3978 $sphinxClient->SetConnectTimeout(1);
3980 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3981 'feed_title' => 20));
3983 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3984 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3985 $sphinxClient->SetLimits($offset, $limit, 1000);
3986 $sphinxClient->SetArrayResult(false);
3987 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3989 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3993 if (is_array($result['matches'])) {
3994 foreach (array_keys($result['matches']) as $int_id) {
3995 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3996 array_push($ids, $ref_id);
4003 function cleanup_tags($days = 14, $limit = 1000) {
4005 if (DB_TYPE
== "pgsql") {
4006 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
4007 } else if (DB_TYPE
== "mysql") {
4008 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
4013 while ($limit > 0) {
4016 $query = "SELECT ttrss_tags.id AS id
4017 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
4018 WHERE post_int_id = int_id AND $interval_query AND
4019 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
4021 $result = db_query($query);
4025 while ($line = db_fetch_assoc($result)) {
4026 array_push($ids, $line['id']);
4029 if (count($ids) > 0) {
4030 $ids = join(",", $ids);
4032 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
4033 $tags_deleted +
= db_affected_rows($tmp_result);
4038 $limit -= $limit_part;
4041 return $tags_deleted;
4044 function print_user_stylesheet() {
4045 $value = get_pref('USER_STYLESHEET');
4048 print "<style type=\"text/css\">";
4049 print str_replace("<br/>", "\n", $value);
4055 function filter_to_sql($filter, $owner_uid) {
4058 if (DB_TYPE
== "pgsql")
4061 $reg_qpart = "REGEXP";
4063 foreach ($filter["rules"] AS $rule) {
4064 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
4065 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
4066 $rule['reg_exp']) !== FALSE;
4068 if ($regexp_valid) {
4070 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
4072 switch ($rule["type"]) {
4074 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4075 $rule['reg_exp'] . "')";
4078 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
4079 $rule['reg_exp'] . "')";
4082 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4083 $rule['reg_exp'] . "') OR LOWER(" .
4084 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
4087 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
4088 $rule['reg_exp'] . "')";
4091 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
4092 $rule['reg_exp'] . "')";
4095 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
4096 $rule['reg_exp'] . "')";
4100 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
4102 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
4103 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
4106 if (isset($rule["cat_id"])) {
4108 if ($rule["cat_id"] > 0) {
4109 $children = getChildCategories($rule["cat_id"], $owner_uid);
4110 array_push($children, $rule["cat_id"]);
4112 $children = join(",", $children);
4114 $cat_qpart = "cat_id IN ($children)";
4116 $cat_qpart = "cat_id IS NULL";
4119 $qpart .= " AND $cat_qpart";
4122 $qpart .= " AND feed_id IS NOT NULL";
4124 array_push($query, "($qpart)");
4129 if (count($query) > 0) {
4130 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
4132 $fullquery = "(false)";
4135 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
4140 if (!function_exists('gzdecode')) {
4141 function gzdecode($string) { // no support for 2nd argument
4142 return file_get_contents('compress.zlib://data:who/cares;base64,'.
4143 base64_encode($string));
4147 function get_random_bytes($length) {
4148 if (function_exists('openssl_random_pseudo_bytes')) {
4149 return openssl_random_pseudo_bytes($length);
4153 for ($i = 0; $i < $length; $i++
)
4154 $output .= chr(mt_rand(0, 255));
4160 function read_stdin() {
4161 $fp = fopen("php://stdin", "r");
4164 $line = trim(fgets($fp));
4172 function tmpdirname($path, $prefix) {
4173 // Use PHP's tmpfile function to create a temporary
4174 // directory name. Delete the file and keep the name.
4175 $tempname = tempnam($path,$prefix);
4179 if (!unlink($tempname))
4185 function getFeedCategory($feed) {
4186 $result = db_query("SELECT cat_id FROM ttrss_feeds
4187 WHERE id = '$feed'");
4189 if (db_num_rows($result) > 0) {
4190 return db_fetch_result($result, 0, "cat_id");
4197 function implements_interface($class, $interface) {
4198 return in_array($interface, class_implements($class));
4201 function geturl($url, $depth = 0){
4203 if ($depth == 20) return $url;
4205 if (!function_exists('curl_init'))
4206 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
);
4208 $curl = curl_init();
4209 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4210 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4211 $header[] = "Cache-Control: max-age=0";
4212 $header[] = "Connection: keep-alive";
4213 $header[] = "Keep-Alive: 300";
4214 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4215 $header[] = "Accept-Language: en-us,en;q=0.5";
4216 $header[] = "Pragma: ";
4218 curl_setopt($curl, CURLOPT_URL
, $url);
4219 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4220 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4221 curl_setopt($curl, CURLOPT_HEADER
, true);
4222 curl_setopt($curl, CURLOPT_REFERER
, $url);
4223 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4224 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4225 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4226 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4227 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4228 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER
, false);
4230 if (defined('_CURL_HTTP_PROXY')) {
4231 curl_setopt($curl, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
4234 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
4235 curl_setopt($curl, CURLOPT_SSLVERSION
, 3);
4238 $html = curl_exec($curl);
4240 $status = curl_getinfo($curl);
4242 if($status['http_code']!=200){
4243 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4245 list($header) = explode("\r\n\r\n", $html, 2);
4247 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4248 $url = trim(str_replace($matches[1],"",$matches[0]));
4249 $url_parsed = parse_url($url);
4250 return (isset($url_parsed))?
geturl($url, $depth +
1):'';
4253 global $fetch_last_error;
4255 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
4259 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4260 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4261 # $handle = @fopen('./curl.error.log', 'a');
4262 # fwrite($handle, $line);
4269 function get_minified_js($files) {
4270 require_once 'lib/jshrink/Minifier.php';
4274 foreach ($files as $js) {
4275 if (!isset($_GET['debug'])) {
4276 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
4278 if (file_exists($cached_file) &&
4279 is_readable($cached_file) &&
4280 filemtime($cached_file) >= filemtime("js/$js.js")) {
4282 $rv .= file_get_contents($cached_file);
4285 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4286 file_put_contents($cached_file, $minified);
4290 $rv .= file_get_contents("js/$js.js");
4297 function stylesheet_tag($filename) {
4298 $timestamp = filemtime($filename);
4300 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4303 function javascript_tag($filename) {
4306 if (!(strpos($filename, "?") === FALSE)) {
4307 $query = substr($filename, strpos($filename, "?")+
1);
4308 $filename = substr($filename, 0, strpos($filename, "?"));
4311 $timestamp = filemtime($filename);
4313 if ($query) $timestamp .= "&$query";
4315 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4318 function calculate_dep_timestamp() {
4319 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
4323 foreach ($files as $file) {
4324 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4330 function T_js_decl($s1, $s2) {
4332 $s1 = preg_replace("/\n/", "", $s1);
4333 $s2 = preg_replace("/\n/", "", $s2);
4335 $s1 = preg_replace("/\"/", "\\\"", $s1);
4336 $s2 = preg_replace("/\"/", "\\\"", $s2);
4338 return "T_messages[\"$s1\"] = \"$s2\";\n";
4342 function init_js_translations() {
4344 print 'var T_messages = new Object();
4347 if (T_messages[msg]) {
4348 return T_messages[msg];
4354 function ngettext(msg1, msg2, n) {
4355 return __((parseInt(n) > 1) ? msg2 : msg1);
4358 $l10n = _get_reader();
4360 for ($i = 0; $i < $l10n->total
; $i++
) {
4361 $orig = $l10n->get_original_string($i);
4362 if(strpos($orig, "\000") !== FALSE) { // Plural forms
4363 $key = explode(chr(0), $orig);
4364 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
4365 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
4367 $translation = __($orig);
4368 print T_js_decl($orig, $translation);
4373 function label_to_feed_id($label) {
4374 return LABEL_BASE_INDEX
- 1 - abs($label);
4377 function feed_to_label_id($feed) {
4378 return LABEL_BASE_INDEX
- 1 +
abs($feed);
4381 function format_libxml_error($error) {
4382 return T_sprintf("LibXML error %s at line %d (column %d): %s",
4383 $error->code
, $error->line
, $error->column
,