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