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