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