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