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