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