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