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