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