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