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