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