]> git.wh0rd.org - tt-rss.git/blob - include/functions.php
e2357f6c0bad4627cdbdb8008324a5b61397dd74
[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 $auth_pass = db_escape_string($link, $auth_pass);
1626
1627 if (db_num_rows($result) == 0) {
1628 $result = db_query($link,
1629 "INSERT INTO ttrss_feeds
1630 (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted)
1631 VALUES ('".$_SESSION["uid"]."', '$url',
1632 '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, $auth_pass_encrypted)");
1633
1634 $result = db_query($link,
1635 "SELECT id FROM ttrss_feeds WHERE feed_url = '$url'
1636 AND owner_uid = " . $_SESSION["uid"]);
1637
1638 $feed_id = db_fetch_result($result, 0, "id");
1639
1640 if ($feed_id) {
1641 update_rss_feed($link, $feed_id, true);
1642 }
1643
1644 return array("code" => 1);
1645 } else {
1646 return array("code" => 0);
1647 }
1648 }
1649
1650 function print_feed_select($link, $id, $default_id = "",
1651 $attributes = "", $include_all_feeds = true,
1652 $root_id = false, $nest_level = 0) {
1653
1654 if (!$root_id) {
1655 print "<select id=\"$id\" name=\"$id\" $attributes>";
1656 if ($include_all_feeds) {
1657 $is_selected = ("0" == $default_id) ? "selected=\"1\"" : "";
1658 print "<option $is_selected value=\"0\">".__('All feeds')."</option>";
1659 }
1660 }
1661
1662 if (get_pref($link, 'ENABLE_FEED_CATS')) {
1663
1664 if ($root_id)
1665 $parent_qpart = "parent_cat = '$root_id'";
1666 else
1667 $parent_qpart = "parent_cat IS NULL";
1668
1669 $result = db_query($link, "SELECT id,title,
1670 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1671 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1672 FROM ttrss_feed_categories
1673 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1674
1675 while ($line = db_fetch_assoc($result)) {
1676
1677 for ($i = 0; $i < $nest_level; $i++)
1678 $line["title"] = " - " . $line["title"];
1679
1680 $is_selected = ("CAT:".$line["id"] == $default_id) ? "selected=\"1\"" : "";
1681
1682 printf("<option $is_selected value='CAT:%d'>%s</option>",
1683 $line["id"], htmlspecialchars($line["title"]));
1684
1685 if ($line["num_children"] > 0)
1686 print_feed_select($link, $id, $default_id, $attributes,
1687 $include_all_feeds, $line["id"], $nest_level+1);
1688
1689 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1690 WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1691
1692 while ($fline = db_fetch_assoc($feed_result)) {
1693 $is_selected = ($fline["id"] == $default_id) ? "selected=\"1\"" : "";
1694
1695 $fline["title"] = " + " . $fline["title"];
1696
1697 for ($i = 0; $i < $nest_level; $i++)
1698 $fline["title"] = " - " . $fline["title"];
1699
1700 printf("<option $is_selected value='%d'>%s</option>",
1701 $fline["id"], htmlspecialchars($fline["title"]));
1702 }
1703 }
1704
1705 if (!$root_id) {
1706 $is_selected = ($default_id == "CAT:0") ? "selected=\"1\"" : "";
1707
1708 printf("<option $is_selected value='CAT:0'>%s</option>",
1709 __("Uncategorized"));
1710
1711 $feed_result = db_query($link, "SELECT id,title FROM ttrss_feeds
1712 WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title");
1713
1714 while ($fline = db_fetch_assoc($feed_result)) {
1715 $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ? "selected=\"1\"" : "";
1716
1717 $fline["title"] = " + " . $fline["title"];
1718
1719 for ($i = 0; $i < $nest_level; $i++)
1720 $fline["title"] = " - " . $fline["title"];
1721
1722 printf("<option $is_selected value='%d'>%s</option>",
1723 $fline["id"], htmlspecialchars($fline["title"]));
1724 }
1725 }
1726
1727 } else {
1728 $result = db_query($link, "SELECT id,title FROM ttrss_feeds
1729 WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title");
1730
1731 while ($line = db_fetch_assoc($result)) {
1732
1733 $is_selected = ($line["id"] == $default_id) ? "selected=\"1\"" : "";
1734
1735 printf("<option $is_selected value='%d'>%s</option>",
1736 $line["id"], htmlspecialchars($line["title"]));
1737 }
1738 }
1739
1740 if (!$root_id) {
1741 print "</select>";
1742 }
1743 }
1744
1745 function print_feed_cat_select($link, $id, $default_id,
1746 $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) {
1747
1748 if (!$root_id) {
1749 print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" onchange=\"catSelectOnChange(this)\" $attributes>";
1750 }
1751
1752 if ($root_id)
1753 $parent_qpart = "parent_cat = '$root_id'";
1754 else
1755 $parent_qpart = "parent_cat IS NULL";
1756
1757 $result = db_query($link, "SELECT id,title,
1758 (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE
1759 c2.parent_cat = ttrss_feed_categories.id) AS num_children
1760 FROM ttrss_feed_categories
1761 WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title");
1762
1763 while ($line = db_fetch_assoc($result)) {
1764 if ($line["id"] == $default_id) {
1765 $is_selected = "selected=\"1\"";
1766 } else {
1767 $is_selected = "";
1768 }
1769
1770 for ($i = 0; $i < $nest_level; $i++)
1771 $line["title"] = " - " . $line["title"];
1772
1773 if ($line["title"])
1774 printf("<option $is_selected value='%d'>%s</option>",
1775 $line["id"], htmlspecialchars($line["title"]));
1776
1777 if ($line["num_children"] > 0)
1778 print_feed_cat_select($link, $id, $default_id, $attributes,
1779 $include_all_cats, $line["id"], $nest_level+1);
1780 }
1781
1782 if (!$root_id) {
1783 if ($include_all_cats) {
1784 if (db_num_rows($result) > 0) {
1785 print "<option disabled=\"1\">--------</option>";
1786 }
1787
1788 if ($default_id == 0) {
1789 $is_selected = "selected=\"1\"";
1790 } else {
1791 $is_selected = "";
1792 }
1793
1794 print "<option $is_selected value=\"0\">".__('Uncategorized')."</option>";
1795 }
1796 print "</select>";
1797 }
1798 }
1799
1800 function checkbox_to_sql_bool($val) {
1801 return ($val == "on") ? "true" : "false";
1802 }
1803
1804 function getFeedCatTitle($link, $id) {
1805 if ($id == -1) {
1806 return __("Special");
1807 } else if ($id < LABEL_BASE_INDEX) {
1808 return __("Labels");
1809 } else if ($id > 0) {
1810 $result = db_query($link, "SELECT ttrss_feed_categories.title
1811 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1812 cat_id = ttrss_feed_categories.id");
1813 if (db_num_rows($result) == 1) {
1814 return db_fetch_result($result, 0, "title");
1815 } else {
1816 return __("Uncategorized");
1817 }
1818 } else {
1819 return "getFeedCatTitle($id) failed";
1820 }
1821
1822 }
1823
1824 function getFeedIcon($id) {
1825 switch ($id) {
1826 case 0:
1827 return "images/archive.png";
1828 break;
1829 case -1:
1830 return "images/mark_set.svg";
1831 break;
1832 case -2:
1833 return "images/pub_set.svg";
1834 break;
1835 case -3:
1836 return "images/fresh.png";
1837 break;
1838 case -4:
1839 return "images/tag.png";
1840 break;
1841 case -6:
1842 return "images/recently_read.png";
1843 break;
1844 default:
1845 if ($id < LABEL_BASE_INDEX) {
1846 return "images/label.png";
1847 } else {
1848 if (file_exists(ICONS_DIR . "/$id.ico"))
1849 return ICONS_URL . "/$id.ico";
1850 }
1851 break;
1852 }
1853 }
1854
1855 function getFeedTitle($link, $id, $cat = false) {
1856 if ($cat) {
1857 return getCategoryTitle($link, $id);
1858 } else if ($id == -1) {
1859 return __("Starred articles");
1860 } else if ($id == -2) {
1861 return __("Published articles");
1862 } else if ($id == -3) {
1863 return __("Fresh articles");
1864 } else if ($id == -4) {
1865 return __("All articles");
1866 } else if ($id === 0 || $id === "0") {
1867 return __("Archived articles");
1868 } else if ($id == -6) {
1869 return __("Recently read");
1870 } else if ($id < LABEL_BASE_INDEX) {
1871 $label_id = feed_to_label_id($id);
1872 $result = db_query($link, "SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'");
1873 if (db_num_rows($result) == 1) {
1874 return db_fetch_result($result, 0, "caption");
1875 } else {
1876 return "Unknown label ($label_id)";
1877 }
1878
1879 } else if (is_numeric($id) && $id > 0) {
1880 $result = db_query($link, "SELECT title FROM ttrss_feeds WHERE id = '$id'");
1881 if (db_num_rows($result) == 1) {
1882 return db_fetch_result($result, 0, "title");
1883 } else {
1884 return "Unknown feed ($id)";
1885 }
1886 } else {
1887 return $id;
1888 }
1889 }
1890
1891 function make_init_params($link) {
1892 $params = array();
1893
1894 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1895 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1896 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1897 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1898
1899 $params[strtolower($param)] = (int) get_pref($link, $param);
1900 }
1901
1902 $params["icons_url"] = ICONS_URL;
1903 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
1904 $params["default_view_mode"] = get_pref($link, "_DEFAULT_VIEW_MODE");
1905 $params["default_view_limit"] = (int) get_pref($link, "_DEFAULT_VIEW_LIMIT");
1906 $params["default_view_order_by"] = get_pref($link, "_DEFAULT_VIEW_ORDER_BY");
1907 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1908 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
1909
1910 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1911 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1912
1913 $max_feed_id = db_fetch_result($result, 0, "mid");
1914 $num_feeds = db_fetch_result($result, 0, "nf");
1915
1916 $params["max_feed_id"] = (int) $max_feed_id;
1917 $params["num_feeds"] = (int) $num_feeds;
1918
1919 $params["collapsed_feedlist"] = (int) get_pref($link, "_COLLAPSED_FEEDLIST");
1920 $params["hotkeys"] = get_hotkeys_map($link);
1921
1922 $params["csrf_token"] = $_SESSION["csrf_token"];
1923 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1924
1925 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
1926
1927 return $params;
1928 }
1929
1930 function get_hotkeys_info($link) {
1931 $hotkeys = array(
1932 __("Navigation") => array(
1933 "next_feed" => __("Open next feed"),
1934 "prev_feed" => __("Open previous feed"),
1935 "next_article" => __("Open next article"),
1936 "prev_article" => __("Open previous article"),
1937 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1938 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1939 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1940 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1941 "search_dialog" => __("Show search dialog")),
1942 __("Article") => array(
1943 "toggle_mark" => __("Toggle starred"),
1944 "toggle_publ" => __("Toggle published"),
1945 "toggle_unread" => __("Toggle unread"),
1946 "edit_tags" => __("Edit tags"),
1947 "dismiss_selected" => __("Dismiss selected"),
1948 "dismiss_read" => __("Dismiss read"),
1949 "open_in_new_window" => __("Open in new window"),
1950 "catchup_below" => __("Mark below as read"),
1951 "catchup_above" => __("Mark above as read"),
1952 "article_scroll_down" => __("Scroll down"),
1953 "article_scroll_up" => __("Scroll up"),
1954 "select_article_cursor" => __("Select article under cursor"),
1955 "email_article" => __("Email article"),
1956 "close_article" => __("Close/collapse article"),
1957 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1958 "toggle_widescreen" => __("Toggle widescreen mode"),
1959 "toggle_embed_original" => __("Toggle embed original")),
1960 __("Article selection") => array(
1961 "select_all" => __("Select all articles"),
1962 "select_unread" => __("Select unread"),
1963 "select_marked" => __("Select starred"),
1964 "select_published" => __("Select published"),
1965 "select_invert" => __("Invert selection"),
1966 "select_none" => __("Deselect everything")),
1967 __("Feed") => array(
1968 "feed_refresh" => __("Refresh current feed"),
1969 "feed_unhide_read" => __("Un/hide read feeds"),
1970 "feed_subscribe" => __("Subscribe to feed"),
1971 "feed_edit" => __("Edit feed"),
1972 "feed_catchup" => __("Mark as read"),
1973 "feed_reverse" => __("Reverse headlines"),
1974 "feed_debug_update" => __("Debug feed update"),
1975 "catchup_all" => __("Mark all feeds as read"),
1976 "cat_toggle_collapse" => __("Un/collapse current category"),
1977 "toggle_combined_mode" => __("Toggle combined mode"),
1978 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1979 __("Go to") => array(
1980 "goto_all" => __("All articles"),
1981 "goto_fresh" => __("Fresh"),
1982 "goto_marked" => __("Starred"),
1983 "goto_published" => __("Published"),
1984 "goto_tagcloud" => __("Tag cloud"),
1985 "goto_prefs" => __("Preferences")),
1986 __("Other") => array(
1987 "create_label" => __("Create label"),
1988 "create_filter" => __("Create filter"),
1989 "collapse_sidebar" => __("Un/collapse sidebar"),
1990 "help_dialog" => __("Show help dialog"))
1991 );
1992
1993 global $pluginhost;
1994 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_INFO) as $plugin) {
1995 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1996 }
1997
1998 return $hotkeys;
1999 }
2000
2001 function get_hotkeys_map($link) {
2002 $hotkeys = array(
2003 // "navigation" => array(
2004 "k" => "next_feed",
2005 "j" => "prev_feed",
2006 "n" => "next_article",
2007 "p" => "prev_article",
2008 "(38)|up" => "prev_article",
2009 "(40)|down" => "next_article",
2010 // "^(38)|Ctrl-up" => "prev_article_noscroll",
2011 // "^(40)|Ctrl-down" => "next_article_noscroll",
2012 "(191)|/" => "search_dialog",
2013 // "article" => array(
2014 "s" => "toggle_mark",
2015 "*s" => "toggle_publ",
2016 "u" => "toggle_unread",
2017 "*t" => "edit_tags",
2018 "*d" => "dismiss_selected",
2019 "*x" => "dismiss_read",
2020 "o" => "open_in_new_window",
2021 "c p" => "catchup_below",
2022 "c n" => "catchup_above",
2023 "*n" => "article_scroll_down",
2024 "*p" => "article_scroll_up",
2025 "*(38)|Shift+up" => "article_scroll_up",
2026 "*(40)|Shift+down" => "article_scroll_down",
2027 "a *w" => "toggle_widescreen",
2028 "a e" => "toggle_embed_original",
2029 "e" => "email_article",
2030 "a q" => "close_article",
2031 // "article_selection" => array(
2032 "a a" => "select_all",
2033 "a u" => "select_unread",
2034 "a *u" => "select_marked",
2035 "a p" => "select_published",
2036 "a i" => "select_invert",
2037 "a n" => "select_none",
2038 // "feed" => array(
2039 "f r" => "feed_refresh",
2040 "f a" => "feed_unhide_read",
2041 "f s" => "feed_subscribe",
2042 "f e" => "feed_edit",
2043 "f q" => "feed_catchup",
2044 "f x" => "feed_reverse",
2045 "f *d" => "feed_debug_update",
2046 "f *c" => "toggle_combined_mode",
2047 "f c" => "toggle_cdm_expanded",
2048 "*q" => "catchup_all",
2049 "x" => "cat_toggle_collapse",
2050 // "goto" => array(
2051 "g a" => "goto_all",
2052 "g f" => "goto_fresh",
2053 "g s" => "goto_marked",
2054 "g p" => "goto_published",
2055 "g t" => "goto_tagcloud",
2056 "g *p" => "goto_prefs",
2057 // "other" => array(
2058 "(9)|Tab" => "select_article_cursor", // tab
2059 "c l" => "create_label",
2060 "c f" => "create_filter",
2061 "c s" => "collapse_sidebar",
2062 "^(191)|Ctrl+/" => "help_dialog",
2063 );
2064
2065 if (get_pref($link, 'COMBINED_DISPLAY_MODE')) {
2066 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
2067 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
2068 }
2069
2070 global $pluginhost;
2071 foreach ($pluginhost->get_hooks($pluginhost::HOOK_HOTKEY_MAP) as $plugin) {
2072 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
2073 }
2074
2075 $prefixes = array();
2076
2077 foreach (array_keys($hotkeys) as $hotkey) {
2078 $pair = explode(" ", $hotkey, 2);
2079
2080 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
2081 array_push($prefixes, $pair[0]);
2082 }
2083 }
2084
2085 return array($prefixes, $hotkeys);
2086 }
2087
2088 function make_runtime_info($link) {
2089 $data = array();
2090
2091 $result = db_query($link, "SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
2092 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
2093
2094 $max_feed_id = db_fetch_result($result, 0, "mid");
2095 $num_feeds = db_fetch_result($result, 0, "nf");
2096
2097 $data["max_feed_id"] = (int) $max_feed_id;
2098 $data["num_feeds"] = (int) $num_feeds;
2099
2100 $data['last_article_id'] = getLastArticleId($link);
2101 $data['cdm_expanded'] = get_pref($link, 'CDM_EXPANDED');
2102
2103 $data['dep_ts'] = calculate_dep_timestamp();
2104 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
2105
2106 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
2107
2108 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
2109
2110 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
2111
2112 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
2113
2114 if ($stamp) {
2115 $stamp_delta = time() - $stamp;
2116
2117 if ($stamp_delta > 1800) {
2118 $stamp_check = 0;
2119 } else {
2120 $stamp_check = 1;
2121 $_SESSION["daemon_stamp_check"] = time();
2122 }
2123
2124 $data['daemon_stamp_ok'] = $stamp_check;
2125
2126 $stamp_fmt = date("Y.m.d, G:i", $stamp);
2127
2128 $data['daemon_stamp'] = $stamp_fmt;
2129 }
2130 }
2131 }
2132
2133 if ($_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
2134 $new_version_details = @check_for_update($link);
2135
2136 $data['new_version_available'] = (int) ($new_version_details != false);
2137
2138 $_SESSION["last_version_check"] = time();
2139 $_SESSION["version_data"] = $new_version_details;
2140 }
2141
2142 return $data;
2143 }
2144
2145 function search_to_sql($link, $search) {
2146
2147 $search_query_part = "";
2148
2149 $keywords = explode(" ", $search);
2150 $query_keywords = array();
2151
2152 foreach ($keywords as $k) {
2153 if (strpos($k, "-") === 0) {
2154 $k = substr($k, 1);
2155 $not = "NOT";
2156 } else {
2157 $not = "";
2158 }
2159
2160 $commandpair = explode(":", mb_strtolower($k), 2);
2161
2162 switch ($commandpair[0]) {
2163 case "title":
2164 if ($commandpair[1]) {
2165 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
2166 db_escape_string($link, mb_strtolower($commandpair[1]))."%'))");
2167 } else {
2168 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2169 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2170 }
2171 break;
2172 case "author":
2173 if ($commandpair[1]) {
2174 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
2175 db_escape_string($link, mb_strtolower($commandpair[1]))."%'))");
2176 } else {
2177 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2178 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2179 }
2180 break;
2181 case "note":
2182 if ($commandpair[1]) {
2183 if ($commandpair[1] == "true")
2184 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
2185 else if ($commandpair[1] == "false")
2186 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
2187 else
2188 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
2189 db_escape_string($link, mb_strtolower($commandpair[1]))."%'))");
2190 } else {
2191 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2192 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2193 }
2194 break;
2195 case "star":
2196
2197 if ($commandpair[1]) {
2198 if ($commandpair[1] == "true")
2199 array_push($query_keywords, "($not (marked = true))");
2200 else
2201 array_push($query_keywords, "($not (marked = false))");
2202 } else {
2203 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2204 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2205 }
2206 break;
2207 case "pub":
2208 if ($commandpair[1]) {
2209 if ($commandpair[1] == "true")
2210 array_push($query_keywords, "($not (published = true))");
2211 else
2212 array_push($query_keywords, "($not (published = false))");
2213
2214 } else {
2215 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2216 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2217 }
2218 break;
2219 default:
2220 if (strpos($k, "@") === 0) {
2221
2222 $user_tz_string = get_pref($link, 'USER_TIMEZONE', $_SESSION['uid']);
2223 $orig_ts = strtotime(substr($k, 1));
2224 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
2225
2226 //$k = date("Y-m-d", strtotime(substr($k, 1)));
2227
2228 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
2229 } else {
2230 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
2231 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
2232 }
2233 }
2234 }
2235
2236 $search_query_part = implode("AND", $query_keywords);
2237
2238 return $search_query_part;
2239 }
2240
2241 function getParentCategories($link, $cat, $owner_uid) {
2242 $rv = array();
2243
2244 $result = db_query($link, "SELECT parent_cat FROM ttrss_feed_categories
2245 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
2246
2247 while ($line = db_fetch_assoc($result)) {
2248 array_push($rv, $line["parent_cat"]);
2249 $rv = array_merge($rv, getParentCategories($link, $line["parent_cat"], $owner_uid));
2250 }
2251
2252 return $rv;
2253 }
2254
2255 function getChildCategories($link, $cat, $owner_uid) {
2256 $rv = array();
2257
2258 $result = db_query($link, "SELECT id FROM ttrss_feed_categories
2259 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
2260
2261 while ($line = db_fetch_assoc($result)) {
2262 array_push($rv, $line["id"]);
2263 $rv = array_merge($rv, getChildCategories($link, $line["id"], $owner_uid));
2264 }
2265
2266 return $rv;
2267 }
2268
2269 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) {
2270
2271 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2272
2273 $ext_tables_part = "";
2274
2275 if ($search) {
2276
2277 if (SPHINX_ENABLED) {
2278 $ids = join(",", @sphinx_search($search, 0, 500));
2279
2280 if ($ids)
2281 $search_query_part = "ref_id IN ($ids) AND ";
2282 else
2283 $search_query_part = "ref_id = -1 AND ";
2284
2285 } else {
2286 $search_query_part = search_to_sql($link, $search);
2287 $search_query_part .= " AND ";
2288 }
2289
2290 } else {
2291 $search_query_part = "";
2292 }
2293
2294 if ($filter) {
2295
2296 if (DB_TYPE == "pgsql") {
2297 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
2298 } else {
2299 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
2300 }
2301
2302 $override_order = "updated DESC";
2303
2304 $filter_query_part = filter_to_sql($link, $filter, $owner_uid);
2305
2306 // Try to check if SQL regexp implementation chokes on a valid regexp
2307 $result = db_query($link, "SELECT true AS true_val FROM ttrss_entries,
2308 ttrss_user_entries, ttrss_feeds, ttrss_feed_categories
2309 WHERE $filter_query_part LIMIT 1", false);
2310
2311 if ($result) {
2312 $test = db_fetch_result($result, 0, "true_val");
2313
2314 if (!$test) {
2315 $filter_query_part = "false AND";
2316 } else {
2317 $filter_query_part .= " AND";
2318 }
2319 } else {
2320 $filter_query_part = "false AND";
2321 }
2322
2323 } else {
2324 $filter_query_part = "";
2325 }
2326
2327 if ($since_id) {
2328 $since_id_part = "ttrss_entries.id > $since_id AND ";
2329 } else {
2330 $since_id_part = "";
2331 }
2332
2333 $view_query_part = "";
2334
2335 if ($view_mode == "adaptive") {
2336 if ($search) {
2337 $view_query_part = " ";
2338 } else if ($feed != -1) {
2339
2340 $unread = getFeedUnread($link, $feed, $cat_view);
2341
2342 if ($cat_view && $feed > 0 && $include_children)
2343 $unread += getCategoryChildrenUnread($link, $feed);
2344
2345 if ($unread > 0)
2346 $view_query_part = " unread = true AND ";
2347
2348 }
2349 }
2350
2351 if ($view_mode == "marked") {
2352 $view_query_part = " marked = true AND ";
2353 }
2354
2355 if ($view_mode == "has_note") {
2356 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
2357 }
2358
2359 if ($view_mode == "published") {
2360 $view_query_part = " published = true AND ";
2361 }
2362
2363 if ($view_mode == "unread" && $feed != -6) {
2364 $view_query_part = " unread = true AND ";
2365 }
2366
2367 if ($limit > 0) {
2368 $limit_query_part = "LIMIT " . $limit;
2369 }
2370
2371 $allow_archived = false;
2372
2373 $vfeed_query_part = "";
2374
2375 // override query strategy and enable feed display when searching globally
2376 if ($search && $search_mode == "all_feeds") {
2377 $query_strategy_part = "true";
2378 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2379 /* tags */
2380 } else if (!is_numeric($feed)) {
2381 $query_strategy_part = "true";
2382 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
2383 id = feed_id) as feed_title,";
2384 } else if ($search && $search_mode == "this_cat") {
2385 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2386
2387 if ($feed > 0) {
2388 if ($include_children) {
2389 $subcats = getChildCategories($link, $feed, $owner_uid);
2390 array_push($subcats, $feed);
2391 $cats_qpart = join(",", $subcats);
2392 } else {
2393 $cats_qpart = $feed;
2394 }
2395
2396 $query_strategy_part = "ttrss_feeds.cat_id IN ($cats_qpart)";
2397
2398 } else {
2399 $query_strategy_part = "ttrss_feeds.cat_id IS NULL";
2400 }
2401
2402 } else if ($feed > 0) {
2403
2404 if ($cat_view) {
2405
2406 if ($feed > 0) {
2407 if ($include_children) {
2408 # sub-cats
2409 $subcats = getChildCategories($link, $feed, $owner_uid);
2410
2411 array_push($subcats, $feed);
2412 $query_strategy_part = "cat_id IN (".
2413 implode(",", $subcats).")";
2414
2415 } else {
2416 $query_strategy_part = "cat_id = '$feed'";
2417 }
2418
2419 } else {
2420 $query_strategy_part = "cat_id IS NULL";
2421 }
2422
2423 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2424
2425 } else {
2426 $query_strategy_part = "feed_id = '$feed'";
2427 }
2428 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
2429 $query_strategy_part = "feed_id IS NULL";
2430 $allow_archived = true;
2431 } else if ($feed == 0 && $cat_view) { // uncategorized
2432 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
2433 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2434 } else if ($feed == -1) { // starred virtual feed
2435 $query_strategy_part = "marked = true";
2436 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2437 $allow_archived = true;
2438
2439 if (!$override_order) {
2440 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
2441 }
2442
2443 } else if ($feed == -2) { // published virtual feed OR labels category
2444
2445 if (!$cat_view) {
2446 $query_strategy_part = "published = true";
2447 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2448 $allow_archived = true;
2449
2450 if (!$override_order) {
2451 $override_order = "last_published DESC, date_entered DESC, updated DESC";
2452 }
2453
2454 } else {
2455 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2456
2457 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2458
2459 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
2460 ttrss_user_labels2.article_id = ref_id";
2461
2462 }
2463 } else if ($feed == -6) { // recently read
2464 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
2465 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2466 $allow_archived = true;
2467
2468 if (!$override_order) $override_order = "last_read DESC";
2469 } else if ($feed == -3) { // fresh virtual feed
2470 $query_strategy_part = "unread = true AND score >= 0";
2471
2472 $intl = get_pref($link, "FRESH_ARTICLE_MAX_AGE", $owner_uid);
2473
2474 if (DB_TYPE == "pgsql") {
2475 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
2476 } else {
2477 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
2478 }
2479
2480 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2481 } else if ($feed == -4) { // all articles virtual feed
2482 $allow_archived = true;
2483 $query_strategy_part = "true";
2484 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2485 } else if ($feed <= LABEL_BASE_INDEX) { // labels
2486 $label_id = feed_to_label_id($feed);
2487
2488 $query_strategy_part = "label_id = '$label_id' AND
2489 ttrss_labels2.id = ttrss_user_labels2.label_id AND
2490 ttrss_user_labels2.article_id = ref_id";
2491
2492 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
2493 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
2494 $allow_archived = true;
2495
2496 } else {
2497 $query_strategy_part = "true";
2498 }
2499
2500 $order_by = "score DESC, date_entered DESC, updated DESC";
2501
2502 if ($view_mode == "unread_first") {
2503 $order_by = "unread DESC, $order_by";
2504 }
2505
2506 if ($override_order) {
2507 $order_by = $override_order;
2508 }
2509
2510 $feed_title = "";
2511
2512 if ($search) {
2513 $feed_title = T_sprintf("Search results: %s", $search);
2514 } else {
2515 if ($cat_view) {
2516 $feed_title = getCategoryTitle($link, $feed);
2517 } else {
2518 if (is_numeric($feed) && $feed > 0) {
2519 $result = db_query($link, "SELECT title,site_url,last_error
2520 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
2521
2522 $feed_title = db_fetch_result($result, 0, "title");
2523 $feed_site_url = db_fetch_result($result, 0, "site_url");
2524 $last_error = db_fetch_result($result, 0, "last_error");
2525 } else {
2526 $feed_title = getFeedTitle($link, $feed);
2527 }
2528 }
2529 }
2530
2531 $content_query_part = "content as content_preview, cached_content, ";
2532
2533 if (is_numeric($feed)) {
2534
2535 if ($feed >= 0) {
2536 $feed_kind = "Feeds";
2537 } else {
2538 $feed_kind = "Labels";
2539 }
2540
2541 if ($limit_query_part) {
2542 $offset_query_part = "OFFSET $offset";
2543 }
2544
2545 // proper override_order applied above
2546 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref($link, 'VFEED_GROUP_BY_FEED', $owner_uid)) {
2547 if (!$override_order) {
2548 $order_by = "ttrss_feeds.title, $order_by";
2549 } else {
2550 $order_by = "ttrss_feeds.title, $override_order";
2551 }
2552 }
2553
2554 if (!$allow_archived) {
2555 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
2556 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
2557
2558 } else {
2559 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
2560 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
2561 }
2562
2563 $query = "SELECT DISTINCT
2564 date_entered,
2565 guid,
2566 ttrss_entries.id,ttrss_entries.title,
2567 updated,
2568 label_cache,
2569 tag_cache,
2570 always_display_enclosures,
2571 site_url,
2572 note,
2573 num_comments,
2574 comments,
2575 int_id,
2576 hide_images,
2577 unread,feed_id,marked,published,link,last_read,orig_feed_id,
2578 last_marked, last_published,
2579 $vfeed_query_part
2580 $content_query_part
2581 author,score
2582 FROM
2583 $from_qpart
2584 WHERE
2585 $feed_check_qpart
2586 ttrss_user_entries.ref_id = ttrss_entries.id AND
2587 ttrss_user_entries.owner_uid = '$owner_uid' AND
2588 $search_query_part
2589 $filter_query_part
2590 $view_query_part
2591 $since_id_part
2592 $query_strategy_part ORDER BY $order_by
2593 $limit_query_part $offset_query_part";
2594
2595 if ($_REQUEST["debug"]) print $query;
2596
2597 $result = db_query($link, $query);
2598
2599 } else {
2600 // browsing by tag
2601
2602 $select_qpart = "SELECT DISTINCT " .
2603 "date_entered," .
2604 "guid," .
2605 "note," .
2606 "ttrss_entries.id as id," .
2607 "title," .
2608 "updated," .
2609 "unread," .
2610 "feed_id," .
2611 "orig_feed_id," .
2612 "marked," .
2613 "num_comments, " .
2614 "comments, " .
2615 "tag_cache," .
2616 "label_cache," .
2617 "link," .
2618 "last_read," .
2619 "(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images," .
2620 "last_marked, last_published, " .
2621 $since_id_part .
2622 $vfeed_query_part .
2623 $content_query_part .
2624 "score ";
2625
2626 $feed_kind = "Tags";
2627 $all_tags = explode(",", $feed);
2628 if ($search_mode == 'any') {
2629 $tag_sql = "tag_name in (" . implode(", ", array_map("db_quote", $all_tags)) . ")";
2630 $from_qpart = " FROM ttrss_entries,ttrss_user_entries,ttrss_tags ";
2631 $where_qpart = " WHERE " .
2632 "ref_id = ttrss_entries.id AND " .
2633 "ttrss_user_entries.owner_uid = $owner_uid AND " .
2634 "post_int_id = int_id AND $tag_sql AND " .
2635 $view_query_part .
2636 $search_query_part .
2637 $query_strategy_part . " ORDER BY $order_by " .
2638 $limit_query_part;
2639
2640 } else {
2641 $i = 1;
2642 $sub_selects = array();
2643 $sub_ands = array();
2644 foreach ($all_tags as $term) {
2645 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");
2646 $i++;
2647 }
2648 if ($i > 2) {
2649 $x = 1;
2650 $y = 2;
2651 do {
2652 array_push($sub_ands, "A$x.post_int_id = A$y.post_int_id");
2653 $x++;
2654 $y++;
2655 } while ($y < $i);
2656 }
2657 array_push($sub_ands, "A1.post_int_id = ttrss_user_entries.int_id and ttrss_user_entries.owner_uid = $owner_uid");
2658 array_push($sub_ands, "ttrss_user_entries.ref_id = ttrss_entries.id");
2659 $from_qpart = " FROM " . implode(", ", $sub_selects) . ", ttrss_user_entries, ttrss_entries";
2660 $where_qpart = " WHERE " . implode(" AND ", $sub_ands);
2661 }
2662 // error_log("TAG SQL: " . $tag_sql);
2663 // $tag_sql = "tag_name = '$feed'"; DEFAULT way
2664
2665 // error_log("[". $select_qpart . "][" . $from_qpart . "][" .$where_qpart . "]");
2666 $result = db_query($link, $select_qpart . $from_qpart . $where_qpart);
2667 }
2668
2669 return array($result, $feed_title, $feed_site_url, $last_error);
2670
2671 }
2672
2673 function sanitize($link, $str, $force_remove_images = false, $owner = false, $site_url = false) {
2674 if (!$owner) $owner = $_SESSION["uid"];
2675
2676 $res = trim($str); if (!$res) return '';
2677
2678 if (strpos($res, "href=") === false)
2679 $res = rewrite_urls($res);
2680
2681 $charset_hack = '<head>
2682 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
2683 </head>';
2684
2685 $res = trim($res); if (!$res) return '';
2686
2687 libxml_use_internal_errors(true);
2688
2689 $doc = new DOMDocument();
2690 $doc->loadHTML($charset_hack . $res);
2691 $xpath = new DOMXPath($doc);
2692
2693 $entries = $xpath->query('(//a[@href]|//img[@src])');
2694
2695 foreach ($entries as $entry) {
2696
2697 if ($site_url) {
2698
2699 if ($entry->hasAttribute('href'))
2700 $entry->setAttribute('href',
2701 rewrite_relative_url($site_url, $entry->getAttribute('href')));
2702
2703 if ($entry->hasAttribute('src')) {
2704 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
2705
2706 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
2707
2708 if (file_exists($cached_filename)) {
2709 $src = SELF_URL_PATH . '/image.php?hash=' . sha1($src);
2710 }
2711
2712 $entry->setAttribute('src', $src);
2713 }
2714
2715 if ($entry->nodeName == 'img') {
2716 if (($owner && get_pref($link, "STRIP_IMAGES", $owner)) ||
2717 $force_remove_images || $_SESSION["bw_limit"]) {
2718
2719 $p = $doc->createElement('p');
2720
2721 $a = $doc->createElement('a');
2722 $a->setAttribute('href', $entry->getAttribute('src'));
2723
2724 $a->appendChild(new DOMText($entry->getAttribute('src')));
2725 $a->setAttribute('target', '_blank');
2726
2727 $p->appendChild($a);
2728
2729 $entry->parentNode->replaceChild($p, $entry);
2730 }
2731 }
2732 }
2733
2734 if (strtolower($entry->nodeName) == "a") {
2735 $entry->setAttribute("target", "_blank");
2736 }
2737 }
2738
2739 $entries = $xpath->query('//iframe');
2740 foreach ($entries as $entry) {
2741 $entry->setAttribute('sandbox', 'allow-scripts');
2742
2743 }
2744
2745 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
2746 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
2747 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
2748 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
2749 'dt', 'em', 'footer', 'figure', 'figcaption',
2750 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
2751 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
2752 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
2753 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
2754 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
2755 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
2756
2757 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
2758
2759 $disallowed_attributes = array('id', 'style', 'class');
2760
2761 global $pluginhost;
2762
2763 if (isset($pluginhost)) {
2764 foreach ($pluginhost->get_hooks($pluginhost::HOOK_SANITIZE) as $plugin) {
2765 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes);
2766 if (is_array($retval)) {
2767 $doc = $retval[0];
2768 $allowed_elements = $retval[1];
2769 $disallowed_attributes = $retval[2];
2770 } else {
2771 $doc = $retval;
2772 }
2773 }
2774 }
2775
2776 $doc->removeChild($doc->firstChild); //remove doctype
2777 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
2778 $res = $doc->saveHTML();
2779 return $res;
2780 }
2781
2782 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
2783 $entries = $doc->getElementsByTagName("*");
2784
2785 foreach ($entries as $entry) {
2786 if (!in_array($entry->nodeName, $allowed_elements)) {
2787 $entry->parentNode->removeChild($entry);
2788 }
2789
2790 if ($entry->hasAttributes()) {
2791 $attrs_to_remove = array();
2792
2793 foreach ($entry->attributes as $attr) {
2794
2795 if (strpos($attr->nodeName, 'on') === 0) {
2796 array_push($attrs_to_remove, $attr);
2797 }
2798
2799 if (in_array($attr->nodeName, $disallowed_attributes)) {
2800 array_push($attrs_to_remove, $attr);
2801 }
2802 }
2803
2804 foreach ($attrs_to_remove as $attr) {
2805 $entry->removeAttributeNode($attr);
2806 }
2807 }
2808 }
2809
2810 return $doc;
2811 }
2812
2813 function check_for_update($link) {
2814 if (CHECK_FOR_NEW_VERSION && $_SESSION['access_level'] >= 10) {
2815 $version_url = "http://tt-rss.org/version.php?ver=" . VERSION .
2816 "&iid=" . sha1(SELF_URL_PATH);
2817
2818 $version_data = @fetch_file_contents($version_url);
2819
2820 if ($version_data) {
2821 $version_data = json_decode($version_data, true);
2822 if ($version_data && $version_data['version']) {
2823
2824 if (version_compare(VERSION, $version_data['version']) == -1) {
2825 return $version_data;
2826 }
2827 }
2828 }
2829 }
2830 return false;
2831 }
2832
2833 function catchupArticlesById($link, $ids, $cmode, $owner_uid = false) {
2834
2835 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2836 if (count($ids) == 0) return;
2837
2838 $tmp_ids = array();
2839
2840 foreach ($ids as $id) {
2841 array_push($tmp_ids, "ref_id = '$id'");
2842 }
2843
2844 $ids_qpart = join(" OR ", $tmp_ids);
2845
2846 if ($cmode == 0) {
2847 db_query($link, "UPDATE ttrss_user_entries SET
2848 unread = false,last_read = NOW()
2849 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2850 } else if ($cmode == 1) {
2851 db_query($link, "UPDATE ttrss_user_entries SET
2852 unread = true
2853 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2854 } else {
2855 db_query($link, "UPDATE ttrss_user_entries SET
2856 unread = NOT unread,last_read = NOW()
2857 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2858 }
2859
2860 /* update ccache */
2861
2862 $result = db_query($link, "SELECT DISTINCT feed_id FROM ttrss_user_entries
2863 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
2864
2865 while ($line = db_fetch_assoc($result)) {
2866 ccache_update($link, $line["feed_id"], $owner_uid);
2867 }
2868 }
2869
2870 function get_article_tags($link, $id, $owner_uid = 0, $tag_cache = false) {
2871
2872 $a_id = db_escape_string($link, $id);
2873
2874 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2875
2876 $query = "SELECT DISTINCT tag_name,
2877 owner_uid as owner FROM
2878 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
2879 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
2880
2881 $obj_id = md5("TAGS:$owner_uid:$id");
2882 $tags = array();
2883
2884 /* check cache first */
2885
2886 if ($tag_cache === false) {
2887 $result = db_query($link, "SELECT tag_cache FROM ttrss_user_entries
2888 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
2889
2890 $tag_cache = db_fetch_result($result, 0, "tag_cache");
2891 }
2892
2893 if ($tag_cache) {
2894 $tags = explode(",", $tag_cache);
2895 } else {
2896
2897 /* do it the hard way */
2898
2899 $tmp_result = db_query($link, $query);
2900
2901 while ($tmp_line = db_fetch_assoc($tmp_result)) {
2902 array_push($tags, $tmp_line["tag_name"]);
2903 }
2904
2905 /* update the cache */
2906
2907 $tags_str = db_escape_string($link, join(",", $tags));
2908
2909 db_query($link, "UPDATE ttrss_user_entries
2910 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
2911 AND owner_uid = $owner_uid");
2912 }
2913
2914 return $tags;
2915 }
2916
2917 function trim_array($array) {
2918 $tmp = $array;
2919 array_walk($tmp, 'trim');
2920 return $tmp;
2921 }
2922
2923 function tag_is_valid($tag) {
2924 if ($tag == '') return false;
2925 if (preg_match("/^[0-9]*$/", $tag)) return false;
2926 if (mb_strlen($tag) > 250) return false;
2927
2928 if (function_exists('iconv')) {
2929 $tag = iconv("utf-8", "utf-8", $tag);
2930 }
2931
2932 if (!$tag) return false;
2933
2934 return true;
2935 }
2936
2937 function render_login_form($link) {
2938 header('Cache-Control: public');
2939
2940 require_once "login_form.php";
2941 exit;
2942 }
2943
2944 function format_warning($msg, $id = "") {
2945 global $link;
2946 return "<div class=\"warning\" id=\"$id\">
2947 <img src=\"images/sign_excl.svg\"><div class='inner'>$msg</div></div>";
2948 }
2949
2950 function format_notice($msg, $id = "") {
2951 global $link;
2952 return "<div class=\"notice\" id=\"$id\">
2953 <img src=\"images/sign_info.svg\"><div class='inner'>$msg</div></div>";
2954 }
2955
2956 function format_error($msg, $id = "") {
2957 global $link;
2958 return "<div class=\"error\" id=\"$id\">
2959 <img src=\"images/sign_excl.svg\"><div class='inner'>$msg</div></div>";
2960 }
2961
2962 function print_notice($msg) {
2963 return print format_notice($msg);
2964 }
2965
2966 function print_warning($msg) {
2967 return print format_warning($msg);
2968 }
2969
2970 function print_error($msg) {
2971 return print format_error($msg);
2972 }
2973
2974
2975 function T_sprintf() {
2976 $args = func_get_args();
2977 return vsprintf(__(array_shift($args)), $args);
2978 }
2979
2980 function format_inline_player($link, $url, $ctype) {
2981
2982 $entry = "";
2983
2984 $url = htmlspecialchars($url);
2985
2986 if (strpos($ctype, "audio/") === 0) {
2987
2988 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
2989 strpos($_SERVER['HTTP_USER_AGENT'], "Chrome") !== false ||
2990 strpos($_SERVER['HTTP_USER_AGENT'], "Safari") !== false )) {
2991
2992 $id = 'AUDIO-' . uniqid();
2993
2994 $entry .= "<audio id=\"$id\"\" controls style='display : none'>
2995 <source type=\"$ctype\" src=\"$url\"></source>
2996 </audio>";
2997
2998 $entry .= "<span onclick=\"player(this)\"
2999 title=\"".__("Click to play")."\" status=\"0\"
3000 class=\"player\" audio-id=\"$id\">".__("Play")."</span>";
3001
3002 } else {
3003
3004 $entry .= "<object type=\"application/x-shockwave-flash\"
3005 data=\"lib/button/musicplayer.swf?song_url=$url\"
3006 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
3007 <param name=\"movie\"
3008 value=\"lib/button/musicplayer.swf?song_url=$url\" />
3009 </object>";
3010 }
3011
3012 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
3013 href=\"$url\">" . basename($url) . "</a>";
3014
3015 return $entry;
3016
3017 }
3018
3019 return "";
3020
3021 /* $filename = substr($url, strrpos($url, "/")+1);
3022
3023 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3024 $filename . " (" . $ctype . ")" . "</a>"; */
3025
3026 }
3027
3028 function format_article($link, $id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
3029 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3030
3031 $rv = array();
3032
3033 $rv['id'] = $id;
3034
3035 /* we can figure out feed_id from article id anyway, why do we
3036 * pass feed_id here? let's ignore the argument :( */
3037
3038 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3039 WHERE ref_id = '$id'");
3040
3041 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
3042
3043 $rv['feed_id'] = $feed_id;
3044
3045 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
3046
3047 if ($mark_as_read) {
3048 $result = db_query($link, "UPDATE ttrss_user_entries
3049 SET unread = false,last_read = NOW()
3050 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
3051
3052 ccache_update($link, $feed_id, $owner_uid);
3053 }
3054
3055 $result = db_query($link, "SELECT id,title,link,content,feed_id,comments,int_id,
3056 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
3057 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
3058 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
3059 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
3060 num_comments,
3061 tag_cache,
3062 author,
3063 orig_feed_id,
3064 note,
3065 cached_content
3066 FROM ttrss_entries,ttrss_user_entries
3067 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
3068
3069 if ($result) {
3070
3071 $line = db_fetch_assoc($result);
3072
3073 $tag_cache = $line["tag_cache"];
3074
3075 $line["tags"] = get_article_tags($link, $id, $owner_uid, $line["tag_cache"]);
3076 unset($line["tag_cache"]);
3077
3078 $line["content"] = sanitize($link, $line["content"], false, $owner_uid, $line["site_url"]);
3079
3080 global $pluginhost;
3081
3082 foreach ($pluginhost->get_hooks($pluginhost::HOOK_RENDER_ARTICLE) as $p) {
3083 $line = $p->hook_render_article($line);
3084 }
3085
3086 $num_comments = $line["num_comments"];
3087 $entry_comments = "";
3088
3089 if ($num_comments > 0) {
3090 if ($line["comments"]) {
3091 $comments_url = htmlspecialchars($line["comments"]);
3092 } else {
3093 $comments_url = htmlspecialchars($line["link"]);
3094 }
3095 $entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
3096 } else {
3097 if ($line["comments"] && $line["link"] != $line["comments"]) {
3098 $entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
3099 }
3100 }
3101
3102 if ($zoom_mode) {
3103 header("Content-Type: text/html");
3104 $rv['content'] .= "<html><head>
3105 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
3106 <title>Tiny Tiny RSS - ".$line["title"]."</title>
3107 <link rel=\"stylesheet\" type=\"text/css\" href=\"tt-rss.css\">
3108 </head><body id=\"ttrssZoom\">";
3109 }
3110
3111 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
3112
3113 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
3114
3115 $entry_author = $line["author"];
3116
3117 if ($entry_author) {
3118 $entry_author = __(" - ") . $entry_author;
3119 }
3120
3121 $parsed_updated = make_local_datetime($link, $line["updated"], true,
3122 $owner_uid, true);
3123
3124 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
3125
3126 if ($line["link"]) {
3127 $rv['content'] .= "<div class='postTitle'><a target='_blank'
3128 title=\"".htmlspecialchars($line['title'])."\"
3129 href=\"" .
3130 htmlspecialchars($line["link"]) . "\">" .
3131 $line["title"] . "</a>" .
3132 "<span class='author'>$entry_author</span></div>";
3133 } else {
3134 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
3135 }
3136
3137 $tags_str = format_tags_string($line["tags"], $id);
3138 $tags_str_full = join(", ", $line["tags"]);
3139
3140 if (!$tags_str_full) $tags_str_full = __("no tags");
3141
3142 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
3143
3144 $rv['content'] .= "<div class='postTags' style='float : right'>
3145 <img src='images/tag.png'
3146 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
3147
3148 if (!$zoom_mode) {
3149 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
3150 <a title=\"".__('Edit tags for this article')."\"
3151 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
3152
3153 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
3154 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
3155 position=\"below\">$tags_str_full</div>";
3156
3157 global $pluginhost;
3158 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON) as $p) {
3159 $rv['content'] .= $p->hook_article_button($line);
3160 }
3161
3162 } else {
3163 $tags_str = strip_tags($tags_str);
3164 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
3165 }
3166 $rv['content'] .= "</div>";
3167 $rv['content'] .= "<div clear='both'>";
3168
3169 global $pluginhost;
3170 foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
3171 $rv['content'] .= $p->hook_article_left_button($line);
3172 }
3173
3174 $rv['content'] .= "$entry_comments</div>";
3175
3176 if ($line["orig_feed_id"]) {
3177
3178 $tmp_result = db_query($link, "SELECT * FROM ttrss_archived_feeds
3179 WHERE id = ".$line["orig_feed_id"]);
3180
3181 if (db_num_rows($tmp_result) != 0) {
3182
3183 $rv['content'] .= "<div clear='both'>";
3184 $rv['content'] .= __("Originally from:");
3185
3186 $rv['content'] .= "&nbsp;";
3187
3188 $tmp_line = db_fetch_assoc($tmp_result);
3189
3190 $rv['content'] .= "<a target='_blank'
3191 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
3192 $tmp_line['title'] . "</a>";
3193
3194 $rv['content'] .= "&nbsp;";
3195
3196 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
3197 $rv['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_set.svg'></a>";
3198
3199 $rv['content'] .= "</div>";
3200 }
3201 }
3202
3203 $rv['content'] .= "</div>";
3204
3205 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
3206 if ($line['note']) {
3207 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
3208 }
3209 $rv['content'] .= "</div>";
3210
3211 $rv['content'] .= "<div class=\"postContent\">";
3212
3213 $rv['content'] .= $line["content"];
3214 $rv['content'] .= format_article_enclosures($link, $id,
3215 sql_bool_to_bool($line["always_display_enclosures"]),
3216 $line["content"],
3217 sql_bool_to_bool($line["hide_images"]));
3218
3219 $rv['content'] .= "</div>";
3220
3221 $rv['content'] .= "</div>";
3222
3223 }
3224
3225 if ($zoom_mode) {
3226 $rv['content'] .= "
3227 <div class='footer'>
3228 <button onclick=\"return window.close()\">".
3229 __("Close this window")."</button></div>";
3230 $rv['content'] .= "</body></html>";
3231 }
3232
3233 return $rv;
3234
3235 }
3236
3237 function print_checkpoint($n, $s) {
3238 $ts = microtime(true);
3239 echo sprintf("<!-- CP[$n] %.4f seconds -->", $ts - $s);
3240 return $ts;
3241 }
3242
3243 function sanitize_tag($tag) {
3244 $tag = trim($tag);
3245
3246 $tag = mb_strtolower($tag, 'utf-8');
3247
3248 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
3249
3250 // $tag = str_replace('"', "", $tag);
3251 // $tag = str_replace("+", " ", $tag);
3252 $tag = str_replace("technorati tag: ", "", $tag);
3253
3254 return $tag;
3255 }
3256
3257 function get_self_url_prefix() {
3258 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
3259 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
3260 } else {
3261 return SELF_URL_PATH;
3262 }
3263 }
3264
3265 /**
3266 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
3267 *
3268 * @return string The Mozilla Firefox feed adding URL.
3269 */
3270 function add_feed_url() {
3271 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
3272
3273 $url_path = get_self_url_prefix() .
3274 "/public.php?op=subscribe&feed_url=%s";
3275 return $url_path;
3276 } // function add_feed_url
3277
3278 function encrypt_password($pass, $salt = '', $mode2 = false) {
3279 if ($salt && $mode2) {
3280 return "MODE2:" . hash('sha256', $salt . $pass);
3281 } else if ($salt) {
3282 return "SHA1X:" . sha1("$salt:$pass");
3283 } else {
3284 return "SHA1:" . sha1($pass);
3285 }
3286 } // function encrypt_password
3287
3288 function load_filters($link, $feed_id, $owner_uid, $action_id = false) {
3289 $filters = array();
3290
3291 $cat_id = (int)getFeedCategory($link, $feed_id);
3292
3293 $result = db_query($link, "SELECT * FROM ttrss_filters2 WHERE
3294 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
3295
3296 $check_cats = join(",", array_merge(
3297 getParentCategories($link, $cat_id, $owner_uid),
3298 array($cat_id)));
3299
3300 while ($line = db_fetch_assoc($result)) {
3301 $filter_id = $line["id"];
3302
3303 $result2 = db_query($link, "SELECT
3304 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
3305 FROM ttrss_filters2_rules AS r,
3306 ttrss_filter_types AS t
3307 WHERE
3308 (cat_id IS NULL OR cat_id IN ($check_cats)) AND
3309 (feed_id IS NULL OR feed_id = '$feed_id') AND
3310 filter_type = t.id AND filter_id = '$filter_id'");
3311
3312 $rules = array();
3313 $actions = array();
3314
3315 while ($rule_line = db_fetch_assoc($result2)) {
3316 # print_r($rule_line);
3317
3318 $rule = array();
3319 $rule["reg_exp"] = $rule_line["reg_exp"];
3320 $rule["type"] = $rule_line["type_name"];
3321 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
3322
3323 array_push($rules, $rule);
3324 }
3325
3326 $result2 = db_query($link, "SELECT a.action_param,t.name AS type_name
3327 FROM ttrss_filters2_actions AS a,
3328 ttrss_filter_actions AS t
3329 WHERE
3330 action_id = t.id AND filter_id = '$filter_id'");
3331
3332 while ($action_line = db_fetch_assoc($result2)) {
3333 # print_r($action_line);
3334
3335 $action = array();
3336 $action["type"] = $action_line["type_name"];
3337 $action["param"] = $action_line["action_param"];
3338
3339 array_push($actions, $action);
3340 }
3341
3342
3343 $filter = array();
3344 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
3345 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
3346 $filter["rules"] = $rules;
3347 $filter["actions"] = $actions;
3348
3349 if (count($rules) > 0 && count($actions) > 0) {
3350 array_push($filters, $filter);
3351 }
3352 }
3353
3354 return $filters;
3355 }
3356
3357 function get_score_pic($score) {
3358 if ($score > 100) {
3359 return "score_high.png";
3360 } else if ($score > 0) {
3361 return "score_half_high.png";
3362 } else if ($score < -100) {
3363 return "score_low.png";
3364 } else if ($score < 0) {
3365 return "score_half_low.png";
3366 } else {
3367 return "score_neutral.png";
3368 }
3369 }
3370
3371 function feed_has_icon($id) {
3372 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
3373 }
3374
3375 function init_connection($link) {
3376 if ($link) {
3377
3378 if (DB_TYPE == "pgsql") {
3379 pg_query($link, "set client_encoding = 'UTF-8'");
3380 pg_set_client_encoding("UNICODE");
3381 pg_query($link, "set datestyle = 'ISO, european'");
3382 pg_query($link, "set TIME ZONE 0");
3383 } else {
3384 db_query($link, "SET time_zone = '+0:0'");
3385
3386 if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
3387 db_query($link, "SET NAMES " . MYSQL_CHARSET);
3388 }
3389 }
3390
3391 global $pluginhost;
3392
3393 $pluginhost = new PluginHost($link);
3394 $pluginhost->load(PLUGINS, $pluginhost::KIND_ALL);
3395
3396 return true;
3397 } else {
3398 print "Unable to connect to database:" . db_last_error();
3399 return false;
3400 }
3401 }
3402
3403 function format_tags_string($tags, $id) {
3404
3405 $tags_str = "";
3406 $tags_nolinks_str = "";
3407
3408 $num_tags = 0;
3409
3410 $tag_limit = 6;
3411
3412 $formatted_tags = array();
3413
3414 foreach ($tags as $tag) {
3415 $num_tags++;
3416 $tag_escaped = str_replace("'", "\\'", $tag);
3417
3418 if (mb_strlen($tag) > 30) {
3419 $tag = truncate_string($tag, 30);
3420 }
3421
3422 $tag_str = "<a href=\"javascript:viewfeed('$tag_escaped')\">$tag</a>";
3423
3424 array_push($formatted_tags, $tag_str);
3425
3426 $tmp_tags_str = implode(", ", $formatted_tags);
3427
3428 if ($num_tags == $tag_limit || mb_strlen($tmp_tags_str) > 150) {
3429 break;
3430 }
3431 }
3432
3433 $tags_str = implode(", ", $formatted_tags);
3434
3435 if ($num_tags < count($tags)) {
3436 $tags_str .= ", &hellip;";
3437 }
3438
3439 if ($num_tags == 0) {
3440 $tags_str = __("no tags");
3441 }
3442
3443 return $tags_str;
3444
3445 }
3446
3447 function format_article_labels($labels, $id) {
3448
3449 $labels_str = "";
3450
3451 foreach ($labels as $l) {
3452 $labels_str .= sprintf("<span class='hlLabelRef'
3453 style='color : %s; background-color : %s'>%s</span>",
3454 $l[2], $l[3], $l[1]);
3455 }
3456
3457 return $labels_str;
3458
3459 }
3460
3461 function format_article_note($id, $note, $allow_edit = true) {
3462
3463 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
3464 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
3465 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
3466
3467 return $str;
3468 }
3469
3470
3471 function get_feed_category($link, $feed_cat, $parent_cat_id = false) {
3472 if ($parent_cat_id) {
3473 $parent_qpart = "parent_cat = '$parent_cat_id'";
3474 $parent_insert = "'$parent_cat_id'";
3475 } else {
3476 $parent_qpart = "parent_cat IS NULL";
3477 $parent_insert = "NULL";
3478 }
3479
3480 $result = db_query($link,
3481 "SELECT id FROM ttrss_feed_categories
3482 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3483
3484 if (db_num_rows($result) == 0) {
3485 return false;
3486 } else {
3487 return db_fetch_result($result, 0, "id");
3488 }
3489 }
3490
3491 function add_feed_category($link, $feed_cat, $parent_cat_id = false) {
3492
3493 if (!$feed_cat) return false;
3494
3495 db_query($link, "BEGIN");
3496
3497 if ($parent_cat_id) {
3498 $parent_qpart = "parent_cat = '$parent_cat_id'";
3499 $parent_insert = "'$parent_cat_id'";
3500 } else {
3501 $parent_qpart = "parent_cat IS NULL";
3502 $parent_insert = "NULL";
3503 }
3504
3505 $feed_cat = mb_substr($feed_cat, 0, 250);
3506
3507 $result = db_query($link,
3508 "SELECT id FROM ttrss_feed_categories
3509 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
3510
3511 if (db_num_rows($result) == 0) {
3512
3513 $result = db_query($link,
3514 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
3515 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
3516
3517 db_query($link, "COMMIT");
3518
3519 return true;
3520 }
3521
3522 return false;
3523 }
3524
3525 function getArticleFeed($link, $id) {
3526 $result = db_query($link, "SELECT feed_id FROM ttrss_user_entries
3527 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
3528
3529 if (db_num_rows($result) != 0) {
3530 return db_fetch_result($result, 0, "feed_id");
3531 } else {
3532 return 0;
3533 }
3534 }
3535
3536 /**
3537 * Fixes incomplete URLs by prepending "http://".
3538 * Also replaces feed:// with http://, and
3539 * prepends a trailing slash if the url is a domain name only.
3540 *
3541 * @param string $url Possibly incomplete URL
3542 *
3543 * @return string Fixed URL.
3544 */
3545 function fix_url($url) {
3546 if (strpos($url, '://') === false) {
3547 $url = 'http://' . $url;
3548 } else if (substr($url, 0, 5) == 'feed:') {
3549 $url = 'http:' . substr($url, 5);
3550 }
3551
3552 //prepend slash if the URL has no slash in it
3553 // "http://www.example" -> "http://www.example/"
3554 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
3555 $url .= '/';
3556 }
3557
3558 if ($url != "http:///")
3559 return $url;
3560 else
3561 return '';
3562 }
3563
3564 function validate_feed_url($url) {
3565 $parts = parse_url($url);
3566
3567 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
3568
3569 }
3570
3571 function get_article_enclosures($link, $id) {
3572
3573 $query = "SELECT * FROM ttrss_enclosures
3574 WHERE post_id = '$id' AND content_url != ''";
3575
3576 $rv = array();
3577
3578 $result = db_query($link, $query);
3579
3580 if (db_num_rows($result) > 0) {
3581 while ($line = db_fetch_assoc($result)) {
3582 array_push($rv, $line);
3583 }
3584 }
3585
3586 return $rv;
3587 }
3588
3589 function save_email_address($link, $email) {
3590 // FIXME: implement persistent storage of emails
3591
3592 if (!$_SESSION['stored_emails'])
3593 $_SESSION['stored_emails'] = array();
3594
3595 if (!in_array($email, $_SESSION['stored_emails']))
3596 array_push($_SESSION['stored_emails'], $email);
3597 }
3598
3599
3600 function get_feed_access_key($link, $feed_id, $is_cat, $owner_uid = false) {
3601
3602 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
3603
3604 $sql_is_cat = bool_to_sql_bool($is_cat);
3605
3606 $result = db_query($link, "SELECT access_key FROM ttrss_access_keys
3607 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
3608 AND owner_uid = " . $owner_uid);
3609
3610 if (db_num_rows($result) == 1) {
3611 return db_fetch_result($result, 0, "access_key");
3612 } else {
3613 $key = db_escape_string($link, sha1(uniqid(rand(), true)));
3614
3615 $result = db_query($link, "INSERT INTO ttrss_access_keys
3616 (access_key, feed_id, is_cat, owner_uid)
3617 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
3618
3619 return $key;
3620 }
3621 return false;
3622 }
3623
3624 function get_feeds_from_html($url, $content)
3625 {
3626 $url = fix_url($url);
3627 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
3628
3629 libxml_use_internal_errors(true);
3630
3631 $doc = new DOMDocument();
3632 $doc->loadHTML($content);
3633 $xpath = new DOMXPath($doc);
3634 $entries = $xpath->query('/html/head/link[@rel="alternate"]');
3635 $feedUrls = array();
3636 foreach ($entries as $entry) {
3637 if ($entry->hasAttribute('href')) {
3638 $title = $entry->getAttribute('title');
3639 if ($title == '') {
3640 $title = $entry->getAttribute('type');
3641 }
3642 $feedUrl = rewrite_relative_url(
3643 $baseUrl, $entry->getAttribute('href')
3644 );
3645 $feedUrls[$feedUrl] = $title;
3646 }
3647 }
3648 return $feedUrls;
3649 }
3650
3651 function is_html($content) {
3652 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 20)) !== 0;
3653 }
3654
3655 function url_is_html($url, $login = false, $pass = false) {
3656 return is_html(fetch_file_contents($url, false, $login, $pass));
3657 }
3658
3659 function print_label_select($link, $name, $value, $attributes = "") {
3660
3661 $result = db_query($link, "SELECT caption FROM ttrss_labels2
3662 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
3663
3664 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
3665 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
3666
3667 while ($line = db_fetch_assoc($result)) {
3668
3669 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
3670
3671 print "<option value=\"".htmlspecialchars($line["caption"])."\"
3672 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
3673
3674 }
3675
3676 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
3677
3678 print "</select>";
3679
3680
3681 }
3682
3683 function format_article_enclosures($link, $id, $always_display_enclosures,
3684 $article_content, $hide_images = false) {
3685
3686 $result = get_article_enclosures($link, $id);
3687 $rv = '';
3688
3689 if (count($result) > 0) {
3690
3691 $entries_html = array();
3692 $entries = array();
3693 $entries_inline = array();
3694
3695 foreach ($result as $line) {
3696
3697 $url = $line["content_url"];
3698 $ctype = $line["content_type"];
3699
3700 if (!$ctype) $ctype = __("unknown type");
3701
3702 $filename = substr($url, strrpos($url, "/")+1);
3703
3704 $player = format_inline_player($link, $url, $ctype);
3705
3706 if ($player) array_push($entries_inline, $player);
3707
3708 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
3709 # $filename . " (" . $ctype . ")" . "</a>";
3710
3711 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
3712 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
3713
3714 array_push($entries_html, $entry);
3715
3716 $entry = array();
3717
3718 $entry["type"] = $ctype;
3719 $entry["filename"] = $filename;
3720 $entry["url"] = $url;
3721
3722 array_push($entries, $entry);
3723 }
3724
3725 if ($_SESSION['uid'] && !get_pref($link, "STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
3726 if ($always_display_enclosures ||
3727 !preg_match("/<img/i", $article_content)) {
3728
3729 foreach ($entries as $entry) {
3730
3731 if (preg_match("/image/", $entry["type"]) ||
3732 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
3733
3734 if (!$hide_images) {
3735 $rv .= "<p><img
3736 alt=\"".htmlspecialchars($entry["filename"])."\"
3737 src=\"" .htmlspecialchars($entry["url"]) . "\"/></p>";
3738 } else {
3739 $rv .= "<p><a target=\"_blank\"
3740 href=\"".htmlspecialchars($entry["url"])."\"
3741 >" .htmlspecialchars($entry["url"]) . "</a></p>";
3742
3743 }
3744 }
3745 }
3746 }
3747 }
3748
3749 if (count($entries_inline) > 0) {
3750 $rv .= "<hr clear='both'/>";
3751 foreach ($entries_inline as $entry) { $rv .= $entry; };
3752 $rv .= "<hr clear='both'/>";
3753 }
3754
3755 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
3756 "<option value=''>" . __('Attachments')."</option>";
3757
3758 foreach ($entries as $entry) {
3759 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "</option>";
3760
3761 };
3762
3763 $rv .= "</select>";
3764 }
3765
3766 return $rv;
3767 }
3768
3769 function getLastArticleId($link) {
3770 $result = db_query($link, "SELECT MAX(ref_id) AS id FROM ttrss_user_entries
3771 WHERE owner_uid = " . $_SESSION["uid"]);
3772
3773 if (db_num_rows($result) == 1) {
3774 return db_fetch_result($result, 0, "id");
3775 } else {
3776 return -1;
3777 }
3778 }
3779
3780 function build_url($parts) {
3781 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
3782 }
3783
3784 /**
3785 * Converts a (possibly) relative URL to a absolute one.
3786 *
3787 * @param string $url Base URL (i.e. from where the document is)
3788 * @param string $rel_url Possibly relative URL in the document
3789 *
3790 * @return string Absolute URL
3791 */
3792 function rewrite_relative_url($url, $rel_url) {
3793 if (strpos($rel_url, "magnet:") === 0) {
3794 return $rel_url;
3795 } else if (strpos($rel_url, "://") !== false) {
3796 return $rel_url;
3797 } else if (strpos($rel_url, "//") === 0) {
3798 # protocol-relative URL (rare but they exist)
3799 return $rel_url;
3800 } else if (strpos($rel_url, "/") === 0)
3801 {
3802 $parts = parse_url($url);
3803 $parts['path'] = $rel_url;
3804
3805 return build_url($parts);
3806
3807 } else {
3808 $parts = parse_url($url);
3809 if (!isset($parts['path'])) {
3810 $parts['path'] = '/';
3811 }
3812 $dir = $parts['path'];
3813 if (substr($dir, -1) !== '/') {
3814 $dir = dirname($parts['path']);
3815 $dir !== '/' && $dir .= '/';
3816 }
3817 $parts['path'] = $dir . $rel_url;
3818
3819 return build_url($parts);
3820 }
3821 }
3822
3823 function sphinx_search($query, $offset = 0, $limit = 30) {
3824 require_once 'lib/sphinxapi.php';
3825
3826 $sphinxClient = new SphinxClient();
3827
3828 $sphinxClient->SetServer('localhost', 9312);
3829 $sphinxClient->SetConnectTimeout(1);
3830
3831 $sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
3832 'feed_title' => 20));
3833
3834 $sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
3835 $sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
3836 $sphinxClient->SetLimits($offset, $limit, 1000);
3837 $sphinxClient->SetArrayResult(false);
3838 $sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
3839
3840 $result = $sphinxClient->Query($query, SPHINX_INDEX);
3841
3842 $ids = array();
3843
3844 if (is_array($result['matches'])) {
3845 foreach (array_keys($result['matches']) as $int_id) {
3846 $ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
3847 array_push($ids, $ref_id);
3848 }
3849 }
3850
3851 return $ids;
3852 }
3853
3854 function cleanup_tags($link, $days = 14, $limit = 1000) {
3855
3856 if (DB_TYPE == "pgsql") {
3857 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
3858 } else if (DB_TYPE == "mysql") {
3859 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
3860 }
3861
3862 $tags_deleted = 0;
3863
3864 while ($limit > 0) {
3865 $limit_part = 500;
3866
3867 $query = "SELECT ttrss_tags.id AS id
3868 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
3869 WHERE post_int_id = int_id AND $interval_query AND
3870 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
3871
3872 $result = db_query($link, $query);
3873
3874 $ids = array();
3875
3876 while ($line = db_fetch_assoc($result)) {
3877 array_push($ids, $line['id']);
3878 }
3879
3880 if (count($ids) > 0) {
3881 $ids = join(",", $ids);
3882
3883 $tmp_result = db_query($link, "DELETE FROM ttrss_tags WHERE id IN ($ids)");
3884 $tags_deleted += db_affected_rows($link, $tmp_result);
3885 } else {
3886 break;
3887 }
3888
3889 $limit -= $limit_part;
3890 }
3891
3892 return $tags_deleted;
3893 }
3894
3895 function print_user_stylesheet($link) {
3896 $value = get_pref($link, 'USER_STYLESHEET');
3897
3898 if ($value) {
3899 print "<style type=\"text/css\">";
3900 print str_replace("<br/>", "\n", $value);
3901 print "</style>";
3902 }
3903
3904 }
3905
3906 function rewrite_urls($html) {
3907 libxml_use_internal_errors(true);
3908
3909 $charset_hack = '<head>
3910 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
3911 </head>';
3912
3913 $doc = new DOMDocument();
3914 $doc->loadHTML($charset_hack . $html);
3915 $xpath = new DOMXPath($doc);
3916
3917 $entries = $xpath->query('//*/text()');
3918
3919 foreach ($entries as $entry) {
3920 if (strstr($entry->wholeText, "://") !== false) {
3921 $text = preg_replace("/((?<!=.)((http|https|ftp)+):\/\/[^ ,!]+)/i",
3922 "<a target=\"_blank\" href=\"\\1\">\\1</a>", $entry->wholeText);
3923
3924 if ($text != $entry->wholeText) {
3925 $cdoc = new DOMDocument();
3926 $cdoc->loadHTML($charset_hack . $text);
3927
3928
3929 foreach ($cdoc->childNodes as $cnode) {
3930 $cnode = $doc->importNode($cnode, true);
3931
3932 if ($cnode) {
3933 $entry->parentNode->insertBefore($cnode);
3934 }
3935 }
3936
3937 $entry->parentNode->removeChild($entry);
3938
3939 }
3940 }
3941 }
3942
3943 $node = $doc->getElementsByTagName('body')->item(0);
3944
3945 // http://tt-rss.org/forum/viewtopic.php?f=1&t=970
3946 if ($node)
3947 return $doc->saveXML($node);
3948 else
3949 return $html;
3950 }
3951
3952 function filter_to_sql($link, $filter, $owner_uid) {
3953 $query = array();
3954
3955 if (DB_TYPE == "pgsql")
3956 $reg_qpart = "~";
3957 else
3958 $reg_qpart = "REGEXP";
3959
3960 foreach ($filter["rules"] AS $rule) {
3961 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
3962 $rule['reg_exp']) !== FALSE;
3963
3964 if ($regexp_valid) {
3965
3966 $rule['reg_exp'] = db_escape_string($link, $rule['reg_exp']);
3967
3968 switch ($rule["type"]) {
3969 case "title":
3970 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3971 $rule['reg_exp'] . "')";
3972 break;
3973 case "content":
3974 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
3975 $rule['reg_exp'] . "')";
3976 break;
3977 case "both":
3978 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
3979 $rule['reg_exp'] . "') OR LOWER(" .
3980 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
3981 break;
3982 case "tag":
3983 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
3984 $rule['reg_exp'] . "')";
3985 break;
3986 case "link":
3987 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
3988 $rule['reg_exp'] . "')";
3989 break;
3990 case "author":
3991 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
3992 $rule['reg_exp'] . "')";
3993 break;
3994 }
3995
3996 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
3997
3998 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
3999 $qpart .= " AND feed_id = " . db_escape_string($link, $rule["feed_id"]);
4000 }
4001
4002 if (isset($rule["cat_id"])) {
4003
4004 if ($rule["cat_id"] > 0) {
4005 $children = getChildCategories($link, $rule["cat_id"], $owner_uid);
4006 array_push($children, $rule["cat_id"]);
4007
4008 $children = join(",", $children);
4009
4010 $cat_qpart = "cat_id IN ($children)";
4011 } else {
4012 $cat_qpart = "cat_id IS NULL";
4013 }
4014
4015 $qpart .= " AND $cat_qpart";
4016 }
4017
4018 array_push($query, "($qpart)");
4019
4020 }
4021 }
4022
4023 if (count($query) > 0) {
4024 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
4025 } else {
4026 $fullquery = "(false)";
4027 }
4028
4029 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
4030
4031 return $fullquery;
4032 }
4033
4034 if (!function_exists('gzdecode')) {
4035 function gzdecode($string) { // no support for 2nd argument
4036 return file_get_contents('compress.zlib://data:who/cares;base64,'.
4037 base64_encode($string));
4038 }
4039 }
4040
4041 function get_random_bytes($length) {
4042 if (function_exists('openssl_random_pseudo_bytes')) {
4043 return openssl_random_pseudo_bytes($length);
4044 } else {
4045 $output = "";
4046
4047 for ($i = 0; $i < $length; $i++)
4048 $output .= chr(mt_rand(0, 255));
4049
4050 return $output;
4051 }
4052 }
4053
4054 function read_stdin() {
4055 $fp = fopen("php://stdin", "r");
4056
4057 if ($fp) {
4058 $line = trim(fgets($fp));
4059 fclose($fp);
4060 return $line;
4061 }
4062
4063 return null;
4064 }
4065
4066 function tmpdirname($path, $prefix) {
4067 // Use PHP's tmpfile function to create a temporary
4068 // directory name. Delete the file and keep the name.
4069 $tempname = tempnam($path,$prefix);
4070 if (!$tempname)
4071 return false;
4072
4073 if (!unlink($tempname))
4074 return false;
4075
4076 return $tempname;
4077 }
4078
4079 function getFeedCategory($link, $feed) {
4080 $result = db_query($link, "SELECT cat_id FROM ttrss_feeds
4081 WHERE id = '$feed'");
4082
4083 if (db_num_rows($result) > 0) {
4084 return db_fetch_result($result, 0, "cat_id");
4085 } else {
4086 return false;
4087 }
4088
4089 }
4090
4091 function implements_interface($class, $interface) {
4092 return in_array($interface, class_implements($class));
4093 }
4094
4095 function geturl($url){
4096
4097 (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');
4098
4099 $curl = curl_init();
4100 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
4101 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
4102 $header[] = "Cache-Control: max-age=0";
4103 $header[] = "Connection: keep-alive";
4104 $header[] = "Keep-Alive: 300";
4105 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
4106 $header[] = "Accept-Language: en-us,en;q=0.5";
4107 $header[] = "Pragma: ";
4108
4109 curl_setopt($curl, CURLOPT_URL, $url);
4110 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
4111 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
4112 curl_setopt($curl, CURLOPT_HEADER, true);
4113 curl_setopt($curl, CURLOPT_REFERER, $url);
4114 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
4115 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
4116 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
4117 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
4118 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
4119
4120 $html = curl_exec($curl);
4121
4122 $status = curl_getinfo($curl);
4123 curl_close($curl);
4124
4125 if($status['http_code']!=200){
4126 if($status['http_code'] == 301 || $status['http_code'] == 302) {
4127 list($header) = explode("\r\n\r\n", $html, 2);
4128 $matches = array();
4129 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
4130 $url = trim(str_replace($matches[1],"",$matches[0]));
4131 $url_parsed = parse_url($url);
4132 return (isset($url_parsed))? geturl($url, $referer):'';
4133 }
4134 $oline='';
4135 foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
4136 $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
4137 # $handle = @fopen('./curl.error.log', 'a');
4138 # fwrite($handle, $line);
4139 return FALSE;
4140 }
4141 return $url;
4142 }
4143
4144 function get_minified_js($files) {
4145 require_once 'lib/jshrink/Minifier.php';
4146
4147 $rv = '';
4148
4149 foreach ($files as $js) {
4150 if (!isset($_GET['debug'])) {
4151 $cached_file = CACHE_DIR . "/js/$js.js";
4152
4153 if (file_exists($cached_file) &&
4154 is_readable($cached_file) &&
4155 filemtime($cached_file) >= filemtime("js/$js.js")) {
4156
4157 $rv .= file_get_contents($cached_file);
4158
4159 } else {
4160 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
4161 file_put_contents($cached_file, $minified);
4162 $rv .= $minified;
4163 }
4164 } else {
4165 $rv .= file_get_contents("js/$js.js");
4166 }
4167 }
4168
4169 return $rv;
4170 }
4171
4172 function stylesheet_tag($filename) {
4173 $timestamp = filemtime($filename);
4174
4175 echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
4176 }
4177
4178 function javascript_tag($filename) {
4179 $query = "";
4180
4181 if (!(strpos($filename, "?") === FALSE)) {
4182 $query = substr($filename, strpos($filename, "?")+1);
4183 $filename = substr($filename, 0, strpos($filename, "?"));
4184 }
4185
4186 $timestamp = filemtime($filename);
4187
4188 if ($query) $timestamp .= "&$query";
4189
4190 echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
4191 }
4192
4193 function calculate_dep_timestamp() {
4194 $files = array_merge(glob("js/*.js"), glob("*.css"));
4195
4196 $max_ts = -1;
4197
4198 foreach ($files as $file) {
4199 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
4200 }
4201
4202 return $max_ts;
4203 }
4204
4205 function T_js_decl($s1, $s2) {
4206 if ($s1 && $s2) {
4207 $s1 = preg_replace("/\n/", "", $s1);
4208 $s2 = preg_replace("/\n/", "", $s2);
4209
4210 $s1 = preg_replace("/\"/", "\\\"", $s1);
4211 $s2 = preg_replace("/\"/", "\\\"", $s2);
4212
4213 return "T_messages[\"$s1\"] = \"$s2\";\n";
4214 }
4215 }
4216
4217 function init_js_translations() {
4218
4219 print 'var T_messages = new Object();
4220
4221 function __(msg) {
4222 if (T_messages[msg]) {
4223 return T_messages[msg];
4224 } else {
4225 return msg;
4226 }
4227 }
4228
4229 function ngettext(msg1, msg2, n) {
4230 return (parseInt(n) > 1) ? msg2 : msg1;
4231 }';
4232
4233 $l10n = _get_reader();
4234
4235 for ($i = 0; $i < $l10n->total; $i++) {
4236 $orig = $l10n->get_original_string($i);
4237 $translation = __($orig);
4238
4239 print T_js_decl($orig, $translation);
4240 }
4241 }
4242
4243 function label_to_feed_id($label) {
4244 return LABEL_BASE_INDEX - 1 - abs($label);
4245 }
4246
4247 function feed_to_label_id($feed) {
4248 return LABEL_BASE_INDEX - 1 + abs($feed);
4249 }
4250
4251 ?>