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