2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 122);
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;
15 mb_internal_encoding("UTF-8");
16 date_default_timezone_set('UTC');
17 if (defined('E_DEPRECATED')) {
18 error_reporting(E_ALL
& ~E_NOTICE
& ~E_DEPRECATED
);
20 error_reporting(E_ALL
& ~E_NOTICE
);
23 require_once 'config.php';
26 * Define a constant if not already defined
28 * @param string $name The constant name.
29 * @param mixed $value The constant value.
31 * @return boolean True if defined successfully or not.
33 function define_default($name, $value) {
34 defined($name) or define($name, $value);
37 ///// Some defaults that you can override in config.php //////
39 define_default('FEED_FETCH_TIMEOUT', 45);
40 // How may seconds to wait for response when requesting feed from a site
41 define_default('FEED_FETCH_NO_CACHE_TIMEOUT', 15);
42 // How may seconds to wait for response when requesting feed from a
43 // site when that feed wasn't cached before
44 define_default('FILE_FETCH_TIMEOUT', 45);
45 // Default timeout when fetching files from remote sites
46 define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
47 // How many seconds to wait for initial response from website when
48 // fetching files from remote sites
50 if (DB_TYPE
== "pgsql") {
51 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
53 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
57 * Return available translations names.
60 * @return array A array of available translations.
62 function get_translations() {
64 "auto" => "Detect automatically",
70 "fr_FR" => "Français",
71 "hu_HU" => "Magyar (Hungarian)",
72 "it_IT" => "Italiano",
73 "ja_JP" => "日本語 (Japanese)",
74 "lv_LV" => "Latviešu",
75 "nb_NO" => "Norwegian bokmål",
79 "pt_BR" => "Portuguese/Brazil",
80 "zh_CN" => "Simplified Chinese",
87 require_once "lib/accept-to-gettext.php";
88 require_once "lib/gettext/gettext.inc";
90 require_once "lib/languagedetect/LanguageDetect.php";
92 function startup_gettext() {
94 # Get locale from Accept-Language header
95 $lang = al2gt(array_keys(get_translations()), "text/html");
97 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
98 $lang = _TRANSLATION_OVERRIDE_DEFAULT
;
101 if ($_SESSION["uid"] && get_schema_version() >= 120) {
102 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
104 if ($pref_lang && $pref_lang != 'auto') {
110 if (defined('LC_MESSAGES')) {
111 _setlocale(LC_MESSAGES
, $lang);
112 } else if (defined('LC_ALL')) {
113 _setlocale(LC_ALL
, $lang);
116 _bindtextdomain("messages", "locale");
118 _textdomain("messages");
119 _bind_textdomain_codeset("messages", "UTF-8");
123 require_once 'db-prefs.php';
124 require_once 'version.php';
125 require_once 'ccache.php';
126 require_once 'labels.php';
128 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION
. ' (http://tt-rss.org/)');
129 ini_set('user_agent', SELF_USER_AGENT
);
131 require_once 'lib/pubsubhubbub/publisher.php';
133 $schema_version = false;
136 * Print a timestamped debug message.
138 * @param string $msg The debug message.
141 function _debug($msg, $show = true) {
142 if (defined('SUPPRESS_DEBUGGING'))
145 $ts = strftime("%H:%M:%S", time());
146 if (function_exists('posix_getpid')) {
147 $ts = "$ts/" . posix_getpid();
150 if ($show && !(defined('QUIET') && QUIET
)) {
151 print "[$ts] $msg\n";
154 if (defined('LOGFILE')) {
155 $fp = fopen(LOGFILE
, 'a+');
160 if (function_exists("flock")) {
163 // try to lock logfile for writing
164 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB
)) {
175 fputs($fp, "[$ts] $msg\n");
177 if (function_exists("flock")) {
188 * Purge a feed old posts.
190 * @param mixed $link A database connection.
191 * @param mixed $feed_id The id of the purged feed.
192 * @param mixed $purge_interval Olderness of purged posts.
193 * @param boolean $debug Set to True to enable the debug. False by default.
197 function purge_feed($feed_id, $purge_interval, $debug = false) {
199 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
204 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
208 if (db_num_rows($result) == 1) {
209 $owner_uid = db_fetch_result($result, 0, "owner_uid");
212 if ($purge_interval == -1 ||
!$purge_interval) {
214 ccache_update($feed_id, $owner_uid);
219 if (!$owner_uid) return;
221 if (FORCE_ARTICLE_PURGE
== 0) {
222 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
225 $purge_unread = true;
226 $purge_interval = FORCE_ARTICLE_PURGE
;
229 if (!$purge_unread) $query_limit = " unread = false AND ";
231 if (DB_TYPE
== "pgsql") {
232 $pg_version = get_pgsql_version();
234 if (preg_match("/^7\./", $pg_version) ||
preg_match("/^8\.0/", $pg_version)) {
236 $result = db_query("DELETE FROM ttrss_user_entries WHERE
237 ttrss_entries.id = ref_id AND
239 feed_id = '$feed_id' AND
241 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
245 $result = db_query("DELETE FROM ttrss_user_entries
247 WHERE ttrss_entries.id = ref_id AND
249 feed_id = '$feed_id' AND
251 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
256 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
257 marked = false AND feed_id = '$feed_id' AND
258 (SELECT date_updated FROM ttrss_entries WHERE
259 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
261 $result = db_query("DELETE FROM ttrss_user_entries
262 USING ttrss_user_entries, ttrss_entries
263 WHERE ttrss_entries.id = ref_id AND
265 feed_id = '$feed_id' AND
267 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
270 $rows = db_affected_rows($result);
272 ccache_update($feed_id, $owner_uid);
275 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
279 } // function purge_feed
281 function feed_purge_interval($feed_id) {
283 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
284 WHERE id = '$feed_id'");
286 if (db_num_rows($result) == 1) {
287 $purge_interval = db_fetch_result($result, 0, "purge_interval");
288 $owner_uid = db_fetch_result($result, 0, "owner_uid");
290 if ($purge_interval == 0) $purge_interval = get_pref(
291 'PURGE_OLD_DAYS', $owner_uid);
293 return $purge_interval;
300 function purge_orphans($do_output = false) {
302 // purge orphaned posts in main content table
303 $result = db_query("DELETE FROM ttrss_entries WHERE
304 (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
307 $rows = db_affected_rows($result);
308 _debug("Purged $rows orphaned posts.");
312 function get_feed_update_interval($feed_id) {
313 $result = db_query("SELECT owner_uid, update_interval FROM
314 ttrss_feeds WHERE id = '$feed_id'");
316 if (db_num_rows($result) == 1) {
317 $update_interval = db_fetch_result($result, 0, "update_interval");
318 $owner_uid = db_fetch_result($result, 0, "owner_uid");
320 if ($update_interval != 0) {
321 return $update_interval;
323 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
331 function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false, $timestamp = 0) {
333 global $fetch_last_error;
334 global $fetch_last_error_code;
335 global $fetch_last_content_type;
336 global $fetch_curl_used;
338 $url = str_replace(' ', '%20', $url);
340 if (!defined('NO_CURL') && function_exists('curl_init')) {
342 $fetch_curl_used = true;
344 if (ini_get("safe_mode") ||
ini_get("open_basedir")) {
345 $new_url = geturl($url);
347 // geturl has already populated $fetch_last_error
350 $ch = curl_init($new_url);
352 $ch = curl_init($url);
355 if ($timestamp && !$post_query) {
356 curl_setopt($ch, CURLOPT_HTTPHEADER
,
357 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
360 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT
, $timeout ?
$timeout : FILE_FETCH_CONNECT_TIMEOUT
);
361 curl_setopt($ch, CURLOPT_TIMEOUT
, $timeout ?
$timeout : FILE_FETCH_TIMEOUT
);
362 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, !ini_get("safe_mode") && !ini_get("open_basedir"));
363 curl_setopt($ch, CURLOPT_MAXREDIRS
, 20);
364 curl_setopt($ch, CURLOPT_BINARYTRANSFER
, true);
365 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
366 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER
, false);
367 curl_setopt($ch, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
368 curl_setopt($ch, CURLOPT_USERAGENT
, SELF_USER_AGENT
);
369 curl_setopt($ch, CURLOPT_ENCODING
, "");
370 curl_setopt($ch, CURLOPT_REFERER
, $url);
373 curl_setopt($ch, CURLOPT_POST
, true);
374 curl_setopt($ch, CURLOPT_POSTFIELDS
, $post_query);
377 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
378 curl_setopt($ch, CURLOPT_SSLVERSION
, 3);
382 curl_setopt($ch, CURLOPT_USERPWD
, "$login:$pass");
384 $contents = @curl_exec
($ch);
386 if (curl_errno($ch) === 23 ||
curl_errno($ch) === 61) {
387 curl_setopt($ch, CURLOPT_ENCODING
, 'none');
388 $contents = @curl_exec
($ch);
391 if ($contents === false) {
392 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
397 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
398 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE
);
400 $fetch_last_error_code = $http_code;
402 if ($http_code != 200 ||
$type && strpos($fetch_last_content_type, "$type") === false) {
403 if (curl_errno($ch) != 0) {
404 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
406 $fetch_last_error = "HTTP Code: $http_code";
417 $fetch_curl_used = false;
419 if ($login && $pass){
420 $url_parts = array();
422 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
424 $pass = urlencode($pass);
426 if ($url_parts[1] && $url_parts[2]) {
427 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
431 if (!$post_query && $timestamp) {
432 $context = stream_context_create(array(
435 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
441 $old_error = error_get_last();
443 $data = @file_get_contents
($url, false, $context);
445 $fetch_last_content_type = false; // reset if no type was sent from server
446 if (isset($http_response_header) && is_array($http_response_header)) {
447 foreach ($http_response_header as $h) {
448 if (substr(strtolower($h), 0, 13) == 'content-type:') {
449 $fetch_last_content_type = substr($h, 14);
450 // don't abort here b/c there might be more than one
451 // e.g. if we were being redirected -- last one is the right one
454 if (substr(strtolower($h), 0, 7) == 'http/1.') {
455 $fetch_last_error_code = (int) substr($h, 9, 3);
461 $error = error_get_last();
463 if ($error['message'] != $old_error['message']) {
464 $fetch_last_error = $error["message"];
466 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
475 * Try to determine the favicon URL for a feed.
476 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
477 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
479 * @param string $url A feed or page URL
481 * @return mixed The favicon URL, or false if none was found.
483 function get_favicon_url($url) {
485 $favicon_url = false;
487 if ($html = @fetch_file_contents
($url)) {
489 libxml_use_internal_errors(true);
491 $doc = new DOMDocument();
492 $doc->loadHTML($html);
493 $xpath = new DOMXPath($doc);
495 $base = $xpath->query('/html/head/base');
496 foreach ($base as $b) {
497 $url = $b->getAttribute("href");
501 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
502 if (count($entries) > 0) {
503 foreach ($entries as $entry) {
504 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
511 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
514 } // function get_favicon_url
516 function check_feed_favicon($site_url, $feed) {
517 # print "FAVICON [$site_url]: $favicon_url\n";
519 $icon_file = ICONS_DIR
. "/$feed.ico";
521 if (!file_exists($icon_file)) {
522 $favicon_url = get_favicon_url($site_url);
525 // Limiting to "image" type misses those served with text/plain
526 $contents = fetch_file_contents($favicon_url); // , "image");
529 // Crude image type matching.
530 // Patterns gleaned from the file(1) source code.
531 if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
532 // 0 string \000\000\001\000 MS Windows icon resource
533 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
535 elseif (preg_match('/^GIF8/', $contents)) {
536 // 0 string GIF8 GIF image data
537 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
539 elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
540 // 0 string \x89PNG\x0d\x0a\x1a\x0a PNG image data
541 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
543 elseif (preg_match('/^\xff\xd8/', $contents)) {
544 // 0 beshort 0xffd8 JPEG image data
545 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
548 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
554 $fp = @fopen
($icon_file, "w");
557 fwrite($fp, $contents);
559 chmod($icon_file, 0644);
567 function print_select($id, $default, $values, $attributes = "") {
568 print "<select name=\"$id\" id=\"$id\" $attributes>";
569 foreach ($values as $v) {
571 $sel = "selected=\"1\"";
577 print "<option value=\"$v\" $sel>$v</option>";
582 function print_select_hash($id, $default, $values, $attributes = "") {
583 print "<select name=\"$id\" id='$id' $attributes>";
584 foreach (array_keys($values) as $v) {
586 $sel = 'selected="selected"';
592 print "<option $sel value=\"$v\">".$values[$v]."</option>";
598 function print_radio($id, $default, $true_is, $values, $attributes = "") {
599 foreach ($values as $v) {
606 if ($v == $true_is) {
607 $sel .= " value=\"1\"";
609 $sel .= " value=\"0\"";
612 print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
613 type=\"radio\" $sel $attributes name=\"$id\"> $v ";
618 function initialize_user_prefs($uid, $profile = false) {
620 $uid = db_escape_string($uid);
624 $profile_qpart = "AND profile IS NULL";
626 $profile_qpart = "AND profile = '$profile'";
629 if (get_schema_version() < 63) $profile_qpart = "";
633 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
635 $u_result = db_query("SELECT pref_name
636 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
638 $active_prefs = array();
640 while ($line = db_fetch_assoc($u_result)) {
641 array_push($active_prefs, $line["pref_name"]);
644 while ($line = db_fetch_assoc($result)) {
645 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
646 // print "adding " . $line["pref_name"] . "<br>";
648 $line["def_value"] = db_escape_string($line["def_value"]);
649 $line["pref_name"] = db_escape_string($line["pref_name"]);
651 if (get_schema_version() < 63) {
652 db_query("INSERT INTO ttrss_user_prefs
653 (owner_uid,pref_name,value) VALUES
654 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
657 db_query("INSERT INTO ttrss_user_prefs
658 (owner_uid,pref_name,value, profile) VALUES
659 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
669 function get_ssl_certificate_id() {
670 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
671 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
672 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
673 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
674 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
679 function authenticate_user($login, $password, $check_only = false) {
681 if (!SINGLE_USER_MODE
) {
684 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_AUTH_USER
) as $plugin) {
686 $user_id = (int) $plugin->authenticate($login, $password);
689 $_SESSION["auth_module"] = strtolower(get_class($plugin));
694 if ($user_id && !$check_only) {
697 $_SESSION["uid"] = $user_id;
698 $_SESSION["version"] = VERSION_STATIC
;
700 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
701 WHERE id = '$user_id'");
703 $_SESSION["name"] = db_fetch_result($result, 0, "login");
704 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
705 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
707 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
710 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
711 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
712 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
714 $_SESSION["last_version_check"] = time();
716 initialize_user_prefs($_SESSION["uid"]);
725 $_SESSION["uid"] = 1;
726 $_SESSION["name"] = "admin";
727 $_SESSION["access_level"] = 10;
729 $_SESSION["hide_hello"] = true;
730 $_SESSION["hide_logout"] = true;
732 $_SESSION["auth_module"] = false;
734 if (!$_SESSION["csrf_token"]) {
735 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
738 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
740 initialize_user_prefs($_SESSION["uid"]);
746 function make_password($length = 8) {
749 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
753 while ($i < $length) {
754 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
756 if (!strstr($password, $char)) {
764 // this is called after user is created to initialize default feeds, labels
767 // user preferences are checked on every login, not here
769 function initialize_user($uid) {
771 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
772 values ('$uid', 'Tiny Tiny RSS: New Releases',
773 'http://tt-rss.org/releases.rss')");
775 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
776 values ('$uid', 'Tiny Tiny RSS: Forum',
777 'http://tt-rss.org/forum/rss.php')");
780 function logout_user() {
782 if (isset($_COOKIE[session_name()])) {
783 setcookie(session_name(), '', time()-42000, '/');
787 function validate_csrf($csrf_token) {
788 return $csrf_token == $_SESSION['csrf_token'];
791 function load_user_plugins($owner_uid) {
793 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
795 PluginHost
::getInstance()->load($plugins, PluginHost
::KIND_USER
, $owner_uid);
797 if (get_schema_version() > 100) {
798 PluginHost
::getInstance()->load_data();
803 function login_sequence() {
804 if (SINGLE_USER_MODE
) {
806 authenticate_user("admin", null);
807 load_user_plugins($_SESSION["uid"]);
809 if (!validate_session()) $_SESSION["uid"] = false;
811 if (!$_SESSION["uid"]) {
813 if (AUTH_AUTO_LOGIN
&& authenticate_user(null, null)) {
814 $_SESSION["ref_schema_version"] = get_schema_version(true);
816 authenticate_user(null, null, true);
819 if (!$_SESSION["uid"]) {
821 setcookie(session_name(), '', time()-42000, '/');
828 /* bump login timestamp */
829 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
831 $_SESSION["last_login_update"] = time();
834 if ($_SESSION["uid"]) {
836 load_user_plugins($_SESSION["uid"]);
840 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
841 $_SESSION["uid"] . " AND
842 (SELECT COUNT(id) FROM ttrss_feeds WHERE
843 ttrss_feeds.id = feed_id) = 0");
845 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
846 $_SESSION["uid"] . " AND
847 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
848 ttrss_feed_categories.id = feed_id) = 0");
855 function truncate_string($str, $max_len, $suffix = '…') {
856 if (mb_strlen($str, "utf-8") > $max_len - 3) {
857 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
863 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
866 $source_tz = new DateTimeZone($source_tz);
867 } catch (Exception
$e) {
868 $source_tz = new DateTimeZone('UTC');
872 $dest_tz = new DateTimeZone($dest_tz);
873 } catch (Exception
$e) {
874 $dest_tz = new DateTimeZone('UTC');
877 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
878 return $dt->format('U') +
$dest_tz->getOffset($dt);
881 function make_local_datetime($timestamp, $long, $owner_uid = false,
882 $no_smart_dt = false) {
884 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
885 if (!$timestamp) $timestamp = '1970-01-01 0:00';
890 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
892 $timestamp = substr($timestamp, 0, 19);
894 # We store date in UTC internally
895 $dt = new DateTime($timestamp, $utc_tz);
897 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
899 if ($user_tz_string != 'Automatic') {
902 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
903 } catch (Exception
$e) {
907 $tz_offset = $user_tz->getOffset($dt);
909 $tz_offset = (int) -$_SESSION["clientTzOffset"];
912 $user_timestamp = $dt->format('U') +
$tz_offset;
915 return smart_date_time($user_timestamp,
916 $tz_offset, $owner_uid);
919 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
921 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
923 return date($format, $user_timestamp);
927 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false) {
928 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
930 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() +
$tz_offset)) {
931 return date("G:i", $timestamp);
932 } else if (date("Y", $timestamp) == date("Y", time() +
$tz_offset)) {
933 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
934 return date($format, $timestamp);
936 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
937 return date($format, $timestamp);
941 function sql_bool_to_bool($s) {
942 if ($s == "t" ||
$s == "1" ||
strtolower($s) == "true") {
949 function bool_to_sql_bool($s) {
957 // Session caching removed due to causing wrong redirects to upgrade
958 // script when get_schema_version() is called on an obsolete session
959 // created on a previous schema version.
960 function get_schema_version($nocache = false) {
961 global $schema_version;
963 if (!$schema_version && !$nocache) {
964 $result = db_query("SELECT schema_version FROM ttrss_version");
965 $version = db_fetch_result($result, 0, "schema_version");
966 $schema_version = $version;
969 return $schema_version;
973 function sanity_check() {
974 require_once 'errors.php';
977 $schema_version = get_schema_version(true);
979 if ($schema_version != SCHEMA_VERSION
) {
983 if (DB_TYPE
== "mysql") {
984 $result = db_query("SELECT true", false);
985 if (db_num_rows($result) != 1) {
990 if (db_escape_string("testTEST") != "testTEST") {
994 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
997 function file_is_locked($filename) {
998 if (file_exists(LOCK_DIRECTORY
. "/$filename")) {
999 if (function_exists('flock')) {
1000 $fp = @fopen
(LOCK_DIRECTORY
. "/$filename", "r");
1002 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1003 flock($fp, LOCK_UN
);
1013 return true; // consider the file always locked and skip the test
1020 function make_lockfile($filename) {
1021 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1023 if ($fp && flock($fp, LOCK_EX | LOCK_NB
)) {
1024 $stat_h = fstat($fp);
1025 $stat_f = stat(LOCK_DIRECTORY
. "/$filename");
1027 if (strtoupper(substr(PHP_OS
, 0, 3)) !== 'WIN') {
1028 if ($stat_h["ino"] != $stat_f["ino"] ||
1029 $stat_h["dev"] != $stat_f["dev"]) {
1035 if (function_exists('posix_getpid')) {
1036 fwrite($fp, posix_getpid() . "\n");
1044 function make_stampfile($filename) {
1045 $fp = fopen(LOCK_DIRECTORY
. "/$filename", "w");
1047 if (flock($fp, LOCK_EX | LOCK_NB
)) {
1048 fwrite($fp, time() . "\n");
1049 flock($fp, LOCK_UN
);
1057 function sql_random_function() {
1058 if (DB_TYPE
== "mysql") {
1065 function catchup_feed($feed, $cat_view, $owner_uid = false, $max_id = false, $mode = 'all') {
1067 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
1069 //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
1071 // Todo: all this interval stuff needs some generic generator function
1073 $date_qpart = "false";
1077 if (DB_TYPE
== "pgsql") {
1078 $date_qpart = "date_entered < NOW() - INTERVAL '1 day' ";
1080 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY) ";
1084 if (DB_TYPE
== "pgsql") {
1085 $date_qpart = "date_entered < NOW() - INTERVAL '1 week' ";
1087 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 WEEK) ";
1091 if (DB_TYPE
== "pgsql") {
1092 $date_qpart = "date_entered < NOW() - INTERVAL '2 week' ";
1094 $date_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 2 WEEK) ";
1098 $date_qpart = "true";
1101 if (is_numeric($feed)) {
1107 $children = getChildCategories($feed, $owner_uid);
1108 array_push($children, $feed);
1110 $children = join(",", $children);
1112 $cat_qpart = "cat_id IN ($children)";
1114 $cat_qpart = "cat_id IS NULL";
1117 db_query("UPDATE ttrss_user_entries
1118 SET unread = false, last_read = NOW() WHERE ref_id IN
1120 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1121 AND owner_uid = $owner_uid AND unread = true AND feed_id IN
1122 (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart) as tmp)");
1124 } else if ($feed == -2) {
1126 db_query("UPDATE ttrss_user_entries
1127 SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1128 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1129 AND unread = true AND $date_qpart AND owner_uid = $owner_uid");
1132 } else if ($feed > 0) {
1134 db_query("UPDATE ttrss_user_entries
1135 SET unread = false, last_read = NOW() WHERE ref_id IN
1137 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1138 AND owner_uid = $owner_uid AND unread = true AND feed_id = $feed AND $date_qpart) as tmp)");
1140 } else if ($feed < 0 && $feed > LABEL_BASE_INDEX
) { // special, like starred
1143 db_query("UPDATE ttrss_user_entries
1144 SET unread = false, last_read = NOW() WHERE ref_id IN
1146 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1147 AND owner_uid = $owner_uid AND unread = true AND marked = true AND $date_qpart) as tmp)");
1151 db_query("UPDATE ttrss_user_entries
1152 SET unread = false, last_read = NOW() WHERE ref_id IN
1154 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1155 AND owner_uid = $owner_uid AND unread = true AND published = true AND $date_qpart) as tmp)");
1160 $intl = get_pref("FRESH_ARTICLE_MAX_AGE");
1162 if (DB_TYPE
== "pgsql") {
1163 $match_part = "date_entered > NOW() - INTERVAL '$intl hour' ";
1165 $match_part = "date_entered > DATE_SUB(NOW(),
1166 INTERVAL $intl HOUR) ";
1169 db_query("UPDATE ttrss_user_entries
1170 SET unread = false, last_read = NOW() WHERE ref_id IN
1172 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1173 AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $match_part) as tmp)");
1177 db_query("UPDATE ttrss_user_entries
1178 SET unread = false, last_read = NOW() WHERE ref_id IN
1180 (SELECT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id
1181 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1184 } else if ($feed < LABEL_BASE_INDEX
) { // label
1186 $label_id = feed_to_label_id($feed);
1188 db_query("UPDATE ttrss_user_entries
1189 SET unread = false, last_read = NOW() WHERE ref_id IN
1191 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id
1192 AND label_id = '$label_id' AND ref_id = article_id
1193 AND owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1197 ccache_update($feed, $owner_uid, $cat_view);
1200 db_query("UPDATE ttrss_user_entries
1201 SET unread = false, last_read = NOW() WHERE ref_id IN
1203 (SELECT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id
1204 AND post_int_id = int_id AND tag_name = '$feed'
1205 AND ttrss_user_entries.owner_uid = $owner_uid AND unread = true AND $date_qpart) as tmp)");
1210 function getAllCounters() {
1211 $data = getGlobalCounters();
1213 $data = array_merge($data, getVirtCounters());
1214 $data = array_merge($data, getLabelCounters());
1215 $data = array_merge($data, getFeedCounters());
1216 $data = array_merge($data, getCategoryCounters());
1221 function getCategoryTitle($cat_id) {
1223 if ($cat_id == -1) {
1224 return __("Special");
1225 } else if ($cat_id == -2) {
1226 return __("Labels");
1229 $result = db_query("SELECT title FROM ttrss_feed_categories WHERE
1232 if (db_num_rows($result) == 1) {
1233 return db_fetch_result($result, 0, "title");
1235 return __("Uncategorized");
1241 function getCategoryCounters() {
1244 /* Labels category */
1246 $cv = array("id" => -2, "kind" => "cat",
1247 "counter" => getCategoryUnread(-2));
1249 array_push($ret_arr, $cv);
1251 $result = db_query("SELECT id AS cat_id, value AS unread,
1252 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1253 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1254 FROM ttrss_feed_categories, ttrss_cat_counters_cache
1255 WHERE ttrss_cat_counters_cache.feed_id = id AND
1256 ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1257 ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1259 while ($line = db_fetch_assoc($result)) {
1260 $line["cat_id"] = (int) $line["cat_id"];
1262 if ($line["num_children"] > 0) {
1263 $child_counter = getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
1268 $cv = array("id" => $line["cat_id"], "kind" => "cat",
1269 "counter" => $line["unread"] +
$child_counter);
1271 array_push($ret_arr, $cv);
1274 /* Special case: NULL category doesn't actually exist in the DB */
1276 $cv = array("id" => 0, "kind" => "cat",
1277 "counter" => (int) ccache_find(0, $_SESSION["uid"], true));
1279 array_push($ret_arr, $cv);
1284 // only accepts real cats (>= 0)
1285 function getCategoryChildrenUnread($cat, $owner_uid = false) {
1286 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1288 $result = db_query("SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1289 AND owner_uid = $owner_uid");
1293 while ($line = db_fetch_assoc($result)) {
1294 $unread +
= getCategoryUnread($line["id"], $owner_uid);
1295 $unread +
= getCategoryChildrenUnread($line["id"], $owner_uid);
1301 function getCategoryUnread($cat, $owner_uid = false) {
1303 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1308 $cat_query = "cat_id = '$cat'";
1310 $cat_query = "cat_id IS NULL";
1313 $result = db_query("SELECT id FROM ttrss_feeds WHERE $cat_query
1314 AND owner_uid = " . $owner_uid);
1316 $cat_feeds = array();
1317 while ($line = db_fetch_assoc($result)) {
1318 array_push($cat_feeds, "feed_id = " . $line["id"]);
1321 if (count($cat_feeds) == 0) return 0;
1323 $match_part = implode(" OR ", $cat_feeds);
1325 $result = db_query("SELECT COUNT(int_id) AS unread
1326 FROM ttrss_user_entries
1327 WHERE unread = true AND ($match_part)
1328 AND owner_uid = " . $owner_uid);
1332 # this needs to be rewritten
1333 while ($line = db_fetch_assoc($result)) {
1334 $unread +
= $line["unread"];
1338 } else if ($cat == -1) {
1339 return getFeedUnread(-1) +
getFeedUnread(-2) +
getFeedUnread(-3) +
getFeedUnread(0);
1340 } else if ($cat == -2) {
1342 $result = db_query("
1343 SELECT COUNT(unread) AS unread FROM
1344 ttrss_user_entries, ttrss_user_labels2
1345 WHERE article_id = ref_id AND unread = true
1346 AND ttrss_user_entries.owner_uid = '$owner_uid'");
1348 $unread = db_fetch_result($result, 0, "unread");
1355 function getFeedUnread($feed, $is_cat = false) {
1356 return getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1359 function getLabelUnread($label_id, $owner_uid = false) {
1360 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1362 $result = db_query("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1363 WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1365 if (db_num_rows($result) != 0) {
1366 return db_fetch_result($result, 0, "unread");
1372 function getFeedArticles($feed, $is_cat = false, $unread_only = false,
1373 $owner_uid = false) {
1375 $n_feed = (int) $feed;
1376 $need_entries = false;
1378 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1381 $unread_qpart = "unread = true";
1383 $unread_qpart = "true";
1387 return getCategoryUnread($n_feed, $owner_uid);
1388 } else if ($n_feed == -6) {
1390 } else if ($feed != "0" && $n_feed == 0) {
1392 $feed = db_escape_string($feed);
1394 $result = db_query("SELECT SUM((SELECT COUNT(int_id)
1395 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1396 AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1397 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1398 return db_fetch_result($result, 0, "count");
1400 } else if ($n_feed == -1) {
1401 $match_part = "marked = true";
1402 } else if ($n_feed == -2) {
1403 $match_part = "published = true";
1404 } else if ($n_feed == -3) {
1405 $match_part = "unread = true AND score >= 0";
1407 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
1409 if (DB_TYPE
== "pgsql") {
1410 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1412 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1415 $need_entries = true;
1417 } else if ($n_feed == -4) {
1418 $match_part = "true";
1419 } else if ($n_feed >= 0) {
1422 $match_part = "feed_id = '$n_feed'";
1424 $match_part = "feed_id IS NULL";
1427 } else if ($feed < LABEL_BASE_INDEX
) {
1429 $label_id = feed_to_label_id($feed);
1431 return getLabelUnread($label_id, $owner_uid);
1437 if ($need_entries) {
1438 $from_qpart = "ttrss_user_entries,ttrss_entries";
1439 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1441 $from_qpart = "ttrss_user_entries";
1444 $query = "SELECT count(int_id) AS unread
1445 FROM $from_qpart WHERE
1446 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1448 //echo "[$feed/$query]\n";
1450 $result = db_query($query);
1454 $result = db_query("SELECT COUNT(post_int_id) AS unread
1455 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1456 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1457 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1460 $unread = db_fetch_result($result, 0, "unread");
1465 function getGlobalUnread($user_id = false) {
1468 $user_id = $_SESSION["uid"];
1471 $result = db_query("SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1472 WHERE owner_uid = '$user_id' AND feed_id > 0");
1474 $c_id = db_fetch_result($result, 0, "c_id");
1479 function getGlobalCounters($global_unread = -1) {
1482 if ($global_unread == -1) {
1483 $global_unread = getGlobalUnread();
1486 $cv = array("id" => "global-unread",
1487 "counter" => (int) $global_unread);
1489 array_push($ret_arr, $cv);
1491 $result = db_query("SELECT COUNT(id) AS fn FROM
1492 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1494 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1496 $cv = array("id" => "subscribed-feeds",
1497 "counter" => (int) $subscribed_feeds);
1499 array_push($ret_arr, $cv);
1504 function getVirtCounters() {
1508 for ($i = 0; $i >= -4; $i--) {
1510 $count = getFeedUnread($i);
1512 if ($i == 0 ||
$i == -1 ||
$i == -2)
1513 $auxctr = getFeedArticles($i, false);
1517 $cv = array("id" => $i,
1518 "counter" => (int) $count,
1519 "auxcounter" => $auxctr);
1521 // if (get_pref('EXTENDED_FEEDLIST'))
1522 // $cv["xmsg"] = getFeedArticles($i)." ".__("total");
1524 array_push($ret_arr, $cv);
1527 $feeds = PluginHost
::getInstance()->get_feeds(-1);
1529 if (is_array($feeds)) {
1530 foreach ($feeds as $feed) {
1531 $cv = array("id" => PluginHost
::pfeed_to_feed_id($feed['id']),
1532 "counter" => $feed['sender']->get_unread($feed['id']));
1534 if (method_exists($feed['sender'], 'get_total'))
1535 $cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
1537 array_push($ret_arr, $cv);
1544 function getLabelCounters($descriptions = false) {
1548 $owner_uid = $_SESSION["uid"];
1550 $result = db_query("SELECT id,caption,COUNT(u1.unread) AS unread,COUNT(u2.unread) AS total
1551 FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1552 (ttrss_labels2.id = label_id)
1553 LEFT JOIN ttrss_user_entries AS u1 ON (u1.ref_id = article_id AND u1.unread = true
1554 AND u1.owner_uid = $owner_uid)
1555 LEFT JOIN ttrss_user_entries AS u2 ON (u2.ref_id = article_id AND u2.unread = false
1556 AND u2.owner_uid = $owner_uid)
1557 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1558 ttrss_labels2.caption");
1560 while ($line = db_fetch_assoc($result)) {
1562 $id = label_to_feed_id($line["id"]);
1564 $cv = array("id" => $id,
1565 "counter" => (int) $line["unread"],
1566 "auxcounter" => (int) $line["total"]);
1569 $cv["description"] = $line["caption"];
1571 array_push($ret_arr, $cv);
1577 function getFeedCounters($active_feed = false) {
1581 $query = "SELECT ttrss_feeds.id,
1583 ".SUBSTRING_FOR_DATE
."(ttrss_feeds.last_updated,1,19) AS last_updated,
1584 last_error, value AS count
1585 FROM ttrss_feeds, ttrss_counters_cache
1586 WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1587 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1588 AND ttrss_counters_cache.feed_id = id";
1590 $result = db_query($query);
1591 $fctrs_modified = false;
1593 while ($line = db_fetch_assoc($result)) {
1596 $count = $line["count"];
1597 $last_error = htmlspecialchars($line["last_error"]);
1599 $last_updated = make_local_datetime($line['last_updated'], false);
1601 $has_img = feed_has_icon($id);
1603 if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1606 $cv = array("id" => $id,
1607 "updated" => $last_updated,
1608 "counter" => (int) $count,
1609 "has_img" => (int) $has_img);
1612 $cv["error"] = $last_error;
1614 // if (get_pref('EXTENDED_FEEDLIST'))
1615 // $cv["xmsg"] = getFeedArticles($id)." ".__("total");
1617 if ($active_feed && $id == $active_feed)
1618 $cv["title"] = truncate_string($line["title"], 30);
1620 array_push($ret_arr, $cv);
1627 function get_pgsql_version() {
1628 $result = db_query("SELECT version() AS version");
1629 $version = explode(" ", db_fetch_result($result, 0, "version"));
1634 * @return array (code => Status code, message => error message if available)
1636 * 0 - OK, Feed already exists
1637 * 1 - OK, Feed added
1639 * 3 - URL content is HTML, no feeds available
1640 * 4 - URL content is HTML which contains multiple feeds.
1641 * Here you should call extractfeedurls in rpc-backend
1642 * to get all possible feeds.
1643 * 5 - Couldn't download the URL content.
1644 * 6 - Content is an invalid XML.
1646 function subscribe_to_feed($url, $cat_id = 0,
1647 $auth_login = '', $auth_pass = '') {
1649 global $fetch_last_error;
1651 require_once "include/rssfuncs.php";
1653 $url = fix_url($url);
1655 if (!$url ||
!validate_feed_url($url)) return array("code" => 2);
1657 $contents = @fetch_file_contents
($url, false, $auth_login, $auth_pass);
1660 return array("code" => 5, "message" => $fetch_last_error);
1663 if (is_html($contents)) {
1664 $feedUrls = get_feeds_from_html($url, $contents);
1666 if (count($feedUrls) == 0) {
1667 return array("code" => 3);
1668 } else if (count($feedUrls) > 1) {
1669 return array("code" => 4, "feeds" => $feedUrls);
1671 //use feed url as new URL
1672 $url = key($feedUrls);
1675 /* libxml_use_internal_errors(true);
1676 $doc = new DOMDocument();
1677 $doc->loadXML($contents);
1678 $error = libxml_get_last_error();
1679 libxml_clear_errors();
1682 $error_message = format_libxml_error($error);
1684 return array("code" => 6, "message" => $error_message);
1687 if ($cat_id == "0" ||
!$cat_id) {
1688 $cat_qpart = "NULL";
1690 $cat_qpart = "'$cat_id'";
1694 "SELECT id FROM ttrss_feeds
1695 WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1697 if (strlen(FEED_CRYPT_KEY
) > 0) {
1698 require_once "crypt.php";
1699 $auth_pass = substr(encrypt_string($auth_pass), 0, 250);
1700 $auth_pass_encrypted = 'true';
1702 $auth_pass_encrypted = 'false';
1705 $auth_pass = db_escape_string($auth_pass);
1707 if (db_num_rows($result) == 0) {
1709 "INSERT INTO ttrss_feeds
1710 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted)
1711 VALUES ('".$_SESSION["uid"]."', '$url',
1712 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, $auth_pass_encrypted)");
1715 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1716 AND owner_uid = " . $_SESSION["uid"]);
1718 $feed_id = db_fetch_result($result, 0, "id");
1721 update_rss_feed($feed_id, true);
1724 return array("code" => 1);
1726 return array("code" => 0);
1730 function print_feed_select($id, $default_id = "",
1731 $attributes = "", $include_all_feeds = true,
1732 $root_id = false, $nest_level = 0) {
1735 print "<select id=\"$id\" name=\"$id\" $attributes>";
1736 if ($include_all_feeds) {
1737 $is_selected = ("0" == $default_id) ?
"selected=\"1\"" : "";
1738 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1742 if (get_pref('ENABLE_FEED_CATS')) {
1745 $parent_qpart = "parent_cat = '$root_id'";
1747 $parent_qpart = "parent_cat IS NULL";
1749 $result = db_query("SELECT id,title,
1750 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1751 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1752 FROM ttrss_feed_categories
1753 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1755 while ($line = db_fetch_assoc($result)) {
1757 for ($i = 0; $i < $nest_level; $i++
)
1758 $line["title"] = " - " . $line["title"];
1760 $is_selected = ("CAT:".$line["id"] == $default_id) ?
"selected=\"1\"" : "";
1762 printf("<option $is_selected value='CAT:%d'>%s</option>",
1763 $line["id"], htmlspecialchars($line["title"]));
1765 if ($line["num_children"] > 0)
1766 print_feed_select($id, $default_id, $attributes,
1767 $include_all_feeds, $line["id"], $nest_level+
1);
1769 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1770 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1772 while ($fline = db_fetch_assoc($feed_result)) {
1773 $is_selected = ($fline["id"] == $default_id) ?
"selected=\"1\"" : "";
1775 $fline["title"] = " + " . $fline["title"];
1777 for ($i = 0; $i < $nest_level; $i++
)
1778 $fline["title"] = " - " . $fline["title"];
1780 printf("<option $is_selected value='%d'>%s</option>",
1781 $fline["id"], htmlspecialchars($fline["title"]));
1786 $default_is_cat = ($default_id == "CAT:0");
1787 $is_selected = $default_is_cat ?
"selected=\"1\"" : "";
1789 printf("<option $is_selected value='CAT:0'>%s</option>",
1790 __("Uncategorized"));
1792 $feed_result = db_query("SELECT id,title FROM ttrss_feeds
1793 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1795 while ($fline = db_fetch_assoc($feed_result)) {
1796 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ?
"selected=\"1\"" : "";
1798 $fline["title"] = " + " . $fline["title"];
1800 for ($i = 0; $i < $nest_level; $i++
)
1801 $fline["title"] = " - " . $fline["title"];
1803 printf("<option $is_selected value='%d'>%s</option>",
1804 $fline["id"], htmlspecialchars($fline["title"]));
1809 $result = db_query("SELECT id,title FROM ttrss_feeds
1810 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1812 while ($line = db_fetch_assoc($result)) {
1814 $is_selected = ($line["id"] == $default_id) ?
"selected=\"1\"" : "";
1816 printf("<option $is_selected value='%d'>%s</option>",
1817 $line["id"], htmlspecialchars($line["title"]));
1826 function print_feed_cat_select($id, $default_id,
1827 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1830 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1834 $parent_qpart = "parent_cat = '$root_id'";
1836 $parent_qpart = "parent_cat IS NULL";
1838 $result = db_query("SELECT id,title,
1839 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1840 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1841 FROM ttrss_feed_categories
1842 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1844 while ($line = db_fetch_assoc($result)) {
1845 if ($line["id"] == $default_id) {
1846 $is_selected = "selected=\"1\"";
1851 for ($i = 0; $i < $nest_level; $i++
)
1852 $line["title"] = " - " . $line["title"];
1855 printf("<option $is_selected value='%d'>%s</option>",
1856 $line["id"], htmlspecialchars($line["title"]));
1858 if ($line["num_children"] > 0)
1859 print_feed_cat_select($id, $default_id, $attributes,
1860 $include_all_cats, $line["id"], $nest_level+
1);
1864 if ($include_all_cats) {
1865 if (db_num_rows($result) > 0) {
1866 print "<option disabled=\"1\">--------</option>";
1869 if ($default_id == 0) {
1870 $is_selected = "selected=\"1\"";
1875 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1881 function checkbox_to_sql_bool($val) {
1882 return ($val == "on") ?
"true" : "false";
1885 function getFeedCatTitle($id) {
1887 return __("Special");
1888 } else if ($id < LABEL_BASE_INDEX
) {
1889 return __("Labels");
1890 } else if ($id > 0) {
1891 $result = db_query("SELECT ttrss_feed_categories.title
1892 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1893 cat_id = ttrss_feed_categories.id");
1894 if (db_num_rows($result) == 1) {
1895 return db_fetch_result($result, 0, "title");
1897 return __("Uncategorized");
1900 return "getFeedCatTitle($id) failed";
1905 function getFeedIcon($id) {
1908 return "images/archive.png";
1911 return "images/star.png";
1914 return "images/feed.png";
1917 return "images/fresh.png";
1920 return "images/folder.png";
1923 return "images/time.png";
1926 if ($id < LABEL_BASE_INDEX
) {
1927 return "images/label.png";
1929 if (file_exists(ICONS_DIR
. "/$id.ico"))
1930 return ICONS_URL
. "/$id.ico";
1938 function getFeedTitle($id, $cat = false) {
1940 return getCategoryTitle($id);
1941 } else if ($id == -1) {
1942 return __("Starred articles");
1943 } else if ($id == -2) {
1944 return __("Published articles");
1945 } else if ($id == -3) {
1946 return __("Fresh articles");
1947 } else if ($id == -4) {
1948 return __("All articles");
1949 } else if ($id === 0 ||
$id === "0") {
1950 return __("Archived articles");
1951 } else if ($id == -6) {
1952 return __("Recently read");
1953 } else if ($id < LABEL_BASE_INDEX
) {
1954 $label_id = feed_to_label_id($id);
1955 $result = db_query("SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1956 if (db_num_rows($result) == 1) {
1957 return db_fetch_result($result, 0, "caption");
1959 return "Unknown label ($label_id)";
1962 } else if (is_numeric($id) && $id > 0) {
1963 $result = db_query("SELECT title FROM ttrss_feeds WHERE id = '$id'");
1964 if (db_num_rows($result) == 1) {
1965 return db_fetch_result($result, 0, "title");
1967 return "Unknown feed ($id)";
1974 function make_init_params() {
1977 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1978 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1979 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1980 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1982 $params[strtolower($param)] = (int) get_pref($param);
1985 $params["icons_url"] = ICONS_URL
;
1986 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME
;
1987 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1988 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1989 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1990 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1991 $params["label_base_index"] = (int) LABEL_BASE_INDEX
;
1993 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1994 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1996 $max_feed_id = db_fetch_result($result, 0, "mid");
1997 $num_feeds = db_fetch_result($result, 0, "nf");
1999 $params["max_feed_id"] = (int) $max_feed_id;
2000 $params["num_feeds"] = (int) $num_feeds;
2002 $params["hotkeys"] = get_hotkeys_map();
2004 $params["csrf_token"] = $_SESSION["csrf_token"];
2005 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
2007 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE
;
2012 function get_hotkeys_info() {
2014 __("Navigation") => array(
2015 "next_feed" => __("Open next feed"),
2016 "prev_feed" => __("Open previous feed"),
2017 "next_article" => __("Open next article"),
2018 "prev_article" => __("Open previous article"),
2019 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
2020 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
2021 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
2022 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
2023 "search_dialog" => __("Show search dialog")),
2024 __("Article") => array(
2025 "toggle_mark" => __("Toggle starred"),
2026 "toggle_publ" => __("Toggle published"),
2027 "toggle_unread" => __("Toggle unread"),
2028 "edit_tags" => __("Edit tags"),
2029 "dismiss_selected" => __("Dismiss selected"),
2030 "dismiss_read" => __("Dismiss read"),
2031 "open_in_new_window" => __("Open in new window"),
2032 "catchup_below" => __("Mark below as read"),
2033 "catchup_above" => __("Mark above as read"),
2034 "article_scroll_down" => __("Scroll down"),
2035 "article_scroll_up" => __("Scroll up"),
2036 "select_article_cursor" => __("Select article under cursor"),
2037 "email_article" => __("Email article"),
2038 "close_article" => __("Close/collapse article"),
2039 "toggle_expand" => __("Toggle article expansion (combined mode)"),
2040 "toggle_widescreen" => __("Toggle widescreen mode"),
2041 "toggle_embed_original" => __("Toggle embed original")),
2042 __("Article selection") => array(
2043 "select_all" => __("Select all articles"),
2044 "select_unread" => __("Select unread"),
2045 "select_marked" => __("Select starred"),
2046 "select_published" => __("Select published"),
2047 "select_invert" => __("Invert selection"),
2048 "select_none" => __("Deselect everything")),
2049 __("Feed") => array(
2050 "feed_refresh" => __("Refresh current feed"),
2051 "feed_unhide_read" => __("Un/hide read feeds"),
2052 "feed_subscribe" => __("Subscribe to feed"),
2053 "feed_edit" => __("Edit feed"),
2054 "feed_catchup" => __("Mark as read"),
2055 "feed_reverse" => __("Reverse headlines"),
2056 "feed_debug_update" => __("Debug feed update"),
2057 "catchup_all" => __("Mark all feeds as read"),
2058 "cat_toggle_collapse" => __("Un/collapse current category"),
2059 "toggle_combined_mode" => __("Toggle combined mode"),
2060 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
2061 __("Go to") => array(
2062 "goto_all" => __("All articles"),
2063 "goto_fresh" => __("Fresh"),
2064 "goto_marked" => __("Starred"),
2065 "goto_published" => __("Published"),
2066 "goto_tagcloud" => __("Tag cloud"),
2067 "goto_prefs" => __("Preferences")),
2068 __("Other") => array(
2069 "create_label" => __("Create label"),
2070 "create_filter" => __("Create filter"),
2071 "collapse_sidebar" => __("Un/collapse sidebar"),
2072 "help_dialog" => __("Show help dialog"))
2075 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_INFO
) as $plugin) {
2076 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
2082 function get_hotkeys_map() {
2084 // "navigation" => array(
2087 "n" => "next_article",
2088 "p" => "prev_article",
2089 "(38)|up" => "prev_article",
2090 "(40)|down" => "next_article",
2091 // "^(38)|Ctrl-up" => "prev_article_noscroll",
2092 // "^(40)|Ctrl-down" => "next_article_noscroll",
2093 "(191)|/" => "search_dialog",
2094 // "article" => array(
2095 "s" => "toggle_mark",
2096 "*s" => "toggle_publ",
2097 "u" => "toggle_unread",
2098 "*t" => "edit_tags",
2099 "*d" => "dismiss_selected",
2100 "*x" => "dismiss_read",
2101 "o" => "open_in_new_window",
2102 "c p" => "catchup_below",
2103 "c n" => "catchup_above",
2104 "*n" => "article_scroll_down",
2105 "*p" => "article_scroll_up",
2106 "*(38)|Shift+up" => "article_scroll_up",
2107 "*(40)|Shift+down" => "article_scroll_down",
2108 "a *w" => "toggle_widescreen",
2109 "a e" => "toggle_embed_original",
2110 "e" => "email_article",
2111 "a q" => "close_article",
2112 // "article_selection" => array(
2113 "a a" => "select_all",
2114 "a u" => "select_unread",
2115 "a *u" => "select_marked",
2116 "a p" => "select_published",
2117 "a i" => "select_invert",
2118 "a n" => "select_none",
2120 "f r" => "feed_refresh",
2121 "f a" => "feed_unhide_read",
2122 "f s" => "feed_subscribe",
2123 "f e" => "feed_edit",
2124 "f q" => "feed_catchup",
2125 "f x" => "feed_reverse",
2126 "f *d" => "feed_debug_update",
2127 "f *c" => "toggle_combined_mode",
2128 "f c" => "toggle_cdm_expanded",
2129 "*q" => "catchup_all",
2130 "x" => "cat_toggle_collapse",
2132 "g a" => "goto_all",
2133 "g f" => "goto_fresh",
2134 "g s" => "goto_marked",
2135 "g p" => "goto_published",
2136 "g t" => "goto_tagcloud",
2137 "g *p" => "goto_prefs",
2138 // "other" => array(
2139 "(9)|Tab" => "select_article_cursor", // tab
2140 "c l" => "create_label",
2141 "c f" => "create_filter",
2142 "c s" => "collapse_sidebar",
2143 "^(191)|Ctrl+/" => "help_dialog",
2146 if (get_pref('COMBINED_DISPLAY_MODE')) {
2147 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2148 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2151 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_HOTKEY_MAP
) as $plugin) {
2152 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2155 $prefixes = array();
2157 foreach (array_keys($hotkeys) as $hotkey) {
2158 $pair = explode(" ", $hotkey, 2);
2160 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2161 array_push($prefixes, $pair[0]);
2165 return array($prefixes, $hotkeys);
2168 function make_runtime_info() {
2171 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2172 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2174 $max_feed_id = db_fetch_result($result, 0, "mid");
2175 $num_feeds = db_fetch_result($result, 0, "nf");
2177 $data["max_feed_id"] = (int) $max_feed_id;
2178 $data["num_feeds"] = (int) $num_feeds;
2180 $data['last_article_id'] = getLastArticleId();
2181 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
2183 $data['dep_ts'] = calculate_dep_timestamp();
2184 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
2186 if (file_exists(LOCK_DIRECTORY
. "/update_daemon.lock")) {
2188 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2190 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2192 $stamp = (int) @file_get_contents
(LOCK_DIRECTORY
. "/update_daemon.stamp");
2195 $stamp_delta = time() - $stamp;
2197 if ($stamp_delta > 1800) {
2201 $_SESSION["daemon_stamp_check"] = time();
2204 $data['daemon_stamp_ok'] = $stamp_check;
2206 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2208 $data['daemon_stamp'] = $stamp_fmt;
2213 if ($_SESSION["last_version_check"] +
86400 +
rand(-1000, 1000) < time()) {
2214 $new_version_details = @check_for_update
();
2216 $data['new_version_available'] = (int) ($new_version_details != false);
2218 $_SESSION["last_version_check"] = time();
2219 $_SESSION["version_data"] = $new_version_details;
2225 function search_to_sql($search) {
2227 $search_query_part = "";
2229 $keywords = explode(" ", $search);
2230 $query_keywords = array();
2231 $search_words = array();
2233 foreach ($keywords as $k) {
2234 if (strpos($k, "-") === 0) {
2241 $commandpair = explode(":", mb_strtolower($k), 2);
2243 switch ($commandpair[0]) {
2245 if ($commandpair[1]) {
2246 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
2247 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2249 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2250 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2251 array_push($search_words, $k);
2255 if ($commandpair[1]) {
2256 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
2257 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2259 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2260 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2261 array_push($search_words, $k);
2265 if ($commandpair[1]) {
2266 if ($commandpair[1] == "true")
2267 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2268 else if ($commandpair[1] == "false")
2269 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2271 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
2272 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
2274 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2275 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2276 if (!$not) array_push($search_words, $k);
2281 if ($commandpair[1]) {
2282 if ($commandpair[1] == "true")
2283 array_push($query_keywords, "($not (marked = true))");
2285 array_push($query_keywords, "($not (marked = false))");
2287 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2288 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2289 if (!$not) array_push($search_words, $k);
2293 if ($commandpair[1]) {
2294 if ($commandpair[1] == "true")
2295 array_push($query_keywords, "($not (published = true))");
2297 array_push($query_keywords, "($not (published = false))");
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);
2306 if (strpos($k, "@") === 0) {
2308 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
2309 $orig_ts = strtotime(substr($k, 1));
2310 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2312 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2314 array_push($query_keywords, "(".SUBSTRING_FOR_DATE
."(updated,1,LENGTH('$k')) $not = '$k')");
2316 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2317 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2319 if (!$not) array_push($search_words, $k);
2324 $search_query_part = implode("AND", $query_keywords);
2326 return array($search_query_part, $search_words);
2329 function getParentCategories($cat, $owner_uid) {
2332 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
2333 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2335 while ($line = db_fetch_assoc($result)) {
2336 array_push($rv, $line["parent_cat"]);
2337 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
2343 function getChildCategories($cat, $owner_uid) {
2346 $result = db_query("SELECT id FROM ttrss_feed_categories
2347 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2349 while ($line = db_fetch_assoc($result)) {
2350 array_push($rv, $line["id"]);
2351 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
2357 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) {
2359 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2361 $ext_tables_part = "";
2362 $search_words = array();
2366 if (SPHINX_ENABLED
) {
2367 $ids = join(",", @sphinx_search
($search, 0, 500));
2370 $search_query_part = "ref_id IN ($ids) AND ";
2372 $search_query_part = "ref_id = -1 AND ";
2375 list($search_query_part, $search_words) = search_to_sql($search);
2376 $search_query_part .= " AND ";
2380 $search_query_part = "";
2385 if (DB_TYPE
== "pgsql") {
2386 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2388 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2391 $override_order = "updated DESC";
2393 $filter_query_part = filter_to_sql($filter, $owner_uid);
2395 // Try to check if SQL regexp implementation chokes on a valid regexp
2398 $result = db_query("SELECT true AS true_val FROM ttrss_entries,
2399 ttrss_user_entries, ttrss_feeds
2400 WHERE $filter_query_part LIMIT 1", false);
2403 $test = db_fetch_result($result, 0, "true_val");
2406 $filter_query_part = "false AND";
2408 $filter_query_part .= " AND";
2411 $filter_query_part = "false AND";
2415 $filter_query_part = "";
2419 $since_id_part = "ttrss_entries.id > $since_id AND ";
2421 $since_id_part = "";
2424 $view_query_part = "";
2426 if ($view_mode == "adaptive") {
2428 $view_query_part = " ";
2429 } else if ($feed != -1) {
2431 $unread = getFeedUnread($feed, $cat_view);
2433 if ($cat_view && $feed > 0 && $include_children)
2434 $unread +
= getCategoryChildrenUnread($feed);
2437 $view_query_part = " unread = true AND ";
2442 if ($view_mode == "marked") {
2443 $view_query_part = " marked = true AND ";
2446 if ($view_mode == "has_note") {
2447 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
2450 if ($view_mode == "published") {
2451 $view_query_part = " published = true AND ";
2454 if ($view_mode == "unread" && $feed != -6) {
2455 $view_query_part = " unread = true AND ";
2459 $limit_query_part = "LIMIT " . $limit;
2462 $allow_archived = false;
2464 $vfeed_query_part = "";
2466 // override query strategy and enable feed display when searching globally
2467 if ($search && $search_mode == "all_feeds") {
2468 $query_strategy_part = "true";
2469 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2471 } else if (!is_numeric($feed)) {
2472 $query_strategy_part = "true";
2473 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2474 id = feed_id) as feed_title,";
2475 } else if ($search && $search_mode == "this_cat") {
2476 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2479 if ($include_children) {
2480 $subcats = getChildCategories($feed, $owner_uid);
2481 array_push($subcats, $feed);
2482 $cats_qpart = join(",", $subcats);
2484 $cats_qpart = $feed;
2487 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2490 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2493 } else if ($feed > 0) {
2498 if ($include_children) {
2500 $subcats = getChildCategories($feed, $owner_uid);
2502 array_push($subcats, $feed);
2503 $query_strategy_part = "cat_id IN (".
2504 implode(",", $subcats).")";
2507 $query_strategy_part = "cat_id = '$feed'";
2511 $query_strategy_part = "cat_id IS NULL";
2514 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2517 $query_strategy_part = "feed_id = '$feed'";
2519 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2520 $query_strategy_part = "feed_id IS NULL";
2521 $allow_archived = true;
2522 } else if ($feed == 0 && $cat_view) { // uncategorized
2523 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2524 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2525 } else if ($feed == -1) { // starred virtual feed
2526 $query_strategy_part = "marked = true";
2527 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2528 $allow_archived = true;
2530 if (!$override_order) {
2531 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
2534 } else if ($feed == -2) { // published virtual feed OR labels category
2537 $query_strategy_part = "published = true";
2538 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2539 $allow_archived = true;
2541 if (!$override_order) {
2542 $override_order = "last_published DESC, date_entered DESC, updated DESC";
2546 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2548 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2550 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2551 ttrss_user_labels2.article_id = ref_id";
2554 } else if ($feed == -6) { // recently read
2555 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2556 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2557 $allow_archived = true;
2559 if (!$override_order) $override_order = "last_read DESC";
2561 /* } else if ($feed == -7) { // shared
2562 $query_strategy_part = "uuid != ''";
2563 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2564 $allow_archived = true; */
2565 } else if ($feed == -3) { // fresh virtual feed
2566 $query_strategy_part = "unread = true AND score >= 0";
2568 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
2570 if (DB_TYPE
== "pgsql") {
2571 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
2573 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2576 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2577 } else if ($feed == -4) { // all articles virtual feed
2578 $allow_archived = true;
2579 $query_strategy_part = "true";
2580 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2581 } else if ($feed <= LABEL_BASE_INDEX
) { // labels
2582 $label_id = feed_to_label_id($feed);
2584 $query_strategy_part = "label_id = '$label_id' AND
2585 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2586 ttrss_user_labels2.article_id = ref_id";
2588 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2589 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2590 $allow_archived = true;
2593 $query_strategy_part = "true";
2596 $order_by = "score DESC, date_entered DESC, updated DESC";
2598 if ($view_mode == "unread_first") {
2599 $order_by = "unread DESC, $order_by";
2602 if ($override_order) {
2603 $order_by = $override_order;
2606 if ($override_strategy) {
2607 $query_strategy_part = $override_strategy;
2610 if ($override_vfeed) {
2611 $vfeed_query_part = $override_vfeed;
2617 $feed_title = T_sprintf("Search results: %s", $search);
2620 $feed_title = getCategoryTitle($feed);
2622 if (is_numeric($feed) && $feed > 0) {
2623 $result = db_query("SELECT title,site_url,last_error,last_updated
2624 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2626 $feed_title = db_fetch_result($result, 0, "title");
2627 $feed_site_url = db_fetch_result($result, 0, "site_url");
2628 $last_error = db_fetch_result($result, 0, "last_error");
2629 $last_updated = db_fetch_result($result, 0, "last_updated");
2631 $feed_title = getFeedTitle($feed);
2637 $content_query_part = "content, content AS content_preview, ";
2640 if (is_numeric($feed)) {
2643 $feed_kind = "Feeds";
2645 $feed_kind = "Labels";
2648 if ($limit_query_part) {
2649 $offset_query_part = "OFFSET $offset";
2652 // proper override_order applied above
2653 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
2654 if (!$override_order) {
2655 $order_by = "ttrss_feeds.title, $order_by";
2657 $order_by = "ttrss_feeds.title, $override_order";
2661 if (!$allow_archived) {
2662 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2663 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2666 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2667 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2670 if ($vfeed_query_part)
2671 $vfeed_query_part .= "favicon_avg_color,";
2673 $query = "SELECT DISTINCT
2676 ttrss_entries.id,ttrss_entries.title,
2680 always_display_enclosures,
2689 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2690 last_marked, last_published,
2698 ttrss_user_entries.ref_id = ttrss_entries.id AND
2699 ttrss_user_entries.owner_uid = '$owner_uid' AND
2704 $query_strategy_part ORDER BY $order_by
2705 $limit_query_part $offset_query_part";
2707 if ($_REQUEST["debug"]) print $query;
2709 $result = db_query($query);
2714 $select_qpart = "SELECT DISTINCT " .
2718 "ttrss_entries.id as id," .
2733 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2734 "last_marked, last_published, " .
2737 $content_query_part .
2740 $feed_kind = "Tags";
2741 $all_tags = explode(",", $feed);
2742 if ($search_mode == 'any') {
2743 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2744 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2745 $where_qpart = " WHERE " .
2746 "ref_id = ttrss_entries.id AND " .
2747 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2748 "post_int_id = int_id AND $tag_sql AND " .
2750 $search_query_part .
2751 $query_strategy_part . " ORDER BY $order_by " .
2756 $sub_selects = array();
2757 $sub_ands = array();
2758 foreach ($all_tags as $term) {
2759 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");
2766 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2771 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2772 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2773 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2774 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2776 // error_log("TAG SQL: " . $tag_sql);
2777 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2779 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2780 $result = db_query($select_qpart . $from_qpart . $where_qpart);
2783 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words);
2787 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
2788 if (!$owner) $owner = $_SESSION["uid"];
2790 $res = trim($str); if (!$res) return '';
2792 $charset_hack = '<head>
2793 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2796 $res = trim($res); if (!$res) return '';
2798 libxml_use_internal_errors(true);
2800 $doc = new DOMDocument();
2801 $doc->loadHTML($charset_hack . $res);
2802 $xpath = new DOMXPath($doc);
2804 $entries = $xpath->query('(//a[@href]|//img[@src])');
2806 foreach ($entries as $entry) {
2810 if ($entry->hasAttribute('href'))
2811 $entry->setAttribute('href',
2812 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2814 if ($entry->hasAttribute('src')) {
2815 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2817 $cached_filename = CACHE_DIR
. '/images/' . sha1($src) . '.png';
2819 if (file_exists($cached_filename)) {
2820 $src = SELF_URL_PATH
. '/image.php?hash=' . sha1($src);
2823 $entry->setAttribute('src', $src);
2826 if ($entry->nodeName
== 'img') {
2827 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
2828 $force_remove_images ||
$_SESSION["bw_limit"]) {
2830 $p = $doc->createElement('p');
2832 $a = $doc->createElement('a');
2833 $a->setAttribute('href', $entry->getAttribute('src'));
2835 $a->appendChild(new DOMText($entry->getAttribute('src')));
2836 $a->setAttribute('target', '_blank');
2838 $p->appendChild($a);
2840 $entry->parentNode
->replaceChild($p, $entry);
2845 if (strtolower($entry->nodeName
) == "a") {
2846 $entry->setAttribute("target", "_blank");
2850 $entries = $xpath->query('//iframe');
2851 foreach ($entries as $entry) {
2852 $entry->setAttribute('sandbox', 'allow-scripts');
2856 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
2857 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
2858 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
2859 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
2860 'dt', 'em', 'footer', 'figure', 'figcaption',
2861 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
2862 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
2863 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
2864 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2865 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
2866 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2868 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
2870 $disallowed_attributes = array('id', 'style', 'class');
2872 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_SANITIZE
) as $plugin) {
2873 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
2874 if (is_array($retval)) {
2876 $allowed_elements = $retval[1];
2877 $disallowed_attributes = $retval[2];
2883 $doc->removeChild($doc->firstChild
); //remove doctype
2884 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
2886 if ($highlight_words) {
2887 foreach ($highlight_words as $word) {
2889 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
2891 $elements = $xpath->query("//*/text()");
2893 foreach ($elements as $child) {
2895 $fragment = $doc->createDocumentFragment();
2896 $text = $child->textContent
;
2899 while (($pos = mb_stripos($text, $word)) !== false) {
2900 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
2901 $word = mb_substr($text, $pos, mb_strlen($word));
2902 $highlight = $doc->createElement('span');
2903 $highlight->appendChild(new DomText($word));
2904 $highlight->setAttribute('class', 'highlight');
2905 $fragment->appendChild($highlight);
2906 $text = mb_substr($text, $pos +
mb_strlen($word));
2909 if (!empty($text)) $fragment->appendChild(new DomText($text));
2911 $child->parentNode
->replaceChild($fragment, $child);
2916 $res = $doc->saveHTML();
2921 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
2922 $xpath = new DOMXPath($doc);
2923 $entries = $xpath->query('//*');
2925 foreach ($entries as $entry) {
2926 if (!in_array($entry->nodeName
, $allowed_elements)) {
2927 $entry->parentNode
->removeChild($entry);
2930 if ($entry->hasAttributes()) {
2931 $attrs_to_remove = array();
2933 foreach ($entry->attributes
as $attr) {
2935 if (strpos($attr->nodeName
, 'on') === 0) {
2936 array_push($attrs_to_remove, $attr);
2939 if (in_array($attr->nodeName
, $disallowed_attributes)) {
2940 array_push($attrs_to_remove, $attr);
2944 foreach ($attrs_to_remove as $attr) {
2945 $entry->removeAttributeNode($attr);
2953 function check_for_update() {
2954 if (CHECK_FOR_NEW_VERSION
&& $_SESSION['access_level'] >= 10) {
2955 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION
.
2956 "&iid=" . sha1(SELF_URL_PATH
);
2958 $version_data = @fetch_file_contents
($version_url);
2960 if ($version_data) {
2961 $version_data = json_decode($version_data, true);
2962 if ($version_data && $version_data['version']) {
2963 if (version_compare(VERSION_STATIC
, $version_data['version']) == -1) {
2964 return $version_data;
2972 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
2974 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2975 if (count($ids) == 0) return;
2979 foreach ($ids as $id) {
2980 array_push($tmp_ids, "ref_id = '$id'");
2983 $ids_qpart = join(" OR ", $tmp_ids);
2986 db_query("UPDATE ttrss_user_entries SET
2987 unread = false,last_read = NOW()
2988 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2989 } else if ($cmode == 1) {
2990 db_query("UPDATE ttrss_user_entries SET
2992 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2994 db_query("UPDATE ttrss_user_entries SET
2995 unread = NOT unread,last_read = NOW()
2996 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3001 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
3002 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
3004 while ($line = db_fetch_assoc($result)) {
3005 ccache_update($line["feed_id"], $owner_uid);
3009 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
3011 $a_id = db_escape_string($id);
3013 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3015 $query = "SELECT DISTINCT tag_name,
3016 owner_uid as owner FROM
3017 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
3018 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
3022 /* check cache first */
3024 if ($tag_cache === false) {
3025 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
3026 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3028 $tag_cache = db_fetch_result($result, 0, "tag_cache");
3032 $tags = explode(",", $tag_cache);
3035 /* do it the hard way */
3037 $tmp_result = db_query($query);
3039 while ($tmp_line = db_fetch_assoc($tmp_result)) {
3040 array_push($tags, $tmp_line["tag_name"]);
3043 /* update the cache */
3045 $tags_str = db_escape_string(join(",", $tags));
3047 db_query("UPDATE ttrss_user_entries
3048 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
3049 AND owner_uid = $owner_uid");
3055 function trim_array($array) {
3057 array_walk($tmp, 'trim');
3061 function tag_is_valid($tag) {
3062 if ($tag == '') return false;
3063 if (preg_match("/^[0-9]*$/", $tag)) return false;
3064 if (mb_strlen($tag) > 250) return false;
3066 if (!$tag) return false;
3071 function render_login_form() {
3072 header('Cache-Control: public');
3074 require_once "login_form.php";
3078 function format_warning($msg, $id = "") {
3080 return "<div class=\"warning\" id=\"$id\">
3081 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3084 function format_notice($msg, $id = "") {
3086 return "<div class=\"notice\" id=\"$id\">
3087 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
3090 function format_error($msg, $id = "") {
3092 return "<div class=\"error\" id=\"$id\">
3093 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
3096 function print_notice($msg) {
3097 return print format_notice($msg);
3100 function print_warning($msg) {
3101 return print format_warning($msg);
3104 function print_error($msg) {
3105 return print format_error($msg);
3109 function T_sprintf() {
3110 $args = func_get_args();
3111 return vsprintf(__(array_shift($args)), $args);
3114 function format_inline_player($url, $ctype) {
3118 $url = htmlspecialchars($url);
3120 if (strpos($ctype, "audio/") === 0) {
3122 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
3123 $_SESSION["hasMp3"])) {
3125 $entry .= "<audio preload=\"none\" controls>
3126 <source type=\"$ctype\" src=\"$url\"></source>
3131 $entry .= "<object type=\"application/x-shockwave-flash\"
3132 data=\"lib/button/musicplayer.swf?song_url=$url\"
3133 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
3134 <param name=\"movie\"
3135 value=\"lib/button/musicplayer.swf?song_url=$url\" />
3139 if ($entry) $entry .= " <a target=\"_blank\"
3140 href=\"$url\">" . basename($url) . "</a>";
3148 /* $filename = substr($url, strrpos($url, "/")+1);
3150 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3151 $filename . " (" . $ctype . ")" . "</a>"; */
3155 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
3156 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3162 /* we can figure out feed_id from article id anyway, why do we
3163 * pass feed_id here? let's ignore the argument :(*/
3165 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3166 WHERE ref_id = '$id'");
3168 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
3170 $rv['feed_id'] = $feed_id;
3172 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
3174 if ($mark_as_read) {
3175 $result = db_query("UPDATE ttrss_user_entries
3176 SET unread = false,last_read = NOW()
3177 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3179 ccache_update($feed_id, $owner_uid);
3182 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
3183 ".SUBSTRING_FOR_DATE
."(updated,1,16) as updated,
3184 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
3185 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
3186 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
3192 FROM ttrss_entries,ttrss_user_entries
3193 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
3197 $line = db_fetch_assoc($result);
3199 $tag_cache = $line["tag_cache"];
3201 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
3202 unset($line["tag_cache"]);
3204 $line["content"] = sanitize($line["content"],
3205 sql_bool_to_bool($line['hide_images']),
3206 $owner_uid, $line["site_url"], false, $line["id"]);
3208 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_RENDER_ARTICLE
) as $p) {
3209 $line = $p->hook_render_article($line);
3212 $num_comments = $line["num_comments"];
3213 $entry_comments = "";
3215 if ($num_comments > 0) {
3216 if ($line["comments"]) {
3217 $comments_url = htmlspecialchars($line["comments"]);
3219 $comments_url = htmlspecialchars($line["link"]);
3221 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
3223 if ($line["comments"] && $line["link"] != $line["comments"]) {
3224 $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
3229 header("Content-Type: text/html");
3230 $rv['content'] .= "<html><head>
3231 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3232 <title>Tiny Tiny RSS - ".$line["title"]."</title>
3233 <link rel=\"stylesheet\" type=\"text/css\" href=\"css/tt-rss.css\">
3234 <script type=\"text/javascript\">
3235 function openSelectedAttachment(elem) {
3237 var url = elem[elem.selectedIndex].value;
3241 elem.selectedIndex = 0;
3245 exception_error(\"openSelectedAttachment\", e);
3249 </head><body id=\"ttrssZoom\">";
3252 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3254 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3256 $entry_author = $line["author"];
3258 if ($entry_author) {
3259 $entry_author = __(" - ") . $entry_author;
3262 $parsed_updated = make_local_datetime($line["updated"], true,
3266 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3268 if ($line["link"]) {
3269 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3270 title=\"".htmlspecialchars($line['title'])."\"
3272 htmlspecialchars($line["link"]) . "\">" .
3273 $line["title"] . "</a>" .
3274 "<span class='author'>$entry_author</span></div>";
3276 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3280 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3282 $tags_str = format_tags_string($line["tags"], $id);
3283 $tags_str_full = join(", ", $line["tags"]);
3285 if (!$tags_str_full) $tags_str_full = __("no tags");
3287 if (!$entry_comments) $entry_comments = " "; # placeholder
3289 $rv['content'] .= "<div class='postTags' style='float : right'>
3290 <img src='images/tag.png'
3291 class='tagsPic' alt='Tags' title='Tags'> ";
3294 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3295 <a title=\"".__('Edit tags for this article')."\"
3296 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3298 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3299 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3300 position=\"below\">$tags_str_full</div>";
3302 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_BUTTON
) as $p) {
3303 $rv['content'] .= $p->hook_article_button($line);
3307 $tags_str = strip_tags($tags_str);
3308 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3310 $rv['content'] .= "</div>";
3311 $rv['content'] .= "<div clear='both'>";
3313 foreach (PluginHost
::getInstance()->get_hooks(PluginHost
::HOOK_ARTICLE_LEFT_BUTTON
) as $p) {
3314 $rv['content'] .= $p->hook_article_left_button($line);
3317 $rv['content'] .= "$entry_comments</div>";
3319 if ($line["orig_feed_id"]) {
3321 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
3322 WHERE id = ".$line["orig_feed_id"]);
3324 if (db_num_rows($tmp_result) != 0) {
3326 $rv['content'] .= "<div clear='both'>";
3327 $rv['content'] .= __("Originally from:");
3329 $rv['content'] .= " ";
3331 $tmp_line = db_fetch_assoc($tmp_result);
3333 $rv['content'] .= "<a target='_blank'
3334 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3335 $tmp_line['title'] . "</a>";
3337 $rv['content'] .= " ";
3339 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3340 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3342 $rv['content'] .= "</div>";
3346 $rv['content'] .= "</div>";
3348 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3349 if ($line['note']) {
3350 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3352 $rv['content'] .= "</div>";
3354 if (!$line['lang']) $line['lang'] = 'en';
3356 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
3358 $rv['content'] .= $line["content"];
3359 $rv['content'] .= format_article_enclosures($id,
3360 sql_bool_to_bool($line["always_display_enclosures"]),
3362 sql_bool_to_bool($line["hide_images"]));
3364 $rv['content'] .= "</div>";
3366 $rv['content'] .= "</div>";
3372 <div class='footer'>
3373 <button onclick=\"return window.close()\">".
3374 __("Close this window")."</button></div>";
3375 $rv['content'] .= "</body></html>";
3382 function print_checkpoint($n, $s) {
3383 $ts = microtime(true);
3384 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
3388 function sanitize_tag($tag) {
3391 $tag = mb_strtolower($tag, 'utf-8');
3393 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3395 // $tag = str_replace('"', "", $tag);
3396 // $tag = str_replace("+", " ", $tag);
3397 $tag = str_replace("technorati tag: ", "", $tag);
3402 function get_self_url_prefix() {
3403 if (strrpos(SELF_URL_PATH
, "/") === strlen(SELF_URL_PATH
)-1) {
3404 return substr(SELF_URL_PATH
, 0, strlen(SELF_URL_PATH
)-1);
3406 return SELF_URL_PATH
;
3411 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3413 * @return string The Mozilla Firefox feed adding URL.
3415 function add_feed_url() {
3416 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3418 $url_path = get_self_url_prefix() .
3419 "/public.php?op=subscribe&feed_url=%s";
3421 } // function add_feed_url
3423 function encrypt_password($pass, $salt = '', $mode2 = false) {
3424 if ($salt && $mode2) {
3425 return "MODE2:" . hash('sha256', $salt . $pass);
3427 return "SHA1X:" . sha1("$salt:$pass");
3429 return "SHA1:" . sha1($pass);
3431 } // function encrypt_password
3433 function load_filters($feed_id, $owner_uid, $action_id = false) {
3436 $cat_id = (int)getFeedCategory($feed_id);
3439 $null_cat_qpart = "cat_id IS NULL OR";
3441 $null_cat_qpart = "";
3443 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
3444 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
3446 $check_cats = join(",", array_merge(
3447 getParentCategories($cat_id, $owner_uid),
3450 while ($line = db_fetch_assoc($result)) {
3451 $filter_id = $line["id"];
3453 $result2 = db_query("SELECT
3454 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3455 FROM ttrss_filters2_rules AS r,
3456 ttrss_filter_types AS t
3458 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
3459 (feed_id IS NULL OR feed_id = '$feed_id') AND
3460 filter_type = t.id AND filter_id = '$filter_id'");
3465 while ($rule_line = db_fetch_assoc($result2)) {
3466 # print_r($rule_line);
3469 $rule["reg_exp"] = $rule_line["reg_exp"];
3470 $rule["type"] = $rule_line["type_name"];
3471 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
3473 array_push($rules, $rule);
3476 $result2 = db_query("SELECT a.action_param,t.name AS type_name
3477 FROM ttrss_filters2_actions AS a,
3478 ttrss_filter_actions AS t
3480 action_id = t.id AND filter_id = '$filter_id'");
3482 while ($action_line = db_fetch_assoc($result2)) {
3483 # print_r($action_line);
3486 $action["type"] = $action_line["type_name"];
3487 $action["param"] = $action_line["action_param"];
3489 array_push($actions, $action);
3494 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3495 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
3496 $filter["rules"] = $rules;
3497 $filter["actions"] = $actions;
3499 if (count($rules) > 0 && count($actions) > 0) {
3500 array_push($filters, $filter);
3507 function get_score_pic($score) {
3509 return "score_high.png";
3510 } else if ($score > 0) {
3511 return "score_half_high.png";
3512 } else if ($score < -100) {
3513 return "score_low.png";
3514 } else if ($score < 0) {
3515 return "score_half_low.png";
3517 return "score_neutral.png";
3521 function feed_has_icon($id) {
3522 return is_file(ICONS_DIR
. "/$id.ico") && filesize(ICONS_DIR
. "/$id.ico") > 0;
3525 function init_plugins() {
3526 PluginHost
::getInstance()->load(PLUGINS
, PluginHost
::KIND_ALL
);
3531 function format_tags_string($tags, $id) {
3532 if (!is_array($tags) ||
count($tags) == 0) {
3533 return __("no tags");
3535 $maxtags = min(5, count($tags));
3537 for ($i = 0; $i < $maxtags; $i++
) {
3538 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
3541 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
3543 if (count($tags) > $maxtags)
3544 $tags_str .= ", …";
3550 function format_article_labels($labels, $id) {
3552 if (!is_array($labels)) return '';
3556 foreach ($labels as $l) {
3557 $labels_str .= sprintf("<span class='hlLabelRef'
3558 style='color : %s; background-color : %s'>%s</span>",
3559 $l[2], $l[3], $l[1]);
3566 function format_article_note($id, $note, $allow_edit = true) {
3568 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3569 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3570 ($allow_edit ?
__('(edit note)') : "")."</div>$note</div>";
3576 function get_feed_category($feed_cat, $parent_cat_id = false) {
3577 if ($parent_cat_id) {
3578 $parent_qpart = "parent_cat = '$parent_cat_id'";
3579 $parent_insert = "'$parent_cat_id'";
3581 $parent_qpart = "parent_cat IS NULL";
3582 $parent_insert = "NULL";
3586 "SELECT id FROM ttrss_feed_categories
3587 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3589 if (db_num_rows($result) == 0) {
3592 return db_fetch_result($result, 0, "id");
3596 function add_feed_category($feed_cat, $parent_cat_id = false) {
3598 if (!$feed_cat) return false;
3602 if ($parent_cat_id) {
3603 $parent_qpart = "parent_cat = '$parent_cat_id'";
3604 $parent_insert = "'$parent_cat_id'";
3606 $parent_qpart = "parent_cat IS NULL";
3607 $parent_insert = "NULL";
3610 $feed_cat = mb_substr($feed_cat, 0, 250);
3613 "SELECT id FROM ttrss_feed_categories
3614 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3616 if (db_num_rows($result) == 0) {
3619 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3620 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3630 function getArticleFeed($id) {
3631 $result = db_query("SELECT feed_id FROM ttrss_user_entries
3632 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3634 if (db_num_rows($result) != 0) {
3635 return db_fetch_result($result, 0, "feed_id");
3642 * Fixes incomplete URLs by prepending "http://".
3643 * Also replaces feed:// with http://, and
3644 * prepends a trailing slash if the url is a domain name only.
3646 * @param string $url Possibly incomplete URL
3648 * @return string Fixed URL.
3650 function fix_url($url) {
3651 if (strpos($url, '://') === false) {
3652 $url = 'http://' . $url;
3653 } else if (substr($url, 0, 5) == 'feed:') {
3654 $url = 'http:' . substr($url, 5);
3657 //prepend slash if the URL has no slash in it
3658 // "http://www.example" -> "http://www.example/"
3659 if (strpos($url, '/', strpos($url, ':') +
3) === false) {
3663 if ($url != "http:///")
3669 function validate_feed_url($url) {
3670 $parts = parse_url($url);
3672 return ($parts['scheme'] == 'http' ||
$parts['scheme'] == 'feed' ||
$parts['scheme'] == 'https');
3676 function get_article_enclosures($id) {
3678 $query = "SELECT * FROM ttrss_enclosures
3679 WHERE post_id = '$id' AND content_url != ''";
3683 $result = db_query($query);
3685 if (db_num_rows($result) > 0) {
3686 while ($line = db_fetch_assoc($result)) {
3687 array_push($rv, $line);
3694 function save_email_address($email) {
3695 // FIXME: implement persistent storage of emails
3697 if (!$_SESSION['stored_emails'])
3698 $_SESSION['stored_emails'] = array();
3700 if (!in_array($email, $_SESSION['stored_emails']))
3701 array_push($_SESSION['stored_emails'], $email);
3705 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
3707 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3709 $sql_is_cat = bool_to_sql_bool($is_cat);
3711 $result = db_query("SELECT access_key FROM ttrss_access_keys
3712 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3713 AND owner_uid = " . $owner_uid);
3715 if (db_num_rows($result) == 1) {
3716 return db_fetch_result($result, 0, "access_key");
3718 $key = db_escape_string(sha1(uniqid(rand(), true)));
3720 $result = db_query("INSERT INTO ttrss_access_keys
3721 (access_key, feed_id, is_cat, owner_uid)
3722 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3729 function get_feeds_from_html($url, $content)
3731 $url = fix_url($url);
3732 $baseUrl = substr($url, 0, strrpos($url, '/') +
1);
3734 libxml_use_internal_errors(true);
3736 $doc = new DOMDocument();
3737 $doc->loadHTML($content);
3738 $xpath = new DOMXPath($doc);
3739 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3740 $feedUrls = array();
3741 foreach ($entries as $entry) {
3742 if ($entry->hasAttribute('href')) {
3743 $title = $entry->getAttribute('title');
3745 $title = $entry->getAttribute('type');
3747 $feedUrl = rewrite_relative_url(
3748 $baseUrl, $entry->getAttribute('href')
3750 $feedUrls[$feedUrl] = $title;
3756 function is_html($content) {
3757 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3760 function url_is_html($url, $login = false, $pass = false) {
3761 return is_html(fetch_file_contents($url, false, $login, $pass));
3764 function print_label_select($name, $value, $attributes = "") {
3766 $result = db_query("SELECT caption FROM ttrss_labels2
3767 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3769 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3770 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3772 while ($line = db_fetch_assoc($result)) {
3774 $issel = ($line["caption"] == $value) ?
"selected=\"1\"" : "";
3776 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3777 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3781 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3788 function format_article_enclosures($id, $always_display_enclosures,
3789 $article_content, $hide_images = false) {
3791 $result = get_article_enclosures($id);
3794 if (count($result) > 0) {
3796 $entries_html = array();
3798 $entries_inline = array();
3800 foreach ($result as $line) {
3802 $url = $line["content_url"];
3803 $ctype = $line["content_type"];
3804 $title = $line["title"];
3806 if (!$ctype) $ctype = __("unknown type");
3808 $filename = substr($url, strrpos($url, "/")+
1);
3810 $player = format_inline_player($url, $ctype);
3812 if ($player) array_push($entries_inline, $player);
3814 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3815 # $filename . " (" . $ctype . ")" . "</a>";
3817 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3818 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3820 array_push($entries_html, $entry);
3824 $entry["type"] = $ctype;
3825 $entry["filename"] = $filename;
3826 $entry["url"] = $url;
3827 $entry["title"] = $title;
3829 array_push($entries, $entry);
3832 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
3833 if ($always_display_enclosures ||
3834 !preg_match("/<img/i", $article_content)) {
3836 foreach ($entries as $entry) {
3838 if (preg_match("/image/", $entry["type"]) ||
3839 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3841 if (!$hide_images) {
3843 alt=\"".htmlspecialchars($entry["filename"])."\"
3844 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3846 $rv .= "<p><a target=\"_blank\"
3847 href=\"".htmlspecialchars($entry["url"])."\"
3848 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3851 if ($entry['title']) {
3852 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
3859 if (count($entries_inline) > 0) {
3860 $rv .= "<hr clear='both'/>";
3861 foreach ($entries_inline as $entry) { $rv .= $entry; };
3862 $rv .= "<hr clear='both'/>";
3865 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
3866 "<option value=''>" . __('Attachments')."</option>";
3868 foreach ($entries as $entry) {
3869 if ($entry["title"])
3870 $title = "— " . truncate_string($entry["title"], 30);
3874 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
3884 function getLastArticleId() {
3885 $result = db_query("SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3886 WHERE owner_uid = " . $_SESSION["uid"]);
3888 if (db_num_rows($result) == 1) {
3889 return db_fetch_result($result, 0, "id");
3895 function build_url($parts) {
3896 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3900 * Converts a (possibly) relative URL to a absolute one.
3902 * @param string $url Base URL (i.e. from where the document is)
3903 * @param string $rel_url Possibly relative URL in the document
3905 * @return string Absolute URL
3907 function rewrite_relative_url($url, $rel_url) {
3908 if (strpos($rel_url, ":") !== false) {
3910 } else if (strpos($rel_url, "://") !== false) {
3912 } else if (strpos($rel_url, "//") === 0) {
3913 # protocol-relative URL (rare but they exist)
3915 } else if (strpos($rel_url, "/") === 0)
3917 $parts = parse_url($url);
3918 $parts['path'] = $rel_url;
3920 return build_url($parts);
3923 $parts = parse_url($url);
3924 if (!isset($parts['path'])) {
3925 $parts['path'] = '/';
3927 $dir = $parts['path'];
3928 if (substr($dir, -1) !== '/') {
3929 $dir = dirname($parts['path']);
3930 $dir !== '/' && $dir .= '/';
3932 $parts['path'] = $dir . $rel_url;
3934 return build_url($parts);
3938 function sphinx_search($query, $offset = 0, $limit = 30) {
3939 require_once 'lib/sphinxapi.php';
3941 $sphinxClient = new SphinxClient();
3943 $sphinxpair = explode(":", SPHINX_SERVER
, 2);
3945 $sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
3946 $sphinxClient->SetConnectTimeout(1);
3948 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3949 'feed_title' => 20));
3951 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2
);
3952 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25
);
3953 $sphinxClient->SetLimits($offset, $limit, 1000);
3954 $sphinxClient->SetArrayResult(false);
3955 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3957 $result = $sphinxClient->Query($query, SPHINX_INDEX
);
3961 if (is_array($result['matches'])) {
3962 foreach (array_keys($result['matches']) as $int_id) {
3963 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3964 array_push($ids, $ref_id);
3971 function cleanup_tags($days = 14, $limit = 1000) {
3973 if (DB_TYPE
== "pgsql") {
3974 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3975 } else if (DB_TYPE
== "mysql") {
3976 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3981 while ($limit > 0) {
3984 $query = "SELECT ttrss_tags.id AS id
3985 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3986 WHERE post_int_id = int_id AND $interval_query AND
3987 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3989 $result = db_query($query);
3993 while ($line = db_fetch_assoc($result)) {
3994 array_push($ids, $line['id']);
3997 if (count($ids) > 0) {
3998 $ids = join(",", $ids);
4000 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
4001 $tags_deleted +
= db_affected_rows($tmp_result);
4006 $limit -= $limit_part;
4009 return $tags_deleted;
4012 function print_user_stylesheet() {
4013 $value = get_pref('USER_STYLESHEET');
4016 print "<style type=\"text/css\">";
4017 print str_replace("<br/>", "\n", $value);
4023 function filter_to_sql($filter, $owner_uid) {
4026 if (DB_TYPE
== "pgsql")
4029 $reg_qpart = "REGEXP";
4031 foreach ($filter["rules"] AS $rule) {
4032 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
4033 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
4034 $rule['reg_exp']) !== FALSE;
4036 if ($regexp_valid) {
4038 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
4040 switch ($rule["type"]) {
4042 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4043 $rule['reg_exp'] . "')";
4046 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
4047 $rule['reg_exp'] . "')";
4050 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
4051 $rule['reg_exp'] . "') OR LOWER(" .
4052 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
4055 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
4056 $rule['reg_exp'] . "')";
4059 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
4060 $rule['reg_exp'] . "')";
4063 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
4064 $rule['reg_exp'] . "')";
4068 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
4070 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
4071 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
4074 if (isset($rule["cat_id"])) {
4076 if ($rule["cat_id"] > 0) {
4077 $children = getChildCategories($rule["cat_id"], $owner_uid);
4078 array_push($children, $rule["cat_id"]);
4080 $children = join(",", $children);
4082 $cat_qpart = "cat_id IN ($children)";
4084 $cat_qpart = "cat_id IS NULL";
4087 $qpart .= " AND $cat_qpart";
4090 $qpart .= " AND feed_id IS NOT NULL";
4092 array_push($query, "($qpart)");
4097 if (count($query) > 0) {
4098 $fullquery = "(" . join($filter["match_any_rule"] ?
"OR" : "AND", $query) . ")";
4100 $fullquery = "(false)";
4103 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
4108 if (!function_exists('gzdecode')) {
4109 function gzdecode($string) { // no support for 2nd argument
4110 return file_get_contents('compress.zlib://data:who/cares;base64,'.
4111 base64_encode($string));
4115 function get_random_bytes($length) {
4116 if (function_exists('openssl_random_pseudo_bytes')) {
4117 return openssl_random_pseudo_bytes($length);
4121 for ($i = 0; $i < $length; $i++
)
4122 $output .= chr(mt_rand(0, 255));
4128 function read_stdin() {
4129 $fp = fopen("php://stdin", "r");
4132 $line = trim(fgets($fp));
4140 function tmpdirname($path, $prefix) {
4141 // Use PHP's tmpfile function to create a temporary
4142 // directory name. Delete the file and keep the name.
4143 $tempname = tempnam($path,$prefix);
4147 if (!unlink($tempname))
4153 function getFeedCategory($feed) {
4154 $result = db_query("SELECT cat_id FROM ttrss_feeds
4155 WHERE id = '$feed'");
4157 if (db_num_rows($result) > 0) {
4158 return db_fetch_result($result, 0, "cat_id");
4165 function implements_interface($class, $interface) {
4166 return in_array($interface, class_implements($class));
4169 function geturl($url, $depth = 0){
4171 if ($depth == 20) return $url;
4173 if (!function_exists('curl_init'))
4174 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
);
4176 $curl = curl_init();
4177 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4178 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4179 $header[] = "Cache-Control: max-age=0";
4180 $header[] = "Connection: keep-alive";
4181 $header[] = "Keep-Alive: 300";
4182 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4183 $header[] = "Accept-Language: en-us,en;q=0.5";
4184 $header[] = "Pragma: ";
4186 curl_setopt($curl, CURLOPT_URL
, $url);
4187 curl_setopt($curl, CURLOPT_USERAGENT
, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4188 curl_setopt($curl, CURLOPT_HTTPHEADER
, $header);
4189 curl_setopt($curl, CURLOPT_HEADER
, true);
4190 curl_setopt($curl, CURLOPT_REFERER
, $url);
4191 curl_setopt($curl, CURLOPT_ENCODING
, 'gzip,deflate');
4192 curl_setopt($curl, CURLOPT_AUTOREFERER
, true);
4193 curl_setopt($curl, CURLOPT_RETURNTRANSFER
, true);
4194 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4195 curl_setopt($curl, CURLOPT_TIMEOUT
, 60);
4196 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER
, false);
4198 if ((OPENSSL_VERSION_NUMBER
>= 0x0090808f) && (OPENSSL_VERSION_NUMBER
< 0x10000000)) {
4199 curl_setopt($curl, CURLOPT_SSLVERSION
, 3);
4202 $html = curl_exec($curl);
4204 $status = curl_getinfo($curl);
4206 if($status['http_code']!=200){
4207 if($status['http_code'] == 301 ||
$status['http_code'] == 302) {
4209 list($header) = explode("\r\n\r\n", $html, 2);
4211 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4212 $url = trim(str_replace($matches[1],"",$matches[0]));
4213 $url_parsed = parse_url($url);
4214 return (isset($url_parsed))?
geturl($url, $depth +
1):'';
4217 global $fetch_last_error;
4219 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
4223 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4224 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4225 # $handle = @fopen('./curl.error.log', 'a');
4226 # fwrite($handle, $line);
4233 function get_minified_js($files) {
4234 require_once 'lib/jshrink/Minifier.php';
4238 foreach ($files as $js) {
4239 if (!isset($_GET['debug'])) {
4240 $cached_file = CACHE_DIR
. "/js/".basename($js).".js";
4242 if (file_exists($cached_file) &&
4243 is_readable($cached_file) &&
4244 filemtime($cached_file) >= filemtime("js/$js.js")) {
4246 $rv .= file_get_contents($cached_file);
4249 $minified = JShrink\Minifier
::minify(file_get_contents("js/$js.js"));
4250 file_put_contents($cached_file, $minified);
4254 $rv .= file_get_contents("js/$js.js");
4261 function stylesheet_tag($filename) {
4262 $timestamp = filemtime($filename);
4264 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4267 function javascript_tag($filename) {
4270 if (!(strpos($filename, "?") === FALSE)) {
4271 $query = substr($filename, strpos($filename, "?")+
1);
4272 $filename = substr($filename, 0, strpos($filename, "?"));
4275 $timestamp = filemtime($filename);
4277 if ($query) $timestamp .= "&$query";
4279 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4282 function calculate_dep_timestamp() {
4283 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
4287 foreach ($files as $file) {
4288 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4294 function T_js_decl($s1, $s2) {
4296 $s1 = preg_replace("/\n/", "", $s1);
4297 $s2 = preg_replace("/\n/", "", $s2);
4299 $s1 = preg_replace("/\"/", "\\\"", $s1);
4300 $s2 = preg_replace("/\"/", "\\\"", $s2);
4302 return "T_messages[\"$s1\"] = \"$s2\";\n";
4306 function init_js_translations() {
4308 print 'var T_messages = new Object();
4311 if (T_messages[msg]) {
4312 return T_messages[msg];
4318 function ngettext(msg1, msg2, n) {
4319 return __((parseInt(n) > 1) ? msg2 : msg1);
4322 $l10n = _get_reader();
4324 for ($i = 0; $i < $l10n->total
; $i++
) {
4325 $orig = $l10n->get_original_string($i);
4326 if(strpos($orig, "\000") !== FALSE) { // Plural forms
4327 $key = explode(chr(0), $orig);
4328 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
4329 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
4331 $translation = __($orig);
4332 print T_js_decl($orig, $translation);
4337 function label_to_feed_id($label) {
4338 return LABEL_BASE_INDEX
- 1 - abs($label);
4341 function feed_to_label_id($feed) {
4342 return LABEL_BASE_INDEX
- 1 +
abs($feed);
4345 function format_libxml_error($error) {
4346 return T_sprintf("LibXML error %s at line %d (column %d): %s",
4347 $error->code
, $error->line
, $error->column
,