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