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