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