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