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