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