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