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