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