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