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