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