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