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