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