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