]> git.wh0rd.org - tt-rss.git/blob - include/functions.php
b448c5a9d1757f83d565f8e30480f7fdaa9dd62b
[tt-rss.git] / include / functions.php
1 <?php
2 define('EXPECTED_CONFIG_VERSION', 26);
3 define('SCHEMA_VERSION', 130);
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_last_error_content = false; // curl only for the time being
14 $fetch_curl_used = false;
15 $suppress_debugging = false;
16
17 libxml_disable_entity_loader(true);
18
19 // separate test because this is included before sanity checks
20 if (function_exists("mb_internal_encoding")) mb_internal_encoding("UTF-8");
21
22 date_default_timezone_set('UTC');
23 if (defined('E_DEPRECATED')) {
24 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
25 } else {
26 error_reporting(E_ALL & ~E_NOTICE);
27 }
28
29 require_once 'config.php';
30
31 /**
32 * Define a constant if not already defined
33 *
34 * @param string $name The constant name.
35 * @param mixed $value The constant value.
36 * @access public
37 * @return boolean True if defined successfully or not.
38 */
39 function define_default($name, $value) {
40 defined($name) or define($name, $value);
41 }
42
43 ///// Some defaults that you can override in config.php //////
44
45 define_default('FEED_FETCH_TIMEOUT', 45);
46 // How may seconds to wait for response when requesting feed from a site
47 define_default('FEED_FETCH_NO_CACHE_TIMEOUT', 15);
48 // How may seconds to wait for response when requesting feed from a
49 // site when that feed wasn't cached before
50 define_default('FILE_FETCH_TIMEOUT', 45);
51 // Default timeout when fetching files from remote sites
52 define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
53 // How many seconds to wait for initial response from website when
54 // fetching files from remote sites
55
56 if (DB_TYPE == "pgsql") {
57 define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
58 } else {
59 define('SUBSTRING_FOR_DATE', 'SUBSTRING');
60 }
61
62 /**
63 * Return available translations names.
64 *
65 * @access public
66 * @return array A array of available translations.
67 */
68 function get_translations() {
69 $tr = array(
70 "auto" => "Detect automatically",
71 "ar_SA" => "العربيّة (Arabic)",
72 "bg_BG" => "Bulgarian",
73 "da_DA" => "Dansk",
74 "ca_CA" => "Català",
75 "cs_CZ" => "Česky",
76 "en_US" => "English",
77 "el_GR" => "Ελληνικά",
78 "es_ES" => "Español (España)",
79 "es_LA" => "Español",
80 "de_DE" => "Deutsch",
81 "fr_FR" => "Français",
82 "hu_HU" => "Magyar (Hungarian)",
83 "it_IT" => "Italiano",
84 "ja_JP" => "日本語 (Japanese)",
85 "lv_LV" => "Latviešu",
86 "nb_NO" => "Norwegian bokmål",
87 "nl_NL" => "Dutch",
88 "pl_PL" => "Polski",
89 "ru_RU" => "Русский",
90 "pt_BR" => "Portuguese/Brazil",
91 "pt_PT" => "Portuguese/Portugal",
92 "zh_CN" => "Simplified Chinese",
93 "zh_TW" => "Traditional Chinese",
94 "sv_SE" => "Svenska",
95 "fi_FI" => "Suomi",
96 "tr_TR" => "Türkçe");
97
98 return $tr;
99 }
100
101 require_once "lib/accept-to-gettext.php";
102 require_once "lib/gettext/gettext.inc";
103
104 function startup_gettext() {
105
106 # Get locale from Accept-Language header
107 $lang = al2gt(array_keys(get_translations()), "text/html");
108
109 if (defined('_TRANSLATION_OVERRIDE_DEFAULT')) {
110 $lang = _TRANSLATION_OVERRIDE_DEFAULT;
111 }
112
113 if ($_SESSION["uid"] && get_schema_version() >= 120) {
114 $pref_lang = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
115
116 if ($pref_lang && $pref_lang != 'auto') {
117 $lang = $pref_lang;
118 }
119 }
120
121 if ($lang) {
122 if (defined('LC_MESSAGES')) {
123 _setlocale(LC_MESSAGES, $lang);
124 } else if (defined('LC_ALL')) {
125 _setlocale(LC_ALL, $lang);
126 }
127
128 _bindtextdomain("messages", "locale");
129
130 _textdomain("messages");
131 _bind_textdomain_codeset("messages", "UTF-8");
132 }
133 }
134
135 require_once 'db-prefs.php';
136 require_once 'version.php';
137 require_once 'controls.php';
138
139 define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION . ' (http://tt-rss.org/)');
140 ini_set('user_agent', SELF_USER_AGENT);
141
142 require_once 'lib/pubsubhubbub/Publisher.php';
143
144 $schema_version = false;
145
146 function _debug_suppress($suppress) {
147 global $suppress_debugging;
148
149 $suppress_debugging = $suppress;
150 }
151
152 /**
153 * Print a timestamped debug message.
154 *
155 * @param string $msg The debug message.
156 * @return void
157 */
158 function _debug($msg, $show = true) {
159 global $suppress_debugging;
160
161 //echo "[$suppress_debugging] $msg $show\n";
162
163 if ($suppress_debugging) return false;
164
165 $ts = strftime("%H:%M:%S", time());
166 if (function_exists('posix_getpid')) {
167 $ts = "$ts/" . posix_getpid();
168 }
169
170 if ($show && !(defined('QUIET') && QUIET)) {
171 print "[$ts] $msg\n";
172 }
173
174 if (defined('LOGFILE')) {
175 $fp = fopen(LOGFILE, 'a+');
176
177 if ($fp) {
178 $locked = false;
179
180 if (function_exists("flock")) {
181 $tries = 0;
182
183 // try to lock logfile for writing
184 while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
185 sleep(1);
186 ++$tries;
187 }
188
189 if (!$locked) {
190 fclose($fp);
191 return;
192 }
193 }
194
195 fputs($fp, "[$ts] $msg\n");
196
197 if (function_exists("flock")) {
198 flock($fp, LOCK_UN);
199 }
200
201 fclose($fp);
202 }
203 }
204
205 } // function _debug
206
207 /**
208 * Purge a feed old posts.
209 *
210 * @param mixed $link A database connection.
211 * @param mixed $feed_id The id of the purged feed.
212 * @param mixed $purge_interval Olderness of purged posts.
213 * @param boolean $debug Set to True to enable the debug. False by default.
214 * @access public
215 * @return void
216 */
217 function purge_feed($feed_id, $purge_interval, $debug = false) {
218
219 if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
220
221 $rows = -1;
222
223 $result = db_query(
224 "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'");
225
226 $owner_uid = false;
227
228 if (db_num_rows($result) == 1) {
229 $owner_uid = db_fetch_result($result, 0, "owner_uid");
230 }
231
232 if ($purge_interval == -1 || !$purge_interval) {
233 if ($owner_uid) {
234 CCache::update($feed_id, $owner_uid);
235 }
236 return;
237 }
238
239 if (!$owner_uid) return;
240
241 if (FORCE_ARTICLE_PURGE == 0) {
242 $purge_unread = get_pref("PURGE_UNREAD_ARTICLES",
243 $owner_uid, false);
244 } else {
245 $purge_unread = true;
246 $purge_interval = FORCE_ARTICLE_PURGE;
247 }
248
249 if (!$purge_unread) $query_limit = " unread = false AND ";
250
251 if (DB_TYPE == "pgsql") {
252 $result = db_query("DELETE FROM ttrss_user_entries
253 USING ttrss_entries
254 WHERE ttrss_entries.id = ref_id AND
255 marked = false AND
256 feed_id = '$feed_id' AND
257 $query_limit
258 ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
259
260 } else {
261
262 /* $result = db_query("DELETE FROM ttrss_user_entries WHERE
263 marked = false AND feed_id = '$feed_id' AND
264 (SELECT date_updated FROM ttrss_entries WHERE
265 id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */
266
267 $result = db_query("DELETE FROM ttrss_user_entries
268 USING ttrss_user_entries, ttrss_entries
269 WHERE ttrss_entries.id = ref_id AND
270 marked = false AND
271 feed_id = '$feed_id' AND
272 $query_limit
273 ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
274 }
275
276 $rows = db_affected_rows($result);
277
278 CCache::update($feed_id, $owner_uid);
279
280 if ($debug) {
281 _debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
282 }
283
284 return $rows;
285 } // function purge_feed
286
287 function feed_purge_interval($feed_id) {
288
289 $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds
290 WHERE id = '$feed_id'");
291
292 if (db_num_rows($result) == 1) {
293 $purge_interval = db_fetch_result($result, 0, "purge_interval");
294 $owner_uid = db_fetch_result($result, 0, "owner_uid");
295
296 if ($purge_interval == 0) $purge_interval = get_pref(
297 'PURGE_OLD_DAYS', $owner_uid);
298
299 return $purge_interval;
300
301 } else {
302 return -1;
303 }
304 }
305
306 /*function get_feed_update_interval($feed_id) {
307 $result = db_query("SELECT owner_uid, update_interval FROM
308 ttrss_feeds WHERE id = '$feed_id'");
309
310 if (db_num_rows($result) == 1) {
311 $update_interval = db_fetch_result($result, 0, "update_interval");
312 $owner_uid = db_fetch_result($result, 0, "owner_uid");
313
314 if ($update_interval != 0) {
315 return $update_interval;
316 } else {
317 return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false);
318 }
319
320 } else {
321 return -1;
322 }
323 }*/
324
325 // TODO: multiple-argument way is deprecated, first parameter is a hash now
326 function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
327 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
328
329 global $fetch_last_error;
330 global $fetch_last_error_code;
331 global $fetch_last_error_content;
332 global $fetch_last_content_type;
333 global $fetch_curl_used;
334
335 $fetch_last_error = false;
336 $fetch_last_error_code = -1;
337 $fetch_last_error_content = "";
338 $fetch_last_content_type = "";
339 $fetch_curl_used = false;
340
341 if (!is_array($options)) {
342
343 // falling back on compatibility shim
344 $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
345 $tmp = [];
346
347 for ($i = 0; $i < func_num_args(); $i++) {
348 $tmp[$option_names[$i]] = func_get_arg($i);
349 }
350
351 $options = $tmp;
352
353 /*$options = array(
354 "url" => func_get_arg(0),
355 "type" => @func_get_arg(1),
356 "login" => @func_get_arg(2),
357 "pass" => @func_get_arg(3),
358 "post_query" => @func_get_arg(4),
359 "timeout" => @func_get_arg(5),
360 "timestamp" => @func_get_arg(6),
361 "useragent" => @func_get_arg(7)
362 ); */
363 }
364
365 $url = $options["url"];
366 $type = isset($options["type"]) ? $options["type"] : false;
367 $login = isset($options["login"]) ? $options["login"] : false;
368 $pass = isset($options["pass"]) ? $options["pass"] : false;
369 $post_query = isset($options["post_query"]) ? $options["post_query"] : false;
370 $timeout = isset($options["timeout"]) ? $options["timeout"] : false;
371 $timestamp = isset($options["timestamp"]) ? $options["timestamp"] : 0;
372 $useragent = isset($options["useragent"]) ? $options["useragent"] : false;
373 $followlocation = isset($options["followlocation"]) ? $options["followlocation"] : true;
374
375 $url = ltrim($url, ' ');
376 $url = str_replace(' ', '%20', $url);
377
378 if (strpos($url, "//") === 0)
379 $url = 'http:' . $url;
380
381 if (!defined('NO_CURL') && function_exists('curl_init') && !ini_get("open_basedir")) {
382
383 $fetch_curl_used = true;
384
385 $ch = curl_init($url);
386
387 if ($timestamp && !$post_query) {
388 curl_setopt($ch, CURLOPT_HTTPHEADER,
389 array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
390 }
391
392 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
393 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : FILE_FETCH_TIMEOUT);
394 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir") && $followlocation);
395 curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
396 curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
397 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
398 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
399 curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
400 SELF_USER_AGENT);
401 curl_setopt($ch, CURLOPT_ENCODING, "");
402 //curl_setopt($ch, CURLOPT_REFERER, $url);
403
404 if (!ini_get("open_basedir")) {
405 curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
406 }
407
408 if (defined('_CURL_HTTP_PROXY')) {
409 curl_setopt($ch, CURLOPT_PROXY, _CURL_HTTP_PROXY);
410 }
411
412 if ($post_query) {
413 curl_setopt($ch, CURLOPT_POST, true);
414 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
415 }
416
417 if ($login && $pass)
418 curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
419
420 $contents = @curl_exec($ch);
421
422 if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
423 curl_setopt($ch, CURLOPT_ENCODING, 'none');
424 $contents = @curl_exec($ch);
425 }
426
427 if ($contents === false) {
428 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
429 curl_close($ch);
430 return false;
431 }
432
433 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
434 $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
435
436 $fetch_last_error_code = $http_code;
437
438 if ($http_code != 200 || $type && strpos($fetch_last_content_type, "$type") === false) {
439 if (curl_errno($ch) != 0) {
440 $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
441 } else {
442 $fetch_last_error = "HTTP Code: $http_code";
443 }
444 $fetch_last_error_content = $contents;
445 curl_close($ch);
446 return false;
447 }
448
449 curl_close($ch);
450
451 return $contents;
452 } else {
453
454 $fetch_curl_used = false;
455
456 if ($login && $pass){
457 $url_parts = array();
458
459 preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
460
461 $pass = urlencode($pass);
462
463 if ($url_parts[1] && $url_parts[2]) {
464 $url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
465 }
466 }
467
468 // TODO: should this support POST requests or not? idk
469
470 if (!$post_query && $timestamp) {
471 $context = stream_context_create(array(
472 'http' => array(
473 'method' => 'GET',
474 'ignore_errors' => true,
475 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
476 'protocol_version'=> 1.1,
477 'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
478 )));
479 } else {
480 $context = stream_context_create(array(
481 'http' => array(
482 'method' => 'GET',
483 'ignore_errors' => true,
484 'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
485 'protocol_version'=> 1.1
486 )));
487 }
488
489 $old_error = error_get_last();
490
491 $data = @file_get_contents($url, false, $context);
492
493 if (isset($http_response_header) && is_array($http_response_header)) {
494 foreach ($http_response_header as $h) {
495 if (substr(strtolower($h), 0, 13) == 'content-type:') {
496 $fetch_last_content_type = substr($h, 14);
497 // don't abort here b/c there might be more than one
498 // e.g. if we were being redirected -- last one is the right one
499 }
500
501 if (substr(strtolower($h), 0, 7) == 'http/1.') {
502 $fetch_last_error_code = (int) substr($h, 9, 3);
503 }
504 }
505 }
506
507 if ($fetch_last_error_code != 200) {
508 $error = error_get_last();
509
510 if ($error['message'] != $old_error['message']) {
511 $fetch_last_error = $error["message"];
512 } else {
513 $fetch_last_error = "HTTP Code: $fetch_last_error_code";
514 }
515
516 $fetch_last_error_content = $data;
517
518 return false;
519 }
520 return $data;
521 }
522
523 }
524
525 /**
526 * Try to determine the favicon URL for a feed.
527 * adapted from wordpress favicon plugin by Jeff Minard (http://thecodepro.com/)
528 * http://dev.wp-plugins.org/file/favatars/trunk/favatars.php
529 *
530 * @param string $url A feed or page URL
531 * @access public
532 * @return mixed The favicon URL, or false if none was found.
533 */
534 function get_favicon_url($url) {
535
536 $favicon_url = false;
537
538 if ($html = @fetch_file_contents($url)) {
539
540 libxml_use_internal_errors(true);
541
542 $doc = new DOMDocument();
543 $doc->loadHTML($html);
544 $xpath = new DOMXPath($doc);
545
546 $base = $xpath->query('/html/head/base');
547 foreach ($base as $b) {
548 $url = $b->getAttribute("href");
549 break;
550 }
551
552 $entries = $xpath->query('/html/head/link[@rel="shortcut icon" or @rel="icon"]');
553 if (count($entries) > 0) {
554 foreach ($entries as $entry) {
555 $favicon_url = rewrite_relative_url($url, $entry->getAttribute("href"));
556 break;
557 }
558 }
559 }
560
561 if (!$favicon_url)
562 $favicon_url = rewrite_relative_url($url, "/favicon.ico");
563
564 return $favicon_url;
565 } // function get_favicon_url
566
567 function initialize_user_prefs($uid, $profile = false) {
568
569 $uid = db_escape_string($uid);
570
571 if (!$profile) {
572 $profile = "NULL";
573 $profile_qpart = "AND profile IS NULL";
574 } else {
575 $profile_qpart = "AND profile = '$profile'";
576 }
577
578 if (get_schema_version() < 63) $profile_qpart = "";
579
580 db_query("BEGIN");
581
582 $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs");
583
584 $u_result = db_query("SELECT pref_name
585 FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart");
586
587 $active_prefs = array();
588
589 while ($line = db_fetch_assoc($u_result)) {
590 array_push($active_prefs, $line["pref_name"]);
591 }
592
593 while ($line = db_fetch_assoc($result)) {
594 if (array_search($line["pref_name"], $active_prefs) === FALSE) {
595 // print "adding " . $line["pref_name"] . "<br>";
596
597 $line["def_value"] = db_escape_string($line["def_value"]);
598 $line["pref_name"] = db_escape_string($line["pref_name"]);
599
600 if (get_schema_version() < 63) {
601 db_query("INSERT INTO ttrss_user_prefs
602 (owner_uid,pref_name,value) VALUES
603 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')");
604
605 } else {
606 db_query("INSERT INTO ttrss_user_prefs
607 (owner_uid,pref_name,value, profile) VALUES
608 ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)");
609 }
610
611 }
612 }
613
614 db_query("COMMIT");
615
616 }
617
618 function get_ssl_certificate_id() {
619 if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"]) {
620 return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
621 $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
622 $_SERVER["REDIRECT_SSL_CLIENT_V_END"] .
623 $_SERVER["REDIRECT_SSL_CLIENT_S_DN"]);
624 }
625 if ($_SERVER["SSL_CLIENT_M_SERIAL"]) {
626 return sha1($_SERVER["SSL_CLIENT_M_SERIAL"] .
627 $_SERVER["SSL_CLIENT_V_START"] .
628 $_SERVER["SSL_CLIENT_V_END"] .
629 $_SERVER["SSL_CLIENT_S_DN"]);
630 }
631 return "";
632 }
633
634 function authenticate_user($login, $password, $check_only = false) {
635
636 if (!SINGLE_USER_MODE) {
637 $user_id = false;
638
639 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
640
641 $user_id = (int) $plugin->authenticate($login, $password);
642
643 if ($user_id) {
644 $_SESSION["auth_module"] = strtolower(get_class($plugin));
645 break;
646 }
647 }
648
649 if ($user_id && !$check_only) {
650 @session_start();
651
652 $_SESSION["uid"] = $user_id;
653 $_SESSION["version"] = VERSION_STATIC;
654
655 $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users
656 WHERE id = '$user_id'");
657
658 $_SESSION["name"] = db_fetch_result($result, 0, "login");
659 $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level");
660 $_SESSION["csrf_token"] = uniqid_short();
661
662 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
663 $_SESSION["uid"]);
664
665 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
666 $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']);
667 $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash");
668
669 $_SESSION["last_version_check"] = time();
670
671 initialize_user_prefs($_SESSION["uid"]);
672
673 return true;
674 }
675
676 return false;
677
678 } else {
679
680 $_SESSION["uid"] = 1;
681 $_SESSION["name"] = "admin";
682 $_SESSION["access_level"] = 10;
683
684 $_SESSION["hide_hello"] = true;
685 $_SESSION["hide_logout"] = true;
686
687 $_SESSION["auth_module"] = false;
688
689 if (!$_SESSION["csrf_token"]) {
690 $_SESSION["csrf_token"] = uniqid_short();
691 }
692
693 $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"];
694
695 initialize_user_prefs($_SESSION["uid"]);
696
697 return true;
698 }
699 }
700
701 function make_password($length = 8) {
702
703 $password = "";
704 $possible = "0123456789abcdfghjkmnpqrstvwxyzABCDFGHJKMNPQRSTVWXYZ";
705
706 $i = 0;
707
708 while ($i < $length) {
709 $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
710
711 if (!strstr($password, $char)) {
712 $password .= $char;
713 $i++;
714 }
715 }
716 return $password;
717 }
718
719 // this is called after user is created to initialize default feeds, labels
720 // or whatever else
721
722 // user preferences are checked on every login, not here
723
724 function initialize_user($uid) {
725
726 db_query("insert into ttrss_feeds (owner_uid,title,feed_url)
727 values ('$uid', 'Tiny Tiny RSS: Forum',
728 'http://tt-rss.org/forum/rss.php')");
729 }
730
731 function logout_user() {
732 session_destroy();
733 if (isset($_COOKIE[session_name()])) {
734 setcookie(session_name(), '', time()-42000, '/');
735 }
736 }
737
738 function validate_csrf($csrf_token) {
739 return $csrf_token == $_SESSION['csrf_token'];
740 }
741
742 function load_user_plugins($owner_uid, $pluginhost = false) {
743
744 if (!$pluginhost) $pluginhost = PluginHost::getInstance();
745
746 if ($owner_uid && SCHEMA_VERSION >= 100) {
747 $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
748
749 $pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
750
751 if (get_schema_version() > 100) {
752 $pluginhost->load_data();
753 }
754 }
755 }
756
757 function login_sequence() {
758 if (SINGLE_USER_MODE) {
759 @session_start();
760 authenticate_user("admin", null);
761 startup_gettext();
762 load_user_plugins($_SESSION["uid"]);
763 } else {
764 if (!validate_session()) $_SESSION["uid"] = false;
765
766 if (!$_SESSION["uid"]) {
767
768 if (AUTH_AUTO_LOGIN && authenticate_user(null, null)) {
769 $_SESSION["ref_schema_version"] = get_schema_version(true);
770 } else {
771 authenticate_user(null, null, true);
772 }
773
774 if (!$_SESSION["uid"]) {
775 @session_destroy();
776 setcookie(session_name(), '', time()-42000, '/');
777
778 render_login_form();
779 exit;
780 }
781
782 } else {
783 /* bump login timestamp */
784 db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " .
785 $_SESSION["uid"]);
786 $_SESSION["last_login_update"] = time();
787 }
788
789 if ($_SESSION["uid"]) {
790 startup_gettext();
791 load_user_plugins($_SESSION["uid"]);
792
793 /* cleanup ccache */
794
795 db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ".
796 $_SESSION["uid"] . " AND
797 (SELECT COUNT(id) FROM ttrss_feeds WHERE
798 ttrss_feeds.id = feed_id) = 0");
799
800 db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ".
801 $_SESSION["uid"] . " AND
802 (SELECT COUNT(id) FROM ttrss_feed_categories WHERE
803 ttrss_feed_categories.id = feed_id) = 0");
804
805 }
806
807 }
808 }
809
810 function truncate_string($str, $max_len, $suffix = '&hellip;') {
811 if (mb_strlen($str, "utf-8") > $max_len) {
812 return mb_substr($str, 0, $max_len, "utf-8") . $suffix;
813 } else {
814 return $str;
815 }
816 }
817
818 // is not utf8 clean
819 function truncate_middle($str, $max_len, $suffix = '&hellip;') {
820 if (strlen($str) > $max_len) {
821 return substr_replace($str, $suffix, $max_len / 2, mb_strlen($str) - $max_len);
822 } else {
823 return $str;
824 }
825 }
826
827 function convert_timestamp($timestamp, $source_tz, $dest_tz) {
828
829 try {
830 $source_tz = new DateTimeZone($source_tz);
831 } catch (Exception $e) {
832 $source_tz = new DateTimeZone('UTC');
833 }
834
835 try {
836 $dest_tz = new DateTimeZone($dest_tz);
837 } catch (Exception $e) {
838 $dest_tz = new DateTimeZone('UTC');
839 }
840
841 $dt = new DateTime(date('Y-m-d H:i:s', $timestamp), $source_tz);
842 return $dt->format('U') + $dest_tz->getOffset($dt);
843 }
844
845 function make_local_datetime($timestamp, $long, $owner_uid = false,
846 $no_smart_dt = false, $eta_min = false) {
847
848 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
849 if (!$timestamp) $timestamp = '1970-01-01 0:00';
850
851 global $utc_tz;
852 global $user_tz;
853
854 if (!$utc_tz) $utc_tz = new DateTimeZone('UTC');
855
856 $timestamp = substr($timestamp, 0, 19);
857
858 # We store date in UTC internally
859 $dt = new DateTime($timestamp, $utc_tz);
860
861 $user_tz_string = get_pref('USER_TIMEZONE', $owner_uid);
862
863 if ($user_tz_string != 'Automatic') {
864
865 try {
866 if (!$user_tz) $user_tz = new DateTimeZone($user_tz_string);
867 } catch (Exception $e) {
868 $user_tz = $utc_tz;
869 }
870
871 $tz_offset = $user_tz->getOffset($dt);
872 } else {
873 $tz_offset = (int) -$_SESSION["clientTzOffset"];
874 }
875
876 $user_timestamp = $dt->format('U') + $tz_offset;
877
878 if (!$no_smart_dt) {
879 return smart_date_time($user_timestamp,
880 $tz_offset, $owner_uid, $eta_min);
881 } else {
882 if ($long)
883 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
884 else
885 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
886
887 return date($format, $user_timestamp);
888 }
889 }
890
891 function smart_date_time($timestamp, $tz_offset = 0, $owner_uid = false, $eta_min = false) {
892 if (!$owner_uid) $owner_uid = $_SESSION['uid'];
893
894 if ($eta_min && time() + $tz_offset - $timestamp < 3600) {
895 return T_sprintf("%d min", date("i", time() + $tz_offset - $timestamp));
896 } else if (date("Y.m.d", $timestamp) == date("Y.m.d", time() + $tz_offset)) {
897 return date("G:i", $timestamp);
898 } else if (date("Y", $timestamp) == date("Y", time() + $tz_offset)) {
899 $format = get_pref('SHORT_DATE_FORMAT', $owner_uid);
900 return date($format, $timestamp);
901 } else {
902 $format = get_pref('LONG_DATE_FORMAT', $owner_uid);
903 return date($format, $timestamp);
904 }
905 }
906
907 function sql_bool_to_bool($s) {
908 if ($s == "t" || $s == "1" || strtolower($s) == "true") {
909 return true;
910 } else {
911 return false;
912 }
913 }
914
915 function bool_to_sql_bool($s) {
916 if ($s) {
917 return "true";
918 } else {
919 return "false";
920 }
921 }
922
923 // Session caching removed due to causing wrong redirects to upgrade
924 // script when get_schema_version() is called on an obsolete session
925 // created on a previous schema version.
926 function get_schema_version($nocache = false) {
927 global $schema_version;
928
929 if (!$schema_version && !$nocache) {
930 $result = db_query("SELECT schema_version FROM ttrss_version");
931 $version = db_fetch_result($result, 0, "schema_version");
932 $schema_version = $version;
933 return $version;
934 } else {
935 return $schema_version;
936 }
937 }
938
939 function sanity_check() {
940 require_once 'errors.php';
941 global $ERRORS;
942
943 $error_code = 0;
944 $schema_version = get_schema_version(true);
945
946 if ($schema_version != SCHEMA_VERSION) {
947 $error_code = 5;
948 }
949
950 if (DB_TYPE == "mysql") {
951 $result = db_query("SELECT true", false);
952 if (db_num_rows($result) != 1) {
953 $error_code = 10;
954 }
955 }
956
957 if (db_escape_string("testTEST") != "testTEST") {
958 $error_code = 12;
959 }
960
961 return array("code" => $error_code, "message" => $ERRORS[$error_code]);
962 }
963
964 function file_is_locked($filename) {
965 if (file_exists(LOCK_DIRECTORY . "/$filename")) {
966 if (function_exists('flock')) {
967 $fp = @fopen(LOCK_DIRECTORY . "/$filename", "r");
968 if ($fp) {
969 if (flock($fp, LOCK_EX | LOCK_NB)) {
970 flock($fp, LOCK_UN);
971 fclose($fp);
972 return false;
973 }
974 fclose($fp);
975 return true;
976 } else {
977 return false;
978 }
979 }
980 return true; // consider the file always locked and skip the test
981 } else {
982 return false;
983 }
984 }
985
986
987 function make_lockfile($filename) {
988 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
989
990 if ($fp && flock($fp, LOCK_EX | LOCK_NB)) {
991 $stat_h = fstat($fp);
992 $stat_f = stat(LOCK_DIRECTORY . "/$filename");
993
994 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
995 if ($stat_h["ino"] != $stat_f["ino"] ||
996 $stat_h["dev"] != $stat_f["dev"]) {
997
998 return false;
999 }
1000 }
1001
1002 if (function_exists('posix_getpid')) {
1003 fwrite($fp, posix_getpid() . "\n");
1004 }
1005 return $fp;
1006 } else {
1007 return false;
1008 }
1009 }
1010
1011 function make_stampfile($filename) {
1012 $fp = fopen(LOCK_DIRECTORY . "/$filename", "w");
1013
1014 if (flock($fp, LOCK_EX | LOCK_NB)) {
1015 fwrite($fp, time() . "\n");
1016 flock($fp, LOCK_UN);
1017 fclose($fp);
1018 return true;
1019 } else {
1020 return false;
1021 }
1022 }
1023
1024 function sql_random_function() {
1025 if (DB_TYPE == "mysql") {
1026 return "RAND()";
1027 } else {
1028 return "RANDOM()";
1029 }
1030 }
1031
1032 function getFeedUnread($feed, $is_cat = false) {
1033 return Feeds::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
1034 }
1035
1036
1037 /*function get_pgsql_version() {
1038 $result = db_query("SELECT version() AS version");
1039 $version = explode(" ", db_fetch_result($result, 0, "version"));
1040 return $version[1];
1041 }*/
1042
1043 function checkbox_to_sql_bool($val) {
1044 return ($val == "on") ? "true" : "false";
1045 }
1046
1047 /*function getFeedCatTitle($id) {
1048 if ($id == -1) {
1049 return __("Special");
1050 } else if ($id < LABEL_BASE_INDEX) {
1051 return __("Labels");
1052 } else if ($id > 0) {
1053 $result = db_query("SELECT ttrss_feed_categories.title
1054 FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND
1055 cat_id = ttrss_feed_categories.id");
1056 if (db_num_rows($result) == 1) {
1057 return db_fetch_result($result, 0, "title");
1058 } else {
1059 return __("Uncategorized");
1060 }
1061 } else {
1062 return "getFeedCatTitle($id) failed";
1063 }
1064
1065 }*/
1066
1067 function uniqid_short() {
1068 return uniqid(base_convert(rand(), 10, 36));
1069 }
1070
1071 function make_init_params() {
1072 $params = array();
1073
1074 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
1075 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
1076 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
1077 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
1078
1079 $params[strtolower($param)] = (int) get_pref($param);
1080 }
1081
1082 $params["icons_url"] = ICONS_URL;
1083 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
1084 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
1085 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
1086 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
1087 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
1088 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
1089
1090 $theme = get_pref( "USER_CSS_THEME", false, false);
1091 $params["theme"] = theme_valid("$theme") ? $theme : "";
1092
1093 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
1094
1095 $params["php_platform"] = PHP_OS;
1096 $params["php_version"] = PHP_VERSION;
1097
1098 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
1099
1100 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1101 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1102
1103 $max_feed_id = db_fetch_result($result, 0, "mid");
1104 $num_feeds = db_fetch_result($result, 0, "nf");
1105
1106 $params["max_feed_id"] = (int) $max_feed_id;
1107 $params["num_feeds"] = (int) $num_feeds;
1108
1109 $params["hotkeys"] = get_hotkeys_map();
1110
1111 $params["csrf_token"] = $_SESSION["csrf_token"];
1112 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
1113
1114 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
1115
1116 $params["icon_alert"] = base64_img("images/alert.png");
1117 $params["icon_information"] = base64_img("images/information.png");
1118 $params["icon_cross"] = base64_img("images/cross.png");
1119 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
1120
1121 return $params;
1122 }
1123
1124 function get_hotkeys_info() {
1125 $hotkeys = array(
1126 __("Navigation") => array(
1127 "next_feed" => __("Open next feed"),
1128 "prev_feed" => __("Open previous feed"),
1129 "next_article" => __("Open next article"),
1130 "prev_article" => __("Open previous article"),
1131 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
1132 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
1133 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
1134 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
1135 "search_dialog" => __("Show search dialog")),
1136 __("Article") => array(
1137 "toggle_mark" => __("Toggle starred"),
1138 "toggle_publ" => __("Toggle published"),
1139 "toggle_unread" => __("Toggle unread"),
1140 "edit_tags" => __("Edit tags"),
1141 "open_in_new_window" => __("Open in new window"),
1142 "catchup_below" => __("Mark below as read"),
1143 "catchup_above" => __("Mark above as read"),
1144 "article_scroll_down" => __("Scroll down"),
1145 "article_scroll_up" => __("Scroll up"),
1146 "select_article_cursor" => __("Select article under cursor"),
1147 "email_article" => __("Email article"),
1148 "close_article" => __("Close/collapse article"),
1149 "toggle_expand" => __("Toggle article expansion (combined mode)"),
1150 "toggle_widescreen" => __("Toggle widescreen mode"),
1151 "toggle_embed_original" => __("Toggle embed original")),
1152 __("Article selection") => array(
1153 "select_all" => __("Select all articles"),
1154 "select_unread" => __("Select unread"),
1155 "select_marked" => __("Select starred"),
1156 "select_published" => __("Select published"),
1157 "select_invert" => __("Invert selection"),
1158 "select_none" => __("Deselect everything")),
1159 __("Feed") => array(
1160 "feed_refresh" => __("Refresh current feed"),
1161 "feed_unhide_read" => __("Un/hide read feeds"),
1162 "feed_subscribe" => __("Subscribe to feed"),
1163 "feed_edit" => __("Edit feed"),
1164 "feed_catchup" => __("Mark as read"),
1165 "feed_reverse" => __("Reverse headlines"),
1166 "feed_toggle_vgroup" => __("Toggle headline grouping"),
1167 "feed_debug_update" => __("Debug feed update"),
1168 "feed_debug_viewfeed" => __("Debug viewfeed()"),
1169 "catchup_all" => __("Mark all feeds as read"),
1170 "cat_toggle_collapse" => __("Un/collapse current category"),
1171 "toggle_combined_mode" => __("Toggle combined mode"),
1172 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
1173 __("Go to") => array(
1174 "goto_all" => __("All articles"),
1175 "goto_fresh" => __("Fresh"),
1176 "goto_marked" => __("Starred"),
1177 "goto_published" => __("Published"),
1178 "goto_tagcloud" => __("Tag cloud"),
1179 "goto_prefs" => __("Preferences")),
1180 __("Other") => array(
1181 "create_label" => __("Create label"),
1182 "create_filter" => __("Create filter"),
1183 "collapse_sidebar" => __("Un/collapse sidebar"),
1184 "help_dialog" => __("Show help dialog"))
1185 );
1186
1187 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
1188 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
1189 }
1190
1191 return $hotkeys;
1192 }
1193
1194 function get_hotkeys_map() {
1195 $hotkeys = array(
1196 // "navigation" => array(
1197 "k" => "next_feed",
1198 "j" => "prev_feed",
1199 "n" => "next_article",
1200 "p" => "prev_article",
1201 "(38)|up" => "prev_article",
1202 "(40)|down" => "next_article",
1203 // "^(38)|Ctrl-up" => "prev_article_noscroll",
1204 // "^(40)|Ctrl-down" => "next_article_noscroll",
1205 "(191)|/" => "search_dialog",
1206 // "article" => array(
1207 "s" => "toggle_mark",
1208 "*s" => "toggle_publ",
1209 "u" => "toggle_unread",
1210 "*t" => "edit_tags",
1211 "o" => "open_in_new_window",
1212 "c p" => "catchup_below",
1213 "c n" => "catchup_above",
1214 "*n" => "article_scroll_down",
1215 "*p" => "article_scroll_up",
1216 "*(38)|Shift+up" => "article_scroll_up",
1217 "*(40)|Shift+down" => "article_scroll_down",
1218 "a *w" => "toggle_widescreen",
1219 "a e" => "toggle_embed_original",
1220 "e" => "email_article",
1221 "a q" => "close_article",
1222 // "article_selection" => array(
1223 "a a" => "select_all",
1224 "a u" => "select_unread",
1225 "a *u" => "select_marked",
1226 "a p" => "select_published",
1227 "a i" => "select_invert",
1228 "a n" => "select_none",
1229 // "feed" => array(
1230 "f r" => "feed_refresh",
1231 "f a" => "feed_unhide_read",
1232 "f s" => "feed_subscribe",
1233 "f e" => "feed_edit",
1234 "f q" => "feed_catchup",
1235 "f x" => "feed_reverse",
1236 "f g" => "feed_toggle_vgroup",
1237 "f *d" => "feed_debug_update",
1238 "f *g" => "feed_debug_viewfeed",
1239 "f *c" => "toggle_combined_mode",
1240 "f c" => "toggle_cdm_expanded",
1241 "*q" => "catchup_all",
1242 "x" => "cat_toggle_collapse",
1243 // "goto" => array(
1244 "g a" => "goto_all",
1245 "g f" => "goto_fresh",
1246 "g s" => "goto_marked",
1247 "g p" => "goto_published",
1248 "g t" => "goto_tagcloud",
1249 "g *p" => "goto_prefs",
1250 // "other" => array(
1251 "(9)|Tab" => "select_article_cursor", // tab
1252 "c l" => "create_label",
1253 "c f" => "create_filter",
1254 "c s" => "collapse_sidebar",
1255 "^(191)|Ctrl+/" => "help_dialog",
1256 );
1257
1258 if (get_pref('COMBINED_DISPLAY_MODE')) {
1259 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
1260 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
1261 }
1262
1263 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
1264 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
1265 }
1266
1267 $prefixes = array();
1268
1269 foreach (array_keys($hotkeys) as $hotkey) {
1270 $pair = explode(" ", $hotkey, 2);
1271
1272 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
1273 array_push($prefixes, $pair[0]);
1274 }
1275 }
1276
1277 return array($prefixes, $hotkeys);
1278 }
1279
1280 function check_for_update() {
1281 if (defined("GIT_VERSION_TIMESTAMP")) {
1282 $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
1283
1284 if ($content) {
1285 $content = json_decode($content, true);
1286
1287 if ($content && isset($content["changeset"])) {
1288 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
1289 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
1290
1291 return $content["changeset"]["id"];
1292 }
1293 }
1294 }
1295 }
1296
1297 return "";
1298 }
1299
1300 function make_runtime_info($disable_update_check = false) {
1301 $data = array();
1302
1303 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
1304 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
1305
1306 $max_feed_id = db_fetch_result($result, 0, "mid");
1307 $num_feeds = db_fetch_result($result, 0, "nf");
1308
1309 $data["max_feed_id"] = (int) $max_feed_id;
1310 $data["num_feeds"] = (int) $num_feeds;
1311
1312 $data['last_article_id'] = Article::getLastArticleId();
1313 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
1314
1315 $data['dep_ts'] = calculate_dep_timestamp();
1316 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
1317
1318
1319 if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
1320 $update_result = @check_for_update();
1321
1322 $data["update_result"] = $update_result;
1323
1324 $_SESSION["last_version_check"] = time();
1325 }
1326
1327 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
1328
1329 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
1330
1331 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
1332
1333 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
1334
1335 if ($stamp) {
1336 $stamp_delta = time() - $stamp;
1337
1338 if ($stamp_delta > 1800) {
1339 $stamp_check = 0;
1340 } else {
1341 $stamp_check = 1;
1342 $_SESSION["daemon_stamp_check"] = time();
1343 }
1344
1345 $data['daemon_stamp_ok'] = $stamp_check;
1346
1347 $stamp_fmt = date("Y.m.d, G:i", $stamp);
1348
1349 $data['daemon_stamp'] = $stamp_fmt;
1350 }
1351 }
1352 }
1353
1354 return $data;
1355 }
1356
1357 function search_to_sql($search, $search_language) {
1358
1359 $keywords = str_getcsv(trim($search), " ");
1360 $query_keywords = array();
1361 $search_words = array();
1362 $search_query_leftover = array();
1363
1364 if ($search_language)
1365 $search_language = db_escape_string(mb_strtolower($search_language));
1366 else
1367 $search_language = "english";
1368
1369 foreach ($keywords as $k) {
1370 if (strpos($k, "-") === 0) {
1371 $k = substr($k, 1);
1372 $not = "NOT";
1373 } else {
1374 $not = "";
1375 }
1376
1377 $commandpair = explode(":", mb_strtolower($k), 2);
1378
1379 switch ($commandpair[0]) {
1380 case "title":
1381 if ($commandpair[1]) {
1382 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
1383 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1384 } else {
1385 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1386 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1387 array_push($search_words, $k);
1388 }
1389 break;
1390 case "author":
1391 if ($commandpair[1]) {
1392 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
1393 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1394 } else {
1395 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1396 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1397 array_push($search_words, $k);
1398 }
1399 break;
1400 case "note":
1401 if ($commandpair[1]) {
1402 if ($commandpair[1] == "true")
1403 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
1404 else if ($commandpair[1] == "false")
1405 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
1406 else
1407 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
1408 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
1409 } else {
1410 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1411 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1412 if (!$not) array_push($search_words, $k);
1413 }
1414 break;
1415 case "star":
1416
1417 if ($commandpair[1]) {
1418 if ($commandpair[1] == "true")
1419 array_push($query_keywords, "($not (marked = true))");
1420 else
1421 array_push($query_keywords, "($not (marked = false))");
1422 } else {
1423 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1424 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1425 if (!$not) array_push($search_words, $k);
1426 }
1427 break;
1428 case "pub":
1429 if ($commandpair[1]) {
1430 if ($commandpair[1] == "true")
1431 array_push($query_keywords, "($not (published = true))");
1432 else
1433 array_push($query_keywords, "($not (published = false))");
1434
1435 } else {
1436 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1437 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1438 if (!$not) array_push($search_words, $k);
1439 }
1440 break;
1441 case "unread":
1442 if ($commandpair[1]) {
1443 if ($commandpair[1] == "true")
1444 array_push($query_keywords, "($not (unread = true))");
1445 else
1446 array_push($query_keywords, "($not (unread = false))");
1447
1448 } else {
1449 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1450 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1451 if (!$not) array_push($search_words, $k);
1452 }
1453 break;
1454 default:
1455 if (strpos($k, "@") === 0) {
1456
1457 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
1458 $orig_ts = strtotime(substr($k, 1));
1459 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
1460
1461 //$k = date("Y-m-d", strtotime(substr($k, 1)));
1462
1463 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
1464 } else {
1465
1466 if (DB_TYPE == "pgsql") {
1467 $k = mb_strtolower($k);
1468 array_push($search_query_leftover, $not ? "!$k" : $k);
1469 } else {
1470 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
1471 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
1472 }
1473
1474 if (!$not) array_push($search_words, $k);
1475 }
1476 }
1477 }
1478
1479 if (count($search_query_leftover) > 0) {
1480 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
1481
1482 if (DB_TYPE == "pgsql") {
1483 array_push($query_keywords,
1484 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
1485 }
1486
1487 }
1488
1489 $search_query_part = implode("AND", $query_keywords);
1490
1491 return array($search_query_part, $search_words);
1492 }
1493
1494 function iframe_whitelisted($entry) {
1495 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
1496
1497 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
1498
1499 if ($src) {
1500 foreach ($whitelist as $w) {
1501 if ($src == $w || $src == "www.$w")
1502 return true;
1503 }
1504 }
1505
1506 return false;
1507 }
1508
1509 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
1510 if (!$owner) $owner = $_SESSION["uid"];
1511
1512 $res = trim($str); if (!$res) return '';
1513
1514 $charset_hack = '<head>
1515 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
1516 </head>';
1517
1518 $res = trim($res); if (!$res) return '';
1519
1520 libxml_use_internal_errors(true);
1521
1522 $doc = new DOMDocument();
1523 $doc->loadHTML($charset_hack . $res);
1524 $xpath = new DOMXPath($doc);
1525
1526 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https';
1527 $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH;
1528
1529 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
1530
1531 foreach ($entries as $entry) {
1532
1533 if ($entry->hasAttribute('href')) {
1534 $entry->setAttribute('href',
1535 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
1536
1537 $entry->setAttribute('rel', 'noopener noreferrer');
1538 }
1539
1540 if ($entry->hasAttribute('src')) {
1541 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
1542 $cached_filename = CACHE_DIR . '/images/' . sha1($src);
1543
1544 if (file_exists($cached_filename)) {
1545
1546 // this is strictly cosmetic
1547 if ($entry->tagName == 'img') {
1548 $suffix = ".png";
1549 } else if ($entry->parentNode && $entry->parentNode->tagName == "video") {
1550 $suffix = ".mp4";
1551 } else if ($entry->parentNode && $entry->parentNode->tagName == "audio") {
1552 $suffix = ".ogg";
1553 } else {
1554 $suffix = "";
1555 }
1556
1557 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
1558
1559 if ($entry->hasAttribute('srcset')) {
1560 $entry->removeAttribute('srcset');
1561 }
1562
1563 if ($entry->hasAttribute('sizes')) {
1564 $entry->removeAttribute('sizes');
1565 }
1566 }
1567
1568 $entry->setAttribute('src', $src);
1569 }
1570
1571 if ($entry->nodeName == 'img') {
1572
1573 if ($entry->hasAttribute('src')) {
1574 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
1575
1576 if ($ttrss_uses_https && !$is_https_url) {
1577
1578 if ($entry->hasAttribute('srcset')) {
1579 $entry->removeAttribute('srcset');
1580 }
1581
1582 if ($entry->hasAttribute('sizes')) {
1583 $entry->removeAttribute('sizes');
1584 }
1585 }
1586 }
1587
1588 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
1589 $force_remove_images || $_SESSION["bw_limit"]) {
1590
1591 $p = $doc->createElement('p');
1592
1593 $a = $doc->createElement('a');
1594 $a->setAttribute('href', $entry->getAttribute('src'));
1595
1596 $a->appendChild(new DOMText($entry->getAttribute('src')));
1597 $a->setAttribute('target', '_blank');
1598 $a->setAttribute('rel', 'noopener noreferrer');
1599
1600 $p->appendChild($a);
1601
1602 $entry->parentNode->replaceChild($p, $entry);
1603 }
1604 }
1605
1606 if (strtolower($entry->nodeName) == "a") {
1607 $entry->setAttribute("target", "_blank");
1608 $entry->setAttribute("rel", "noopener noreferrer");
1609 }
1610 }
1611
1612 $entries = $xpath->query('//iframe');
1613 foreach ($entries as $entry) {
1614 if (!iframe_whitelisted($entry)) {
1615 $entry->setAttribute('sandbox', 'allow-scripts');
1616 } else {
1617 if ($_SERVER['HTTPS'] == "on") {
1618 $entry->setAttribute("src",
1619 str_replace("http://", "https://",
1620 $entry->getAttribute("src")));
1621 }
1622 }
1623 }
1624
1625 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
1626 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
1627 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
1628 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
1629 'dt', 'em', 'footer', 'figure', 'figcaption',
1630 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
1631 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1632 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1633 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1634 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
1635 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
1636
1637 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1638
1639 $disallowed_attributes = array('id', 'style', 'class');
1640
1641 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
1642 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1643 if (is_array($retval)) {
1644 $doc = $retval[0];
1645 $allowed_elements = $retval[1];
1646 $disallowed_attributes = $retval[2];
1647 } else {
1648 $doc = $retval;
1649 }
1650 }
1651
1652 $doc->removeChild($doc->firstChild); //remove doctype
1653 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1654
1655 if ($highlight_words) {
1656 foreach ($highlight_words as $word) {
1657
1658 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1659
1660 $elements = $xpath->query("//*/text()");
1661
1662 foreach ($elements as $child) {
1663
1664 $fragment = $doc->createDocumentFragment();
1665 $text = $child->textContent;
1666
1667 while (($pos = mb_stripos($text, $word)) !== false) {
1668 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1669 $word = mb_substr($text, $pos, mb_strlen($word));
1670 $highlight = $doc->createElement('span');
1671 $highlight->appendChild(new DomText($word));
1672 $highlight->setAttribute('class', 'highlight');
1673 $fragment->appendChild($highlight);
1674 $text = mb_substr($text, $pos + mb_strlen($word));
1675 }
1676
1677 if (!empty($text)) $fragment->appendChild(new DomText($text));
1678
1679 $child->parentNode->replaceChild($fragment, $child);
1680 }
1681 }
1682 }
1683
1684 $res = $doc->saveHTML();
1685
1686 /* strip everything outside of <body>...</body> */
1687
1688 $res_frag = array();
1689 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1690 return $res_frag[1];
1691 } else {
1692 return $res;
1693 }
1694 }
1695
1696 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1697 $xpath = new DOMXPath($doc);
1698 $entries = $xpath->query('//*');
1699
1700 foreach ($entries as $entry) {
1701 if (!in_array($entry->nodeName, $allowed_elements)) {
1702 $entry->parentNode->removeChild($entry);
1703 }
1704
1705 if ($entry->hasAttributes()) {
1706 $attrs_to_remove = array();
1707
1708 foreach ($entry->attributes as $attr) {
1709
1710 if (strpos($attr->nodeName, 'on') === 0) {
1711 array_push($attrs_to_remove, $attr);
1712 }
1713
1714 if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1715 array_push($attrs_to_remove, $attr);
1716 }
1717
1718 if (in_array($attr->nodeName, $disallowed_attributes)) {
1719 array_push($attrs_to_remove, $attr);
1720 }
1721 }
1722
1723 foreach ($attrs_to_remove as $attr) {
1724 $entry->removeAttributeNode($attr);
1725 }
1726 }
1727 }
1728
1729 return $doc;
1730 }
1731
1732 function trim_array($array) {
1733 $tmp = $array;
1734 array_walk($tmp, 'trim');
1735 return $tmp;
1736 }
1737
1738 function tag_is_valid($tag) {
1739 if ($tag == '') return false;
1740 if (is_numeric($tag)) return false;
1741 if (mb_strlen($tag) > 250) return false;
1742
1743 if (!$tag) return false;
1744
1745 return true;
1746 }
1747
1748 function render_login_form() {
1749 header('Cache-Control: public');
1750
1751 require_once "login_form.php";
1752 exit;
1753 }
1754
1755 function T_sprintf() {
1756 $args = func_get_args();
1757 return vsprintf(__(array_shift($args)), $args);
1758 }
1759
1760 function print_checkpoint($n, $s) {
1761 $ts = microtime(true);
1762 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1763 return $ts;
1764 }
1765
1766 function sanitize_tag($tag) {
1767 $tag = trim($tag);
1768
1769 $tag = mb_strtolower($tag, 'utf-8');
1770
1771 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1772
1773 if (DB_TYPE == "mysql") {
1774 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1775 }
1776
1777 return $tag;
1778 }
1779
1780 function get_self_url_prefix() {
1781 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1782 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1783 } else {
1784 return SELF_URL_PATH;
1785 }
1786 }
1787
1788 /**
1789 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1790 *
1791 * @return string The Mozilla Firefox feed adding URL.
1792 */
1793 function add_feed_url() {
1794 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1795
1796 $url_path = get_self_url_prefix() .
1797 "/public.php?op=subscribe&feed_url=%s";
1798 return $url_path;
1799 } // function add_feed_url
1800
1801 function encrypt_password($pass, $salt = '', $mode2 = false) {
1802 if ($salt && $mode2) {
1803 return "MODE2:" . hash('sha256', $salt . $pass);
1804 } else if ($salt) {
1805 return "SHA1X:" . sha1("$salt:$pass");
1806 } else {
1807 return "SHA1:" . sha1($pass);
1808 }
1809 } // function encrypt_password
1810
1811 function load_filters($feed_id, $owner_uid) {
1812 $filters = array();
1813
1814 $cat_id = (int)Feeds::getFeedCategory($feed_id);
1815
1816 if ($cat_id == 0)
1817 $null_cat_qpart = "cat_id IS NULL OR";
1818 else
1819 $null_cat_qpart = "";
1820
1821 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1822 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1823
1824 $check_cats = join(",", array_merge(
1825 Feeds::getParentCategories($cat_id, $owner_uid),
1826 array($cat_id)));
1827
1828 while ($line = db_fetch_assoc($result)) {
1829 $filter_id = $line["id"];
1830
1831 $result2 = db_query("SELECT
1832 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1833 FROM ttrss_filters2_rules AS r,
1834 ttrss_filter_types AS t
1835 WHERE
1836 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1837 (feed_id IS NULL OR feed_id = '$feed_id') AND
1838 filter_type = t.id AND filter_id = '$filter_id'");
1839
1840 $rules = array();
1841 $actions = array();
1842
1843 while ($rule_line = db_fetch_assoc($result2)) {
1844 # print_r($rule_line);
1845
1846 $rule = array();
1847 $rule["reg_exp"] = $rule_line["reg_exp"];
1848 $rule["type"] = $rule_line["type_name"];
1849 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1850
1851 array_push($rules, $rule);
1852 }
1853
1854 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1855 FROM ttrss_filters2_actions AS a,
1856 ttrss_filter_actions AS t
1857 WHERE
1858 action_id = t.id AND filter_id = '$filter_id'");
1859
1860 while ($action_line = db_fetch_assoc($result2)) {
1861 # print_r($action_line);
1862
1863 $action = array();
1864 $action["type"] = $action_line["type_name"];
1865 $action["param"] = $action_line["action_param"];
1866
1867 array_push($actions, $action);
1868 }
1869
1870
1871 $filter = array();
1872 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1873 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1874 $filter["rules"] = $rules;
1875 $filter["actions"] = $actions;
1876
1877 if (count($rules) > 0 && count($actions) > 0) {
1878 array_push($filters, $filter);
1879 }
1880 }
1881
1882 return $filters;
1883 }
1884
1885 function get_score_pic($score) {
1886 if ($score > 100) {
1887 return "score_high.png";
1888 } else if ($score > 0) {
1889 return "score_half_high.png";
1890 } else if ($score < -100) {
1891 return "score_low.png";
1892 } else if ($score < 0) {
1893 return "score_half_low.png";
1894 } else {
1895 return "score_neutral.png";
1896 }
1897 }
1898
1899 function feed_has_icon($id) {
1900 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1901 }
1902
1903 function init_plugins() {
1904 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1905
1906 return true;
1907 }
1908
1909 function add_feed_category($feed_cat, $parent_cat_id = false) {
1910
1911 if (!$feed_cat) return false;
1912
1913 db_query("BEGIN");
1914
1915 if ($parent_cat_id) {
1916 $parent_qpart = "parent_cat = '$parent_cat_id'";
1917 $parent_insert = "'$parent_cat_id'";
1918 } else {
1919 $parent_qpart = "parent_cat IS NULL";
1920 $parent_insert = "NULL";
1921 }
1922
1923 $feed_cat = mb_substr($feed_cat, 0, 250);
1924
1925 $result = db_query(
1926 "SELECT id FROM ttrss_feed_categories
1927 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1928
1929 if (db_num_rows($result) == 0) {
1930
1931 $result = db_query(
1932 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1933 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1934
1935 db_query("COMMIT");
1936
1937 return true;
1938 }
1939
1940 return false;
1941 }
1942
1943 /**
1944 * Fixes incomplete URLs by prepending "http://".
1945 * Also replaces feed:// with http://, and
1946 * prepends a trailing slash if the url is a domain name only.
1947 *
1948 * @param string $url Possibly incomplete URL
1949 *
1950 * @return string Fixed URL.
1951 */
1952 function fix_url($url) {
1953
1954 // support schema-less urls
1955 if (strpos($url, '//') === 0) {
1956 $url = 'https:' . $url;
1957 }
1958
1959 if (strpos($url, '://') === false) {
1960 $url = 'http://' . $url;
1961 } else if (substr($url, 0, 5) == 'feed:') {
1962 $url = 'http:' . substr($url, 5);
1963 }
1964
1965 //prepend slash if the URL has no slash in it
1966 // "http://www.example" -> "http://www.example/"
1967 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1968 $url .= '/';
1969 }
1970
1971 //convert IDNA hostname to punycode if possible
1972 if (function_exists("idn_to_ascii")) {
1973 $parts = parse_url($url);
1974 if (mb_detect_encoding($parts['host']) != 'ASCII')
1975 {
1976 $parts['host'] = idn_to_ascii($parts['host']);
1977 $url = build_url($parts);
1978 }
1979 }
1980
1981 if ($url != "http:///")
1982 return $url;
1983 else
1984 return '';
1985 }
1986
1987 function validate_feed_url($url) {
1988 $parts = parse_url($url);
1989
1990 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1991
1992 }
1993
1994 /* function save_email_address($email) {
1995 // FIXME: implement persistent storage of emails
1996
1997 if (!$_SESSION['stored_emails'])
1998 $_SESSION['stored_emails'] = array();
1999
2000 if (!in_array($email, $_SESSION['stored_emails']))
2001 array_push($_SESSION['stored_emails'], $email);
2002 } */
2003
2004
2005 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
2006
2007 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
2008
2009 $sql_is_cat = bool_to_sql_bool($is_cat);
2010
2011 $result = db_query("SELECT access_key FROM ttrss_access_keys
2012 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
2013 AND owner_uid = " . $owner_uid);
2014
2015 if (db_num_rows($result) == 1) {
2016 return db_fetch_result($result, 0, "access_key");
2017 } else {
2018 $key = db_escape_string(uniqid_short());
2019
2020 $result = db_query("INSERT INTO ttrss_access_keys
2021 (access_key, feed_id, is_cat, owner_uid)
2022 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
2023
2024 return $key;
2025 }
2026 return false;
2027 }
2028
2029 function get_feeds_from_html($url, $content)
2030 {
2031 $url = fix_url($url);
2032 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
2033
2034 libxml_use_internal_errors(true);
2035
2036 $doc = new DOMDocument();
2037 $doc->loadHTML($content);
2038 $xpath = new DOMXPath($doc);
2039 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
2040 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
2041 $feedUrls = array();
2042 foreach ($entries as $entry) {
2043 if ($entry->hasAttribute('href')) {
2044 $title = $entry->getAttribute('title');
2045 if ($title == '') {
2046 $title = $entry->getAttribute('type');
2047 }
2048 $feedUrl = rewrite_relative_url(
2049 $baseUrl, $entry->getAttribute('href')
2050 );
2051 $feedUrls[$feedUrl] = $title;
2052 }
2053 }
2054 return $feedUrls;
2055 }
2056
2057 function is_html($content) {
2058 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
2059 }
2060
2061 function url_is_html($url, $login = false, $pass = false) {
2062 return is_html(fetch_file_contents($url, false, $login, $pass));
2063 }
2064
2065 function build_url($parts) {
2066 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2067 }
2068
2069 function cleanup_url_path($path) {
2070 $path = str_replace("/./", "/", $path);
2071 $path = str_replace("//", "/", $path);
2072
2073 return $path;
2074 }
2075
2076 /**
2077 * Converts a (possibly) relative URL to a absolute one.
2078 *
2079 * @param string $url Base URL (i.e. from where the document is)
2080 * @param string $rel_url Possibly relative URL in the document
2081 *
2082 * @return string Absolute URL
2083 */
2084 function rewrite_relative_url($url, $rel_url) {
2085 if (strpos($rel_url, "://") !== false) {
2086 return $rel_url;
2087 } else if (strpos($rel_url, "//") === 0) {
2088 # protocol-relative URL (rare but they exist)
2089 return $rel_url;
2090 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2091 # magnet:, feed:, etc
2092 return $rel_url;
2093 } else if (strpos($rel_url, "/") === 0) {
2094 $parts = parse_url($url);
2095 $parts['path'] = $rel_url;
2096 $parts['path'] = cleanup_url_path($parts['path']);
2097
2098 return build_url($parts);
2099
2100 } else {
2101 $parts = parse_url($url);
2102 if (!isset($parts['path'])) {
2103 $parts['path'] = '/';
2104 }
2105 $dir = $parts['path'];
2106 if (substr($dir, -1) !== '/') {
2107 $dir = dirname($parts['path']);
2108 $dir !== '/' && $dir .= '/';
2109 }
2110 $parts['path'] = $dir . $rel_url;
2111 $parts['path'] = cleanup_url_path($parts['path']);
2112
2113 return build_url($parts);
2114 }
2115 }
2116
2117 function cleanup_tags($days = 14, $limit = 1000) {
2118
2119 if (DB_TYPE == "pgsql") {
2120 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2121 } else if (DB_TYPE == "mysql") {
2122 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2123 }
2124
2125 $tags_deleted = 0;
2126
2127 while ($limit > 0) {
2128 $limit_part = 500;
2129
2130 $query = "SELECT ttrss_tags.id AS id
2131 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2132 WHERE post_int_id = int_id AND $interval_query AND
2133 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2134
2135 $result = db_query($query);
2136
2137 $ids = array();
2138
2139 while ($line = db_fetch_assoc($result)) {
2140 array_push($ids, $line['id']);
2141 }
2142
2143 if (count($ids) > 0) {
2144 $ids = join(",", $ids);
2145
2146 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2147 $tags_deleted += db_affected_rows($tmp_result);
2148 } else {
2149 break;
2150 }
2151
2152 $limit -= $limit_part;
2153 }
2154
2155 return $tags_deleted;
2156 }
2157
2158 function print_user_stylesheet() {
2159 $value = get_pref('USER_STYLESHEET');
2160
2161 if ($value) {
2162 print "<style type=\"text/css\">";
2163 print str_replace("<br/>", "\n", $value);
2164 print "</style>";
2165 }
2166
2167 }
2168
2169 function filter_to_sql($filter, $owner_uid) {
2170 $query = array();
2171
2172 if (DB_TYPE == "pgsql")
2173 $reg_qpart = "~";
2174 else
2175 $reg_qpart = "REGEXP";
2176
2177 foreach ($filter["rules"] AS $rule) {
2178 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2179 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2180 $rule['reg_exp']) !== FALSE;
2181
2182 if ($regexp_valid) {
2183
2184 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2185
2186 switch ($rule["type"]) {
2187 case "title":
2188 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2189 $rule['reg_exp'] . "')";
2190 break;
2191 case "content":
2192 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2193 $rule['reg_exp'] . "')";
2194 break;
2195 case "both":
2196 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2197 $rule['reg_exp'] . "') OR LOWER(" .
2198 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2199 break;
2200 case "tag":
2201 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2202 $rule['reg_exp'] . "')";
2203 break;
2204 case "link":
2205 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2206 $rule['reg_exp'] . "')";
2207 break;
2208 case "author":
2209 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2210 $rule['reg_exp'] . "')";
2211 break;
2212 }
2213
2214 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2215
2216 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2217 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2218 }
2219
2220 if (isset($rule["cat_id"])) {
2221
2222 if ($rule["cat_id"] > 0) {
2223 $children = Feeds::getChildCategories($rule["cat_id"], $owner_uid);
2224 array_push($children, $rule["cat_id"]);
2225
2226 $children = join(",", $children);
2227
2228 $cat_qpart = "cat_id IN ($children)";
2229 } else {
2230 $cat_qpart = "cat_id IS NULL";
2231 }
2232
2233 $qpart .= " AND $cat_qpart";
2234 }
2235
2236 $qpart .= " AND feed_id IS NOT NULL";
2237
2238 array_push($query, "($qpart)");
2239
2240 }
2241 }
2242
2243 if (count($query) > 0) {
2244 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2245 } else {
2246 $fullquery = "(false)";
2247 }
2248
2249 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2250
2251 return $fullquery;
2252 }
2253
2254 if (!function_exists('gzdecode')) {
2255 function gzdecode($string) { // no support for 2nd argument
2256 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2257 base64_encode($string));
2258 }
2259 }
2260
2261 function get_random_bytes($length) {
2262 if (function_exists('openssl_random_pseudo_bytes')) {
2263 return openssl_random_pseudo_bytes($length);
2264 } else {
2265 $output = "";
2266
2267 for ($i = 0; $i < $length; $i++)
2268 $output .= chr(mt_rand(0, 255));
2269
2270 return $output;
2271 }
2272 }
2273
2274 function read_stdin() {
2275 $fp = fopen("php://stdin", "r");
2276
2277 if ($fp) {
2278 $line = trim(fgets($fp));
2279 fclose($fp);
2280 return $line;
2281 }
2282
2283 return null;
2284 }
2285
2286 function implements_interface($class, $interface) {
2287 return in_array($interface, class_implements($class));
2288 }
2289
2290 function get_minified_js($files) {
2291 require_once 'lib/jshrink/Minifier.php';
2292
2293 $rv = '';
2294
2295 foreach ($files as $js) {
2296 if (!isset($_GET['debug'])) {
2297 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2298
2299 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2300
2301 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2302
2303 if ($header && $contents) {
2304 list($htag, $hversion) = explode(":", $header);
2305
2306 if ($htag == "tt-rss" && $hversion == VERSION) {
2307 $rv .= $contents;
2308 continue;
2309 }
2310 }
2311 }
2312
2313 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2314 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2315 $rv .= $minified;
2316
2317 } else {
2318 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2319 }
2320 }
2321
2322 return $rv;
2323 }
2324
2325 function calculate_dep_timestamp() {
2326 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2327
2328 $max_ts = -1;
2329
2330 foreach ($files as $file) {
2331 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2332 }
2333
2334 return $max_ts;
2335 }
2336
2337 function T_js_decl($s1, $s2) {
2338 if ($s1 && $s2) {
2339 $s1 = preg_replace("/\n/", "", $s1);
2340 $s2 = preg_replace("/\n/", "", $s2);
2341
2342 $s1 = preg_replace("/\"/", "\\\"", $s1);
2343 $s2 = preg_replace("/\"/", "\\\"", $s2);
2344
2345 return "T_messages[\"$s1\"] = \"$s2\";\n";
2346 }
2347 }
2348
2349 function init_js_translations() {
2350
2351 print 'var T_messages = new Object();
2352
2353 function __(msg) {
2354 if (T_messages[msg]) {
2355 return T_messages[msg];
2356 } else {
2357 return msg;
2358 }
2359 }
2360
2361 function ngettext(msg1, msg2, n) {
2362 return __((parseInt(n) > 1) ? msg2 : msg1);
2363 }';
2364
2365 $l10n = _get_reader();
2366
2367 for ($i = 0; $i < $l10n->total; $i++) {
2368 $orig = $l10n->get_original_string($i);
2369 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2370 $key = explode(chr(0), $orig);
2371 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2372 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2373 } else {
2374 $translation = __($orig);
2375 print T_js_decl($orig, $translation);
2376 }
2377 }
2378 }
2379
2380 function get_theme_path($theme) {
2381 $check = "themes/$theme";
2382 if (file_exists($check)) return $check;
2383
2384 $check = "themes.local/$theme";
2385 if (file_exists($check)) return $check;
2386 }
2387
2388 function theme_valid($theme) {
2389 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2390
2391 if (in_array($theme, $bundled_themes)) return true;
2392
2393 $file = "themes/" . basename($theme);
2394
2395 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2396
2397 if (file_exists($file) && is_readable($file)) {
2398 $fh = fopen($file, "r");
2399
2400 if ($fh) {
2401 $header = fgets($fh);
2402 fclose($fh);
2403
2404 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2405 }
2406 }
2407
2408 return false;
2409 }
2410
2411 /**
2412 * @SuppressWarnings(unused)
2413 */
2414 function error_json($code) {
2415 require_once "errors.php";
2416
2417 @$message = $ERRORS[$code];
2418
2419 return json_encode(array("error" =>
2420 array("code" => $code, "message" => $message)));
2421
2422 }
2423
2424 /*function abs_to_rel_path($dir) {
2425 $tmp = str_replace(dirname(__DIR__), "", $dir);
2426
2427 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2428
2429 return $tmp;
2430 }*/
2431
2432 function get_upload_error_message($code) {
2433
2434 $errors = array(
2435 0 => __('There is no error, the file uploaded with success'),
2436 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2437 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2438 3 => __('The uploaded file was only partially uploaded'),
2439 4 => __('No file was uploaded'),
2440 6 => __('Missing a temporary folder'),
2441 7 => __('Failed to write file to disk.'),
2442 8 => __('A PHP extension stopped the file upload.'),
2443 );
2444
2445 return $errors[$code];
2446 }
2447
2448 function base64_img($filename) {
2449 if (file_exists($filename)) {
2450 $ext = pathinfo($filename, PATHINFO_EXTENSION);
2451
2452 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));
2453 } else {
2454 return "";
2455 }
2456 }
2457