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