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