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