]> git.wh0rd.org Git - tt-rss.git/blob - include/functions.php
remove PTITLE kludge; use ajax
[tt-rss.git] / include / functions.php
1 <?php
2         define('EXPECTED_CONFIG_VERSION', 26);
3         define('SCHEMA_VERSION', 106);
4
5         $fetch_last_error = false;
6         $pluginhost = false;
7
8         function __autoload($class) {
9                 $class_file = str_replace("_", "/", strtolower(basename($class)));
10
11                 $file = dirname(__FILE__)."/../classes/$class_file.php";
12
13                 if (file_exists($file)) {
14                         require $file;
15                 }
16
17         }
18
19         mb_internal_encoding("UTF-8");
20         date_default_timezone_set('UTC');
21         if (defined('E_DEPRECATED')) {
22                 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
23         } else {
24                 error_reporting(E_ALL & ~E_NOTICE);
25         }
26
27         require_once 'config.php';
28
29         if (DB_TYPE == "pgsql") {
30                 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
31         } else {
32                 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
33         }
34
35         define('THEME_VERSION_REQUIRED', 1.1);
36
37         /**
38          * Return available translations names.
39          *
40          * @access public
41          * @return array A array of available translations.
42          */
43         function get_translations() {
44                 $tr = array(
45                                         "auto"  => "Detect automatically",
46                                         "ca_CA" => "Català",
47                                         "cs_CZ" => "Česky",
48                                         "en_US" => "English",
49                                         "es_ES" => "Español",
50                                         "de_DE" => "Deutsch",
51                                         "fr_FR" => "Français",
52                                         "hu_HU" => "Magyar (Hungarian)",
53                                         "it_IT" => "Italiano",
54                                         "ja_JP" => "日本語 (Japanese)",
55                                         "lv_LV" => "Latviešu",
56                                         "nb_NO" => "Norwegian bokmål",
57                                         "pl_PL" => "Polski",
58                                         "ru_RU" => "Русский",
59                                         "pt_BR" => "Portuguese/Brazil",
60                                         "zh_CN" => "Simplified Chinese");
61
62                 return $tr;
63         }
64
65         require_once "lib/accept-to-gettext.php";
66         require_once "lib/gettext/gettext.inc";
67
68
69         function startup_gettext() {
70
71                 # Get locale from Accept-Language header
72                 $lang = al2gt(array_keys(get_translations()), "text/html");
73
74                 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
75                         $lang = _TRANSLATION_OVERRIDE_DEFAULT;
76                 }
77
78                 if ($_SESSION["language"] && $_SESSION["language"] != "auto") {
79                         $lang = $_SESSION["language"];
80                 }
81
82                 if ($lang) {
83                         if (defined('LC_MESSAGES')) {
84                                 _setlocale(LC_MESSAGES, $lang);
85                         } else if (defined('LC_ALL')) {
86                                 _setlocale(LC_ALL, $lang);
87                         }
88
89                         _bindtextdomain("messages", "locale");
90
91                         _textdomain("messages");
92                         _bind_textdomain_codeset("messages", "UTF-8");
93                 }
94         }
95
96         startup_gettext();
97
98         require_once 'db-prefs.php';
99         require_once 'version.php';
100         require_once 'ccache.php';
101         require_once 'labels.php';
102
103         define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION . ' (http://tt-rss.org/)');
104         ini_set('user_agent', SELF_USER_AGENT);
105
106         require_once 'lib/pubsubhubbub/publisher.php';
107
108         $tz_offset = -1;
109         $utc_tz = new DateTimeZone('UTC');
110         $schema_version = false;
111
112         /**
113          * Print a timestamped debug message.
114          *
115          * @param string $msg The debug message.
116          * @return void
117          */
118         function _debug($msg) {
119                 $ts = strftime("%H:%M:%S", time());
120                 if (function_exists('posix_getpid')) {
121                         $ts = "$ts/" . posix_getpid();
122                 }
123
124                 if (!(defined('QUIET') && QUIET)) {
125                         print "[$ts] $msg\n";
126                 }
127
128                 if (defined('LOGFILE'))  {
129                         $fp = fopen(LOGFILE, 'a+');
130
131                         if ($fp) {
132                                 fputs($fp, "[$ts] $msg\n");
133                                 fclose($fp);
134                         }
135                 }
136
137         } // function _debug
138
139         /**
140          * Purge a feed old posts.
141          *
142          * @param mixed $link A database connection.
143          * @param mixed $feed_id The id of the purged feed.
144          * @param mixed $purge_interval Olderness of purged posts.
145          * @param boolean $debug Set to True to enable the debug. False by default.
146          * @access public
147          * @return void
148          */
149         function purge_feed($link, $feed_id, $purge_interval, $debug = false) {
150
151                 if (!$purge_interval) $purge_interval = feed_purge_interval($link, $feed_id);
152
153                 $rows = -1;
154
155                 $result = db_query($link,
156                         "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
157
158                 $owner_uid = false;
159
160                 if (db_num_rows($result) == 1) {
161                         $owner_uid = db_fetch_result($result, 0, "owner_uid");
162                 }
163
164                 if ($purge_interval == -1 || !$purge_interval) {
165                         if ($owner_uid) {
166                                 ccache_update($link, $feed_id, $owner_uid);
167                         }
168                         return;
169                 }
170
171                 if (!$owner_uid) return;
172
173                 if (FORCE_ARTICLE_PURGE == 0) {
174                         $purge_unread = get_pref($link, "PURGE_UNREAD_ARTICLES",
175                                 $owner_uid, false);
176                 } else {
177                         $purge_unread = true;
178                         $purge_interval = FORCE_ARTICLE_PURGE;
179                 }
180
181                 if (!$purge_unread) $query_limit = " unread = false AND ";
182
183                 if (DB_TYPE == "pgsql") {
184                         $pg_version = get_pgsql_version($link);
185
186                         if (preg_match("/^7\./", $pg_version) || preg_match("/^8\.0/", $pg_version)) {
187
188                                 $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
189                                         ttrss_entries.id = ref_id AND
190                                         marked = false AND
191                                         feed_id = '$feed_id' AND
192                                         $query_limit
193                                         ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
194
195                         } else {
196
197                                 $result = db_query($link, "DELETE FROM ttrss_user_entries
198                                         USING ttrss_entries
199                                         WHERE ttrss_entries.id = ref_id AND
200                                         marked = false AND
201                                         feed_id = '$feed_id' AND
202                                         $query_limit
203                                         ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
204                         }
205
206                         $rows = pg_affected_rows($result);
207
208                 } else {
209
210 /*                      $result = db_query($link, "DELETE FROM ttrss_user_entries WHERE
211                                 marked = false AND feed_id = '$feed_id' AND
212                                 (SELECT date_updated FROM ttrss_entries WHERE
213                                         id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
214
215                         $result = db_query($link, "DELETE FROM ttrss_user_entries
216                                 USING ttrss_user_entries, ttrss_entries
217                                 WHERE ttrss_entries.id = ref_id AND
218                                 marked = false AND
219                                 feed_id = '$feed_id' AND
220                                 $query_limit
221                                 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
222
223                         $rows = mysql_affected_rows($link);
224
225                 }
226
227                 ccache_update($link, $feed_id, $owner_uid);
228
229                 if ($debug) {
230                         _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
231                 }
232
233                 return $rows;
234         } // function purge_feed
235
236         function feed_purge_interval($link, $feed_id) {
237
238                 $result = db_query($link, "SELECT purge_interval, owner_uid FROM ttrss_feeds
239                         WHERE id = '$feed_id'");
240
241                 if (db_num_rows($result) == 1) {
242                         $purge_interval = db_fetch_result($result, 0, "purge_interval");
243                         $owner_uid = db_fetch_result($result, 0, "owner_uid");
244
245                         if ($purge_interval == 0) $purge_interval = get_pref($link,
246                                 'PURGE_OLD_DAYS', $owner_uid);
247
248                         return $purge_interval;
249
250                 } else {
251                         return -1;
252                 }
253         }
254
255         function purge_orphans($link, $do_output = false) {
256
257                 // purge orphaned posts in main content table
258                 $result = db_query($link, "DELETE FROM ttrss_entries WHERE
259                         (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0");
260
261                 if ($do_output) {
262                         $rows = db_affected_rows($link, $result);
263                         _debug("Purged $rows orphaned posts.");
264                 }
265         }
266
267         function get_feed_update_interval($link, $feed_id) {
268                 $result = db_query($link, "SELECT owner_uid, update_interval FROM
269                         ttrss_feeds WHERE id = '$feed_id'");
270
271                 if (db_num_rows($result) == 1) {
272                         $update_interval = db_fetch_result($result, 0, "update_interval");
273                         $owner_uid = db_fetch_result($result, 0, "owner_uid");
274
275                         if ($update_interval != 0) {
276                                 return $update_interval;
277                         } else {
278                                 return get_pref($link, 'DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
279                         }
280
281                 } else {
282                         return -1;
283                 }
284         }
285
286         function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false, $timeout = false) {
287                 $login = urlencode($login);
288                 $pass = urlencode($pass);
289
290                 global $fetch_last_error;
291
292                 if (function_exists('curl_init') && !ini_get("open_basedir")) {
293
294                         if (ini_get("safe_mode")) {
295                                 $ch = curl_init(geturl($url));
296                         } else {
297                                 $ch = curl_init($url);
298                         }
299
300                         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : 15);
301                         curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : 45);
302                         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("safe_mode"));
303                         curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
304                         curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
305                         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
306                         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
307                         curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
308                         curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT);
309                         curl_setopt($ch, CURLOPT_ENCODING , "gzip");
310                         curl_setopt($ch, CURLOPT_REFERER, $url);
311
312                         if ($post_query) {
313                                 curl_setopt($ch, CURLOPT_POST, true);
314                                 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
315                         }
316
317                         if ($login && $pass)
318                                 curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
319
320                         $contents = @curl_exec($ch);
321
322                         if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
323                                 curl_setopt($ch, CURLOPT_ENCODING, 'none');
324                                 $contents = @curl_exec($ch);
325                         }
326
327                         if ($contents === false) {
328                                 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
329                                 curl_close($ch);
330                                 return false;
331                         }
332
333                         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
334                         $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
335
336                         if ($http_code != 200 || $type && strpos($content_type, "$type") === false) {
337                                 if (curl_errno($ch) != 0) {
338                                         $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
339                                 } else {
340                                         $fetch_last_error = "HTTP Code: $http_code";
341                                 }
342                                 curl_close($ch);
343                                 return false;
344                         }
345
346                         curl_close($ch);
347
348                         return $contents;
349                 } else {
350                         if ($login && $pass ){
351                                 $url_parts = array();
352
353                                 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
354
355                                 if ($url_parts[1] && $url_parts[2]) {
356                                         $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
357                                 }
358                         }
359
360                         $data = @file_get_contents($url);
361
362                         $gzdecoded = gzdecode($data);
363                         if ($gzdecoded) $data = $gzdecoded;
364
365                         if (!$data && function_exists('error_get_last')) {
366                                 $error = error_get_last();
367                                 $fetch_last_error = $error["message"];
368                         }
369                         return $data;
370                 }
371
372         }
373
374         /**
375          * Try to determine the favicon URL for a feed.
376          * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
377          * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
378          *
379          * @param string $url A feed or page URL
380          * @access public
381          * @return mixed The favicon URL, or false if none was found.
382          */
383         function get_favicon_url($url) {
384
385                 $favicon_url = false;
386
387                 if ($html = @fetch_file_contents($url)) {
388
389                         libxml_use_internal_errors(true);
390
391                         $doc = new DOMDocument();
392                         $doc->loadHTML($html);
393                         $xpath = new DOMXPath($doc);
394
395                         $base = $xpath->query('/html/head/base');
396                         foreach ($base as $b) {
397                                 $url = $b->getAttribute("href");
398                                 break;
399                         }
400
401                         $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
402                         if (count($entries) > 0) {
403                                 foreach ($entries as $entry) {
404                                         $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
405                                         break;
406                                 }
407                         }
408                 }
409
410                 if (!$favicon_url)
411                         $favicon_url = rewrite_relative_url($url, "/favicon.ico");
412
413                 return $favicon_url;
414         } // function get_favicon_url
415
416         function check_feed_favicon($site_url, $feed, $link) {
417 #               print "FAVICON [$site_url]: $favicon_url\n";
418
419                 $icon_file = ICONS_DIR . "/$feed.ico";
420
421                 if (!file_exists($icon_file)) {
422                         $favicon_url = get_favicon_url($site_url);
423
424                         if ($favicon_url) {
425                                 // Limiting to "image" type misses those served with text/plain
426                                 $contents = fetch_file_contents($favicon_url); // , "image");
427
428                                 if ($contents) {
429                                         // Crude image type matching.
430                                         // Patterns gleaned from the file(1) source code.
431                                         if (preg_match('/^\x00\x00\x01\x00/', $contents)) {
432                                                 // 0       string  \000\000\001\000        MS Windows icon resource
433                                                 //error_log("check_feed_favicon: favicon_url=$favicon_url isa MS Windows icon resource");
434                                         }
435                                         elseif (preg_match('/^GIF8/', $contents)) {
436                                                 // 0       string          GIF8            GIF image data
437                                                 //error_log("check_feed_favicon: favicon_url=$favicon_url isa GIF image");
438                                         }
439                                         elseif (preg_match('/^\x89PNG\x0d\x0a\x1a\x0a/', $contents)) {
440                                                 // 0       string          \x89PNG\x0d\x0a\x1a\x0a         PNG image data
441                                                 //error_log("check_feed_favicon: favicon_url=$favicon_url isa PNG image");
442                                         }
443                                         elseif (preg_match('/^\xff\xd8/', $contents)) {
444                                                 // 0       beshort         0xffd8          JPEG image data
445                                                 //error_log("check_feed_favicon: favicon_url=$favicon_url isa JPG image");
446                                         }
447                                         else {
448                                                 //error_log("check_feed_favicon: favicon_url=$favicon_url isa UNKNOWN type");
449                                                 $contents = "";
450                                         }
451                                 }
452
453                                 if ($contents) {
454                                         $fp = @fopen($icon_file, "w");
455
456                                         if ($fp) {
457                                                 fwrite($fp, $contents);
458                                                 fclose($fp);
459                                                 chmod($icon_file, 0644);
460                                         }
461                                 }
462                         }
463                 }
464         }
465
466         function print_select($id, $default, $values, $attributes = "") {
467                 print "<select name=\"$id\" id=\"$id\" $attributes>";
468                 foreach ($values as $v) {
469                         if ($v == $default)
470                                 $sel = "selected=\"1\"";
471                          else
472                                 $sel = "";
473
474                         $v = trim($v);
475
476                         print "<option value=\"$v\" $sel>$v</option>";
477                 }
478                 print "</select>";
479         }
480
481         function print_select_hash($id, $default, $values, $attributes = "") {
482                 print "<select name=\"$id\" id='$id' $attributes>";
483                 foreach (array_keys($values) as $v) {
484                         if ($v == $default)
485                                 $sel = 'selected="selected"';
486                          else
487                                 $sel = "";
488
489                         $v = trim($v);
490
491                         print "<option $sel value=\"$v\">".$values[$v]."</option>";
492                 }
493
494                 print "</select>";
495         }
496
497         function print_radio($id, $default, $true_is, $values, $attributes = "") {
498                 foreach ($values as $v) {
499
500                         if ($v == $default)
501                                 $sel = "checked";
502                          else
503                                 $sel = "";
504
505                         if ($v == $true_is) {
506                                 $sel .= " value=\"1\"";
507                         } else {
508                                 $sel .= " value=\"0\"";
509                         }
510
511                         print "<input class=\"noborder\" dojoType=\"dijit.form.RadioButton\"
512                                 type=\"radio\" $sel $attributes name=\"$id\">&nbsp;$v&nbsp;";
513
514                 }
515         }
516
517         function initialize_user_prefs($link, $uid, $profile = false) {
518
519                 $uid = db_escape_string($link, $uid);
520
521                 if (!$profile) {
522                         $profile = "NULL";
523                         $profile_qpart = "AND profile IS NULL";
524                 } else {
525                         $profile_qpart = "AND profile = '$profile'";
526                 }
527
528                 if (get_schema_version($link) < 63) $profile_qpart = "";
529
530                 db_query($link, "BEGIN");
531
532                 $result = db_query($link, "SELECT pref_name,def_value FROM ttrss_prefs");
533
534                 $u_result = db_query($link, "SELECT pref_name
535                         FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
536
537                 $active_prefs = array();
538
539                 while ($line = db_fetch_assoc($u_result)) {
540                         array_push($active_prefs, $line["pref_name"]);
541                 }
542
543                 while ($line = db_fetch_assoc($result)) {
544                         if (array_search($line["pref_name"], $active_prefs) === FALSE) {
545 //                              print "adding " . $line["pref_name"] . "<br>";
546
547                                 if (get_schema_version($link) < 63) {
548                                         db_query($link, "INSERT INTO ttrss_user_prefs
549                                                 (owner_uid,pref_name,value) VALUES
550                                                 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
551
552                                 } else {
553                                         db_query($link, "INSERT INTO ttrss_user_prefs
554                                                 (owner_uid,pref_name,value, profile) VALUES
555                                                 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
556                                 }
557
558                         }
559                 }
560
561                 db_query($link, "COMMIT");
562
563         }
564
565         function get_ssl_certificate_id() {
566                 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
567                         return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
568                                 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
569                                 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
570                                 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
571                 }
572                 return "";
573         }
574
575         function authenticate_user($link, $login, $password, $check_only = false) {
576
577                 if (!SINGLE_USER_MODE) {
578
579                         $user_id = false;
580
581                         global $pluginhost;
582                         foreach ($pluginhost->get_hooks($pluginhost::HOOK_AUTH_USER) as $plugin) {
583
584                                 $user_id = (int) $plugin->authenticate($login, $password);
585
586                                 if ($user_id) {
587                                         $_SESSION["auth_module"] = strtolower(get_class($plugin));
588                                         break;
589                                 }
590                         }
591
592                         if ($user_id && !$check_only) {
593                                 $_SESSION["uid"] = $user_id;
594
595                                 $result = db_query($link, "SELECT login,access_level,pwd_hash FROM ttrss_users
596                                         WHERE id = '$user_id'");
597
598                                 $_SESSION["name"] = db_fetch_result($result, 0, "login");
599                                 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
600                                 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
601
602                                 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
603                                         $_SESSION["uid"]);
604
605                                 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
606                                 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
607
608                                 $_SESSION["last_version_check"] = time();
609
610                                 initialize_user_prefs($link, $_SESSION["uid"]);
611
612                                 return true;
613                         }
614
615                         return false;
616
617                 } else {
618
619                         $_SESSION["uid"] = 1;
620                         $_SESSION["name"] = "admin";
621                         $_SESSION["access_level"] = 10;
622
623                         $_SESSION["hide_hello"] = true;
624                         $_SESSION["hide_logout"] = true;
625
626                         $_SESSION["auth_module"] = false;
627
628                         if (!$_SESSION["csrf_token"]) {
629                                 $_SESSION["csrf_token"] = sha1(uniqid(rand(), true));
630                         }
631
632                         $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
633
634                         initialize_user_prefs($link, $_SESSION["uid"]);
635
636                         return true;
637                 }
638         }
639
640         function make_password($length = 8) {
641
642                 $password = "";
643                 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
644
645         $i = 0;
646
647                 while ($i < $length) {
648                         $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
649
650                         if (!strstr($password, $char)) {
651                                 $password .= $char;
652                                 $i++;
653                         }
654                 }
655                 return $password;
656         }
657
658         // this is called after user is created to initialize default feeds, labels
659         // or whatever else
660
661         // user preferences are checked on every login, not here
662
663         function initialize_user($link, $uid) {
664
665                 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
666                         values ('$uid', 'Tiny Tiny RSS: New Releases',
667                         'http://tt-rss.org/releases.rss')");
668
669                 db_query($link, "insert into ttrss_feeds (owner_uid,title,feed_url)
670                         values ('$uid', 'Tiny Tiny RSS: Forum',
671                                 'http://tt-rss.org/forum/rss.php')");
672         }
673
674         function logout_user() {
675                 session_destroy();
676                 if (isset($_COOKIE[session_name()])) {
677                    setcookie(session_name(), '', time()-42000, '/');
678                 }
679         }
680
681         function validate_csrf($csrf_token) {
682                 return $csrf_token == $_SESSION['csrf_token'];
683         }
684
685         function validate_session($link) {
686                 if (SINGLE_USER_MODE) return true;
687
688                 $check_ip = $_SESSION['ip_address'];
689
690                 switch (SESSION_CHECK_ADDRESS) {
691                 case 0:
692                         $check_ip = '';
693                         break;
694                 case 1:
695                         $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+1);
696                         break;
697                 case 2:
698                         $check_ip = substr($check_ip, 0, strrpos($check_ip, '.'));
699                         $check_ip = substr($check_ip, 0, strrpos($check_ip, '.')+1);
700                         break;
701                 };
702
703                 if ($check_ip && strpos($_SERVER['REMOTE_ADDR'], $check_ip) !== 0) {
704                         $_SESSION["login_error_msg"] =
705                                 __("Session failed to validate (incorrect IP)");
706                         return false;
707                 }
708
709                 if ($_SESSION["ref_schema_version"] != get_schema_version($link, true))
710                         return false;
711
712                 if ($_SESSION["uid"]) {
713
714                         $result = db_query($link,
715                                 "SELECT pwd_hash FROM ttrss_users WHERE id = '".$_SESSION["uid"]."'");
716
717                         $pwd_hash = db_fetch_result($result, 0, "pwd_hash");
718
719                         if ($pwd_hash != $_SESSION["pwd_hash"]) {
720                                 return false;
721                         }
722                 }
723
724 /*              if ($_SESSION["cookie_lifetime"] && $_SESSION["uid"]) {
725
726                         //print_r($_SESSION);
727
728                         if (time() > $_SESSION["cookie_lifetime"]) {
729                                 return false;
730                         }
731                 } */
732
733                 return true;
734         }
735
736         function load_user_plugins($link, $owner_uid) {
737                 if ($owner_uid) {
738                         $plugins = get_pref($link, "_ENABLED_PLUGINS", $owner_uid);
739
740                         global $pluginhost;
741                         $pluginhost->load($plugins, $pluginhost::KIND_USER, $owner_uid);
742
743                         if (get_schema_version($link) > 100) {
744                                 $pluginhost->load_data();
745                         }
746                 }
747         }
748
749         function login_sequence($link) {
750                 $_SESSION["prefs_cache"] = false;
751
752                 if (SINGLE_USER_MODE) {
753                         authenticate_user($link, "admin", null);
754                         cache_prefs($link);
755                         load_user_plugins($link, $_SESSION["uid"]);
756                 } else {
757                         if (!$_SESSION["uid"] || !validate_session($link)) {
758
759                                 if (AUTH_AUTO_LOGIN && authenticate_user($link, null, null)) {
760                                     $_SESSION["ref_schema_version"] = get_schema_version($link, true);
761                                 } else {
762                                          authenticate_user($link, null, null, true);
763                                 }
764
765                                 if (!$_SESSION["uid"]) render_login_form($link);
766
767                         } else {
768                                 /* bump login timestamp */
769                                 db_query($link, "UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
770                                         $_SESSION["uid"]);
771                         }
772
773                         if ($_SESSION["uid"] && $_SESSION["language"] && SESSION_COOKIE_LIFETIME > 0) {
774                                 setcookie("ttrss_lang", $_SESSION["language"],
775                                         time() + SESSION_COOKIE_LIFETIME);
776                         }
777
778                         if ($_SESSION["uid"]) {
779                                 cache_prefs($link);
780                                 load_user_plugins($link, $_SESSION["uid"]);
781                         }
782                 }
783         }
784
785         function truncate_string($str, $max_len, $suffix = '&hellip;') {
786                 if (mb_strlen($str, "utf-8") > $max_len - 3) {
787                         return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
788                 } else {
789                         return $str;
790                 }
791         }
792
793         function convert_timestamp($timestamp, $source_tz, $dest_tz) {
794
795                 try {
796                         $source_tz = new DateTimeZone($source_tz);
797                 } catch (Exception $e) {
798                         $source_tz = new DateTimeZone('UTC');
799                 }
800
801                 try {
802                         $dest_tz = new DateTimeZone($dest_tz);
803                 } catch (Exception $e) {
804                         $dest_tz = new DateTimeZone('UTC');
805                 }
806
807                 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
808                 return $dt->format('U') + $dest_tz->getOffset($dt);
809         }
810
811         function make_local_datetime($link, $timestamp, $long, $owner_uid = false,
812                                         $no_smart_dt = false) {
813
814                 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
815                 if (!$timestamp) $timestamp = '1970-01-01 0:00';
816
817                 global $utc_tz;
818                 global $tz_offset;
819
820                 # We store date in UTC internally
821                 $dt = new DateTime($timestamp, $utc_tz);
822
823                 if ($tz_offset == -1) {
824
825                         $user_tz_string = get_pref($link, 'USER_TIMEZONE', $owner_uid);
826
827                         try {
828                                 $user_tz = new DateTimeZone($user_tz_string);
829                         } catch (Exception $e) {
830                                 $user_tz = $utc_tz;
831                         }
832
833                         $tz_offset = $user_tz->getOffset($dt);
834                 }
835
836                 $user_timestamp = $dt->format('U') + $tz_offset;
837
838                 if (!$no_smart_dt) {
839                         return smart_date_time($link, $user_timestamp,
840                                 $tz_offset, $owner_uid);
841                 } else {
842                         if ($long)
843                                 $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
844                         else
845                                 $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
846
847                         return date($format, $user_timestamp);
848                 }
849         }
850
851         function smart_date_time($link, $timestamp, $tz_offset = 0, $owner_uid = false) {
852                 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
853
854                 if (date("Y.m.d", $timestamp) == date("Y.m.d", time() + $tz_offset)) {
855                         return date("G:i", $timestamp);
856                 } else if (date("Y", $timestamp) == date("Y", time() + $tz_offset)) {
857                         $format = get_pref($link, 'SHORT_DATE_FORMAT', $owner_uid);
858                         return date($format, $timestamp);
859                 } else {
860                         $format = get_pref($link, 'LONG_DATE_FORMAT', $owner_uid);
861                         return date($format, $timestamp);
862                 }
863         }
864
865         function sql_bool_to_bool($s) {
866                 if ($s == "t" || $s == "1" || strtolower($s) == "true") {
867                         return true;
868                 } else {
869                         return false;
870                 }
871         }
872
873         function bool_to_sql_bool($s) {
874                 if ($s) {
875                         return "true";
876                 } else {
877                         return "false";
878                 }
879         }
880
881         // Session caching removed due to causing wrong redirects to upgrade
882         // script when get_schema_version() is called on an obsolete session
883         // created on a previous schema version.
884         function get_schema_version($link, $nocache = false) {
885                 global $schema_version;
886
887                 if (!$schema_version) {
888                         $result = db_query($link, "SELECT schema_version FROM ttrss_version");
889                         $version = db_fetch_result($result, 0, "schema_version");
890                         $schema_version = $version;
891                         return $version;
892                 } else {
893                         return $schema_version;
894                 }
895         }
896
897         function sanity_check($link) {
898                 require_once 'errors.php';
899
900                 $error_code = 0;
901                 $schema_version = get_schema_version($link, true);
902
903                 if ($schema_version != SCHEMA_VERSION) {
904                         $error_code = 5;
905                 }
906
907                 if (DB_TYPE == "mysql") {
908                         $result = db_query($link, "SELECT true", false);
909                         if (db_num_rows($result) != 1) {
910                                 $error_code = 10;
911                         }
912                 }
913
914                 if (db_escape_string($link, "testTEST") != "testTEST") {
915                         $error_code = 12;
916                 }
917
918                 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
919         }
920
921         function file_is_locked($filename) {
922                 if (function_exists('flock')) {
923                         $fp = @fopen(LOCK_DIRECTORY . "/$filename", "r");
924                         if ($fp) {
925                                 if (flock($fp, LOCK_EX | LOCK_NB)) {
926                                         flock($fp, LOCK_UN);
927                                         fclose($fp);
928                                         return false;
929                                 }
930                                 fclose($fp);
931                                 return true;
932                         } else {
933                                 return false;
934                         }
935                 }
936                 return true; // consider the file always locked and skip the test
937         }
938
939         function make_lockfile($filename) {
940                 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
941
942                 if ($fp && flock($fp, LOCK_EX | LOCK_NB)) {
943                         if (function_exists('posix_getpid')) {
944                                 fwrite($fp, posix_getpid() . "\n");
945                         }
946                         return $fp;
947                 } else {
948                         return false;
949                 }
950         }
951
952         function make_stampfile($filename) {
953                 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
954
955                 if (flock($fp, LOCK_EX | LOCK_NB)) {
956                         fwrite($fp, time() . "\n");
957                         flock($fp, LOCK_UN);
958                         fclose($fp);
959                         return true;
960                 } else {
961                         return false;
962                 }
963         }
964
965         function sql_random_function() {
966                 if (DB_TYPE == "mysql") {
967                         return "RAND()";
968                 } else {
969                         return "RANDOM()";
970                 }
971         }
972
973         function catchup_feed($link, $feed, $cat_view, $owner_uid = false, $max_id = false) {
974
975                         if (!$owner_uid) $owner_uid = $_SESSION['uid'];
976
977                         //if (preg_match("/^-?[0-9][0-9]*$/", $feed) != false) {
978
979                         $ref_check_qpart = ($max_id &&
980                                 !get_pref($link, 'REVERSE_HEADLINES')) ? "ref_id <= '$max_id'" : "true";
981
982                         if (is_numeric($feed)) {
983                                 if ($cat_view) {
984
985                                         if ($feed >= 0) {
986
987                                                 if ($feed > 0) {
988                                                         $children = getChildCategories($link, $feed, $owner_uid);
989                                                         array_push($children, $feed);
990
991                                                         $children = join(",", $children);
992
993                                                         $cat_qpart = "cat_id IN ($children)";
994                                                 } else {
995                                                         $cat_qpart = "cat_id IS NULL";
996                                                 }
997
998                                                 db_query($link, "UPDATE ttrss_user_entries
999                                                         SET unread = false,last_read = NOW()
1000                                                         WHERE feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart)
1001                                                         AND $ref_check_qpart AND unread = true
1002                                                         AND owner_uid = $owner_uid");
1003
1004                                         } else if ($feed == -2) {
1005
1006                                                 db_query($link, "UPDATE ttrss_user_entries
1007                                                         SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*)
1008                                                                 FROM ttrss_user_labels2 WHERE article_id = ref_id) > 0
1009                                                                 AND $ref_check_qpart
1010                                                                 AND unread = true AND owner_uid = $owner_uid");
1011                                         }
1012
1013                                 } else if ($feed > 0) {
1014
1015                                         db_query($link, "UPDATE ttrss_user_entries
1016                                                         SET unread = false,last_read = NOW()
1017                                                         WHERE feed_id = '$feed'
1018                                                         AND $ref_check_qpart AND unread = true
1019                                                         AND owner_uid = $owner_uid");
1020
1021                                 } else if ($feed < 0 && $feed > -10) { // special, like starred
1022
1023                                         if ($feed == -1) {
1024                                                 db_query($link, "UPDATE ttrss_user_entries
1025                                                         SET unread = false,last_read = NOW()
1026                                                         WHERE marked = true
1027                                                         AND $ref_check_qpart AND unread = true
1028                                                         AND owner_uid = $owner_uid");
1029                                         }
1030
1031                                         if ($feed == -2) {
1032                                                 db_query($link, "UPDATE ttrss_user_entries
1033                                                         SET unread = false,last_read = NOW()
1034                                                         WHERE published = true
1035                                                         AND $ref_check_qpart AND unread = true
1036                                                         AND owner_uid = $owner_uid");
1037                                         }
1038
1039                                         if ($feed == -3) {
1040
1041                                                 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE");
1042
1043                                                 if (DB_TYPE == "pgsql") {
1044                                                         $match_part = "updated > NOW() - INTERVAL '$intl hour' ";
1045                                                 } else {
1046                                                         $match_part = "updated > DATE_SUB(NOW(),
1047                                                                 INTERVAL $intl HOUR) ";
1048                                                 }
1049
1050                                                 $result = db_query($link, "SELECT id FROM ttrss_entries,
1051                                                         ttrss_user_entries WHERE $match_part AND
1052                                                         unread = true AND
1053                                                         ttrss_user_entries.ref_id = ttrss_entries.id AND
1054                                                         owner_uid = $owner_uid");
1055
1056                                                 $affected_ids = array();
1057
1058                                                 while ($line = db_fetch_assoc($result)) {
1059                                                         array_push($affected_ids, $line["id"]);
1060                                                 }
1061
1062                                                 catchupArticlesById($link, $affected_ids, 0);
1063                                         }
1064
1065                                         if ($feed == -4) {
1066                                                 db_query($link, "UPDATE ttrss_user_entries
1067                                                         SET unread = false,last_read = NOW()
1068                                                         WHERE $ref_check_qpart AND unread = true AND
1069                                                         owner_uid = $owner_uid");
1070                                         }
1071
1072                                 } else if ($feed < -10) { // label
1073
1074                                         $label_id = -$feed - 11;
1075
1076                                         db_query($link, "UPDATE ttrss_user_entries, ttrss_user_labels2
1077                                                 SET unread = false, last_read = NOW()
1078                                                         WHERE label_id = '$label_id' AND unread = true
1079                                                         AND $ref_check_qpart
1080                                                         AND owner_uid = '$owner_uid' AND ref_id = article_id");
1081
1082                                 }
1083
1084                                 ccache_update($link, $feed, $owner_uid, $cat_view);
1085
1086                         } else { // tag
1087                                 db_query($link, "BEGIN");
1088
1089                                 $tag_name = db_escape_string($link, $feed);
1090
1091                                 $result = db_query($link, "SELECT post_int_id FROM ttrss_tags
1092                                         WHERE tag_name = '$tag_name' AND owner_uid = $owner_uid");
1093
1094                                 while ($line = db_fetch_assoc($result)) {
1095                                         db_query($link, "UPDATE ttrss_user_entries SET
1096                                                 unread = false, last_read = NOW()
1097                                                 WHERE $ref_check_qpart AND unread = true
1098                                                 AND int_id = " . $line["post_int_id"]);
1099                                 }
1100                                 db_query($link, "COMMIT");
1101                         }
1102         }
1103
1104         function getAllCounters($link) {
1105                 $data = getGlobalCounters($link);
1106
1107                 $data = array_merge($data, getVirtCounters($link));
1108                 $data = array_merge($data, getLabelCounters($link));
1109                 $data = array_merge($data, getFeedCounters($link, $active_feed));
1110                 $data = array_merge($data, getCategoryCounters($link));
1111
1112                 return $data;
1113         }
1114
1115         function getCategoryTitle($link, $cat_id) {
1116
1117                 if ($cat_id == -1) {
1118                         return __("Special");
1119                 } else if ($cat_id == -2) {
1120                         return __("Labels");
1121                 } else {
1122
1123                         $result = db_query($link, "SELECT title FROM ttrss_feed_categories WHERE
1124                                 id = '$cat_id'");
1125
1126                         if (db_num_rows($result) == 1) {
1127                                 return db_fetch_result($result, 0, "title");
1128                         } else {
1129                                 return __("Uncategorized");
1130                         }
1131                 }
1132         }
1133
1134
1135         function getCategoryCounters($link) {
1136                 $ret_arr = array();
1137
1138                 /* Labels category */
1139
1140                 $cv = array("id" => -2, "kind" => "cat",
1141                         "counter" => getCategoryUnread($link, -2));
1142
1143                 array_push($ret_arr, $cv);
1144
1145                 $result = db_query($link, "SELECT id AS cat_id, value AS unread,
1146                         (SELECT COUNT(id) FROM ttrss_feed_categories AS c2
1147                                 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
1148                         FROM ttrss_feed_categories, ttrss_cat_counters_cache
1149                         WHERE ttrss_cat_counters_cache.feed_id = id AND
1150                         ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
1151                         ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
1152
1153                 while ($line = db_fetch_assoc($result)) {
1154                         $line["cat_id"] = (int) $line["cat_id"];
1155
1156                         if ($line["num_children"] > 0) {
1157                                 $child_counter = getCategoryChildrenUnread($link, $line["cat_id"], $_SESSION["uid"]);
1158                         } else {
1159                                 $child_counter = 0;
1160                         }
1161
1162                         $cv = array("id" => $line["cat_id"], "kind" => "cat",
1163                                 "counter" => $line["unread"] + $child_counter);
1164
1165                         array_push($ret_arr, $cv);
1166                 }
1167
1168                 /* Special case: NULL category doesn't actually exist in the DB */
1169
1170                 $cv = array("id" => 0, "kind" => "cat",
1171                         "counter" => (int) ccache_find($link, 0, $_SESSION["uid"], true));
1172
1173                 array_push($ret_arr, $cv);
1174
1175                 return $ret_arr;
1176         }
1177
1178         // only accepts real cats (>= 0)
1179         function getCategoryChildrenUnread($link, $cat, $owner_uid = false) {
1180                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1181
1182                 $result = db_query($link, "SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat'
1183                                 AND owner_uid = $owner_uid");
1184
1185                 $unread = 0;
1186
1187                 while ($line = db_fetch_assoc($result)) {
1188                         $unread += getCategoryUnread($link, $line["id"], $owner_uid);
1189                         $unread += getCategoryChildrenUnread($link, $line["id"], $owner_uid);
1190                 }
1191
1192                 return $unread;
1193         }
1194
1195         function getCategoryUnread($link, $cat, $owner_uid = false) {
1196
1197                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1198
1199                 if ($cat >= 0) {
1200
1201                         if ($cat != 0) {
1202                                 $cat_query = "cat_id = '$cat'";
1203                         } else {
1204                                 $cat_query = "cat_id IS NULL";
1205                         }
1206
1207                         $result = db_query($link, "SELECT id FROM ttrss_feeds WHERE $cat_query
1208                                         AND owner_uid = " . $owner_uid);
1209
1210                         $cat_feeds = array();
1211                         while ($line = db_fetch_assoc($result)) {
1212                                 array_push($cat_feeds, "feed_id = " . $line["id"]);
1213                         }
1214
1215                         if (count($cat_feeds) == 0) return 0;
1216
1217                         $match_part = implode(" OR ", $cat_feeds);
1218
1219                         $result = db_query($link, "SELECT COUNT(int_id) AS unread
1220                                 FROM ttrss_user_entries
1221                                 WHERE   unread = true AND ($match_part)
1222                                 AND owner_uid = " . $owner_uid);
1223
1224                         $unread = 0;
1225
1226                         # this needs to be rewritten
1227                         while ($line = db_fetch_assoc($result)) {
1228                                 $unread += $line["unread"];
1229                         }
1230
1231                         return $unread;
1232                 } else if ($cat == -1) {
1233                         return getFeedUnread($link, -1) + getFeedUnread($link, -2) + getFeedUnread($link, -3) + getFeedUnread($link, 0);
1234                 } else if ($cat == -2) {
1235
1236                         $result = db_query($link, "
1237                                 SELECT COUNT(unread) AS unread FROM
1238                                         ttrss_user_entries, ttrss_user_labels2
1239                                 WHERE article_id = ref_id AND unread = true
1240                                         AND ttrss_user_entries.owner_uid = '$owner_uid'");
1241
1242                         $unread = db_fetch_result($result, 0, "unread");
1243
1244                         return $unread;
1245
1246                 }
1247         }
1248
1249         function getFeedUnread($link, $feed, $is_cat = false) {
1250                 return getFeedArticles($link, $feed, $is_cat, true, $_SESSION["uid"]);
1251         }
1252
1253         function getLabelUnread($link, $label_id, $owner_uid = false) {
1254                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1255
1256                 $result = db_query($link, "SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2
1257                         WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id");
1258
1259                 if (db_num_rows($result) != 0) {
1260                         return db_fetch_result($result, 0, "unread");
1261                 } else {
1262                         return 0;
1263                 }
1264         }
1265
1266         function getFeedArticles($link, $feed, $is_cat = false, $unread_only = false,
1267                 $owner_uid = false) {
1268
1269                 $n_feed = (int) $feed;
1270                 $need_entries = false;
1271
1272                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1273
1274                 if ($unread_only) {
1275                         $unread_qpart = "unread = true";
1276                 } else {
1277                         $unread_qpart = "true";
1278                 }
1279
1280                 if ($is_cat) {
1281                         return getCategoryUnread($link, $n_feed, $owner_uid);
1282                 } else if ($n_feed == -6) {
1283                         return 0;
1284                 } else if ($feed != "0" && $n_feed == 0) {
1285
1286                         $feed = db_escape_string($link, $feed);
1287
1288                         $result = db_query($link, "SELECT SUM((SELECT COUNT(int_id)
1289                                 FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id
1290                                         AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags
1291                                 WHERE owner_uid = $owner_uid AND tag_name = '$feed'");
1292                         return db_fetch_result($result, 0, "count");
1293
1294                 } else if ($n_feed == -1) {
1295                         $match_part = "marked = true";
1296                 } else if ($n_feed == -2) {
1297                         $match_part = "published = true";
1298                 } else if ($n_feed == -3) {
1299                         $match_part = "unread = true AND score >= 0";
1300
1301                         $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
1302
1303                         if (DB_TYPE == "pgsql") {
1304                                 $match_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
1305                         } else {
1306                                 $match_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
1307                         }
1308
1309                         $need_entries = true;
1310
1311                 } else if ($n_feed == -4) {
1312                         $match_part = "true";
1313                 } else if ($n_feed >= 0) {
1314
1315                         if ($n_feed != 0) {
1316                                 $match_part = "feed_id = '$n_feed'";
1317                         } else {
1318                                 $match_part = "feed_id IS NULL";
1319                         }
1320
1321                 } else if ($feed < -10) {
1322
1323                         $label_id = -$feed - 11;
1324
1325                         return getLabelUnread($link, $label_id, $owner_uid);
1326
1327                 }
1328
1329                 if ($match_part) {
1330
1331                         if ($need_entries) {
1332                                 $from_qpart = "ttrss_user_entries,ttrss_entries";
1333                                 $from_where = "ttrss_entries.id = ttrss_user_entries.ref_id AND";
1334                         } else {
1335                                 $from_qpart = "ttrss_user_entries";
1336                         }
1337
1338                         $query = "SELECT count(int_id) AS unread
1339                                 FROM $from_qpart WHERE
1340                                 $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid";
1341
1342                         //echo "[$feed/$query]\n";
1343
1344                         $result = db_query($link, $query);
1345
1346                 } else {
1347
1348                         $result = db_query($link, "SELECT COUNT(post_int_id) AS unread
1349                                 FROM ttrss_tags,ttrss_user_entries,ttrss_entries
1350                                 WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id
1351                                 AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid);
1352                 }
1353
1354                 $unread = db_fetch_result($result, 0, "unread");
1355
1356                 return $unread;
1357         }
1358
1359         function getGlobalUnread($link, $user_id = false) {
1360
1361                 if (!$user_id) {
1362                         $user_id = $_SESSION["uid"];
1363                 }
1364
1365                 $result = db_query($link, "SELECT SUM(value) AS c_id FROM ttrss_counters_cache
1366                         WHERE owner_uid = '$user_id' AND feed_id > 0");
1367
1368                 $c_id = db_fetch_result($result, 0, "c_id");
1369
1370                 return $c_id;
1371         }
1372
1373         function getGlobalCounters($link, $global_unread = -1) {
1374                 $ret_arr = array();
1375
1376                 if ($global_unread == -1) {
1377                         $global_unread = getGlobalUnread($link);
1378                 }
1379
1380                 $cv = array("id" => "global-unread",
1381                         "counter" => (int) $global_unread);
1382
1383                 array_push($ret_arr, $cv);
1384
1385                 $result = db_query($link, "SELECT COUNT(id) AS fn FROM
1386                         ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1387
1388                 $subscribed_feeds = db_fetch_result($result, 0, "fn");
1389
1390                 $cv = array("id" => "subscribed-feeds",
1391                         "counter" => (int) $subscribed_feeds);
1392
1393                 array_push($ret_arr, $cv);
1394
1395                 return $ret_arr;
1396         }
1397
1398         function getVirtCounters($link) {
1399
1400                 $ret_arr = array();
1401
1402                 for ($i = 0; $i >= -4; $i--) {
1403
1404                         $count = getFeedUnread($link, $i);
1405
1406                         $cv = array("id" => $i,
1407                                 "counter" => (int) $count);
1408
1409 //                      if (get_pref($link, 'EXTENDED_FEEDLIST'))
1410 //                              $cv["xmsg"] = getFeedArticles($link, $i)." ".__("total");
1411
1412                         array_push($ret_arr, $cv);
1413                 }
1414
1415                 return $ret_arr;
1416         }
1417
1418         function getLabelCounters($link, $descriptions = false) {
1419
1420                 $ret_arr = array();
1421
1422                 $owner_uid = $_SESSION["uid"];
1423
1424                 $result = db_query($link, "SELECT id,caption,COUNT(unread) AS unread
1425                         FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
1426                                 (ttrss_labels2.id = label_id)
1427                                         LEFT JOIN ttrss_user_entries ON (ref_id = article_id AND unread = true)
1428                                 WHERE ttrss_labels2.owner_uid = $owner_uid GROUP BY ttrss_labels2.id,
1429                                         ttrss_labels2.caption");
1430
1431                 while ($line = db_fetch_assoc($result)) {
1432
1433                         $id = -$line["id"] - 11;
1434
1435                         $label_name = $line["caption"];
1436                         $count = $line["unread"];
1437
1438                         $cv = array("id" => $id,
1439                                 "counter" => (int) $count);
1440
1441                         if ($descriptions)
1442                                 $cv["description"] = $label_name;
1443
1444 //                      if (get_pref($link, 'EXTENDED_FEEDLIST'))
1445 //                              $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1446
1447                         array_push($ret_arr, $cv);
1448                 }
1449
1450                 return $ret_arr;
1451         }
1452
1453         function getFeedCounters($link, $active_feed = false) {
1454
1455                 $ret_arr = array();
1456
1457                 $query = "SELECT ttrss_feeds.id,
1458                                 ttrss_feeds.title,
1459                                 ".SUBSTRING_FOR_DATE."(ttrss_feeds.last_updated,1,19) AS last_updated,
1460                                 last_error, value AS count
1461                         FROM ttrss_feeds, ttrss_counters_cache
1462                         WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
1463                                 AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
1464                                 AND ttrss_counters_cache.feed_id = id";
1465
1466                 $result = db_query($link, $query);
1467                 $fctrs_modified = false;
1468
1469                 while ($line = db_fetch_assoc($result)) {
1470
1471                         $id = $line["id"];
1472                         $count = $line["count"];
1473                         $last_error = htmlspecialchars($line["last_error"]);
1474
1475                         $last_updated = make_local_datetime($link, $line['last_updated'], false);
1476
1477                         $has_img = feed_has_icon($id);
1478
1479                         if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
1480                                 $last_updated = '';
1481
1482                         $cv = array("id" => $id,
1483                                 "updated" => $last_updated,
1484                                 "counter" => (int) $count,
1485                                 "has_img" => (int) $has_img);
1486
1487                         if ($last_error)
1488                                 $cv["error"] = $last_error;
1489
1490 //                      if (get_pref($link, 'EXTENDED_FEEDLIST'))
1491 //                              $cv["xmsg"] = getFeedArticles($link, $id)." ".__("total");
1492
1493                         if ($active_feed && $id == $active_feed)
1494                                 $cv["title"] = truncate_string($line["title"], 30);
1495
1496                         array_push($ret_arr, $cv);
1497
1498                 }
1499
1500                 return $ret_arr;
1501         }
1502
1503         function get_pgsql_version($link) {
1504                 $result = db_query($link, "SELECT version() AS version");
1505                 $version = explode(" ", db_fetch_result($result, 0, "version"));
1506                 return $version[1];
1507         }
1508
1509         /**
1510          * @return array (code => Status code, message => error message if available)
1511          *
1512          *                 0 - OK, Feed already exists
1513          *                 1 - OK, Feed added
1514          *                 2 - Invalid URL
1515          *                 3 - URL content is HTML, no feeds available
1516          *                 4 - URL content is HTML which contains multiple feeds.
1517          *                     Here you should call extractfeedurls in rpc-backend
1518          *                     to get all possible feeds.
1519          *                 5 - Couldn't download the URL content.
1520          */
1521         function subscribe_to_feed($link, $url, $cat_id = 0,
1522                         $auth_login = '', $auth_pass = '', $need_auth = false) {
1523
1524                 global $fetch_last_error;
1525
1526                 require_once "include/rssfuncs.php";
1527
1528                 $url = fix_url($url);
1529
1530                 if (!$url || !validate_feed_url($url)) return array("code" => 2);
1531
1532                 $contents = @fetch_file_contents($url, false, $auth_login, $auth_pass);
1533
1534                 if (!$contents) {
1535                         return array("code" => 5, "message" => $fetch_last_error);
1536                 }
1537
1538                 if (is_html($contents)) {
1539                         $feedUrls = get_feeds_from_html($url, $contents);
1540
1541                         if (count($feedUrls) == 0) {
1542                                 return array("code" => 3);
1543                         } else if (count($feedUrls) > 1) {
1544                                 return array("code" => 4, "feeds" => $feedUrls);
1545                         }
1546                         //use feed url as new URL
1547                         $url = key($feedUrls);
1548                 }
1549
1550                 if ($cat_id == "0" || !$cat_id) {
1551                         $cat_qpart = "NULL";
1552                 } else {
1553                         $cat_qpart = "'$cat_id'";
1554                 }
1555
1556                 $result = db_query($link,
1557                         "SELECT id FROM ttrss_feeds
1558                         WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]);
1559
1560                 if (db_num_rows($result) == 0) {
1561                         $result = db_query($link,
1562                                 "INSERT INTO ttrss_feeds
1563                                         (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method)
1564                                 VALUES ('".$_SESSION["uid"]."', '$url',
1565                                 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0)");
1566
1567                         $result = db_query($link,
1568                                 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1569                                         AND owner_uid = " . $_SESSION["uid"]);
1570
1571                         $feed_id = db_fetch_result($result, 0, "id");
1572
1573                         if ($feed_id) {
1574                                 update_rss_feed($link, $feed_id, true);
1575                         }
1576
1577                         return array("code" => 1);
1578                 } else {
1579                         return array("code" => 0);
1580                 }
1581         }
1582
1583         function print_feed_select($link, $id, $default_id = "",
1584                 $attributes = "", $include_all_feeds = true,
1585                 $root_id = false, $nest_level = 0) {
1586
1587                 if (!$root_id) {
1588                         print "<select id=\"$id\" name=\"$id\" $attributes>";
1589                         if ($include_all_feeds) {
1590                                 $is_selected = ("0" == $default_id) ? "selected=\"1\"" : "";
1591                                 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1592                         }
1593                 }
1594
1595                 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1596
1597                         if ($root_id)
1598                                 $parent_qpart = "parent_cat = '$root_id'";
1599                         else
1600                                 $parent_qpart = "parent_cat IS NULL";
1601
1602                         $result = db_query($link, "SELECT id,title,
1603                                 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1604                                         c2.parent_cat = ttrss_feed_categories.id) AS num_children
1605                                 FROM ttrss_feed_categories
1606                                 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1607
1608                         while ($line = db_fetch_assoc($result)) {
1609
1610                                 for ($i = 0; $i < $nest_level; $i++)
1611                                         $line["title"] = " - " . $line["title"];
1612
1613                                 $is_selected = ("CAT:".$line["id"] == $default_id) ? "selected=\"1\"" : "";
1614
1615                                 printf("<option $is_selected value='CAT:%d'>%s</option>",
1616                                         $line["id"], htmlspecialchars($line["title"]));
1617
1618                                 if ($line["num_children"] > 0)
1619                                         print_feed_select($link, $id, $default_id, $attributes,
1620                                                 $include_all_feeds, $line["id"], $nest_level+1);
1621
1622                                 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1623                                         WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1624
1625                                 while ($fline = db_fetch_assoc($feed_result)) {
1626                                         $is_selected = ($fline["id"] == $default_id) ? "selected=\"1\"" : "";
1627
1628                                         $fline["title"] = " + " . $fline["title"];
1629
1630                                         for ($i = 0; $i < $nest_level; $i++)
1631                                                 $fline["title"] = " - " . $fline["title"];
1632
1633                                         printf("<option $is_selected value='%d'>%s</option>",
1634                                                 $fline["id"], htmlspecialchars($fline["title"]));
1635                                 }
1636                         }
1637
1638                         if (!$root_id) {
1639                                 $is_selected = ($default_id == "CAT:0") ? "selected=\"1\"" : "";
1640
1641                                 printf("<option $is_selected value='CAT:0'>%s</option>",
1642                                         __("Uncategorized"));
1643
1644                                 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1645                                         WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1646
1647                                 while ($fline = db_fetch_assoc($feed_result)) {
1648                                         $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ? "selected=\"1\"" : "";
1649
1650                                         $fline["title"] = " + " . $fline["title"];
1651
1652                                         for ($i = 0; $i < $nest_level; $i++)
1653                                                 $fline["title"] = " - " . $fline["title"];
1654
1655                                         printf("<option $is_selected value='%d'>%s</option>",
1656                                                 $fline["id"], htmlspecialchars($fline["title"]));
1657                                 }
1658                         }
1659
1660                 } else {
1661                         $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1662                                 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1663
1664                         while ($line = db_fetch_assoc($result)) {
1665
1666                                 $is_selected = ($line["id"] == $default_id) ? "selected=\"1\"" : "";
1667
1668                                 printf("<option $is_selected value='%d'>%s</option>",
1669                                         $line["id"], htmlspecialchars($line["title"]));
1670                         }
1671                 }
1672
1673                 if (!$root_id) {
1674                         print "</select>";
1675                 }
1676         }
1677
1678         function print_feed_cat_select($link, $id, $default_id,
1679                 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1680
1681                         if (!$root_id) {
1682                                         print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1683                         }
1684
1685                         if ($root_id)
1686                                 $parent_qpart = "parent_cat = '$root_id'";
1687                         else
1688                                 $parent_qpart = "parent_cat IS NULL";
1689
1690                         $result = db_query($link, "SELECT id,title,
1691                                 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1692                                         c2.parent_cat = ttrss_feed_categories.id) AS num_children
1693                                 FROM ttrss_feed_categories
1694                                 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1695
1696                         while ($line = db_fetch_assoc($result)) {
1697                                 if ($line["id"] == $default_id) {
1698                                         $is_selected = "selected=\"1\"";
1699                                 } else {
1700                                         $is_selected = "";
1701                                 }
1702
1703                                 for ($i = 0; $i < $nest_level; $i++)
1704                                         $line["title"] = " - " . $line["title"];
1705
1706                                 if ($line["title"])
1707                                         printf("<option $is_selected value='%d'>%s</option>",
1708                                                 $line["id"], htmlspecialchars($line["title"]));
1709
1710                                 if ($line["num_children"] > 0)
1711                                         print_feed_cat_select($link, $id, $default_id, $attributes,
1712                                                 $include_all_cats, $line["id"], $nest_level+1);
1713                         }
1714
1715                         if (!$root_id) {
1716                                 if ($include_all_cats) {
1717                                         if (db_num_rows($result) > 0) {
1718                                                 print "<option disabled=\"1\">--------</option>";
1719                                         }
1720
1721                                         if ($default_id == 0) {
1722                                                 $is_selected = "selected=\"1\"";
1723                                         } else {
1724                                                 $is_selected = "";
1725                                         }
1726
1727                                         print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1728                                 }
1729                                 print "</select>";
1730                         }
1731                 }
1732
1733         function checkbox_to_sql_bool($val) {
1734                 return ($val == "on") ? "true" : "false";
1735         }
1736
1737         function getFeedCatTitle($link, $id) {
1738                 if ($id == -1) {
1739                         return __("Special");
1740                 } else if ($id < -10) {
1741                         return __("Labels");
1742                 } else if ($id > 0) {
1743                         $result = db_query($link, "SELECT ttrss_feed_categories.title
1744                                 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1745                                         cat_id = ttrss_feed_categories.id");
1746                         if (db_num_rows($result) == 1) {
1747                                 return db_fetch_result($result, 0, "title");
1748                         } else {
1749                                 return __("Uncategorized");
1750                         }
1751                 } else {
1752                         return "getFeedCatTitle($id) failed";
1753                 }
1754
1755         }
1756
1757         function getFeedIcon($id) {
1758                 switch ($id) {
1759                 case 0:
1760                         return "images/archive.png";
1761                         break;
1762                 case -1:
1763                         return "images/mark_set.svg";
1764                         break;
1765                 case -2:
1766                         return "images/pub_set.svg";
1767                         break;
1768                 case -3:
1769                         return "images/fresh.png";
1770                         break;
1771                 case -4:
1772                         return "images/tag.png";
1773                         break;
1774                 case -6:
1775                         return "images/recently_read.png";
1776                         break;
1777                 default:
1778                         if ($id < -10) {
1779                                 return "images/label.png";
1780                         } else {
1781                                 if (file_exists(ICONS_DIR . "/$id.ico"))
1782                                         return ICONS_URL . "/$id.ico";
1783                         }
1784                         break;
1785                 }
1786         }
1787
1788         function getFeedTitle($link, $id, $cat = false) {
1789                 if ($cat) {
1790                         return getCategoryTitle($link, $id);
1791                 } else if ($id == -1) {
1792                         return __("Starred articles");
1793                 } else if ($id == -2) {
1794                         return __("Published articles");
1795                 } else if ($id == -3) {
1796                         return __("Fresh articles");
1797                 } else if ($id == -4) {
1798                         return __("All articles");
1799                 } else if ($id === 0 || $id === "0") {
1800                         return __("Archived articles");
1801                 } else if ($id == -6) {
1802                         return __("Recently read");
1803                 } else if ($id < -10) {
1804                         $label_id = -$id - 11;
1805                         $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1806                         if (db_num_rows($result) == 1) {
1807                                 return db_fetch_result($result, 0, "caption");
1808                         } else {
1809                                 return "Unknown label ($label_id)";
1810                         }
1811
1812                 } else if (is_numeric($id) && $id > 0) {
1813                         $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1814                         if (db_num_rows($result) == 1) {
1815                                 return db_fetch_result($result, 0, "title");
1816                         } else {
1817                                 return "Unknown feed ($id)";
1818                         }
1819                 } else {
1820                         return $id;
1821                 }
1822         }
1823
1824         function make_init_params($link) {
1825                 $params = array();
1826
1827                 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1828                         "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1829                         "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE", "DEFAULT_ARTICLE_LIMIT",
1830                         "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1831
1832                                  $params[strtolower($param)] = (int) get_pref($link, $param);
1833                  }
1834
1835                 $params["icons_url"] = ICONS_URL;
1836                 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
1837                 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1838                 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1839                 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1840                 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1841
1842                 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1843                         ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1844
1845                 $max_feed_id = db_fetch_result($result, 0, "mid");
1846                 $num_feeds = db_fetch_result($result, 0, "nf");
1847
1848                 $params["max_feed_id"] = (int) $max_feed_id;
1849                 $params["num_feeds"] = (int) $num_feeds;
1850
1851                 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1852                 $params["hotkeys"] = get_hotkeys_map($link);
1853
1854                 $params["csrf_token"] = $_SESSION["csrf_token"];
1855                 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1856
1857                 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
1858
1859                 return $params;
1860         }
1861
1862         function get_hotkeys_info($link) {
1863                 $hotkeys = array(
1864                         __("Navigation") => array(
1865                                 "next_feed" => __("Open next feed"),
1866                                 "prev_feed" => __("Open previous feed"),
1867                                 "next_article" => __("Open next article"),
1868                                 "prev_article" => __("Open previous article"),
1869                                 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1870                                 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1871                                 "search_dialog" => __("Show search dialog")),
1872                         __("Article") => array(
1873                                 "toggle_mark" => __("Toggle starred"),
1874                                 "toggle_publ" => __("Toggle published"),
1875                                 "toggle_unread" => __("Toggle unread"),
1876                                 "edit_tags" => __("Edit tags"),
1877                                 "dismiss_selected" => __("Dismiss selected"),
1878                                 "dismiss_read" => __("Dismiss read"),
1879                                 "open_in_new_window" => __("Open in new window"),
1880                                 "catchup_below" => __("Mark below as read"),
1881                                 "catchup_above" => __("Mark above as read"),
1882                                 "article_scroll_down" => __("Scroll down"),
1883                                 "article_scroll_up" => __("Scroll up"),
1884                                 "select_article_cursor" => __("Select article under cursor"),
1885                                 "email_article" => __("Email article"),
1886                                 "close_article" => __("Close/collapse article"),
1887                                 "toggle_widescreen" => __("Toggle widescreen mode"),
1888                                 "toggle_embed_original" => __("Toggle embed original")),
1889                         __("Article selection") => array(
1890                                 "select_all" => __("Select all articles"),
1891                                 "select_unread" => __("Select unread"),
1892                                 "select_marked" => __("Select starred"),
1893                                 "select_published" => __("Select published"),
1894                                 "select_invert" => __("Invert selection"),
1895                                 "select_none" => __("Deselect everything")),
1896                         __("Feed") => array(
1897                                 "feed_refresh" => __("Refresh current feed"),
1898                                 "feed_unhide_read" => __("Un/hide read feeds"),
1899                                 "feed_subscribe" => __("Subscribe to feed"),
1900                                 "feed_edit" => __("Edit feed"),
1901                                 "feed_catchup" => __("Mark as read"),
1902                                 "feed_reverse" => __("Reverse headlines"),
1903                                 "feed_debug_update" => __("Debug feed update"),
1904                                 "catchup_all" => __("Mark all feeds as read"),
1905                                 "cat_toggle_collapse" => __("Un/collapse current category"),
1906                                 "toggle_combined_mode" => __("Toggle combined mode")),
1907                         __("Go to") => array(
1908                                 "goto_all" => __("All articles"),
1909                                 "goto_fresh" => __("Fresh"),
1910                                 "goto_marked" => __("Starred"),
1911                                 "goto_published" => __("Published"),
1912                                 "goto_tagcloud" => __("Tag cloud"),
1913                                 "goto_prefs" => __("Preferences")),
1914                         __("Other") => array(
1915                                 "create_label" => __("Create label"),
1916                                 "create_filter" => __("Create filter"),
1917                                 "collapse_sidebar" => __("Un/collapse sidebar"),
1918                                 "help_dialog" => __("Show help dialog"))
1919                         );
1920
1921                 return $hotkeys;
1922         }
1923
1924         function get_hotkeys_map($link) {
1925                 $hotkeys = array(
1926 //                      "navigation" => array(
1927                                 "k" => "next_feed",
1928                                 "j" => "prev_feed",
1929                                 "n" => "next_article",
1930                                 "p" => "prev_article",
1931                                 "(38)|up" => "prev_article",
1932                                 "(40)|down" => "next_article",
1933 //                              "^(38)|Ctrl-up" => "prev_article_noscroll",
1934 //                              "^(40)|Ctrl-down" => "next_article_noscroll",
1935                                 "(191)|/" => "search_dialog",
1936 //                      "article" => array(
1937                                 "s" => "toggle_mark",
1938                                 "*s" => "toggle_publ",
1939                                 "u" => "toggle_unread",
1940                                 "*t" => "edit_tags",
1941                                 "*d" => "dismiss_selected",
1942                                 "*x" => "dismiss_read",
1943                                 "o" => "open_in_new_window",
1944                                 "c p" => "catchup_below",
1945                                 "c n" => "catchup_above",
1946                                 "*n" => "article_scroll_down",
1947                                 "*p" => "article_scroll_up",
1948                                 "*(38)|Shift+up" => "article_scroll_up",
1949                                 "*(40)|Shift+down" => "article_scroll_down",
1950                                 "a *w" => "toggle_widescreen",
1951                                 "a e" => "toggle_embed_original",
1952                                 "e" => "email_article",
1953                                 "a q" => "close_article",
1954 //                      "article_selection" => array(
1955                                 "a a" => "select_all",
1956                                 "a u" => "select_unread",
1957                                 "a *u" => "select_marked",
1958                                 "a p" => "select_published",
1959                                 "a i" => "select_invert",
1960                                 "a n" => "select_none",
1961 //                      "feed" => array(
1962                                 "f r" => "feed_refresh",
1963                                 "f a" => "feed_unhide_read",
1964                                 "f s" => "feed_subscribe",
1965                                 "f e" => "feed_edit",
1966                                 "f q" => "feed_catchup",
1967                                 "f x" => "feed_reverse",
1968                                 "f *d" => "feed_debug_update",
1969                                 "f *c" => "toggle_combined_mode",
1970                                 "*q" => "catchup_all",
1971                                 "x" => "cat_toggle_collapse",
1972 //                      "goto" => array(
1973                                 "g a" => "goto_all",
1974                                 "g f" => "goto_fresh",
1975                                 "g s" => "goto_marked",
1976                                 "g p" => "goto_published",
1977                                 "g t" => "goto_tagcloud",
1978                                 "g *p" => "goto_prefs",
1979 //                      "other" => array(
1980                                 "(9)|Tab" => "select_article_cursor", // tab
1981                                 "c l" => "create_label",
1982                                 "c f" => "create_filter",
1983                                 "c s" => "collapse_sidebar",
1984                                 "^(191)|Ctrl+/" => "help_dialog",
1985                         );
1986
1987                 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
1988                         $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1989                         $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1990                 }
1991
1992                 global $pluginhost;
1993                 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP) as $plugin) {
1994                         $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1995                 }
1996
1997                 $prefixes = array();
1998
1999                 foreach (array_keys($hotkeys) as $hotkey) {
2000                         $pair = explode(" ", $hotkey, 2);
2001
2002                         if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2003                                 array_push($prefixes, $pair[0]);
2004                         }
2005                 }
2006
2007                 return array($prefixes, $hotkeys);
2008         }
2009
2010         function make_runtime_info($link) {
2011                 $data = array();
2012
2013                 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2014                         ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2015
2016                 $max_feed_id = db_fetch_result($result, 0, "mid");
2017                 $num_feeds = db_fetch_result($result, 0, "nf");
2018
2019                 $data["max_feed_id"] = (int) $max_feed_id;
2020                 $data["num_feeds"] = (int) $num_feeds;
2021
2022                 $data['last_article_id'] = getLastArticleId($link);
2023                 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2024
2025                 $data['dep_ts'] = calculate_dep_timestamp();
2026
2027                 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
2028
2029                         $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2030
2031                         if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2032
2033                                 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
2034
2035                                 if ($stamp) {
2036                                         $stamp_delta = time() - $stamp;
2037
2038                                         if ($stamp_delta > 1800) {
2039                                                 $stamp_check = 0;
2040                                         } else {
2041                                                 $stamp_check = 1;
2042                                                 $_SESSION["daemon_stamp_check"] = time();
2043                                         }
2044
2045                                         $data['daemon_stamp_ok'] = $stamp_check;
2046
2047                                         $stamp_fmt = date("Y.m.d, G:i", $stamp);
2048
2049                                         $data['daemon_stamp'] = $stamp_fmt;
2050                                 }
2051                         }
2052                 }
2053
2054                 if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
2055                                 $new_version_details = @check_for_update($link);
2056
2057                                 $data['new_version_available'] = (int) ($new_version_details != false);
2058
2059                                 $_SESSION["last_version_check"] = time();
2060                                 $_SESSION["version_data"] = $new_version_details;
2061                 }
2062
2063                 return $data;
2064         }
2065
2066         function search_to_sql($link, $search) {
2067
2068                 $search_query_part = "";
2069
2070                 $keywords = explode(" ", $search);
2071                 $query_keywords = array();
2072
2073                 foreach ($keywords as $k) {
2074                         if (strpos($k, "-") === 0) {
2075                                 $k = substr($k, 1);
2076                                 $not = "NOT";
2077                         } else {
2078                                 $not = "";
2079                         }
2080
2081                         $commandpair = explode(":", mb_strtolower($k), 2);
2082
2083                         if ($commandpair[0] == "note" && $commandpair[1]) {
2084
2085                                 if ($commandpair[1] == "true")
2086                                         array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2087                                 else
2088                                         array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2089
2090                         } else if ($commandpair[0] == "star" && $commandpair[1]) {
2091
2092                                 if ($commandpair[1] == "true")
2093                                         array_push($query_keywords, "($not (marked = true))");
2094                                 else
2095                                         array_push($query_keywords, "($not (marked = false))");
2096
2097                         } else if ($commandpair[0] == "pub" && $commandpair[1]) {
2098
2099                                 if ($commandpair[1] == "true")
2100                                         array_push($query_keywords, "($not (published = true))");
2101                                 else
2102                                         array_push($query_keywords, "($not (published = false))");
2103
2104                         } else if (strpos($k, "@") === 0) {
2105
2106                                 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2107                                 $orig_ts = strtotime(substr($k, 1));
2108                                 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2109
2110                                 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2111
2112                                 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
2113                         } else {
2114                                 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2115                                                 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2116                         }
2117                 }
2118
2119                 $search_query_part = implode("AND", $query_keywords);
2120
2121                 return $search_query_part;
2122         }
2123
2124         function getParentCategories($link, $cat, $owner_uid) {
2125                 $rv = array();
2126
2127                 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2128                         WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2129
2130                 while ($line = db_fetch_assoc($result)) {
2131                         array_push($rv, $line["parent_cat"]);
2132                         $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2133                 }
2134
2135                 return $rv;
2136         }
2137
2138         function getChildCategories($link, $cat, $owner_uid) {
2139                 $rv = array();
2140
2141                 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2142                         WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2143
2144                 while ($line = db_fetch_assoc($result)) {
2145                         array_push($rv, $line["id"]);
2146                         $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2147                 }
2148
2149                 return $rv;
2150         }
2151
2152         function queryFeedHeadlines($link, $feed, $limit, $view_mode, $cat_view, $search, $search_mode, $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, $ignore_vfeed_group = false) {
2153
2154                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2155
2156                 $ext_tables_part = "";
2157
2158                         if ($search) {
2159
2160                                 if (SPHINX_ENABLED) {
2161                                         $ids = join(",", @sphinx_search($search, 0, 500));
2162
2163                                         if ($ids)
2164                                                 $search_query_part = "ref_id IN ($ids) AND ";
2165                                         else
2166                                                 $search_query_part = "ref_id = -1 AND ";
2167
2168                                 } else {
2169                                         $search_query_part = search_to_sql($link, $search);
2170                                         $search_query_part .= " AND ";
2171                                 }
2172
2173                         } else {
2174                                 $search_query_part = "";
2175                         }
2176
2177                         if ($filter) {
2178
2179                                 if (DB_TYPE == "pgsql") {
2180                                         $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2181                                 } else {
2182                                         $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2183                                 }
2184
2185                                 $override_order = "updated DESC";
2186
2187                                 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2188
2189                                 // Try to check if SQL regexp implementation chokes on a valid regexp
2190                                 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2191                                         ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2192                                         WHERE $filter_query_part LIMIT 1", false);
2193
2194                                 if ($result) {
2195                                         $test = db_fetch_result($result, 0, "true_val");
2196
2197                                         if (!$test) {
2198                                                 $filter_query_part = "false AND";
2199                                         } else {
2200                                                 $filter_query_part .= " AND";
2201                                         }
2202                                 } else {
2203                                         $filter_query_part = "false AND";
2204                                 }
2205
2206                         } else {
2207                                 $filter_query_part = "";
2208                         }
2209
2210                         if ($since_id) {
2211                                 $since_id_part = "ttrss_entries.id > $since_id AND ";
2212                         } else {
2213                                 $since_id_part = "";
2214                         }
2215
2216                         $view_query_part = "";
2217
2218                         if ($view_mode == "adaptive" || $view_query_part == "noscores") {
2219                                 if ($search) {
2220                                         $view_query_part = " ";
2221                                 } else if ($feed != -1) {
2222                                         $unread = getFeedUnread($link, $feed, $cat_view);
2223
2224                                         if ($cat_view && $feed > 0 && $include_children)
2225                                                 $unread += getCategoryChildrenUnread($link, $feed);
2226
2227                                         if ($unread > 0) {
2228                                                 $view_query_part = " unread = true AND ";
2229                                         }
2230                                 }
2231                         }
2232
2233                         if ($view_mode == "marked") {
2234                                 $view_query_part = " marked = true AND ";
2235                         }
2236
2237                         if ($view_mode == "published") {
2238                                 $view_query_part = " published = true AND ";
2239                         }
2240
2241                         if ($view_mode == "unread") {
2242                                 $view_query_part = " unread = true AND ";
2243                         }
2244
2245                         if ($view_mode == "updated") {
2246                                 $view_query_part = " (last_read is null and unread = false) AND ";
2247                         }
2248
2249                         if ($limit > 0) {
2250                                 $limit_query_part = "LIMIT " . $limit;
2251                         }
2252
2253                         $allow_archived = false;
2254
2255                         $vfeed_query_part = "";
2256
2257                         // override query strategy and enable feed display when searching globally
2258                         if ($search && $search_mode == "all_feeds") {
2259                                 $query_strategy_part = "true";
2260                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2261                         /* tags */
2262                         } else if (!is_numeric($feed)) {
2263                                 $query_strategy_part = "true";
2264                                 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2265                                         id = feed_id) as feed_title,";
2266                         } else if ($search && $search_mode == "this_cat") {
2267                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2268
2269                                 if ($feed > 0) {
2270                                         if ($include_children) {
2271                                                 $subcats = getChildCategories($link, $feed, $owner_uid);
2272                                                 array_push($subcats, $feed);
2273                                                 $cats_qpart = join(",", $subcats);
2274                                         } else {
2275                                                 $cats_qpart = $feed;
2276                                         }
2277
2278                                         $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2279
2280                                 } else {
2281                                         $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2282                                 }
2283
2284                         } else if ($feed > 0) {
2285
2286                                 if ($cat_view) {
2287
2288                                         if ($feed > 0) {
2289                                                 if ($include_children) {
2290                                                         # sub-cats
2291                                                         $subcats = getChildCategories($link, $feed, $owner_uid);
2292
2293                                                         array_push($subcats, $feed);
2294                                                         $query_strategy_part = "cat_id IN (".
2295                                                                         implode(",", $subcats).")";
2296
2297                                                 } else {
2298                                                         $query_strategy_part = "cat_id = '$feed'";
2299                                                 }
2300
2301                                         } else {
2302                                                 $query_strategy_part = "cat_id IS NULL";
2303                                         }
2304
2305                                         $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2306
2307                                 } else {
2308                                         $query_strategy_part = "feed_id = '$feed'";
2309                                 }
2310                         } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2311                                 $query_strategy_part = "feed_id IS NULL";
2312                                 $allow_archived = true;
2313                         } else if ($feed == 0 && $cat_view) { // uncategorized
2314                                 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2315                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2316                         } else if ($feed == -1) { // starred virtual feed
2317                                 $query_strategy_part = "marked = true";
2318                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2319                                 $allow_archived = true;
2320
2321                                 if (!$override_order) $override_order = "last_marked DESC, updated DESC";
2322
2323                         } else if ($feed == -2) { // published virtual feed OR labels category
2324
2325                                 if (!$cat_view) {
2326                                         $query_strategy_part = "published = true";
2327                                         $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2328                                         $allow_archived = true;
2329
2330                                         if (!$override_order) $override_order = "last_published DESC, updated DESC";
2331                                 } else {
2332                                         $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2333
2334                                         $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2335
2336                                         $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2337                                                 ttrss_user_labels2.article_id = ref_id";
2338
2339                                 }
2340                         } else if ($feed == -6) { // recently read
2341                                 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2342                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2343                                 $allow_archived = true;
2344
2345                                 if (!$override_order) $override_order = "last_read DESC";
2346                         } else if ($feed == -3) { // fresh virtual feed
2347                                 $query_strategy_part = "unread = true AND score >= 0";
2348
2349                                 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2350
2351                                 if (DB_TYPE == "pgsql") {
2352                                         $query_strategy_part .= " AND updated > NOW() - INTERVAL '$intl hour' ";
2353                                 } else {
2354                                         $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2355                                 }
2356
2357                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2358                         } else if ($feed == -4) { // all articles virtual feed
2359                                 $query_strategy_part = "true";
2360                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2361                         } else if ($feed <= -10) { // labels
2362                                 $label_id = -$feed - 11;
2363
2364                                 $query_strategy_part = "label_id = '$label_id' AND
2365                                         ttrss_labels2.id = ttrss_user_labels2.label_id AND
2366                                         ttrss_user_labels2.article_id = ref_id";
2367
2368                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2369                                 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2370                                 $allow_archived = true;
2371
2372                         } else {
2373                                 $query_strategy_part = "true";
2374                         }
2375
2376                         if (get_pref($link, "SORT_HEADLINES_BY_FEED_DATE", $owner_uid)) {
2377                                 $date_sort_field = "updated";
2378                         } else {
2379                                 $date_sort_field = "date_entered";
2380                         }
2381
2382                         if (get_pref($link, 'REVERSE_HEADLINES', $owner_uid)) {
2383                                 $order_by = "$date_sort_field";
2384                         } else {
2385                                 $order_by = "$date_sort_field DESC";
2386                         }
2387
2388                         if ($view_mode != "noscores") {
2389                                 $order_by = "score DESC, $order_by";
2390                         }
2391
2392                         if ($override_order) {
2393                                 $order_by = $override_order;
2394                         }
2395
2396                         $feed_title = "";
2397
2398                         if ($search) {
2399                                 $feed_title = T_sprintf("Search results: %s", $search);
2400                         } else {
2401                                 if ($cat_view) {
2402                                         $feed_title = getCategoryTitle($link, $feed);
2403                                 } else {
2404                                         if (is_numeric($feed) && $feed > 0) {
2405                                                 $result = db_query($link, "SELECT title,site_url,last_error
2406                                                         FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2407
2408                                                 $feed_title = db_fetch_result($result, 0, "title");
2409                                                 $feed_site_url = db_fetch_result($result, 0, "site_url");
2410                                                 $last_error = db_fetch_result($result, 0, "last_error");
2411                                         } else {
2412                                                 $feed_title = getFeedTitle($link, $feed);
2413                                         }
2414                                 }
2415                         }
2416
2417                         $content_query_part = "content as content_preview, cached_content, ";
2418
2419                         if (is_numeric($feed)) {
2420
2421                                 if ($feed >= 0) {
2422                                         $feed_kind = "Feeds";
2423                                 } else {
2424                                         $feed_kind = "Labels";
2425                                 }
2426
2427                                 if ($limit_query_part) {
2428                                         $offset_query_part = "OFFSET $offset";
2429                                 }
2430
2431                                 // proper override_order applied above
2432                                 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2433                                         if (!$override_order) {
2434                                                 $order_by = "ttrss_feeds.title, $order_by";
2435                                         } else {
2436                                                 $order_by = "ttrss_feeds.title, $override_order";
2437                                         }
2438                                 }
2439
2440                                 if (!$allow_archived) {
2441                                         $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2442                                         $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2443
2444                                 } else {
2445                                         $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2446                                                 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2447                                 }
2448
2449                                 $query = "SELECT DISTINCT
2450                                                 date_entered,
2451                                                 guid,
2452                                                 ttrss_entries.id,ttrss_entries.title,
2453                                                 updated,
2454                                                 label_cache,
2455                                                 tag_cache,
2456                                                 always_display_enclosures,
2457                                                 site_url,
2458                                                 note,
2459                                                 num_comments,
2460                                                 comments,
2461                                                 int_id,
2462                                                 hide_images,
2463                                                 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2464                                                 last_marked, last_published,
2465                                                 ".SUBSTRING_FOR_DATE."(last_read,1,19) as last_read_noms,
2466                                                 $vfeed_query_part
2467                                                 $content_query_part
2468                                                 ".SUBSTRING_FOR_DATE."(updated,1,19) as updated_noms,
2469                                                 author,score
2470                                         FROM
2471                                                 $from_qpart
2472                                         WHERE
2473                                         $feed_check_qpart
2474                                         ttrss_user_entries.ref_id = ttrss_entries.id AND
2475                                         ttrss_user_entries.owner_uid = '$owner_uid' AND
2476                                         $search_query_part
2477                                         $filter_query_part
2478                                         $view_query_part
2479                                         $since_id_part
2480                                         $query_strategy_part ORDER BY $order_by
2481                                         $limit_query_part $offset_query_part";
2482
2483                                 if ($_REQUEST["debug"]) print $query;
2484
2485                                 $result = db_query($link, $query);
2486
2487                         } else {
2488                                 // browsing by tag
2489
2490                                 $select_qpart = "SELECT DISTINCT " .
2491                                                                 "date_entered," .
2492                                                                 "guid," .
2493                                                                 "note," .
2494                                                                 "ttrss_entries.id as id," .
2495                                                                 "title," .
2496                                                                 "updated," .
2497                                                                 "unread," .
2498                                                                 "feed_id," .
2499                                                                 "orig_feed_id," .
2500                                                                 "marked," .
2501                                                                 "num_comments, " .
2502                                                                 "comments, " .
2503                                                                 "tag_cache," .
2504                                                                 "label_cache," .
2505                                                                 "link," .
2506                                                                 "last_read," .
2507                                                                 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2508                                                                 "last_marked, last_published, " .
2509                                                                 SUBSTRING_FOR_DATE . "(last_read,1,19) as last_read_noms," .
2510                                                                 $since_id_part .
2511                                                                 $vfeed_query_part .
2512                                                                 $content_query_part .
2513                                                                 SUBSTRING_FOR_DATE . "(updated,1,19) as updated_noms," .
2514                                                                 "score ";
2515
2516                                 $feed_kind = "Tags";
2517                                 $all_tags = explode(",", $feed);
2518                                 if ($search_mode == 'any') {
2519                                         $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2520                                         $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2521                                         $where_qpart = " WHERE " .
2522                                                                    "ref_id = ttrss_entries.id AND " .
2523                                                                    "ttrss_user_entries.owner_uid = $owner_uid AND " .
2524                                                                    "post_int_id = int_id AND $tag_sql AND " .
2525                                                                    $view_query_part .
2526                                                                    $search_query_part .
2527                                                                    $query_strategy_part . " ORDER BY $order_by " .
2528                                                                    $limit_query_part;
2529
2530                                 } else {
2531                                         $i = 1;
2532                                         $sub_selects = array();
2533                                         $sub_ands = array();
2534                                         foreach ($all_tags as $term) {
2535                                                 array_push($sub_selects, "(SELECT post_int_id from ttrss_tags WHERE tag_name = " . db_quote($term) . " AND owner_uid = $owner_uid) as A$i");
2536                                                 $i++;
2537                                         }
2538                                         if ($i > 2) {
2539                                                 $x = 1;
2540                                                 $y = 2;
2541                                                 do {
2542                                                         array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2543                                                         $x++;
2544                                                         $y++;
2545                                                 } while ($y < $i);
2546                                         }
2547                                         array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2548                                         array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2549                                         $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2550                                         $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2551                                 }
2552                                 //                              error_log("TAG SQL: " . $tag_sql);
2553                                 // $tag_sql = "tag_name = '$feed'";   DEFAULT way
2554
2555                                 //                              error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2556                                 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2557                         }
2558
2559                         return array($result, $feed_title, $feed_site_url, $last_error);
2560
2561         }
2562
2563         function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2564                 if (!$owner) $owner = $_SESSION["uid"];
2565
2566                 $res = trim($str); if (!$res) return '';
2567
2568                 if (strpos($res, "href=") === false)
2569                         $res = rewrite_urls($res);
2570
2571                 $charset_hack = '<head>
2572                         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2573                 </head>';
2574
2575                 $res = trim($res); if (!$res) return '';
2576
2577                 libxml_use_internal_errors(true);
2578
2579                 $doc = new DOMDocument();
2580                 $doc->loadHTML($charset_hack . $res);
2581                 $xpath = new DOMXPath($doc);
2582
2583                 $entries = $xpath->query('(//a[@href]|//img[@src])');
2584
2585                 foreach ($entries as $entry) {
2586
2587                         if ($site_url) {
2588
2589                                 if ($entry->hasAttribute('href'))
2590                                         $entry->setAttribute('href',
2591                                                 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2592
2593                                 if ($entry->hasAttribute('src')) {
2594                                         $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2595
2596                                         $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
2597
2598                                         if (file_exists($cached_filename)) {
2599                                                 $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src);
2600                                         }
2601
2602                                         $entry->setAttribute('src', $src);
2603                                 }
2604
2605                                 if ($entry->nodeName == 'img') {
2606                                         if (($owner && get_pref($link, "STRIP_IMAGES", $owner)) ||
2607                                                         $force_remove_images) {
2608
2609                                                 $p = $doc->createElement('p');
2610
2611                                                 $a = $doc->createElement('a');
2612                                                 $a->setAttribute('href', $entry->getAttribute('src'));
2613
2614                                                 $a->appendChild(new DOMText($entry->getAttribute('src')));
2615                                                 $a->setAttribute('target', '_blank');
2616
2617                                                 $p->appendChild($a);
2618
2619                                                 $entry->parentNode->replaceChild($p, $entry);
2620                                         }
2621                                 }
2622                         }
2623
2624                         if (strtolower($entry->nodeName) == "a") {
2625                                 $entry->setAttribute("target", "_blank");
2626                         }
2627                 }
2628
2629                 $entries = $xpath->query('//iframe');
2630                 foreach ($entries as $entry) {
2631                         $entry->setAttribute('sandbox', 'allow-scripts');
2632
2633                 }
2634
2635                 global $pluginhost;
2636
2637                 if (isset($pluginhost)) {
2638                         foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE) as $plugin) {
2639                                 $doc = $plugin->hook_sanitize($doc, $site_url);
2640                         }
2641                 }
2642
2643                 $doc->removeChild($doc->firstChild); //remove doctype
2644                 $doc = strip_harmful_tags($doc);
2645                 $res = $doc->saveHTML();
2646                 return $res;
2647         }
2648
2649         function strip_harmful_tags($doc) {
2650                 $entries = $doc->getElementsByTagName("*");
2651
2652                 $allowed_elements = array('a', 'address', 'audio', 'article',
2653                         'b', 'big', 'blockquote', 'body', 'br', 'cite',
2654                         'code', 'dd', 'del', 'details', 'div', 'dl', 'font',
2655                         'dt', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
2656                         'header', 'html', 'i', 'img', 'ins', 'kbd',
2657                         'li', 'nav', 'ol', 'p', 'pre', 'q', 's','small',
2658                         'source', 'span', 'strike', 'strong', 'sub', 'summary',
2659                         'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
2660                         'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2661
2662                 if ($_SESSION['hasSandbox']) array_push($allowed_elements, 'iframe');
2663
2664                 $disallowed_attributes = array('id', 'style', 'class');
2665
2666                 foreach ($entries as $entry) {
2667                         if (!in_array($entry->nodeName, $allowed_elements)) {
2668                                 $entry->parentNode->removeChild($entry);
2669                         }
2670
2671                         if ($entry->hasAttributes()) {
2672                                 foreach (iterator_to_array($entry->attributes) as $attr) {
2673
2674                                         if (strpos($attr->nodeName, 'on') === 0) {
2675                                                 $entry->removeAttributeNode($attr);
2676                                         }
2677
2678                                         if (in_array($attr->nodeName, $disallowed_attributes)) {
2679                                                 $entry->removeAttributeNode($attr);
2680                                         }
2681                                 }
2682                         }
2683                 }
2684
2685                 return $doc;
2686         }
2687
2688         function check_for_update($link) {
2689                 if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) {
2690                         $version_url = "http://tt-rss.org/version.php?ver=" . VERSION .
2691                                 "&iid=" . sha1(SELF_URL_PATH);
2692
2693                         $version_data = @fetch_file_contents($version_url);
2694
2695                         if ($version_data) {
2696                                 $version_data = json_decode($version_data, true);
2697                                 if ($version_data && $version_data['version']) {
2698
2699                                         if (version_compare(VERSION, $version_data['version']) == -1) {
2700                                                 return $version_data;
2701                                         }
2702                                 }
2703                         }
2704                 }
2705                 return false;
2706         }
2707
2708         function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2709
2710                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2711                 if (count($ids) == 0) return;
2712
2713                 $tmp_ids = array();
2714
2715                 foreach ($ids as $id) {
2716                         array_push($tmp_ids, "ref_id = '$id'");
2717                 }
2718
2719                 $ids_qpart = join(" OR ", $tmp_ids);
2720
2721                 if ($cmode == 0) {
2722                         db_query($link, "UPDATE ttrss_user_entries SET
2723                         unread = false,last_read = NOW()
2724                         WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2725                 } else if ($cmode == 1) {
2726                         db_query($link, "UPDATE ttrss_user_entries SET
2727                         unread = true
2728                         WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2729                 } else {
2730                         db_query($link, "UPDATE ttrss_user_entries SET
2731                         unread = NOT unread,last_read = NOW()
2732                         WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2733                 }
2734
2735                 /* update ccache */
2736
2737                 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2738                         WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2739
2740                 while ($line = db_fetch_assoc($result)) {
2741                         ccache_update($link, $line["feed_id"], $owner_uid);
2742                 }
2743         }
2744
2745         function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2746
2747                 $a_id = db_escape_string($link, $id);
2748
2749                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2750
2751                 $query = "SELECT DISTINCT tag_name,
2752                         owner_uid as owner FROM
2753                         ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2754                         ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2755
2756                 $obj_id = md5("TAGS:$owner_uid:$id");
2757                 $tags = array();
2758
2759                 /* check cache first */
2760
2761                 if ($tag_cache === false) {
2762                         $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2763                                 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2764
2765                         $tag_cache = db_fetch_result($result, 0, "tag_cache");
2766                 }
2767
2768                 if ($tag_cache) {
2769                         $tags = explode(",", $tag_cache);
2770                 } else {
2771
2772                         /* do it the hard way */
2773
2774                         $tmp_result = db_query($link, $query);
2775
2776                         while ($tmp_line = db_fetch_assoc($tmp_result)) {
2777                                 array_push($tags, $tmp_line["tag_name"]);
2778                         }
2779
2780                         /* update the cache */
2781
2782                         $tags_str = db_escape_string($link, join(",", $tags));
2783
2784                         db_query($link, "UPDATE ttrss_user_entries
2785                                 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2786                                 AND owner_uid = $owner_uid");
2787                 }
2788
2789                 return $tags;
2790         }
2791
2792         function trim_array($array) {
2793                 $tmp = $array;
2794                 array_walk($tmp, 'trim');
2795                 return $tmp;
2796         }
2797
2798         function tag_is_valid($tag) {
2799                 if ($tag == '') return false;
2800                 if (preg_match("/^[0-9]*$/", $tag)) return false;
2801                 if (mb_strlen($tag) > 250) return false;
2802
2803                 if (function_exists('iconv')) {
2804                         $tag = iconv("utf-8", "utf-8", $tag);
2805                 }
2806
2807                 if (!$tag) return false;
2808
2809                 return true;
2810         }
2811
2812         function render_login_form($link) {
2813                 require_once "login_form.php";
2814                 exit;
2815         }
2816
2817         // from http://developer.apple.com/internet/safari/faq.html
2818         function no_cache_incantation() {
2819                 header("Expires: Mon, 22 Dec 1980 00:00:00 GMT"); // Happy birthday to me :)
2820                 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
2821                 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
2822                 header("Cache-Control: post-check=0, pre-check=0", false);
2823                 header("Pragma: no-cache"); // HTTP/1.0
2824         }
2825
2826         function format_warning($msg, $id = "") {
2827                 global $link;
2828                 return "<div class=\"warning\" id=\"$id\">
2829                         <img src=\"images/sign_excl.svg\">$msg</div>";
2830         }
2831
2832         function format_notice($msg, $id = "") {
2833                 global $link;
2834                 return "<div class=\"notice\" id=\"$id\">
2835                         <img src=\"images/sign_info.svg\">$msg</div>";
2836         }
2837
2838         function format_error($msg, $id = "") {
2839                 global $link;
2840                 return "<div class=\"error\" id=\"$id\">
2841                         <img src=\"images/sign_excl.svg\">$msg</div>";
2842         }
2843
2844         function print_notice($msg) {
2845                 return print format_notice($msg);
2846         }
2847
2848         function print_warning($msg) {
2849                 return print format_warning($msg);
2850         }
2851
2852         function print_error($msg) {
2853                 return print format_error($msg);
2854         }
2855
2856
2857         function T_sprintf() {
2858                 $args = func_get_args();
2859                 return vsprintf(__(array_shift($args)), $args);
2860         }
2861
2862         function format_inline_player($link, $url, $ctype) {
2863
2864                 $entry = "";
2865
2866                 $url = htmlspecialchars($url);
2867
2868                 if (strpos($ctype, "audio/") === 0) {
2869
2870                         if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2871                                 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2872                                 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2873
2874                                 $id = 'AUDIO-' . uniqid();
2875
2876                                 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2877                                         <source type=\"$ctype\" src=\"$url\"></source>
2878                                         </audio>";
2879
2880                                 $entry .= "<span onclick=\"player(this)\"
2881                                         title=\"".__("Click to play")."\" status=\"0\"
2882                                         class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
2883
2884                         } else {
2885
2886                                 $entry .= "<object type=\"application/x-shockwave-flash\"
2887                                         data=\"lib/button/musicplayer.swf?song_url=$url\"
2888                                         width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
2889                                         <param name=\"movie\"
2890                                                 value=\"lib/button/musicplayer.swf?song_url=$url\" />
2891                                         </object>";
2892                         }
2893
2894                         if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
2895                                 href=\"$url\">" . basename($url) . "</a>";
2896
2897                         return $entry;
2898
2899                 }
2900
2901                 return "";
2902
2903 /*              $filename = substr($url, strrpos($url, "/")+1);
2904
2905                 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
2906                         $filename . " (" . $ctype . ")" . "</a>"; */
2907
2908         }
2909
2910         function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
2911                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2912
2913                 $rv = array();
2914
2915                 $rv['id'] = $id;
2916
2917                 /* we can figure out feed_id from article id anyway, why do we
2918                  * pass feed_id here? let's ignore the argument :( */
2919
2920                 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
2921                         WHERE ref_id = '$id'");
2922
2923                 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
2924
2925                 $rv['feed_id'] = $feed_id;
2926
2927                 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
2928
2929                 if ($mark_as_read) {
2930                         $result = db_query($link, "UPDATE ttrss_user_entries
2931                                 SET unread = false,last_read = NOW()
2932                                 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2933
2934                         ccache_update($link, $feed_id, $owner_uid);
2935                 }
2936
2937                 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
2938                         ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
2939                         (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
2940                         (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
2941                         num_comments,
2942                         tag_cache,
2943                         author,
2944                         orig_feed_id,
2945                         note,
2946                         cached_content
2947                         FROM ttrss_entries,ttrss_user_entries
2948                         WHERE   id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
2949
2950                 if ($result) {
2951
2952                         $line = db_fetch_assoc($result);
2953
2954                         $tag_cache = $line["tag_cache"];
2955
2956                         $line["tags"] = get_article_tags($link, $id, $owner_uid, $line["tag_cache"]);
2957                         unset($line["tag_cache"]);
2958
2959                         $line["content"] = sanitize($link, $line["content"], false, $owner_uid, $line["site_url"]);
2960
2961                         global $pluginhost;
2962
2963                         foreach ($pluginhost->get_hooks($pluginhost::HOOK_RENDER_ARTICLE) as $p) {
2964                                 $line = $p->hook_render_article($line);
2965                         }
2966
2967                         $num_comments = $line["num_comments"];
2968                         $entry_comments = "";
2969
2970                         if ($num_comments > 0) {
2971                                 if ($line["comments"]) {
2972                                         $comments_url = htmlspecialchars($line["comments"]);
2973                                 } else {
2974                                         $comments_url = htmlspecialchars($line["link"]);
2975                                 }
2976                                 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
2977                         } else {
2978                                 if ($line["comments"] && $line["link"] != $line["comments"]) {
2979                                         $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
2980                                 }
2981                         }
2982
2983                         if ($zoom_mode) {
2984                                 header("Content-Type: text/html");
2985                                 $rv['content'] .= "<html><head>
2986                                                 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
2987                                                 <title>Tiny Tiny RSS - ".$line["title"]."</title>
2988                                                 <link rel=\"stylesheet\" type=\"text/css\" href=\"tt-rss.css\">
2989                                         </head><body>";
2990                         }
2991
2992                         $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
2993
2994                         $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
2995
2996                         $entry_author = $line["author"];
2997
2998                         if ($entry_author) {
2999                                 $entry_author = __(" - ") . $entry_author;
3000                         }
3001
3002                         $parsed_updated = make_local_datetime($link, $line["updated"], true,
3003                                 $owner_uid, true);
3004
3005                         $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3006
3007                         if ($line["link"]) {
3008                                 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3009                                         title=\"".htmlspecialchars($line['title'])."\"
3010                                         href=\"" .
3011                                         htmlspecialchars($line["link"]) . "\">" .
3012                                         $line["title"] .
3013                                         "<span class='author'>$entry_author</span></a></div>";
3014                         } else {
3015                                 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3016                         }
3017
3018                         $tags_str = format_tags_string($line["tags"], $id);
3019                         $tags_str_full = join(", ", $line["tags"]);
3020
3021                         if (!$tags_str_full) $tags_str_full = __("no tags");
3022
3023                         if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
3024
3025                         $rv['content'] .= "<div class='postTags' style='float : right'>
3026                                 <img src='images/tag.png'
3027                                 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
3028
3029                         if (!$zoom_mode) {
3030                                 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3031                                         <a title=\"".__('Edit tags for this article')."\"
3032                                         href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3033
3034                                 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3035                                         id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3036                                         position=\"below\">$tags_str_full</div>";
3037
3038                                 global $pluginhost;
3039
3040                                 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON) as $p) {
3041                                         $rv['content'] .= $p->hook_article_button($line);
3042                                 }
3043
3044
3045                         } else {
3046                                 $tags_str = strip_tags($tags_str);
3047                                 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3048                         }
3049                         $rv['content'] .= "</div>";
3050                         $rv['content'] .= "<div clear='both'>$entry_comments</div>";
3051
3052                         if ($line["orig_feed_id"]) {
3053
3054                                 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3055                                         WHERE id = ".$line["orig_feed_id"]);
3056
3057                                 if (db_num_rows($tmp_result) != 0) {
3058
3059                                         $rv['content'] .= "<div clear='both'>";
3060                                         $rv['content'] .= __("Originally from:");
3061
3062                                         $rv['content'] .= "&nbsp;";
3063
3064                                         $tmp_line = db_fetch_assoc($tmp_result);
3065
3066                                         $rv['content'] .= "<a target='_blank'
3067                                                 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3068                                                 $tmp_line['title'] . "</a>";
3069
3070                                         $rv['content'] .= "&nbsp;";
3071
3072                                         $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3073                                         $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3074
3075                                         $rv['content'] .= "</div>";
3076                                 }
3077                         }
3078
3079                         $rv['content'] .= "</div>";
3080
3081                         $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3082                                 if ($line['note']) {
3083                                         $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3084                                 }
3085                         $rv['content'] .= "</div>";
3086
3087                         $rv['content'] .= "<div class=\"postContent\">";
3088
3089                         $rv['content'] .= $line["content"];
3090
3091                         $rv['content'] .= format_article_enclosures($link, $id,
3092                                 $always_display_enclosures, $line["content"], $line["hide_images"]);
3093
3094                         $rv['content'] .= "</div>";
3095
3096                         $rv['content'] .= "</div>";
3097
3098                 }
3099
3100                 if ($zoom_mode) {
3101                         $rv['content'] .= "
3102                                 <div style=\"text-align : center\">
3103                                 <button onclick=\"return window.close()\">".
3104                                         __("Close this window")."</button></div>";
3105                         $rv['content'] .= "</body></html>";
3106                 }
3107
3108                 return $rv;
3109
3110         }
3111
3112         function print_checkpoint($n, $s) {
3113                 $ts = microtime(true);
3114                 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3115                 return $ts;
3116         }
3117
3118         function sanitize_tag($tag) {
3119                 $tag = trim($tag);
3120
3121                 $tag = mb_strtolower($tag, 'utf-8');
3122
3123                 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3124
3125 //              $tag = str_replace('"', "", $tag);
3126 //              $tag = str_replace("+", " ", $tag);
3127                 $tag = str_replace("technorati tag: ", "", $tag);
3128
3129                 return $tag;
3130         }
3131
3132         function get_self_url_prefix() {
3133                 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
3134                         return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
3135                 } else {
3136                         return SELF_URL_PATH;
3137                 }
3138         }
3139
3140         /**
3141          * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3142          *
3143          * @return string The Mozilla Firefox feed adding URL.
3144          */
3145         function add_feed_url() {
3146                 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' :  'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3147
3148                 $url_path = get_self_url_prefix() .
3149                         "/public.php?op=subscribe&feed_url=%s";
3150                 return $url_path;
3151         } // function add_feed_url
3152
3153         function encrypt_password($pass, $salt = '', $mode2 = false) {
3154                 if ($salt && $mode2) {
3155                         return "MODE2:" . hash('sha256', $salt . $pass);
3156                 } else if ($salt) {
3157                         return "SHA1X:" . sha1("$salt:$pass");
3158                 } else {
3159                         return "SHA1:" . sha1($pass);
3160                 }
3161         } // function encrypt_password
3162
3163         function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3164                 $filters = array();
3165
3166                 $cat_id = (int)getFeedCategory($link, $feed_id);
3167
3168                 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3169                         owner_uid = $owner_uid AND enabled = true");
3170
3171                 $check_cats = join(",", array_merge(
3172                         getParentCategories($link, $cat_id, $owner_uid),
3173                         array($cat_id)));
3174
3175                 while ($line = db_fetch_assoc($result)) {
3176                         $filter_id = $line["id"];
3177
3178                         $result2 = db_query($link, "SELECT
3179                                 r.reg_exp, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3180                                 FROM ttrss_filters2_rules AS r,
3181                                 ttrss_filter_types AS t
3182                                 WHERE
3183                                         (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3184                                         (feed_id IS NULL OR feed_id = '$feed_id') AND
3185                                         filter_type = t.id AND filter_id = '$filter_id'");
3186
3187                         $rules = array();
3188                         $actions = array();
3189
3190                         while ($rule_line = db_fetch_assoc($result2)) {
3191 #                               print_r($rule_line);
3192
3193                                 $rule = array();
3194                                 $rule["reg_exp"] = $rule_line["reg_exp"];
3195                                 $rule["type"] = $rule_line["type_name"];
3196
3197                                 array_push($rules, $rule);
3198                         }
3199
3200                         $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3201                                 FROM ttrss_filters2_actions AS a,
3202                                 ttrss_filter_actions AS t
3203                                 WHERE
3204                                         action_id = t.id AND filter_id = '$filter_id'");
3205
3206                         while ($action_line = db_fetch_assoc($result2)) {
3207 #                               print_r($action_line);
3208
3209                                 $action = array();
3210                                 $action["type"] = $action_line["type_name"];
3211                                 $action["param"] = $action_line["action_param"];
3212
3213                                 array_push($actions, $action);
3214                         }
3215
3216
3217                         $filter = array();
3218                         $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3219                         $filter["rules"] = $rules;
3220                         $filter["actions"] = $actions;
3221
3222                         if (count($rules) > 0 && count($actions) > 0) {
3223                                 array_push($filters, $filter);
3224                         }
3225                 }
3226
3227                 return $filters;
3228         }
3229
3230         function get_score_pic($score) {
3231                 if ($score > 100) {
3232                         return "score_high.png";
3233                 } else if ($score > 0) {
3234                         return "score_half_high.png";
3235                 } else if ($score < -100) {
3236                         return "score_low.png";
3237                 } else if ($score < 0) {
3238                         return "score_half_low.png";
3239                 } else {
3240                         return "score_neutral.png";
3241                 }
3242         }
3243
3244         function feed_has_icon($id) {
3245                 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
3246         }
3247
3248         function init_connection($link) {
3249                 if ($link) {
3250
3251                         if (DB_TYPE == "pgsql") {
3252                                 pg_query($link, "set client_encoding = 'UTF-8'");
3253                                 pg_set_client_encoding("UNICODE");
3254                                 pg_query($link, "set datestyle = 'ISO, european'");
3255                                 pg_query($link, "set TIME ZONE 0");
3256                         } else {
3257                                 db_query($link, "SET time_zone = '+0:0'");
3258
3259                                 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
3260                                         db_query($link, "SET NAMES " . MYSQL_CHARSET);
3261                                 }
3262                         }
3263
3264                         global $pluginhost;
3265
3266                         $pluginhost = new PluginHost($link);
3267                         $pluginhost->load(PLUGINS, $pluginhost::KIND_ALL);
3268
3269                         return true;
3270                 } else {
3271                         print "Unable to connect to database:" . db_last_error();
3272                         return false;
3273                 }
3274         }
3275
3276         function format_tags_string($tags, $id) {
3277
3278                 $tags_str = "";
3279                 $tags_nolinks_str = "";
3280
3281                 $num_tags = 0;
3282
3283                 $tag_limit = 6;
3284
3285                 $formatted_tags = array();
3286
3287                 foreach ($tags as $tag) {
3288                         $num_tags++;
3289                         $tag_escaped = str_replace("'", "\\'", $tag);
3290
3291                         if (mb_strlen($tag) > 30) {
3292                                 $tag = truncate_string($tag, 30);
3293                         }
3294
3295                         $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3296
3297                         array_push($formatted_tags, $tag_str);
3298
3299                         $tmp_tags_str = implode(", ", $formatted_tags);
3300
3301                         if ($num_tags == $tag_limit || mb_strlen($tmp_tags_str) > 150) {
3302                                 break;
3303                         }
3304                 }
3305
3306                 $tags_str = implode(", ", $formatted_tags);
3307
3308                 if ($num_tags < count($tags)) {
3309                         $tags_str .= ", &hellip;";
3310                 }
3311
3312                 if ($num_tags == 0) {
3313                         $tags_str = __("no tags");
3314                 }
3315
3316                 return $tags_str;
3317
3318         }
3319
3320         function format_article_labels($labels, $id) {
3321
3322                 $labels_str = "";
3323
3324                 foreach ($labels as $l) {
3325                         $labels_str .= sprintf("<span class='hlLabelRef'
3326                                 style='color : %s; background-color : %s'>%s</span>",
3327                                         $l[2], $l[3], $l[1]);
3328                         }
3329
3330                 return $labels_str;
3331
3332         }
3333
3334         function format_article_note($id, $note, $allow_edit = true) {
3335
3336                 $str = "<div class='articleNote'        onclick=\"editArticleNote($id)\">
3337                         <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3338                         ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
3339
3340                 return $str;
3341         }
3342
3343
3344         function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3345                 if ($parent_cat_id) {
3346                         $parent_qpart = "parent_cat = '$parent_cat_id'";
3347                         $parent_insert = "'$parent_cat_id'";
3348                 } else {
3349                         $parent_qpart = "parent_cat IS NULL";
3350                         $parent_insert = "NULL";
3351                 }
3352
3353                 $result = db_query($link,
3354                         "SELECT id FROM ttrss_feed_categories
3355                         WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3356
3357                 if (db_num_rows($result) == 0) {
3358                         return false;
3359                 } else {
3360                         return db_fetch_result($result, 0, "id");
3361                 }
3362         }
3363
3364         function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3365
3366                 if (!$feed_cat) return false;
3367
3368                 db_query($link, "BEGIN");
3369
3370                 if ($parent_cat_id) {
3371                         $parent_qpart = "parent_cat = '$parent_cat_id'";
3372                         $parent_insert = "'$parent_cat_id'";
3373                 } else {
3374                         $parent_qpart = "parent_cat IS NULL";
3375                         $parent_insert = "NULL";
3376                 }
3377
3378                 $result = db_query($link,
3379                         "SELECT id FROM ttrss_feed_categories
3380                         WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3381
3382                 if (db_num_rows($result) == 0) {
3383
3384                         $result = db_query($link,
3385                                 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3386                                 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3387
3388                         db_query($link, "COMMIT");
3389
3390                         return true;
3391                 }
3392
3393                 return false;
3394         }
3395
3396         function getArticleFeed($link, $id) {
3397                 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3398                         WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3399
3400                 if (db_num_rows($result) != 0) {
3401                         return db_fetch_result($result, 0, "feed_id");
3402                 } else {
3403                         return 0;
3404                 }
3405         }
3406
3407         /**
3408          * Fixes incomplete URLs by prepending "http://".
3409          * Also replaces feed:// with http://, and
3410          * prepends a trailing slash if the url is a domain name only.
3411          *
3412          * @param string $url Possibly incomplete URL
3413          *
3414          * @return string Fixed URL.
3415          */
3416         function fix_url($url) {
3417                 if (strpos($url, '://') === false) {
3418                         $url = 'http://' . $url;
3419                 } else if (substr($url, 0, 5) == 'feed:') {
3420                         $url = 'http:' . substr($url, 5);
3421                 }
3422
3423                 //prepend slash if the URL has no slash in it
3424                 // "http://www.example" -> "http://www.example/"
3425                 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
3426                         $url .= '/';
3427                 }
3428
3429                 if ($url != "http:///")
3430                         return $url;
3431                 else
3432                         return '';
3433         }
3434
3435         function validate_feed_url($url) {
3436                 $parts = parse_url($url);
3437
3438                 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
3439
3440         }
3441
3442         function get_article_enclosures($link, $id) {
3443
3444                 $query = "SELECT * FROM ttrss_enclosures
3445                         WHERE post_id = '$id' AND content_url != ''";
3446
3447                 $rv = array();
3448
3449                 $result = db_query($link, $query);
3450
3451                 if (db_num_rows($result) > 0) {
3452                         while ($line = db_fetch_assoc($result)) {
3453                                 array_push($rv, $line);
3454                         }
3455                 }
3456
3457                 return $rv;
3458         }
3459
3460         function save_email_address($link, $email) {
3461                 // FIXME: implement persistent storage of emails
3462
3463                 if (!$_SESSION['stored_emails'])
3464                         $_SESSION['stored_emails'] = array();
3465
3466                 if (!in_array($email, $_SESSION['stored_emails']))
3467                         array_push($_SESSION['stored_emails'], $email);
3468         }
3469
3470
3471         function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3472
3473                 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3474
3475                 $sql_is_cat = bool_to_sql_bool($is_cat);
3476
3477                 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3478                         WHERE feed_id = '$feed_id'      AND is_cat = $sql_is_cat
3479                         AND owner_uid = " . $owner_uid);
3480
3481                 if (db_num_rows($result) == 1) {
3482                         return db_fetch_result($result, 0, "access_key");
3483                 } else {
3484                         $key = db_escape_string($link, sha1(uniqid(rand(), true)));
3485
3486                         $result = db_query($link, "INSERT INTO ttrss_access_keys
3487                                 (access_key, feed_id, is_cat, owner_uid)
3488                                 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3489
3490                         return $key;
3491                 }
3492                 return false;
3493         }
3494
3495         function get_feeds_from_html($url, $content)
3496         {
3497                 $url     = fix_url($url);
3498                 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
3499
3500                 libxml_use_internal_errors(true);
3501
3502                 $doc = new DOMDocument();
3503                 $doc->loadHTML($content);
3504                 $xpath = new DOMXPath($doc);
3505                 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3506                 $feedUrls = array();
3507                 foreach ($entries as $entry) {
3508                         if ($entry->hasAttribute('href')) {
3509                                 $title = $entry->getAttribute('title');
3510                                 if ($title == '') {
3511                                         $title = $entry->getAttribute('type');
3512                                 }
3513                                 $feedUrl = rewrite_relative_url(
3514                                         $baseUrl, $entry->getAttribute('href')
3515                                 );
3516                                 $feedUrls[$feedUrl] = $title;
3517                         }
3518                 }
3519                 return $feedUrls;
3520         }
3521
3522         function is_html($content) {
3523                 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3524         }
3525
3526         function url_is_html($url, $login = false, $pass = false) {
3527                 return is_html(fetch_file_contents($url, false, $login, $pass));
3528         }
3529
3530         function print_label_select($link, $name, $value, $attributes = "") {
3531
3532                 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3533                         WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3534
3535                 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3536                         "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3537
3538                 while ($line = db_fetch_assoc($result)) {
3539
3540                         $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
3541
3542                         print "<option value=\"".htmlspecialchars($line["caption"])."\"
3543                                 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3544
3545                 }
3546
3547 #               print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3548
3549                 print "</select>";
3550
3551
3552         }
3553
3554         function format_article_enclosures($link, $id, $always_display_enclosures,
3555                                         $article_content, $hide_images = false) {
3556
3557                 $result = get_article_enclosures($link, $id);
3558                 $rv = '';
3559
3560                 if (count($result) > 0) {
3561
3562                         $entries_html = array();
3563                         $entries = array();
3564                         $entries_inline = array();
3565
3566                         foreach ($result as $line) {
3567
3568                                 $url = $line["content_url"];
3569                                 $ctype = $line["content_type"];
3570
3571                                 if (!$ctype) $ctype = __("unknown type");
3572
3573                                 $filename = substr($url, strrpos($url, "/")+1);
3574
3575                                 $player = format_inline_player($link, $url, $ctype);
3576
3577                                 if ($player) array_push($entries_inline, $player);
3578
3579 #                               $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3580 #                                       $filename . " (" . $ctype . ")" . "</a>";
3581
3582                                 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3583                                         dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3584
3585                                 array_push($entries_html, $entry);
3586
3587                                 $entry = array();
3588
3589                                 $entry["type"] = $ctype;
3590                                 $entry["filename"] = $filename;
3591                                 $entry["url"] = $url;
3592
3593                                 array_push($entries, $entry);
3594                         }
3595
3596                         if ($_SESSION['uid'] && !get_pref($link, "STRIP_IMAGES")) {
3597                                 if ($always_display_enclosures ||
3598                                                         !preg_match("/<img/i", $article_content)) {
3599
3600                                         foreach ($entries as $entry) {
3601
3602                                                 if (preg_match("/image/", $entry["type"]) ||
3603                                                                 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3604
3605                                                                 if (!$hide_images) {
3606                                                                         $rv .= "<p><img
3607                                                                         alt=\"".htmlspecialchars($entry["filename"])."\"
3608                                                                         src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3609                                                                 } else {
3610                                                                         $rv .= "<p><a target=\"_blank\"
3611                                                                         href=\"".htmlspecialchars($entry["url"])."\"
3612                                                                         >" .htmlspecialchars($entry["url"]) . "</a></p>";
3613
3614                                                                 }
3615                                                 }
3616                                         }
3617                                 }
3618                         }
3619
3620                         if (count($entries_inline) > 0) {
3621                                 $rv .= "<hr clear='both'/>";
3622                                 foreach ($entries_inline as $entry) { $rv .= $entry; };
3623                                 $rv .= "<hr clear='both'/>";
3624                         }
3625
3626                         $rv .= "<br/><div dojoType=\"dijit.form.DropDownButton\">".
3627                                 "<span>" . __('Attachments')."</span>";
3628                         $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
3629
3630                         foreach ($entries_html as $entry) { $rv .= $entry; };
3631
3632                         $rv .= "</div></div>";
3633                 }
3634
3635                 return $rv;
3636         }
3637
3638         function getLastArticleId($link) {
3639                 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3640                         WHERE owner_uid = " . $_SESSION["uid"]);
3641
3642                 if (db_num_rows($result) == 1) {
3643                         return db_fetch_result($result, 0, "id");
3644                 } else {
3645                         return -1;
3646                 }
3647         }
3648
3649         function build_url($parts) {
3650                 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3651         }
3652
3653         /**
3654          * Converts a (possibly) relative URL to a absolute one.
3655          *
3656          * @param string $url     Base URL (i.e. from where the document is)
3657          * @param string $rel_url Possibly relative URL in the document
3658          *
3659          * @return string Absolute URL
3660          */
3661         function rewrite_relative_url($url, $rel_url) {
3662                 if (strpos($rel_url, "magnet:") === 0) {
3663                         return $rel_url;
3664                 } else if (strpos($rel_url, "://") !== false) {
3665                         return $rel_url;
3666                 } else if (strpos($rel_url, "//") === 0) {
3667                         # protocol-relative URL (rare but they exist)
3668                         return $rel_url;
3669                 } else if (strpos($rel_url, "/") === 0)
3670                 {
3671                         $parts = parse_url($url);
3672                         $parts['path'] = $rel_url;
3673
3674                         return build_url($parts);
3675
3676                 } else {
3677                         $parts = parse_url($url);
3678                         if (!isset($parts['path'])) {
3679                                 $parts['path'] = '/';
3680                         }
3681                         $dir = $parts['path'];
3682                         if (substr($dir, -1) !== '/') {
3683                                 $dir = dirname($parts['path']);
3684                                 $dir !== '/' && $dir .= '/';
3685                         }
3686                         $parts['path'] = $dir . $rel_url;
3687
3688                         return build_url($parts);
3689                 }
3690         }
3691
3692         function sphinx_search($query, $offset = 0, $limit = 30) {
3693                 require_once 'lib/sphinxapi.php';
3694
3695                 $sphinxClient = new SphinxClient();
3696
3697                 $sphinxClient->SetServer('localhost', 9312);
3698                 $sphinxClient->SetConnectTimeout(1);
3699
3700                 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3701                         'feed_title' => 20));
3702
3703                 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
3704                 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
3705                 $sphinxClient->SetLimits($offset, $limit, 1000);
3706                 $sphinxClient->SetArrayResult(false);
3707                 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3708
3709                 $result = $sphinxClient->Query($query, SPHINX_INDEX);
3710
3711                 $ids = array();
3712
3713                 if (is_array($result['matches'])) {
3714                         foreach (array_keys($result['matches']) as $int_id) {
3715                                 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3716                                 array_push($ids, $ref_id);
3717                         }
3718                 }
3719
3720                 return $ids;
3721         }
3722
3723         function cleanup_tags($link, $days = 14, $limit = 1000) {
3724
3725                 if (DB_TYPE == "pgsql") {
3726                         $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3727                 } else if (DB_TYPE == "mysql") {
3728                         $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3729                 }
3730
3731                 $tags_deleted = 0;
3732
3733                 while ($limit > 0) {
3734                         $limit_part = 500;
3735
3736                         $query = "SELECT ttrss_tags.id AS id
3737                                 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3738                                 WHERE post_int_id = int_id AND $interval_query AND
3739                                 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3740
3741                         $result = db_query($link, $query);
3742
3743                         $ids = array();
3744
3745                         while ($line = db_fetch_assoc($result)) {
3746                                 array_push($ids, $line['id']);
3747                         }
3748
3749                         if (count($ids) > 0) {
3750                                 $ids = join(",", $ids);
3751                                 print ".";
3752
3753                                 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3754                                 $tags_deleted += db_affected_rows($link, $tmp_result);
3755                         } else {
3756                                 break;
3757                         }
3758
3759                         $limit -= $limit_part;
3760                 }
3761
3762                 print "\n";
3763
3764                 return $tags_deleted;
3765         }
3766
3767         function print_user_stylesheet($link) {
3768                 $value = get_pref($link, 'USER_STYLESHEET');
3769
3770                 if ($value) {
3771                         print "<style type=\"text/css\">";
3772                         print str_replace("<br/>", "\n", $value);
3773                         print "</style>";
3774                 }
3775
3776         }
3777
3778         function rewrite_urls($html) {
3779                 libxml_use_internal_errors(true);
3780
3781                 $charset_hack = '<head>
3782                         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3783                 </head>';
3784
3785                 $doc = new DOMDocument();
3786                 $doc->loadHTML($charset_hack . $html);
3787                 $xpath = new DOMXPath($doc);
3788
3789                 $entries = $xpath->query('//*/text()');
3790
3791                 foreach ($entries as $entry) {
3792                         if (strstr($entry->wholeText, "://") !== false) {
3793                                 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3794                                         "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText);
3795
3796                                 if ($text != $entry->wholeText) {
3797                                         $cdoc = new DOMDocument();
3798                                         $cdoc->loadHTML($charset_hack . $text);
3799
3800
3801                                         foreach ($cdoc->childNodes as $cnode) {
3802                                                 $cnode = $doc->importNode($cnode, true);
3803
3804                                                 if ($cnode) {
3805                                                         $entry->parentNode->insertBefore($cnode);
3806                                                 }
3807                                         }
3808
3809                                         $entry->parentNode->removeChild($entry);
3810
3811                                 }
3812                         }
3813                 }
3814
3815                 $node = $doc->getElementsByTagName('body')->item(0);
3816
3817                 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3818                 if ($node)
3819                         return $doc->saveXML($node);
3820                 else
3821                         return $html;
3822         }
3823
3824         function filter_to_sql($link, $filter, $owner_uid) {
3825                 $query = array();
3826
3827                 if (DB_TYPE == "pgsql")
3828                         $reg_qpart = "~";
3829                 else
3830                         $reg_qpart = "REGEXP";
3831
3832                 foreach ($filter["rules"] AS $rule) {
3833                         $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3834                                 $rule['reg_exp']) !== FALSE;
3835
3836                         if ($regexp_valid) {
3837
3838                                 $rule['reg_exp'] = db_escape_string($link, $rule['reg_exp']);
3839
3840                                 switch ($rule["type"]) {
3841                                         case "title":
3842                                                 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3843                                                         $rule['reg_exp'] . "')";
3844                                                 break;
3845                                         case "content":
3846                                                 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3847                                                         $rule['reg_exp'] . "')";
3848                                                 break;
3849                                         case "both":
3850                                                 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3851                                                         $rule['reg_exp'] . "') OR LOWER(" .
3852                                                         "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3853                                                 break;
3854                                         case "tag":
3855                                                 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3856                                                         $rule['reg_exp'] . "')";
3857                                                 break;
3858                                         case "link":
3859                                                 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3860                                                         $rule['reg_exp'] . "')";
3861                                                 break;
3862                                         case "author":
3863                                                 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3864                                                         $rule['reg_exp'] . "')";
3865                                                 break;
3866                                 }
3867
3868                                 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3869                                         $qpart .= " AND feed_id = " . db_escape_string($link, $rule["feed_id"]);
3870                                 }
3871
3872                                 if (isset($rule["cat_id"])) {
3873
3874                                         if ($rule["cat_id"] > 0) {
3875                                                 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
3876                                                 array_push($children, $rule["cat_id"]);
3877
3878                                                 $children = join(",", $children);
3879
3880                                                 $cat_qpart = "cat_id IN ($children)";
3881                                         } else {
3882                                                 $cat_qpart = "cat_id IS NULL";
3883                                         }
3884
3885                                         $qpart .= " AND $cat_qpart";
3886                                 }
3887
3888                                 array_push($query, "($qpart)");
3889
3890                         }
3891                 }
3892
3893                 if (count($query) > 0) {
3894                         return "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
3895                 } else {
3896                         return "(false)";
3897                 }
3898         }
3899
3900         if (!function_exists('gzdecode')) {
3901                 function gzdecode($string) { // no support for 2nd argument
3902                         return file_get_contents('compress.zlib://data:who/cares;base64,'.
3903                                 base64_encode($string));
3904                 }
3905         }
3906
3907         function get_random_bytes($length) {
3908                 if (function_exists('openssl_random_pseudo_bytes')) {
3909                         return openssl_random_pseudo_bytes($length);
3910                 } else {
3911                         $output = "";
3912
3913                         for ($i = 0; $i < $length; $i++)
3914                                 $output .= chr(mt_rand(0, 255));
3915
3916                         return $output;
3917                 }
3918         }
3919
3920         function read_stdin() {
3921                 $fp = fopen("php://stdin", "r");
3922
3923                 if ($fp) {
3924                         $line = trim(fgets($fp));
3925                         fclose($fp);
3926                         return $line;
3927                 }
3928
3929                 return null;
3930         }
3931
3932         function tmpdirname($path, $prefix) {
3933                 // Use PHP's tmpfile function to create a temporary
3934                 // directory name. Delete the file and keep the name.
3935                 $tempname = tempnam($path,$prefix);
3936                 if (!$tempname)
3937                         return false;
3938
3939                 if (!unlink($tempname))
3940                         return false;
3941
3942        return $tempname;
3943         }
3944
3945         function getFeedCategory($link, $feed) {
3946                 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
3947                         WHERE id = '$feed'");
3948
3949                 if (db_num_rows($result) > 0) {
3950                         return db_fetch_result($result, 0, "cat_id");
3951                 } else {
3952                         return false;
3953                 }
3954
3955         }
3956
3957         function implements_interface($class, $interface) {
3958                 return in_array($interface, class_implements($class));
3959         }
3960
3961         function geturl($url){
3962
3963                 (function_exists('curl_init')) ? '' : die('cURL Must be installed for geturl function to work. Ask your host to enable it or uncomment extension=php_curl.dll in php.ini');
3964
3965                 $curl = curl_init();
3966                 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
3967                 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
3968                 $header[] = "Cache-Control: max-age=0";
3969                 $header[] = "Connection: keep-alive";
3970                 $header[] = "Keep-Alive: 300";
3971                 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
3972                 $header[] = "Accept-Language: en-us,en;q=0.5";
3973                 $header[] = "Pragma: ";
3974
3975                 curl_setopt($curl, CURLOPT_URL, $url);
3976                 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
3977                 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
3978                 curl_setopt($curl, CURLOPT_HEADER, true);
3979                 curl_setopt($curl, CURLOPT_REFERER, $url);
3980                 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
3981                 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
3982                 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
3983                 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
3984                 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
3985
3986                 $html = curl_exec($curl);
3987
3988                 $status = curl_getinfo($curl);
3989                 curl_close($curl);
3990
3991                 if($status['http_code']!=200){
3992                         if($status['http_code'] == 301 || $status['http_code'] == 302) {
3993                                 list($header) = explode("\r\n\r\n", $html, 2);
3994                                 $matches = array();
3995                                 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
3996                                 $url = trim(str_replace($matches[1],"",$matches[0]));
3997                                 $url_parsed = parse_url($url);
3998                                 return (isset($url_parsed))? geturl($url, $referer):'';
3999                         }
4000                         $oline='';
4001                         foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4002                         $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4003 #                       $handle = @fopen('./curl.error.log', 'a');
4004 #                       fwrite($handle, $line);
4005                         return FALSE;
4006                 }
4007                 return $url;
4008         }
4009
4010         function get_minified_js($files) {
4011                 require_once 'lib/jshrink/Minifier.php';
4012
4013                 $rv = '';
4014
4015                 foreach ($files as $js) {
4016                         if (!isset($_GET['debug'])) {
4017                                 $cached_file = CACHE_DIR . "/js/$js.js";
4018
4019                                 if (file_exists($cached_file) &&
4020                                                 is_readable($cached_file) &&
4021                                                 filemtime($cached_file) >= filemtime("js/$js.js")) {
4022
4023                                         $rv .= file_get_contents($cached_file);
4024
4025                                 } else {
4026                                         $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
4027                                         file_put_contents($cached_file, $minified);
4028                                         $rv .= $minified;
4029                                 }
4030                         } else {
4031                                 $rv .= file_get_contents("js/$js.js");
4032                         }
4033                 }
4034
4035                 return $rv;
4036         }
4037
4038         function stylesheet_tag($filename) {
4039                 $timestamp = filemtime($filename);
4040
4041                 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4042         }
4043
4044         function javascript_tag($filename) {
4045                 $query = "";
4046
4047                 if (!(strpos($filename, "?") === FALSE)) {
4048                         $query = substr($filename, strpos($filename, "?")+1);
4049                         $filename = substr($filename, 0, strpos($filename, "?"));
4050                 }
4051
4052                 $timestamp = filemtime($filename);
4053
4054                 if ($query) $timestamp .= "&$query";
4055
4056                 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4057         }
4058
4059         function calculate_dep_timestamp() {
4060                 $files = array_merge(glob("js/*.js"), glob("*.css"));
4061
4062                 $max_ts = -1;
4063
4064                 foreach ($files as $file) {
4065                         if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4066                 }
4067
4068                 return $max_ts;
4069         }
4070
4071     function get_site_title() {
4072         if (defined("_SITE_TITLE")) {
4073             return _SITE_TITLE;
4074         } else {
4075                 return "Tiny Tiny RSS";
4076                   }
4077     }
4078 ?>