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