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