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