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