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",
82 "zh_TW" => "Traditional Chinese",
90 require_once "lib/accept-to-gettext.php";
91 require_once "lib/gettext/gettext.inc";
93 require_once "lib/languagedetect/LanguageDetect.php";
95 function startup_gettext() {
97 # Get locale from Accept-Language header
98 $lang = al2gt(array_keys(get_translations()), "text/html");
100 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
101 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
104 if ($_SESSION["uid"] && get_schema_version() >= 120) {
105 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
107 if ($pref_lang && $pref_lang != 'auto') {
113 if (defined('LC_MESSAGES')) {
114 _setlocale(LC_MESSAGES
, $lang);
115 } else if (defined('LC_ALL')) {
116 _setlocale(LC_ALL
, $lang);
119 _bindtextdomain("messages", "locale");
121 _textdomain("messages");
122 _bind_textdomain_codeset("messages", "UTF-8");
126 require_once 'db-prefs.php';
127 require_once 'version.php';
128 require_once 'ccache.php';
129 require_once 'labels.php';
131 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
132 ini_set('user_agent', SELF_USER_AGENT
);
134 require_once 'lib/pubsubhubbub/publisher.php';
136 $schema_version = false;
138 function _debug_suppress($suppress) {
139 global $suppress_debugging;
141 $suppress_debugging = $suppress;
145 * Print a timestamped debug message.
147 * @param string $msg The debug message.
150 function _debug($msg, $show = true) {
151 global $suppress_debugging;
153 //echo "[$suppress_debugging] $msg $show\n";
155 if ($suppress_debugging) return false;
157 $ts = strftime("%H:%M:%S", time());
158 if (function_exists('posix_getpid')) {
159 $ts = "$ts/" . posix_getpid();
162 if ($show && !(defined('QUIET') && QUIET
)) {
163 print "[$ts] $msg\n";
166 if (defined('LOGFILE')) {
167 $fp = fopen(LOGFILE
, 'a+');
172 if (function_exists("flock")) {
175 // try to lock logfile for writing
176 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
187 fputs($fp, "[$ts] $msg\n");
189 if (function_exists("flock")) {
200 * Purge a feed old posts.
202 * @param mixed $link A database connection.
203 * @param mixed $feed_id The id of the purged feed.
204 * @param mixed $purge_interval Olderness of purged posts.
205 * @param boolean $debug Set to True to enable the debug. False by default.
209 function purge_feed($feed_id, $purge_interval, $debug = false) {
211 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
216 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
220 if (db_num_rows($result) == 1) {
221 $owner_uid = db_fetch_result($result, 0, "owner_uid");
224 if ($purge_interval == -1 ||
!$purge_interval) {
226 ccache_update($feed_id, $owner_uid);
231 if (!$owner_uid) return;
233 if (FORCE_ARTICLE_PURGE
== 0) {
234 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
237 $purge_unread = true;
238 $purge_interval = FORCE_ARTICLE_PURGE
;
241 if (!$purge_unread) $query_limit = " unread = false AND ";
243 if (DB_TYPE
== "pgsql") {
244 $pg_version = get_pgsql_version();
246 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
248 $result = db_query("DELETE FROM ttrss_user_entries WHERE
249 ttrss_entries.id = ref_id AND
251 feed_id = '$feed_id' AND
253 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
257 $result = db_query("DELETE FROM ttrss_user_entries
259 WHERE ttrss_entries.id = ref_id AND
261 feed_id = '$feed_id' AND
263 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
268 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
269 marked = false AND feed_id = '$feed_id' AND
270 (SELECT date_updated FROM ttrss_entries WHERE
271 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
273 $result = db_query("DELETE FROM ttrss_user_entries
274 USING ttrss_user_entries, ttrss_entries
275 WHERE ttrss_entries.id = ref_id AND
277 feed_id = '$feed_id' AND
279 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
282 $rows = db_affected_rows($result);
284 ccache_update($feed_id, $owner_uid);
287 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
291 } // function purge_feed
293 function feed_purge_interval($feed_id) {
295 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
296 WHERE id = '$feed_id'");
298 if (db_num_rows($result) == 1) {
299 $purge_interval = db_fetch_result($result, 0, "purge_interval");
300 $owner_uid = db_fetch_result($result, 0, "owner_uid");
302 if ($purge_interval == 0) $purge_interval = get_pref(
303 'PURGE_OLD_DAYS', $owner_uid);
305 return $purge_interval;
312 function purge_orphans($do_output = false) {
314 // purge orphaned posts in main content table
315 $result = db_query("DELETE FROM ttrss_entries WHERE
316 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
319 $rows = db_affected_rows($result);
320 _debug("Purged $rows orphaned posts.");
324 function get_feed_update_interval($feed_id) {
325 $result = db_query("SELECT owner_uid, update_interval FROM
326 ttrss_feeds WHERE id = '$feed_id'");
328 if (db_num_rows($result) == 1) {
329 $update_interval = db_fetch_result($result, 0, "update_interval");
330 $owner_uid = db_fetch_result($result, 0, "owner_uid");
332 if ($update_interval != 0) {
333 return $update_interval;
335 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
343 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0, $useragent = false) {
345 global $fetch_last_error;
346 global $fetch_last_error_code;
347 global $fetch_last_content_type;
348 global $fetch_curl_used;
350 $url = str_replace(' ', '%20', $url);
352 if (!defined('NO_CURL') && function_exists('curl_init')) {
354 $fetch_curl_used = true;
356 if (ini_get("safe_mode") ||
ini_get("open_basedir")) {
357 $new_url = geturl($url);
359 // geturl has already populated $fetch_last_error
362 $ch = curl_init($new_url);
364 $ch = curl_init($url);
367 if ($timestamp && !$post_query) {
368 curl_setopt($ch, CURLOPT_HTTPHEADER
,
369 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
372 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
373 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
374 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("safe_mode") && !ini_get("open_basedir"));
375 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
376 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
377 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
378 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
379 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
380 curl_setopt($ch, CURLOPT_USERAGENT
, $useragent ?
$useragent :
382 curl_setopt($ch, CURLOPT_ENCODING
, "");
383 curl_setopt($ch, CURLOPT_REFERER
, $url);
385 if (!ini_get("safe_mode") && !ini_get("open_basedir")) {
386 curl_setopt($ch, CURLOPT_COOKIEJAR
, "/dev/null");
389 if (defined('_CURL_HTTP_PROXY')) {
390 curl_setopt($ch, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
394 curl_setopt($ch, CURLOPT_POST
, true);
395 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
398 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
399 curl_setopt($ch, CURLOPT_SSLVERSION
, 3);
403 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
405 $contents = @curl_exec
($ch);
407 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
408 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
409 $contents = @curl_exec
($ch);
412 if ($contents === false) {
413 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
418 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
419 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
421 $fetch_last_error_code = $http_code;
423 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
424 if (curl_errno($ch) != 0) {
425 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
427 $fetch_last_error = "HTTP Code: $http_code";
438 $fetch_curl_used = false;
440 if ($login && $pass){
441 $url_parts = array();
443 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
445 $pass = urlencode($pass);
447 if ($url_parts[1] && $url_parts[2]) {
448 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
452 if (!$post_query && $timestamp) {
453 $context = stream_context_create(array(
456 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
462 $old_error = error_get_last();
464 $data = @file_get_contents
($url, false, $context);
466 $fetch_last_content_type = false; // reset if no type was sent from server
467 if (isset($http_response_header) && is_array($http_response_header)) {
468 foreach ($http_response_header as $h) {
469 if (substr(strtolower($h), 0, 13) == 'content-type:') {
470 $fetch_last_content_type = substr($h, 14);
471 // don't abort here b/c there might be more than one
472 // e.g. if we were being redirected -- last one is the right one
475 if (substr(strtolower($h), 0, 7) == 'http/1.') {
476 $fetch_last_error_code = (int) substr($h, 9, 3);
482 $error = error_get_last();
484 if ($error['message'] != $old_error['message']) {
485 $fetch_last_error = $error["message"];
487 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
496 * Try to determine the favicon URL for a feed.
497 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
498 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
500 * @param string $url A feed or page URL
502 * @return mixed The favicon URL, or false if none was found.
504 function get_favicon_url($url) {
506 $favicon_url = false;
508 if ($html = @fetch_file_contents
($url)) {
510 libxml_use_internal_errors(true);
512 $doc = new DOMDocument();
513 $doc->loadHTML($html);
514 $xpath = new DOMXPath($doc);
516 $base = $xpath->query('/html/head/base');
517 foreach ($base as $b) {
518 $url = $b->getAttribute("href");
522 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
523 if (count($entries) > 0) {
524 foreach ($entries as $entry) {
525 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
532 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
535 } // function get_favicon_url
537 function check_feed_favicon($site_url, $feed) {
538 # print "FAVICON [$site_url]: $favicon_url\n";
540 $icon_file = ICONS_DIR
. "/$feed.ico";
542 if (!file_exists($icon_file)) {
543 $favicon_url = get_favicon_url($site_url);
546 // Limiting to "image" type misses those served with text/plain
547 $contents = fetch_file_contents($favicon_url); // , "image");
550 // Crude image type matching.
551 // Patterns gleaned from the file(1) source code.
552 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
553 // 0 string \000\000\001\000 MS Windows icon resource
554 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
556 elseif (preg_match('/^GIF8/', $contents)) {
557 // 0 string GIF8 GIF image data
558 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
560 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
561 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
562 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
564 elseif (preg_match('/^\xff\xd8/', $contents)) {
565 // 0 beshort 0xffd8 JPEG image data
566 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
569 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
575 $fp = @fopen
($icon_file, "w");
578 fwrite($fp, $contents);
580 chmod($icon_file, 0644);
588 function print_select($id, $default, $values, $attributes = "") {
589 print "<select name=\"$id\" id=\"$id\" $attributes>";
590 foreach ($values as $v) {
592 $sel = "selected=\"1\"";
598 print "<option value=\"$v\" $sel>$v</option>";
603 function print_select_hash($id, $default, $values, $attributes = "") {
604 print "<select name=\"$id\" id='$id' $attributes>";
605 foreach (array_keys($values) as $v) {
607 $sel = 'selected="selected"';
613 print "<option $sel value=\"$v\">".$values[$v]."</option>";
619 function print_radio($id, $default, $true_is, $values, $attributes = "") {
620 foreach ($values as $v) {
627 if ($v == $true_is) {
628 $sel .= " value=\"1\"";
630 $sel .= " value=\"0\"";
633 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
634 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
639 function initialize_user_prefs($uid, $profile = false) {
641 $uid = db_escape_string($uid);
645 $profile_qpart = "AND profile IS NULL";
647 $profile_qpart = "AND profile = '$profile'";
650 if (get_schema_version() < 63) $profile_qpart = "";
654 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
656 $u_result = db_query("SELECT pref_name
657 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
659 $active_prefs = array();
661 while ($line = db_fetch_assoc($u_result)) {
662 array_push($active_prefs, $line["pref_name"]);
665 while ($line = db_fetch_assoc($result)) {
666 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
667 // print "adding " . $line["pref_name"] . "<br>";
669 $line["def_value"] = db_escape_string($line["def_value"]);
670 $line["pref_name"] = db_escape_string($line["pref_name"]);
672 if (get_schema_version() < 63) {
673 db_query("INSERT INTO ttrss_user_prefs
674 (owner_uid,pref_name,value) VALUES
675 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
678 db_query("INSERT INTO ttrss_user_prefs
679 (owner_uid,pref_name,value, profile) VALUES
680 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
690 function get_ssl_certificate_id() {
691 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
692 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
693 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
694 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
695 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
697 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
698 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
699 $_SERVER["SSL_CLIENT_V_START"] .
700 $_SERVER["SSL_CLIENT_V_END"] .
701 $_SERVER["SSL_CLIENT_S_DN"]);
706 function authenticate_user($login, $password, $check_only = false) {
708 if (!SINGLE_USER_MODE
) {
711 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
713 $user_id = (int) $plugin->authenticate($login, $password);
716 $_SESSION["auth_module"] = strtolower(get_class($plugin));
721 if ($user_id && !$check_only) {
724 $_SESSION["uid"] = $user_id;
725 $_SESSION["version"] = VERSION_STATIC
;
727 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
728 WHERE id = '$user_id'");
730 $_SESSION["name"] = db_fetch_result($result, 0, "login");
731 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
732 $_SESSION["csrf_token"] = uniqid(rand(), true);
734 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
737 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
738 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
739 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
741 $_SESSION["last_version_check"] = time();
743 initialize_user_prefs($_SESSION["uid"]);
752 $_SESSION["uid"] = 1;
753 $_SESSION["name"] = "admin";
754 $_SESSION["access_level"] = 10;
756 $_SESSION["hide_hello"] = true;
757 $_SESSION["hide_logout"] = true;
759 $_SESSION["auth_module"] = false;
761 if (!$_SESSION["csrf_token"]) {
762 $_SESSION["csrf_token"] = uniqid(rand(), true);
765 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
767 initialize_user_prefs($_SESSION["uid"]);
773 function make_password($length = 8) {
776 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
780 while ($i < $length) {
781 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
783 if (!strstr($password, $char)) {
791 // this is called after user is created to initialize default feeds, labels
794 // user preferences are checked on every login, not here
796 function initialize_user($uid) {
798 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
799 values ('$uid', 'Tiny Tiny RSS: New Releases',
800 'http://tt-rss.org/releases.rss')");
802 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
803 values ('$uid', 'Tiny Tiny RSS: Forum',
804 'http://tt-rss.org/forum/rss.php')");
807 function logout_user() {
809 if (isset($_COOKIE[session_name()])) {
810 setcookie(session_name(), '', time()-42000, '/');
814 function validate_csrf($csrf_token) {
815 return $csrf_token == $_SESSION['csrf_token'];
818 function load_user_plugins($owner_uid) {
819 if ($owner_uid && SCHEMA_VERSION
>= 100) {
820 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
822 PluginHost
::getInstance()->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
824 if (get_schema_version() > 100) {
825 PluginHost
::getInstance()->load_data();
830 function login_sequence() {
831 if (SINGLE_USER_MODE
) {
833 authenticate_user("admin", null);
835 load_user_plugins($_SESSION["uid"]);
837 if (!validate_session()) $_SESSION["uid"] = false;
839 if (!$_SESSION["uid"]) {
841 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
842 $_SESSION["ref_schema_version"] = get_schema_version(true);
844 authenticate_user(null, null, true);
847 if (!$_SESSION["uid"]) {
849 setcookie(session_name(), '', time()-42000, '/');
856 /* bump login timestamp */
857 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
859 $_SESSION["last_login_update"] = time();
862 if ($_SESSION["uid"]) {
864 load_user_plugins($_SESSION["uid"]);
868 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
869 $_SESSION["uid"] . " AND
870 (SELECT COUNT(id) FROM ttrss_feeds WHERE
871 ttrss_feeds.id = feed_id) = 0");
873 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
874 $_SESSION["uid"] . " AND
875 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
876 ttrss_feed_categories.id = feed_id) = 0");
883 function truncate_string($str, $max_len, $suffix = '…') {
884 if (mb_strlen($str, "utf-8") > $max_len) {
885 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
891 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
894 $source_tz = new DateTimeZone($source_tz);
895 } catch (Exception
$e) {
896 $source_tz = new DateTimeZone('UTC');
900 $dest_tz = new DateTimeZone($dest_tz);
901 } catch (Exception
$e) {
902 $dest_tz = new DateTimeZone('UTC');
905 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
906 return $dt->format('U') +
$dest_tz->getOffset($dt);
909 function make_local_datetime($timestamp, $long, $owner_uid = false,
910 $no_smart_dt = false) {
912 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
913 if (!$timestamp) $timestamp = '1970-01-01 0:00';
918 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
920 $timestamp = substr($timestamp, 0, 19);
922 # We store date in UTC internally
923 $dt = new DateTime($timestamp, $utc_tz);
925 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
927 if ($user_tz_string != 'Automatic') {
930 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
931 } catch (Exception
$e) {
935 $tz_offset = $user_tz->getOffset($dt);
937 $tz_offset = (int) -$_SESSION["clientTzOffset"];
940 $user_timestamp = $dt->format('U') +
$tz_offset;
943 return smart_date_time($user_timestamp,
944 $tz_offset, $owner_uid);
947 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
949 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
951 return date($format, $user_timestamp);
955 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false) {
956 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
958 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
959 return date("G:i", $timestamp);
960 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
961 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
962 return date($format, $timestamp);
964 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
965 return date($format, $timestamp);
969 function sql_bool_to_bool($s) {
970 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
977 function bool_to_sql_bool($s) {
985 // Session caching removed due to causing wrong redirects to upgrade
986 // script when get_schema_version() is called on an obsolete session
987 // created on a previous schema version.
988 function get_schema_version($nocache = false) {
989 global $schema_version;
991 if (!$schema_version && !$nocache) {
992 $result = db_query("SELECT schema_version FROM ttrss_version");
993 $version = db_fetch_result($result, 0, "schema_version");
994 $schema_version = $version;
997 return $schema_version;
1001 function sanity_check() {
1002 require_once 'errors.php';
1005 $schema_version = get_schema_version(true);
1007 if ($schema_version != SCHEMA_VERSION
) {
1011 if (DB_TYPE
== "mysql") {
1012 $result = db_query("SELECT true", false);
1013 if (db_num_rows($result) != 1) {
1018 if (db_escape_string("testTEST") != "testTEST") {
1022 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
1025 function file_is_locked($filename) {
1026 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
1027 if (function_exists('flock')) {
1028 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
1030 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1031 flock($fp, LOCK_UN
);
1041 return true; // consider the file always locked and skip the test
1048 function make_lockfile($filename) {
1049 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1051 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
1052 $stat_h = fstat($fp);
1053 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
1055 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
1056 if ($stat_h["ino"] != $stat_f["ino"] ||
1057 $stat_h["dev"] != $stat_f["dev"]) {
1063 if (function_exists('posix_getpid')) {
1064 fwrite($fp, posix_getpid() . "\n");
1072 function make_stampfile($filename) {
1073 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1075 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1076 fwrite($fp, time() . "\n");
1077 flock($fp, LOCK_UN
);
1085 function sql_random_function() {
1086 if (DB_TYPE
== "mysql") {
1093 function catchup_feed($feed, $cat_view, $owner_uid = false, $max_id = false, $mode = 'all') {
1095 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
1097 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
1099 // Todo: all this interval stuff needs some generic generator function
1101 $date_qpart = "false";
1105 if (DB_TYPE
== "pgsql") {
1106 $date_qpart = "date_entered < NOW() - INTERVAL '1 day' ";
1108 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) ";
1112 if (DB_TYPE
== "pgsql") {
1113 $date_qpart = "date_entered < NOW() - INTERVAL '1 week' ";
1115 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) ";
1119 if (DB_TYPE
== "pgsql") {
1120 $date_qpart = "date_entered < NOW() - INTERVAL '2 week' ";
1122 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) ";
1126 $date_qpart = "true";
1129 if (is_numeric($feed)) {
1135 $children = getChildCategories($feed, $owner_uid);
1136 array_push($children, $feed);
1138 $children = join(",", $children);
1140 $cat_qpart = "cat_id IN ($children)";
1142 $cat_qpart = "cat_id IS NULL";
1145 db_query("UPDATE ttrss_user_entries
1146 SET unread = false, last_read = NOW() WHERE ref_id IN
1148 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1149 AND owner_uid = $owner_uid AND unread = true AND feed_id IN
1150 (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart) as tmp)");
1152 } else if ($feed == -2) {
1154 db_query("UPDATE ttrss_user_entries
1155 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1156 FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart) > 0
1157 AND unread = true AND owner_uid = $owner_uid");
1160 } else if ($feed > 0) {
1162 db_query("UPDATE ttrss_user_entries
1163 SET unread = false, last_read = NOW() WHERE ref_id IN
1165 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1166 AND owner_uid = $owner_uid AND unread = true AND feed_id = $feed AND $date_qpart) as tmp)");
1168 } else if ($feed < 0 && $feed > LABEL_BASE_INDEX
) { // special, like starred
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 marked = true AND $date_qpart) as tmp)");
1179 db_query("UPDATE ttrss_user_entries
1180 SET unread = false, last_read = NOW() WHERE ref_id IN
1182 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1183 AND owner_uid = $owner_uid AND unread = true AND published = true AND $date_qpart) as tmp)");
1188 $intl = get_pref("FRESH_ARTICLE_MAX_AGE");
1190 if (DB_TYPE
== "pgsql") {
1191 $match_part = "date_entered > NOW() - INTERVAL '$intl hour' ";
1193 $match_part = "date_entered > DATE_SUB(NOW(),
1194 INTERVAL $intl HOUR) ";
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 AND $match_part) as tmp)");
1205 db_query("UPDATE ttrss_user_entries
1206 SET unread = false, last_read = NOW() WHERE ref_id IN
1208 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1209 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1212 } else if ($feed < LABEL_BASE_INDEX
) { // label
1214 $label_id = feed_to_label_id($feed);
1216 db_query("UPDATE ttrss_user_entries
1217 SET unread = false, last_read = NOW() WHERE ref_id IN
1219 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id
1220 AND label_id = '$label_id' AND ref_id = article_id
1221 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1225 ccache_update($feed, $owner_uid, $cat_view);
1228 db_query("UPDATE ttrss_user_entries
1229 SET unread = false, last_read = NOW() WHERE ref_id IN
1231 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id
1232 AND post_int_id = int_id AND tag_name = '$feed'
1233 AND ttrss_user_entries.owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1238 function getAllCounters() {
1239 $data = getGlobalCounters();
1241 $data = array_merge($data, getVirtCounters());
1242 $data = array_merge($data, getLabelCounters());
1243 $data = array_merge($data, getFeedCounters());
1244 $data = array_merge($data, getCategoryCounters());
1249 function getCategoryTitle($cat_id) {
1251 if ($cat_id == -1) {
1252 return __("Special");
1253 } else if ($cat_id == -2) {
1254 return __("Labels");
1257 $result = db_query("SELECT title FROM ttrss_feed_categories WHERE
1260 if (db_num_rows($result) == 1) {
1261 return db_fetch_result($result, 0, "title");
1263 return __("Uncategorized");
1269 function getCategoryCounters() {
1272 /* Labels category */
1274 $cv = array("id" => -2, "kind" => "cat",
1275 "counter" => getCategoryUnread(-2));
1277 array_push($ret_arr, $cv);
1279 $result = db_query("SELECT id AS cat_id, value AS unread,
1280 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1281 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1282 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1283 WHERE ttrss_cat_counters_cache.feed_id = id AND
1284 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1285 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1287 while ($line = db_fetch_assoc($result)) {
1288 $line["cat_id"] = (int) $line["cat_id"];
1290 if ($line["num_children"] > 0) {
1291 $child_counter = getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
1296 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1297 "counter" => $line["unread"] +
$child_counter);
1299 array_push($ret_arr, $cv);
1302 /* Special case: NULL category doesn't actually exist in the DB */
1304 $cv = array("id" => 0, "kind" => "cat",
1305 "counter" => (int) ccache_find(0, $_SESSION["uid"], true));
1307 array_push($ret_arr, $cv);
1312 // only accepts real cats (>= 0)
1313 function getCategoryChildrenUnread($cat, $owner_uid = false) {
1314 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1316 $result = db_query("SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1317 AND owner_uid = $owner_uid");
1321 while ($line = db_fetch_assoc($result)) {
1322 $unread +
= getCategoryUnread($line["id"], $owner_uid);
1323 $unread +
= getCategoryChildrenUnread($line["id"], $owner_uid);
1329 function getCategoryUnread($cat, $owner_uid = false) {
1331 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1336 $cat_query = "cat_id = '$cat'";
1338 $cat_query = "cat_id IS NULL";
1341 $result = db_query("SELECT id FROM ttrss_feeds WHERE $cat_query
1342 AND owner_uid = " . $owner_uid);
1344 $cat_feeds = array();
1345 while ($line = db_fetch_assoc($result)) {
1346 array_push($cat_feeds, "feed_id = " . $line["id"]);
1349 if (count($cat_feeds) == 0) return 0;
1351 $match_part = implode(" OR ", $cat_feeds);
1353 $result = db_query("SELECT COUNT(int_id) AS unread
1354 FROM ttrss_user_entries
1355 WHERE unread = true AND ($match_part)
1356 AND owner_uid = " . $owner_uid);
1360 # this needs to be rewritten
1361 while ($line = db_fetch_assoc($result)) {
1362 $unread +
= $line["unread"];
1366 } else if ($cat == -1) {
1367 return getFeedUnread(-1) +
getFeedUnread(-2) +
getFeedUnread(-3) +
getFeedUnread(0);
1368 } else if ($cat == -2) {
1370 $result = db_query("
1371 SELECT COUNT(unread) AS unread FROM
1372 ttrss_user_entries, ttrss_user_labels2
1373 WHERE article_id = ref_id AND unread = true
1374 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1376 $unread = db_fetch_result($result, 0, "unread");
1383 function getFeedUnread($feed, $is_cat = false) {
1384 return getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1387 function getLabelUnread($label_id, $owner_uid = false) {
1388 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1390 $result = db_query("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1391 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1393 if (db_num_rows($result) != 0) {
1394 return db_fetch_result($result, 0, "unread");
1400 function getFeedArticles($feed, $is_cat = false, $unread_only = false,
1401 $owner_uid = false) {
1403 $n_feed = (int) $feed;
1404 $need_entries = false;
1406 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1409 $unread_qpart = "unread = true";
1411 $unread_qpart = "true";
1415 return getCategoryUnread($n_feed, $owner_uid);
1416 } else if ($n_feed == -6) {
1418 } else if ($feed != "0" && $n_feed == 0) {
1420 $feed = db_escape_string($feed);
1422 $result = db_query("SELECT SUM((SELECT COUNT(int_id)
1423 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1424 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1425 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1426 return db_fetch_result($result, 0, "count");
1428 } else if ($n_feed == -1) {
1429 $match_part = "marked = true";
1430 } else if ($n_feed == -2) {
1431 $match_part = "published = true";
1432 } else if ($n_feed == -3) {
1433 $match_part = "unread = true AND score >= 0";
1435 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
1437 if (DB_TYPE
== "pgsql") {
1438 $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
1440 $match_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1443 $need_entries = true;
1445 } else if ($n_feed == -4) {
1446 $match_part = "true";
1447 } else if ($n_feed >= 0) {
1450 $match_part = "feed_id = '$n_feed'";
1452 $match_part = "feed_id IS NULL";
1455 } else if ($feed < LABEL_BASE_INDEX
) {
1457 $label_id = feed_to_label_id($feed);
1459 return getLabelUnread($label_id, $owner_uid);
1465 if ($need_entries) {
1466 $from_qpart = "ttrss_user_entries,ttrss_entries";
1467 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1469 $from_qpart = "ttrss_user_entries";
1473 $query = "SELECT count(int_id) AS unread
1474 FROM $from_qpart WHERE
1475 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1477 //echo "[$feed/$query]\n";
1479 $result = db_query($query);
1483 $result = db_query("SELECT COUNT(post_int_id) AS unread
1484 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1485 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1486 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1489 $unread = db_fetch_result($result, 0, "unread");
1494 function getGlobalUnread($user_id = false) {
1497 $user_id = $_SESSION["uid"];
1500 $result = db_query("SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1501 WHERE owner_uid = '$user_id' AND feed_id > 0");
1503 $c_id = db_fetch_result($result, 0, "c_id");
1508 function getGlobalCounters($global_unread = -1) {
1511 if ($global_unread == -1) {
1512 $global_unread = getGlobalUnread();
1515 $cv = array("id" => "global-unread",
1516 "counter" => (int) $global_unread);
1518 array_push($ret_arr, $cv);
1520 $result = db_query("SELECT COUNT(id) AS fn FROM
1521 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1523 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1525 $cv = array("id" => "subscribed-feeds",
1526 "counter" => (int) $subscribed_feeds);
1528 array_push($ret_arr, $cv);
1533 function getVirtCounters() {
1537 for ($i = 0; $i >= -4; $i--) {
1539 $count = getFeedUnread($i);
1541 if ($i == 0 ||
$i == -1 ||
$i == -2)
1542 $auxctr = getFeedArticles($i, false);
1546 $cv = array("id" => $i,
1547 "counter" => (int) $count,
1548 "auxcounter" => $auxctr);
1550 // if (get_pref('EXTENDED_FEEDLIST'))
1551 // $cv["xmsg"] = getFeedArticles($i)." ".__("total");
1553 array_push($ret_arr, $cv);
1556 $feeds = PluginHost
::getInstance()->get_feeds(-1);
1558 if (is_array($feeds)) {
1559 foreach ($feeds as $feed) {
1560 $cv = array("id" => PluginHost
::pfeed_to_feed_id($feed['id']),
1561 "counter" => $feed['sender']->get_unread($feed['id']));
1563 if (method_exists($feed['sender'], 'get_total'))
1564 $cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
1566 array_push($ret_arr, $cv);
1573 function getLabelCounters($descriptions = false) {
1577 $owner_uid = $_SESSION["uid"];
1579 $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total
1580 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1581 (ttrss_labels2.id = label_id)
1582 LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id
1583 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1584 ttrss_labels2.caption");
1586 while ($line = db_fetch_assoc($result)) {
1588 $id = label_to_feed_id($line["id"]);
1590 $cv = array("id" => $id,
1591 "counter" => (int) $line["unread"],
1592 "auxcounter" => (int) $line["total"]);
1595 $cv["description"] = $line["caption"];
1597 array_push($ret_arr, $cv);
1603 function getFeedCounters($active_feed = false) {
1607 $query = "SELECT ttrss_feeds.id,
1609 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1610 last_error, value AS count
1611 FROM ttrss_feeds, ttrss_counters_cache
1612 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1613 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1614 AND ttrss_counters_cache.feed_id = id";
1616 $result = db_query($query);
1617 $fctrs_modified = false;
1619 while ($line = db_fetch_assoc($result)) {
1622 $count = $line["count"];
1623 $last_error = htmlspecialchars($line["last_error"]);
1625 $last_updated = make_local_datetime($line['last_updated'], false);
1627 $has_img = feed_has_icon($id);
1629 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1632 $cv = array("id" => $id,
1633 "updated" => $last_updated,
1634 "counter" => (int) $count,
1635 "has_img" => (int) $has_img);
1638 $cv["error"] = $last_error;
1640 // if (get_pref('EXTENDED_FEEDLIST'))
1641 // $cv["xmsg"] = getFeedArticles($id)." ".__("total");
1643 if ($active_feed && $id == $active_feed)
1644 $cv["title"] = truncate_string($line["title"], 30);
1646 array_push($ret_arr, $cv);
1653 function get_pgsql_version() {
1654 $result = db_query("SELECT version() AS version");
1655 $version = explode(" ", db_fetch_result($result, 0, "version"));
1660 * @return array (code => Status code, message => error message if available)
1662 * 0 - OK, Feed already exists
1663 * 1 - OK, Feed added
1665 * 3 - URL content is HTML, no feeds available
1666 * 4 - URL content is HTML which contains multiple feeds.
1667 * Here you should call extractfeedurls in rpc-backend
1668 * to get all possible feeds.
1669 * 5 - Couldn't download the URL content.
1670 * 6 - Content is an invalid XML.
1672 function subscribe_to_feed($url, $cat_id = 0,
1673 $auth_login = '', $auth_pass = '') {
1675 global $fetch_last_error;
1677 require_once "include/rssfuncs.php";
1679 $url = fix_url($url);
1681 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1683 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1686 return array("code" => 5, "message" => $fetch_last_error);
1689 if (is_html($contents)) {
1690 $feedUrls = get_feeds_from_html($url, $contents);
1692 if (count($feedUrls) == 0) {
1693 return array("code" => 3);
1694 } else if (count($feedUrls) > 1) {
1695 return array("code" => 4, "feeds" => $feedUrls);
1697 //use feed url as new URL
1698 $url = key($feedUrls);
1701 /* libxml_use_internal_errors(true);
1702 $doc = new DOMDocument();
1703 $doc->loadXML($contents);
1704 $error = libxml_get_last_error();
1705 libxml_clear_errors();
1708 $error_message = format_libxml_error($error);
1710 return array("code" => 6, "message" => $error_message);
1713 if ($cat_id == "0" ||
!$cat_id) {
1714 $cat_qpart = "NULL";
1716 $cat_qpart = "'$cat_id'";
1720 "SELECT id FROM ttrss_feeds
1721 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1723 if (strlen(FEED_CRYPT_KEY
) > 0) {
1724 require_once "crypt.php";
1725 $auth_pass = substr(encrypt_string($auth_pass), 0, 250);
1726 $auth_pass_encrypted = 'true';
1728 $auth_pass_encrypted = 'false';
1731 $auth_pass = db_escape_string($auth_pass);
1733 if (db_num_rows($result) == 0) {
1735 "INSERT INTO ttrss_feeds
1736 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted)
1737 VALUES ('".$_SESSION["uid"]."', '$url',
1738 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, $auth_pass_encrypted)");
1741 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1742 AND owner_uid = " . $_SESSION["uid"]);
1744 $feed_id = db_fetch_result($result, 0, "id");
1747 update_rss_feed($feed_id, true);
1750 return array("code" => 1);
1752 return array("code" => 0);
1756 function print_feed_select($id, $default_id = "",
1757 $attributes = "", $include_all_feeds = true,
1758 $root_id = false, $nest_level = 0) {
1761 print "<select id=\"$id\" name=\"$id\" $attributes>";
1762 if ($include_all_feeds) {
1763 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1764 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1768 if (get_pref('ENABLE_FEED_CATS')) {
1771 $parent_qpart = "parent_cat = '$root_id'";
1773 $parent_qpart = "parent_cat IS NULL";
1775 $result = db_query("SELECT id,title,
1776 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1777 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1778 FROM ttrss_feed_categories
1779 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1781 while ($line = db_fetch_assoc($result)) {
1783 for ($i = 0; $i < $nest_level; $i++
)
1784 $line["title"] = " - " . $line["title"];
1786 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1788 printf("<option $is_selected value='CAT:%d'>%s</option>",
1789 $line["id"], htmlspecialchars($line["title"]));
1791 if ($line["num_children"] > 0)
1792 print_feed_select($id, $default_id, $attributes,
1793 $include_all_feeds, $line["id"], $nest_level+
1);
1795 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1796 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1798 while ($fline = db_fetch_assoc($feed_result)) {
1799 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1801 $fline["title"] = " + " . $fline["title"];
1803 for ($i = 0; $i < $nest_level; $i++
)
1804 $fline["title"] = " - " . $fline["title"];
1806 printf("<option $is_selected value='%d'>%s</option>",
1807 $fline["id"], htmlspecialchars($fline["title"]));
1812 $default_is_cat = ($default_id == "CAT:0");
1813 $is_selected = $default_is_cat ?
"selected=\"1\"" : "";
1815 printf("<option $is_selected value='CAT:0'>%s</option>",
1816 __("Uncategorized"));
1818 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1819 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1821 while ($fline = db_fetch_assoc($feed_result)) {
1822 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1824 $fline["title"] = " + " . $fline["title"];
1826 for ($i = 0; $i < $nest_level; $i++
)
1827 $fline["title"] = " - " . $fline["title"];
1829 printf("<option $is_selected value='%d'>%s</option>",
1830 $fline["id"], htmlspecialchars($fline["title"]));
1835 $result = db_query("SELECT id,title FROM ttrss_feeds
1836 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1838 while ($line = db_fetch_assoc($result)) {
1840 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1842 printf("<option $is_selected value='%d'>%s</option>",
1843 $line["id"], htmlspecialchars($line["title"]));
1852 function print_feed_cat_select($id, $default_id,
1853 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1856 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1860 $parent_qpart = "parent_cat = '$root_id'";
1862 $parent_qpart = "parent_cat IS NULL";
1864 $result = db_query("SELECT id,title,
1865 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1866 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1867 FROM ttrss_feed_categories
1868 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1870 while ($line = db_fetch_assoc($result)) {
1871 if ($line["id"] == $default_id) {
1872 $is_selected = "selected=\"1\"";
1877 for ($i = 0; $i < $nest_level; $i++
)
1878 $line["title"] = " - " . $line["title"];
1881 printf("<option $is_selected value='%d'>%s</option>",
1882 $line["id"], htmlspecialchars($line["title"]));
1884 if ($line["num_children"] > 0)
1885 print_feed_cat_select($id, $default_id, $attributes,
1886 $include_all_cats, $line["id"], $nest_level+
1);
1890 if ($include_all_cats) {
1891 if (db_num_rows($result) > 0) {
1892 print "<option disabled=\"1\">--------</option>";
1895 if ($default_id == 0) {
1896 $is_selected = "selected=\"1\"";
1901 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1907 function checkbox_to_sql_bool($val) {
1908 return ($val == "on") ?
"true" : "false";
1911 function getFeedCatTitle($id) {
1913 return __("Special");
1914 } else if ($id < LABEL_BASE_INDEX
) {
1915 return __("Labels");
1916 } else if ($id > 0) {
1917 $result = db_query("SELECT ttrss_feed_categories.title
1918 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1919 cat_id = ttrss_feed_categories.id");
1920 if (db_num_rows($result) == 1) {
1921 return db_fetch_result($result, 0, "title");
1923 return __("Uncategorized");
1926 return "getFeedCatTitle($id) failed";
1931 function getFeedIcon($id) {
1934 return "images/archive.png";
1937 return "images/star.png";
1940 return "images/feed.png";
1943 return "images/fresh.png";
1946 return "images/folder.png";
1949 return "images/time.png";
1952 if ($id < LABEL_BASE_INDEX
) {
1953 return "images/label.png";
1955 if (file_exists(ICONS_DIR
. "/$id.ico"))
1956 return ICONS_URL
. "/$id.ico";
1964 function getFeedTitle($id, $cat = false) {
1966 return getCategoryTitle($id);
1967 } else if ($id == -1) {
1968 return __("Starred articles");
1969 } else if ($id == -2) {
1970 return __("Published articles");
1971 } else if ($id == -3) {
1972 return __("Fresh articles");
1973 } else if ($id == -4) {
1974 return __("All articles");
1975 } else if ($id === 0 ||
$id === "0") {
1976 return __("Archived articles");
1977 } else if ($id == -6) {
1978 return __("Recently read");
1979 } else if ($id < LABEL_BASE_INDEX
) {
1980 $label_id = feed_to_label_id($id);
1981 $result = db_query("SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1982 if (db_num_rows($result) == 1) {
1983 return db_fetch_result($result, 0, "caption");
1985 return "Unknown label ($label_id)";
1988 } else if (is_numeric($id) && $id > 0) {
1989 $result = db_query("SELECT title FROM ttrss_feeds WHERE id = '$id'");
1990 if (db_num_rows($result) == 1) {
1991 return db_fetch_result($result, 0, "title");
1993 return "Unknown feed ($id)";
2000 function make_init_params() {
2003 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
2004 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
2005 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
2006 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
2008 $params[strtolower($param)] = (int) get_pref($param);
2011 $params["icons_url"] = ICONS_URL
;
2012 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
2013 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
2014 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
2015 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
2016 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
2017 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
2019 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2020 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2022 $max_feed_id = db_fetch_result($result, 0, "mid");
2023 $num_feeds = db_fetch_result($result, 0, "nf");
2025 $params["max_feed_id"] = (int) $max_feed_id;
2026 $params["num_feeds"] = (int) $num_feeds;
2028 $params["hotkeys"] = get_hotkeys_map();
2030 $params["csrf_token"] = $_SESSION["csrf_token"];
2031 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
2033 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
2038 function get_hotkeys_info() {
2040 __("Navigation") => array(
2041 "next_feed" => __("Open next feed"),
2042 "prev_feed" => __("Open previous feed"),
2043 "next_article" => __("Open next article"),
2044 "prev_article" => __("Open previous article"),
2045 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
2046 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
2047 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
2048 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
2049 "search_dialog" => __("Show search dialog")),
2050 __("Article") => array(
2051 "toggle_mark" => __("Toggle starred"),
2052 "toggle_publ" => __("Toggle published"),
2053 "toggle_unread" => __("Toggle unread"),
2054 "edit_tags" => __("Edit tags"),
2055 "dismiss_selected" => __("Dismiss selected"),
2056 "dismiss_read" => __("Dismiss read"),
2057 "open_in_new_window" => __("Open in new window"),
2058 "catchup_below" => __("Mark below as read"),
2059 "catchup_above" => __("Mark above as read"),
2060 "article_scroll_down" => __("Scroll down"),
2061 "article_scroll_up" => __("Scroll up"),
2062 "select_article_cursor" => __("Select article under cursor"),
2063 "email_article" => __("Email article"),
2064 "close_article" => __("Close/collapse article"),
2065 "toggle_expand" => __("Toggle article expansion (combined mode)"),
2066 "toggle_widescreen" => __("Toggle widescreen mode"),
2067 "toggle_embed_original" => __("Toggle embed original")),
2068 __("Article selection") => array(
2069 "select_all" => __("Select all articles"),
2070 "select_unread" => __("Select unread"),
2071 "select_marked" => __("Select starred"),
2072 "select_published" => __("Select published"),
2073 "select_invert" => __("Invert selection"),
2074 "select_none" => __("Deselect everything")),
2075 __("Feed") => array(
2076 "feed_refresh" => __("Refresh current feed"),
2077 "feed_unhide_read" => __("Un/hide read feeds"),
2078 "feed_subscribe" => __("Subscribe to feed"),
2079 "feed_edit" => __("Edit feed"),
2080 "feed_catchup" => __("Mark as read"),
2081 "feed_reverse" => __("Reverse headlines"),
2082 "feed_debug_update" => __("Debug feed update"),
2083 "catchup_all" => __("Mark all feeds as read"),
2084 "cat_toggle_collapse" => __("Un/collapse current category"),
2085 "toggle_combined_mode" => __("Toggle combined mode"),
2086 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
2087 __("Go to") => array(
2088 "goto_all" => __("All articles"),
2089 "goto_fresh" => __("Fresh"),
2090 "goto_marked" => __("Starred"),
2091 "goto_published" => __("Published"),
2092 "goto_tagcloud" => __("Tag cloud"),
2093 "goto_prefs" => __("Preferences")),
2094 __("Other") => array(
2095 "create_label" => __("Create label"),
2096 "create_filter" => __("Create filter"),
2097 "collapse_sidebar" => __("Un/collapse sidebar"),
2098 "help_dialog" => __("Show help dialog"))
2101 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
2102 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
2108 function get_hotkeys_map() {
2110 // "navigation" => array(
2113 "n" => "next_article",
2114 "p" => "prev_article",
2115 "(38)|up" => "prev_article",
2116 "(40)|down" => "next_article",
2117 // "^(38)|Ctrl-up" => "prev_article_noscroll",
2118 // "^(40)|Ctrl-down" => "next_article_noscroll",
2119 "(191)|/" => "search_dialog",
2120 // "article" => array(
2121 "s" => "toggle_mark",
2122 "*s" => "toggle_publ",
2123 "u" => "toggle_unread",
2124 "*t" => "edit_tags",
2125 "*d" => "dismiss_selected",
2126 "*x" => "dismiss_read",
2127 "o" => "open_in_new_window",
2128 "c p" => "catchup_below",
2129 "c n" => "catchup_above",
2130 "*n" => "article_scroll_down",
2131 "*p" => "article_scroll_up",
2132 "*(38)|Shift+up" => "article_scroll_up",
2133 "*(40)|Shift+down" => "article_scroll_down",
2134 "a *w" => "toggle_widescreen",
2135 "a e" => "toggle_embed_original",
2136 "e" => "email_article",
2137 "a q" => "close_article",
2138 // "article_selection" => array(
2139 "a a" => "select_all",
2140 "a u" => "select_unread",
2141 "a *u" => "select_marked",
2142 "a p" => "select_published",
2143 "a i" => "select_invert",
2144 "a n" => "select_none",
2146 "f r" => "feed_refresh",
2147 "f a" => "feed_unhide_read",
2148 "f s" => "feed_subscribe",
2149 "f e" => "feed_edit",
2150 "f q" => "feed_catchup",
2151 "f x" => "feed_reverse",
2152 "f *d" => "feed_debug_update",
2153 "f *c" => "toggle_combined_mode",
2154 "f c" => "toggle_cdm_expanded",
2155 "*q" => "catchup_all",
2156 "x" => "cat_toggle_collapse",
2158 "g a" => "goto_all",
2159 "g f" => "goto_fresh",
2160 "g s" => "goto_marked",
2161 "g p" => "goto_published",
2162 "g t" => "goto_tagcloud",
2163 "g *p" => "goto_prefs",
2164 // "other" => array(
2165 "(9)|Tab" => "select_article_cursor", // tab
2166 "c l" => "create_label",
2167 "c f" => "create_filter",
2168 "c s" => "collapse_sidebar",
2169 "^(191)|Ctrl+/" => "help_dialog",
2172 if (get_pref('COMBINED_DISPLAY_MODE')) {
2173 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2174 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2177 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
2178 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2181 $prefixes = array();
2183 foreach (array_keys($hotkeys) as $hotkey) {
2184 $pair = explode(" ", $hotkey, 2);
2186 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2187 array_push($prefixes, $pair[0]);
2191 return array($prefixes, $hotkeys);
2194 function make_runtime_info() {
2197 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2198 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2200 $max_feed_id = db_fetch_result($result, 0, "mid");
2201 $num_feeds = db_fetch_result($result, 0, "nf");
2203 $data["max_feed_id"] = (int) $max_feed_id;
2204 $data["num_feeds"] = (int) $num_feeds;
2206 $data['last_article_id'] = getLastArticleId();
2207 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
2209 $data['dep_ts'] = calculate_dep_timestamp();
2210 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
2212 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2214 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2216 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2218 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2221 $stamp_delta = time() - $stamp;
2223 if ($stamp_delta > 1800) {
2227 $_SESSION["daemon_stamp_check"] = time();
2230 $data['daemon_stamp_ok'] = $stamp_check;
2232 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2234 $data['daemon_stamp'] = $stamp_fmt;
2239 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2240 $new_version_details = @check_for_update
();
2242 $data['new_version_available'] = (int) ($new_version_details != false);
2244 $_SESSION["last_version_check"] = time();
2245 $_SESSION["version_data"] = $new_version_details;
2251 function search_to_sql($search) {
2253 $search_query_part = "";
2255 $keywords = str_getcsv($search, " ");
2256 $query_keywords = array();
2257 $search_words = array();
2259 foreach ($keywords as $k) {
2260 if (strpos($k, "-") === 0) {
2267 $commandpair = explode(":", mb_strtolower($k), 2);
2269 switch ($commandpair[0]) {
2271 if ($commandpair[1]) {
2272 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
2273 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2275 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2276 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2277 array_push($search_words, $k);
2281 if ($commandpair[1]) {
2282 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
2283 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2285 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2286 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2287 array_push($search_words, $k);
2291 if ($commandpair[1]) {
2292 if ($commandpair[1] == "true")
2293 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2294 else if ($commandpair[1] == "false")
2295 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2297 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
2298 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2300 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2301 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2302 if (!$not) array_push($search_words, $k);
2307 if ($commandpair[1]) {
2308 if ($commandpair[1] == "true")
2309 array_push($query_keywords, "($not (marked = true))");
2311 array_push($query_keywords, "($not (marked = false))");
2313 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2314 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2315 if (!$not) array_push($search_words, $k);
2319 if ($commandpair[1]) {
2320 if ($commandpair[1] == "true")
2321 array_push($query_keywords, "($not (published = true))");
2323 array_push($query_keywords, "($not (published = false))");
2326 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2327 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2328 if (!$not) array_push($search_words, $k);
2332 if (strpos($k, "@") === 0) {
2334 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
2335 $orig_ts = strtotime(substr($k, 1));
2336 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2338 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2340 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2342 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2343 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2345 if (!$not) array_push($search_words, $k);
2350 $search_query_part = implode("AND", $query_keywords);
2352 return array($search_query_part, $search_words);
2355 function getParentCategories($cat, $owner_uid) {
2358 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
2359 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2361 while ($line = db_fetch_assoc($result)) {
2362 array_push($rv, $line["parent_cat"]);
2363 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
2369 function getChildCategories($cat, $owner_uid) {
2372 $result = db_query("SELECT id FROM ttrss_feed_categories
2373 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2375 while ($line = db_fetch_assoc($result)) {
2376 array_push($rv, $line["id"]);
2377 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
2383 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) {
2385 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2387 $ext_tables_part = "";
2388 $search_words = array();
2392 if (SPHINX_ENABLED
) {
2393 $ids = join(",", @sphinx_search
($search, 0, 500));
2396 $search_query_part = "ref_id IN ($ids) AND ";
2398 $search_query_part = "ref_id = -1 AND ";
2401 list($search_query_part, $search_words) = search_to_sql($search);
2402 $search_query_part .= " AND ";
2406 $search_query_part = "";
2411 if (DB_TYPE
== "pgsql") {
2412 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2414 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2417 $override_order = "updated DESC";
2419 $filter_query_part = filter_to_sql($filter, $owner_uid);
2421 // Try to check if SQL regexp implementation chokes on a valid regexp
2424 $result = db_query("SELECT true AS true_val FROM ttrss_entries,
2425 ttrss_user_entries, ttrss_feeds
2426 WHERE $filter_query_part LIMIT 1", false);
2429 $test = db_fetch_result($result, 0, "true_val");
2432 $filter_query_part = "false AND";
2434 $filter_query_part .= " AND";
2437 $filter_query_part = "false AND";
2441 $filter_query_part = "";
2445 $since_id_part = "ttrss_entries.id > $since_id AND ";
2447 $since_id_part = "";
2450 $view_query_part = "";
2452 if ($view_mode == "adaptive") {
2454 $view_query_part = " ";
2455 } else if ($feed != -1) {
2457 $unread = getFeedUnread($feed, $cat_view);
2459 if ($cat_view && $feed > 0 && $include_children)
2460 $unread +
= getCategoryChildrenUnread($feed);
2463 $view_query_part = " unread = true AND ";
2468 if ($view_mode == "marked") {
2469 $view_query_part = " marked = true AND ";
2472 if ($view_mode == "has_note") {
2473 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
2476 if ($view_mode == "published") {
2477 $view_query_part = " published = true AND ";
2480 if ($view_mode == "unread" && $feed != -6) {
2481 $view_query_part = " unread = true AND ";
2485 $limit_query_part = "LIMIT " . $limit;
2488 $allow_archived = false;
2490 $vfeed_query_part = "";
2492 // override query strategy and enable feed display when searching globally
2493 if ($search && $search_mode == "all_feeds") {
2494 $query_strategy_part = "true";
2495 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2497 } else if (!is_numeric($feed)) {
2498 $query_strategy_part = "true";
2499 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2500 id = feed_id) as feed_title,";
2501 } else if ($search && $search_mode == "this_cat") {
2502 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2505 if ($include_children) {
2506 $subcats = getChildCategories($feed, $owner_uid);
2507 array_push($subcats, $feed);
2508 $cats_qpart = join(",", $subcats);
2510 $cats_qpart = $feed;
2513 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2516 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2519 } else if ($feed > 0) {
2524 if ($include_children) {
2526 $subcats = getChildCategories($feed, $owner_uid);
2528 array_push($subcats, $feed);
2529 $query_strategy_part = "cat_id IN (".
2530 implode(",", $subcats).")";
2533 $query_strategy_part = "cat_id = '$feed'";
2537 $query_strategy_part = "cat_id IS NULL";
2540 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2543 $query_strategy_part = "feed_id = '$feed'";
2545 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2546 $query_strategy_part = "feed_id IS NULL";
2547 $allow_archived = true;
2548 } else if ($feed == 0 && $cat_view) { // uncategorized
2549 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2550 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2551 } else if ($feed == -1) { // starred virtual feed
2552 $query_strategy_part = "marked = true";
2553 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2554 $allow_archived = true;
2556 if (!$override_order) {
2557 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
2560 } else if ($feed == -2) { // published virtual feed OR labels category
2563 $query_strategy_part = "published = true";
2564 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2565 $allow_archived = true;
2567 if (!$override_order) {
2568 $override_order = "last_published DESC, date_entered DESC, updated DESC";
2572 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2574 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2576 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2577 ttrss_user_labels2.article_id = ref_id";
2580 } else if ($feed == -6) { // recently read
2581 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2582 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2583 $allow_archived = true;
2585 if (!$override_order) $override_order = "last_read DESC";
2587 /* } else if ($feed == -7) { // shared
2588 $query_strategy_part = "uuid != ''";
2589 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2590 $allow_archived = true; */
2591 } else if ($feed == -3) { // fresh virtual feed
2592 $query_strategy_part = "unread = true AND score >= 0";
2594 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
2596 if (DB_TYPE
== "pgsql") {
2597 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
2599 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2602 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2603 } else if ($feed == -4) { // all articles virtual feed
2604 $allow_archived = true;
2605 $query_strategy_part = "true";
2606 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2607 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
2608 $label_id = feed_to_label_id($feed);
2610 $query_strategy_part = "label_id = '$label_id' AND
2611 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2612 ttrss_user_labels2.article_id = ref_id";
2614 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2615 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2616 $allow_archived = true;
2619 $query_strategy_part = "true";
2622 $order_by = "score DESC, date_entered DESC, updated DESC";
2624 if ($view_mode == "unread_first") {
2625 $order_by = "unread DESC, $order_by";
2628 if ($override_order) {
2629 $order_by = $override_order;
2632 if ($override_strategy) {
2633 $query_strategy_part = $override_strategy;
2636 if ($override_vfeed) {
2637 $vfeed_query_part = $override_vfeed;
2643 $feed_title = T_sprintf("Search results: %s", $search);
2646 $feed_title = getCategoryTitle($feed);
2648 if (is_numeric($feed) && $feed > 0) {
2649 $result = db_query("SELECT title,site_url,last_error,last_updated
2650 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2652 $feed_title = db_fetch_result($result, 0, "title");
2653 $feed_site_url = db_fetch_result($result, 0, "site_url");
2654 $last_error = db_fetch_result($result, 0, "last_error");
2655 $last_updated = db_fetch_result($result, 0, "last_updated");
2657 $feed_title = getFeedTitle($feed);
2663 $content_query_part = "content, ";
2666 if (is_numeric($feed)) {
2669 $feed_kind = "Feeds";
2671 $feed_kind = "Labels";
2674 if ($limit_query_part) {
2675 $offset_query_part = "OFFSET $offset";
2678 // proper override_order applied above
2679 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
2680 if (!$override_order) {
2681 $order_by = "ttrss_feeds.title, $order_by";
2683 $order_by = "ttrss_feeds.title, $override_order";
2687 if (!$allow_archived) {
2688 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2689 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2692 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2693 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2696 if ($vfeed_query_part)
2697 $vfeed_query_part .= "favicon_avg_color,";
2699 $query = "SELECT DISTINCT
2702 ttrss_entries.id,ttrss_entries.title,
2706 always_display_enclosures,
2715 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2716 last_marked, last_published,
2724 ttrss_user_entries.ref_id = ttrss_entries.id AND
2725 ttrss_user_entries.owner_uid = '$owner_uid' AND
2730 $query_strategy_part ORDER BY $order_by
2731 $limit_query_part $offset_query_part";
2733 if ($_REQUEST["debug"]) print $query;
2735 $result = db_query($query);
2740 $select_qpart = "SELECT DISTINCT " .
2744 "ttrss_entries.id as id," .
2759 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2760 "last_marked, last_published, " .
2763 $content_query_part .
2766 $feed_kind = "Tags";
2767 $all_tags = explode(",", $feed);
2768 if ($search_mode == 'any') {
2769 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2770 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2771 $where_qpart = " WHERE " .
2772 "ref_id = ttrss_entries.id AND " .
2773 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2774 "post_int_id = int_id AND $tag_sql AND " .
2776 $search_query_part .
2777 $query_strategy_part . " ORDER BY $order_by " .
2782 $sub_selects = array();
2783 $sub_ands = array();
2784 foreach ($all_tags as $term) {
2785 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");
2792 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2797 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2798 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2799 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2800 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2802 // error_log("TAG SQL: " . $tag_sql);
2803 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2805 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2806 $result = db_query($select_qpart . $from_qpart . $where_qpart);
2809 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
2813 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
2814 if (!$owner) $owner = $_SESSION["uid"];
2816 $res = trim($str); if (!$res) return '';
2818 $charset_hack = '<head>
2819 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2822 $res = trim($res); if (!$res) return '';
2824 libxml_use_internal_errors(true);
2826 $doc = new DOMDocument();
2827 $doc->loadHTML($charset_hack . $res);
2828 $xpath = new DOMXPath($doc);
2830 $entries = $xpath->query('(//a[@href]|//img[@src])');
2832 foreach ($entries as $entry) {
2836 if ($entry->hasAttribute('href')) {
2837 $entry->setAttribute('href',
2838 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2840 $entry->setAttribute('rel', 'noreferrer');
2843 if ($entry->hasAttribute('src')) {
2844 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2846 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2848 if (file_exists($cached_filename)) {
2849 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2852 $entry->setAttribute('src', $src);
2855 if ($entry->nodeName
== 'img') {
2856 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
2857 $force_remove_images ||
$_SESSION["bw_limit"]) {
2859 $p = $doc->createElement('p');
2861 $a = $doc->createElement('a');
2862 $a->setAttribute('href', $entry->getAttribute('src'));
2864 $a->appendChild(new DOMText($entry->getAttribute('src')));
2865 $a->setAttribute('target', '_blank');
2867 $p->appendChild($a);
2869 $entry->parentNode
->replaceChild($p, $entry);
2874 if (strtolower($entry->nodeName
) == "a") {
2875 $entry->setAttribute("target", "_blank");
2879 $entries = $xpath->query('//iframe');
2880 foreach ($entries as $entry) {
2881 $entry->setAttribute('sandbox', 'allow-scripts');
2885 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
2886 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
2887 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
2888 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
2889 'dt', 'em', 'footer', 'figure', 'figcaption',
2890 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
2891 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
2892 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
2893 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2894 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
2895 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2897 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
2899 $disallowed_attributes = array('id', 'style', 'class');
2901 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
2902 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
2903 if (is_array($retval)) {
2905 $allowed_elements = $retval[1];
2906 $disallowed_attributes = $retval[2];
2912 $doc->removeChild($doc->firstChild
); //remove doctype
2913 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
2915 if ($highlight_words) {
2916 foreach ($highlight_words as $word) {
2918 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
2920 $elements = $xpath->query("//*/text()");
2922 foreach ($elements as $child) {
2924 $fragment = $doc->createDocumentFragment();
2925 $text = $child->textContent
;
2928 while (($pos = mb_stripos($text, $word)) !== false) {
2929 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
2930 $word = mb_substr($text, $pos, mb_strlen($word));
2931 $highlight = $doc->createElement('span');
2932 $highlight->appendChild(new DomText($word));
2933 $highlight->setAttribute('class', 'highlight');
2934 $fragment->appendChild($highlight);
2935 $text = mb_substr($text, $pos +
mb_strlen($word));
2938 if (!empty($text)) $fragment->appendChild(new DomText($text));
2940 $child->parentNode
->replaceChild($fragment, $child);
2945 $res = $doc->saveHTML();
2950 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
2951 $xpath = new DOMXPath($doc);
2952 $entries = $xpath->query('//*');
2954 foreach ($entries as $entry) {
2955 if (!in_array($entry->nodeName
, $allowed_elements)) {
2956 $entry->parentNode
->removeChild($entry);
2959 if ($entry->hasAttributes()) {
2960 $attrs_to_remove = array();
2962 foreach ($entry->attributes
as $attr) {
2964 if (strpos($attr->nodeName
, 'on') === 0) {
2965 array_push($attrs_to_remove, $attr);
2968 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2969 array_push($attrs_to_remove, $attr);
2973 foreach ($attrs_to_remove as $attr) {
2974 $entry->removeAttributeNode($attr);
2982 function check_for_update() {
2983 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2984 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2985 "&iid=" . sha1(SELF_URL_PATH
);
2987 $version_data = @fetch_file_contents
($version_url);
2989 if ($version_data) {
2990 $version_data = json_decode($version_data, true);
2991 if ($version_data && $version_data['version']) {
2992 if (version_compare(VERSION_STATIC
, $version_data['version']) == -1) {
2993 return $version_data;
3001 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
3003 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3004 if (count($ids) == 0) return;
3008 foreach ($ids as $id) {
3009 array_push($tmp_ids, "ref_id = '$id'");
3012 $ids_qpart = join(" OR ", $tmp_ids);
3015 db_query("UPDATE ttrss_user_entries SET
3016 unread = false,last_read = NOW()
3017 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3018 } else if ($cmode == 1) {
3019 db_query("UPDATE ttrss_user_entries SET
3021 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3023 db_query("UPDATE ttrss_user_entries SET
3024 unread = NOT unread,last_read = NOW()
3025 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3030 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
3031 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3033 while ($line = db_fetch_assoc($result)) {
3034 ccache_update($line["feed_id"], $owner_uid);
3038 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
3040 $a_id = db_escape_string($id);
3042 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3044 $query = "SELECT DISTINCT tag_name,
3045 owner_uid as owner FROM
3046 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
3047 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
3051 /* check cache first */
3053 if ($tag_cache === false) {
3054 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
3055 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3057 $tag_cache = db_fetch_result($result, 0, "tag_cache");
3061 $tags = explode(",", $tag_cache);
3064 /* do it the hard way */
3066 $tmp_result = db_query($query);
3068 while ($tmp_line = db_fetch_assoc($tmp_result)) {
3069 array_push($tags, $tmp_line["tag_name"]);
3072 /* update the cache */
3074 $tags_str = db_escape_string(join(",", $tags));
3076 db_query("UPDATE ttrss_user_entries
3077 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
3078 AND owner_uid = $owner_uid");
3084 function trim_array($array) {
3086 array_walk($tmp, 'trim');
3090 function tag_is_valid($tag) {
3091 if ($tag == '') return false;
3092 if (preg_match("/^[0-9]*$/", $tag)) return false;
3093 if (mb_strlen($tag) > 250) return false;
3095 if (!$tag) return false;
3100 function render_login_form() {
3101 header('Cache-Control: public');
3103 require_once "login_form.php";
3107 function format_warning($msg, $id = "") {
3109 return "<div class=\"warning\" id=\"$id\">
3110 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3113 function format_notice($msg, $id = "") {
3115 return "<div class=\"notice\" id=\"$id\">
3116 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
3119 function format_error($msg, $id = "") {
3121 return "<div class=\"error\" id=\"$id\">
3122 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3125 function print_notice($msg) {
3126 return print format_notice($msg);
3129 function print_warning($msg) {
3130 return print format_warning($msg);
3133 function print_error($msg) {
3134 return print format_error($msg);
3138 function T_sprintf() {
3139 $args = func_get_args();
3140 return vsprintf(__(array_shift($args)), $args);
3143 function format_inline_player($url, $ctype) {
3147 $url = htmlspecialchars($url);
3149 if (strpos($ctype, "audio/") === 0) {
3151 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
3152 $_SESSION["hasMp3"])) {
3154 $entry .= "<audio preload=\"none\" controls>
3155 <source type=\"$ctype\" src=\"$url\"></source>
3160 $entry .= "<object type=\"application/x-shockwave-flash\"
3161 data=\"lib/button/musicplayer.swf?song_url=$url\"
3162 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
3163 <param name=\"movie\"
3164 value=\"lib/button/musicplayer.swf?song_url=$url\" />
3168 if ($entry) $entry .= " <a target=\"_blank\"
3169 href=\"$url\">" . basename($url) . "</a>";
3177 /* $filename = substr($url, strrpos($url, "/")+1);
3179 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3180 $filename . " (" . $ctype . ")" . "</a>"; */
3184 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
3185 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3191 /* we can figure out feed_id from article id anyway, why do we
3192 * pass feed_id here? let's ignore the argument :(*/
3194 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3195 WHERE ref_id = '$id'");
3197 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
3199 $rv['feed_id'] = $feed_id;
3201 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
3203 if ($mark_as_read) {
3204 $result = db_query("UPDATE ttrss_user_entries
3205 SET unread = false,last_read = NOW()
3206 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3208 ccache_update($feed_id, $owner_uid);
3211 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
3212 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
3213 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
3214 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
3215 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
3216 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
3222 FROM ttrss_entries,ttrss_user_entries
3223 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
3227 $line = db_fetch_assoc($result);
3229 $tag_cache = $line["tag_cache"];
3231 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
3232 unset($line["tag_cache"]);
3234 $line["content"] = sanitize($line["content"],
3235 sql_bool_to_bool($line['hide_images']),
3236 $owner_uid, $line["site_url"], false, $line["id"]);
3238 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
3239 $line = $p->hook_render_article($line);
3242 $num_comments = $line["num_comments"];
3243 $entry_comments = "";
3245 if ($num_comments > 0) {
3246 if ($line["comments"]) {
3247 $comments_url = htmlspecialchars($line["comments"]);
3249 $comments_url = htmlspecialchars($line["link"]);
3251 $entry_comments = "<a class=\"postComments\"
3252 target='_blank' href=\"$comments_url\">$num_comments ".
3253 _ngettext("comment", "comments", $num_comments)."</a>";
3256 if ($line["comments"] && $line["link"] != $line["comments"]) {
3257 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
3262 header("Content-Type: text/html");
3263 $rv['content'] .= "<html><head>
3264 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3265 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
3266 stylesheet_tag("css/tt-rss.css").
3267 stylesheet_tag("css/zoom.css").
3268 stylesheet_tag("css/dijit.css")."
3270 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
3271 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
3273 <script type=\"text/javascript\">
3274 function openSelectedAttachment(elem) {
3276 var url = elem[elem.selectedIndex].value;
3280 elem.selectedIndex = 0;
3284 exception_error(\"openSelectedAttachment\", e);
3288 </head><body id=\"ttrssZoom\">";
3291 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3293 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3295 $entry_author = $line["author"];
3297 if ($entry_author) {
3298 $entry_author = __(" - ") . $entry_author;
3301 $parsed_updated = make_local_datetime($line["updated"], true,
3305 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3307 if ($line["link"]) {
3308 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3309 title=\"".htmlspecialchars($line['title'])."\"
3311 htmlspecialchars($line["link"]) . "\">" .
3312 $line["title"] . "</a>" .
3313 "<span class='author'>$entry_author</span></div>";
3315 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3319 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
3320 "\" target=\"_blank\">".
3321 htmlspecialchars($line["feed_title"])."</a>";
3323 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
3325 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3328 $tags_str = format_tags_string($line["tags"], $id);
3329 $tags_str_full = join(", ", $line["tags"]);
3331 if (!$tags_str_full) $tags_str_full = __("no tags");
3333 if (!$entry_comments) $entry_comments = " "; # placeholder
3335 $rv['content'] .= "<div class='postTags' style='float : right'>
3336 <img src='images/tag.png'
3337 class='tagsPic' alt='Tags' title='Tags'> ";
3340 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3341 <a title=\"".__('Edit tags for this article')."\"
3342 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3344 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3345 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3346 position=\"below\">$tags_str_full</div>";
3348 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
3349 $rv['content'] .= $p->hook_article_button($line);
3353 $tags_str = strip_tags($tags_str);
3354 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3356 $rv['content'] .= "</div>";
3357 $rv['content'] .= "<div clear='both'>";
3359 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
3360 $rv['content'] .= $p->hook_article_left_button($line);
3363 $rv['content'] .= "$entry_comments</div>";
3365 if ($line["orig_feed_id"]) {
3367 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
3368 WHERE id = ".$line["orig_feed_id"]);
3370 if (db_num_rows($tmp_result) != 0) {
3372 $rv['content'] .= "<div clear='both'>";
3373 $rv['content'] .= __("Originally from:");
3375 $rv['content'] .= " ";
3377 $tmp_line = db_fetch_assoc($tmp_result);
3379 $rv['content'] .= "<a target='_blank'
3380 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3381 $tmp_line['title'] . "</a>";
3383 $rv['content'] .= " ";
3385 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3386 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3388 $rv['content'] .= "</div>";
3392 $rv['content'] .= "</div>";
3394 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3395 if ($line['note']) {
3396 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3398 $rv['content'] .= "</div>";
3400 if (!$line['lang']) $line['lang'] = 'en';
3402 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
3404 $rv['content'] .= $line["content"];
3405 $rv['content'] .= format_article_enclosures($id,
3406 sql_bool_to_bool($line["always_display_enclosures"]),
3408 sql_bool_to_bool($line["hide_images"]));
3410 $rv['content'] .= "</div>";
3412 $rv['content'] .= "</div>";
3418 <div class='footer'>
3419 <button onclick=\"return window.close()\">".
3420 __("Close this window")."</button></div>";
3421 $rv['content'] .= "</body></html>";
3428 function print_checkpoint($n, $s) {
3429 $ts = microtime(true);
3430 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
3434 function sanitize_tag($tag) {
3437 $tag = mb_strtolower($tag, 'utf-8');
3439 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3441 // $tag = str_replace('"', "", $tag);
3442 // $tag = str_replace("+", " ", $tag);
3443 $tag = str_replace("technorati tag: ", "", $tag);
3448 function get_self_url_prefix() {
3449 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3450 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3452 return SELF_URL_PATH
;
3457 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3459 * @return string The Mozilla Firefox feed adding URL.
3461 function add_feed_url() {
3462 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3464 $url_path = get_self_url_prefix() .
3465 "/public.php?op=subscribe&feed_url=%s";
3467 } // function add_feed_url
3469 function encrypt_password($pass, $salt = '', $mode2 = false) {
3470 if ($salt && $mode2) {
3471 return "MODE2:" . hash('sha256', $salt . $pass);
3473 return "SHA1X:" . sha1("$salt:$pass");
3475 return "SHA1:" . sha1($pass);
3477 } // function encrypt_password
3479 function load_filters($feed_id, $owner_uid, $action_id = false) {
3482 $cat_id = (int)getFeedCategory($feed_id);
3485 $null_cat_qpart = "cat_id IS NULL OR";
3487 $null_cat_qpart = "";
3489 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
3490 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
3492 $check_cats = join(",", array_merge(
3493 getParentCategories($cat_id, $owner_uid),
3496 while ($line = db_fetch_assoc($result)) {
3497 $filter_id = $line["id"];
3499 $result2 = db_query("SELECT
3500 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3501 FROM ttrss_filters2_rules AS r,
3502 ttrss_filter_types AS t
3504 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
3505 (feed_id IS NULL OR feed_id = '$feed_id') AND
3506 filter_type = t.id AND filter_id = '$filter_id'");
3511 while ($rule_line = db_fetch_assoc($result2)) {
3512 # print_r($rule_line);
3515 $rule["reg_exp"] = $rule_line["reg_exp"];
3516 $rule["type"] = $rule_line["type_name"];
3517 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
3519 array_push($rules, $rule);
3522 $result2 = db_query("SELECT a.action_param,t.name AS type_name
3523 FROM ttrss_filters2_actions AS a,
3524 ttrss_filter_actions AS t
3526 action_id = t.id AND filter_id = '$filter_id'");
3528 while ($action_line = db_fetch_assoc($result2)) {
3529 # print_r($action_line);
3532 $action["type"] = $action_line["type_name"];
3533 $action["param"] = $action_line["action_param"];
3535 array_push($actions, $action);
3540 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3541 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
3542 $filter["rules"] = $rules;
3543 $filter["actions"] = $actions;
3545 if (count($rules) > 0 && count($actions) > 0) {
3546 array_push($filters, $filter);
3553 function get_score_pic($score) {
3555 return "score_high.png";
3556 } else if ($score > 0) {
3557 return "score_half_high.png";
3558 } else if ($score < -100) {
3559 return "score_low.png";
3560 } else if ($score < 0) {
3561 return "score_half_low.png";
3563 return "score_neutral.png";
3567 function feed_has_icon($id) {
3568 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3571 function init_plugins() {
3572 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
3577 function format_tags_string($tags, $id) {
3578 if (!is_array($tags) ||
count($tags) == 0) {
3579 return __("no tags");
3581 $maxtags = min(5, count($tags));
3583 for ($i = 0; $i < $maxtags; $i++
) {
3584 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
3587 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
3589 if (count($tags) > $maxtags)
3590 $tags_str .= ", …";
3596 function format_article_labels($labels, $id) {
3598 if (!is_array($labels)) return '';
3602 foreach ($labels as $l) {
3603 $labels_str .= sprintf("<span class='hlLabelRef'
3604 style='color : %s; background-color : %s'>%s</span>",
3605 $l[2], $l[3], $l[1]);
3612 function format_article_note($id, $note, $allow_edit = true) {
3614 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3615 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3616 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3622 function get_feed_category($feed_cat, $parent_cat_id = false) {
3623 if ($parent_cat_id) {
3624 $parent_qpart = "parent_cat = '$parent_cat_id'";
3625 $parent_insert = "'$parent_cat_id'";
3627 $parent_qpart = "parent_cat IS NULL";
3628 $parent_insert = "NULL";
3632 "SELECT id FROM ttrss_feed_categories
3633 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3635 if (db_num_rows($result) == 0) {
3638 return db_fetch_result($result, 0, "id");
3642 function add_feed_category($feed_cat, $parent_cat_id = false) {
3644 if (!$feed_cat) return false;
3648 if ($parent_cat_id) {
3649 $parent_qpart = "parent_cat = '$parent_cat_id'";
3650 $parent_insert = "'$parent_cat_id'";
3652 $parent_qpart = "parent_cat IS NULL";
3653 $parent_insert = "NULL";
3656 $feed_cat = mb_substr($feed_cat, 0, 250);
3659 "SELECT id FROM ttrss_feed_categories
3660 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3662 if (db_num_rows($result) == 0) {
3665 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3666 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3676 function getArticleFeed($id) {
3677 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3678 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3680 if (db_num_rows($result) != 0) {
3681 return db_fetch_result($result, 0, "feed_id");
3688 * Fixes incomplete URLs by prepending "http://".
3689 * Also replaces feed:// with http://, and
3690 * prepends a trailing slash if the url is a domain name only.
3692 * @param string $url Possibly incomplete URL
3694 * @return string Fixed URL.
3696 function fix_url($url) {
3697 if (strpos($url, '://') === false) {
3698 $url = 'http://' . $url;
3699 } else if (substr($url, 0, 5) == 'feed:') {
3700 $url = 'http:' . substr($url, 5);
3703 //prepend slash if the URL has no slash in it
3704 // "http://www.example" -> "http://www.example/"
3705 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3709 if ($url != "http:///")
3715 function validate_feed_url($url) {
3716 $parts = parse_url($url);
3718 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3722 function get_article_enclosures($id) {
3724 $query = "SELECT * FROM ttrss_enclosures
3725 WHERE post_id = '$id' AND content_url != ''";
3729 $result = db_query($query);
3731 if (db_num_rows($result) > 0) {
3732 while ($line = db_fetch_assoc($result)) {
3733 array_push($rv, $line);
3740 function save_email_address($email) {
3741 // FIXME: implement persistent storage of emails
3743 if (!$_SESSION['stored_emails'])
3744 $_SESSION['stored_emails'] = array();
3746 if (!in_array($email, $_SESSION['stored_emails']))
3747 array_push($_SESSION['stored_emails'], $email);
3751 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
3753 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3755 $sql_is_cat = bool_to_sql_bool($is_cat);
3757 $result = db_query("SELECT access_key FROM ttrss_access_keys
3758 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3759 AND owner_uid = " . $owner_uid);
3761 if (db_num_rows($result) == 1) {
3762 return db_fetch_result($result, 0, "access_key");
3764 $key = db_escape_string(uniqid(base_convert(rand(), 10, 36)));
3766 $result = db_query("INSERT INTO ttrss_access_keys
3767 (access_key, feed_id, is_cat, owner_uid)
3768 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3775 function get_feeds_from_html($url, $content)
3777 $url = fix_url($url);
3778 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3780 libxml_use_internal_errors(true);
3782 $doc = new DOMDocument();
3783 $doc->loadHTML($content);
3784 $xpath = new DOMXPath($doc);
3785 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3786 $feedUrls = array();
3787 foreach ($entries as $entry) {
3788 if ($entry->hasAttribute('href')) {
3789 $title = $entry->getAttribute('title');
3791 $title = $entry->getAttribute('type');
3793 $feedUrl = rewrite_relative_url(
3794 $baseUrl, $entry->getAttribute('href')
3796 $feedUrls[$feedUrl] = $title;
3802 function is_html($content) {
3803 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3806 function url_is_html($url, $login = false, $pass = false) {
3807 return is_html(fetch_file_contents($url, false, $login, $pass));
3810 function print_label_select($name, $value, $attributes = "") {
3812 $result = db_query("SELECT caption FROM ttrss_labels2
3813 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3815 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3816 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3818 while ($line = db_fetch_assoc($result)) {
3820 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3822 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3823 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3827 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3834 function format_article_enclosures($id, $always_display_enclosures,
3835 $article_content, $hide_images = false) {
3837 $result = get_article_enclosures($id);
3840 if (count($result) > 0) {
3842 $entries_html = array();
3844 $entries_inline = array();
3846 foreach ($result as $line) {
3848 $url = $line["content_url"];
3849 $ctype = $line["content_type"];
3850 $title = $line["title"];
3852 if (!$ctype) $ctype = __("unknown type");
3854 $filename = substr($url, strrpos($url, "/")+
1);
3856 $player = format_inline_player($url, $ctype);
3858 if ($player) array_push($entries_inline, $player);
3860 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3861 # $filename . " (" . $ctype . ")" . "</a>";
3863 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3864 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3866 array_push($entries_html, $entry);
3870 $entry["type"] = $ctype;
3871 $entry["filename"] = $filename;
3872 $entry["url"] = $url;
3873 $entry["title"] = $title;
3875 array_push($entries, $entry);
3878 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
3879 if ($always_display_enclosures ||
3880 !preg_match("/<img/i", $article_content)) {
3882 foreach ($entries as $entry) {
3884 if (preg_match("/image/", $entry["type"]) ||
3885 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3887 if (!$hide_images) {
3889 alt=\"".htmlspecialchars($entry["filename"])."\"
3890 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3892 $rv .= "<p><a target=\"_blank\"
3893 href=\"".htmlspecialchars($entry["url"])."\"
3894 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3897 if ($entry['title']) {
3898 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
3905 if (count($entries_inline) > 0) {
3906 $rv .= "<hr clear='both'/>";
3907 foreach ($entries_inline as $entry) { $rv .= $entry; };
3908 $rv .= "<hr clear='both'/>";
3911 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
3912 "<option value=''>" . __('Attachments')."</option>";
3914 foreach ($entries as $entry) {
3915 if ($entry["title"])
3916 $title = "— " . truncate_string($entry["title"], 30);
3920 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
3930 function getLastArticleId() {
3931 $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3932 WHERE owner_uid = " . $_SESSION["uid"]);
3934 if (db_num_rows($result) == 1) {
3935 return db_fetch_result($result, 0, "id");
3941 function build_url($parts) {
3942 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3946 * Converts a (possibly) relative URL to a absolute one.
3948 * @param string $url Base URL (i.e. from where the document is)
3949 * @param string $rel_url Possibly relative URL in the document
3951 * @return string Absolute URL
3953 function rewrite_relative_url($url, $rel_url) {
3954 if (strpos($rel_url, ":") !== false) {
3956 } else if (strpos($rel_url, "://") !== false) {
3958 } else if (strpos($rel_url, "//") === 0) {
3959 # protocol-relative URL (rare but they exist)
3961 } else if (strpos($rel_url, "/") === 0)
3963 $parts = parse_url($url);
3964 $parts['path'] = $rel_url;
3966 return build_url($parts);
3969 $parts = parse_url($url);
3970 if (!isset($parts['path'])) {
3971 $parts['path'] = '/';
3973 $dir = $parts['path'];
3974 if (substr($dir, -1) !== '/') {
3975 $dir = dirname($parts['path']);
3976 $dir !== '/' && $dir .= '/';
3978 $parts['path'] = $dir . $rel_url;
3980 return build_url($parts);
3984 function sphinx_search($query, $offset = 0, $limit = 30) {
3985 require_once 'lib/sphinxapi.php';
3987 $sphinxClient = new SphinxClient();
3989 $sphinxpair = explode(":", SPHINX_SERVER
, 2);
3991 $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
3992 $sphinxClient->SetConnectTimeout(1);
3994 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3995 'feed_title' => 20));
3997 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3998 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3999 $sphinxClient->SetLimits($offset, $limit, 1000);
4000 $sphinxClient->SetArrayResult(false);
4001 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
4003 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
4007 if (is_array($result['matches'])) {
4008 foreach (array_keys($result['matches']) as $int_id) {
4009 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
4010 array_push($ids, $ref_id);
4017 function cleanup_tags($days = 14, $limit = 1000) {
4019 if (DB_TYPE
== "pgsql") {
4020 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
4021 } else if (DB_TYPE
== "mysql") {
4022 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
4027 while ($limit > 0) {
4030 $query = "SELECT ttrss_tags.id AS id
4031 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
4032 WHERE post_int_id = int_id AND $interval_query AND
4033 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
4035 $result = db_query($query);
4039 while ($line = db_fetch_assoc($result)) {
4040 array_push($ids, $line['id']);
4043 if (count($ids) > 0) {
4044 $ids = join(",", $ids);
4046 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
4047 $tags_deleted +
= db_affected_rows($tmp_result);
4052 $limit -= $limit_part;
4055 return $tags_deleted;
4058 function print_user_stylesheet() {
4059 $value = get_pref('USER_STYLESHEET');
4062 print "<style type=\"text/css\">";
4063 print str_replace("<br/>", "\n", $value);
4069 function filter_to_sql($filter, $owner_uid) {
4072 if (DB_TYPE
== "pgsql")
4075 $reg_qpart = "REGEXP";
4077 foreach ($filter["rules"] AS $rule) {
4078 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
4079 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
4080 $rule['reg_exp']) !== FALSE;
4082 if ($regexp_valid) {
4084 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
4086 switch ($rule["type"]) {
4088 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4089 $rule['reg_exp'] . "')";
4092 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
4093 $rule['reg_exp'] . "')";
4096 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4097 $rule['reg_exp'] . "') OR LOWER(" .
4098 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
4101 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
4102 $rule['reg_exp'] . "')";
4105 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
4106 $rule['reg_exp'] . "')";
4109 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
4110 $rule['reg_exp'] . "')";
4114 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
4116 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
4117 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
4120 if (isset($rule["cat_id"])) {
4122 if ($rule["cat_id"] > 0) {
4123 $children = getChildCategories($rule["cat_id"], $owner_uid);
4124 array_push($children, $rule["cat_id"]);
4126 $children = join(",", $children);
4128 $cat_qpart = "cat_id IN ($children)";
4130 $cat_qpart = "cat_id IS NULL";
4133 $qpart .= " AND $cat_qpart";
4136 $qpart .= " AND feed_id IS NOT NULL";
4138 array_push($query, "($qpart)");
4143 if (count($query) > 0) {
4144 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
4146 $fullquery = "(false)";
4149 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
4154 if (!function_exists('gzdecode')) {
4155 function gzdecode($string) { // no support for 2nd argument
4156 return file_get_contents('compress.zlib://data:who/cares;base64,'.
4157 base64_encode($string));
4161 function get_random_bytes($length) {
4162 if (function_exists('openssl_random_pseudo_bytes')) {
4163 return openssl_random_pseudo_bytes($length);
4167 for ($i = 0; $i < $length; $i++
)
4168 $output .= chr(mt_rand(0, 255));
4174 function read_stdin() {
4175 $fp = fopen("php://stdin", "r");
4178 $line = trim(fgets($fp));
4186 function tmpdirname($path, $prefix) {
4187 // Use PHP's tmpfile function to create a temporary
4188 // directory name. Delete the file and keep the name.
4189 $tempname = tempnam($path,$prefix);
4193 if (!unlink($tempname))
4199 function getFeedCategory($feed) {
4200 $result = db_query("SELECT cat_id FROM ttrss_feeds
4201 WHERE id = '$feed'");
4203 if (db_num_rows($result) > 0) {
4204 return db_fetch_result($result, 0, "cat_id");
4211 function implements_interface($class, $interface) {
4212 return in_array($interface, class_implements($class));
4215 function geturl($url, $depth = 0){
4217 if ($depth == 20) return $url;
4219 if (!function_exists('curl_init'))
4220 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
);
4222 $curl = curl_init();
4223 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4224 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4225 $header[] = "Cache-Control: max-age=0";
4226 $header[] = "Connection: keep-alive";
4227 $header[] = "Keep-Alive: 300";
4228 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4229 $header[] = "Accept-Language: en-us,en;q=0.5";
4230 $header[] = "Pragma: ";
4232 curl_setopt($curl, CURLOPT_URL
, $url);
4233 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4234 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4235 curl_setopt($curl, CURLOPT_HEADER
, true);
4236 curl_setopt($curl, CURLOPT_REFERER
, $url);
4237 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4238 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4239 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4240 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4241 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4242 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER
, false);
4244 if (defined('_CURL_HTTP_PROXY')) {
4245 curl_setopt($curl, CURLOPT_PROXY
, _CURL_HTTP_PROXY
);
4248 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
4249 curl_setopt($curl, CURLOPT_SSLVERSION
, 3);
4252 $html = curl_exec($curl);
4254 $status = curl_getinfo($curl);
4256 if($status['http_code']!=200){
4257 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4259 list($header) = explode("\r\n\r\n", $html, 2);
4261 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4262 $url = trim(str_replace($matches[1],"",$matches[0]));
4263 $url_parsed = parse_url($url);
4264 return (isset($url_parsed))?
geturl($url, $depth +
1):'';
4267 global $fetch_last_error;
4269 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
4273 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4274 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4275 # $handle = @fopen('./curl.error.log', 'a');
4276 # fwrite($handle, $line);
4283 function get_minified_js($files) {
4284 require_once 'lib/jshrink/Minifier.php';
4288 foreach ($files as $js) {
4289 if (!isset($_GET['debug'])) {
4290 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
4292 if (file_exists($cached_file) &&
4293 is_readable($cached_file) &&
4294 filemtime($cached_file) >= filemtime("js/$js.js")) {
4296 $rv .= file_get_contents($cached_file);
4299 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4300 file_put_contents($cached_file, $minified);
4304 $rv .= file_get_contents("js/$js.js");
4311 function stylesheet_tag($filename) {
4312 $timestamp = filemtime($filename);
4314 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4317 function javascript_tag($filename) {
4320 if (!(strpos($filename, "?") === FALSE)) {
4321 $query = substr($filename, strpos($filename, "?")+
1);
4322 $filename = substr($filename, 0, strpos($filename, "?"));
4325 $timestamp = filemtime($filename);
4327 if ($query) $timestamp .= "&$query";
4329 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4332 function calculate_dep_timestamp() {
4333 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
4337 foreach ($files as $file) {
4338 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4344 function T_js_decl($s1, $s2) {
4346 $s1 = preg_replace("/\n/", "", $s1);
4347 $s2 = preg_replace("/\n/", "", $s2);
4349 $s1 = preg_replace("/\"/", "\\\"", $s1);
4350 $s2 = preg_replace("/\"/", "\\\"", $s2);
4352 return "T_messages[\"$s1\"] = \"$s2\";\n";
4356 function init_js_translations() {
4358 print 'var T_messages = new Object();
4361 if (T_messages[msg]) {
4362 return T_messages[msg];
4368 function ngettext(msg1, msg2, n) {
4369 return __((parseInt(n) > 1) ? msg2 : msg1);
4372 $l10n = _get_reader();
4374 for ($i = 0; $i < $l10n->total
; $i++
) {
4375 $orig = $l10n->get_original_string($i);
4376 if(strpos($orig, "\000") !== FALSE) { // Plural forms
4377 $key = explode(chr(0), $orig);
4378 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
4379 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
4381 $translation = __($orig);
4382 print T_js_decl($orig, $translation);
4387 function label_to_feed_id($label) {
4388 return LABEL_BASE_INDEX
- 1 - abs($label);
4391 function feed_to_label_id($feed) {
4392 return LABEL_BASE_INDEX
- 1 +
abs($feed);
4395 function format_libxml_error($error) {
4396 return T_sprintf("LibXML error %s at line %d (column %d): %s",
4397 $error->code
, $error->line
, $error->column
,