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