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