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