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