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