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