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