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