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