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