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