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