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