]> git.wh0rd.org - tt-rss.git/blame - include/functions2.php
fix various issues reported by static analysis
[tt-rss.git] / include / functions2.php
CommitLineData
97b7d5c0
AD
1<?php
2 function make_init_params() {
3 $params = array();
4
5 foreach (array("ON_CATCHUP_SHOW_NEXT_FEED", "HIDE_READ_FEEDS",
6 "ENABLE_FEED_CATS", "FEEDS_SORT_BY_UNREAD", "CONFIRM_FEED_CATCHUP",
7 "CDM_AUTO_CATCHUP", "FRESH_ARTICLE_MAX_AGE",
8 "HIDE_READ_SHOWS_SPECIAL", "COMBINED_DISPLAY_MODE") as $param) {
9
10 $params[strtolower($param)] = (int) get_pref($param);
11 }
12
13 $params["icons_url"] = ICONS_URL;
14 $params["cookie_lifetime"] = SESSION_COOKIE_LIFETIME;
15 $params["default_view_mode"] = get_pref("_DEFAULT_VIEW_MODE");
16 $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT");
17 $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY");
18 $params["bw_limit"] = (int) $_SESSION["bw_limit"];
19 $params["label_base_index"] = (int) LABEL_BASE_INDEX;
f6cbe9a5
AD
20
21 $theme = get_pref( "USER_CSS_THEME", false, false);
22 $params["theme"] = theme_valid("$theme") ? $theme : "";
23
84e36b61 24 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
97b7d5c0 25
31460f84
AD
26 $params["php_platform"] = PHP_OS;
27 $params["php_version"] = PHP_VERSION;
28
29 $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php"));
30
97b7d5c0
AD
31 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
32 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
33
34 $max_feed_id = db_fetch_result($result, 0, "mid");
35 $num_feeds = db_fetch_result($result, 0, "nf");
36
37 $params["max_feed_id"] = (int) $max_feed_id;
38 $params["num_feeds"] = (int) $num_feeds;
39
40 $params["hotkeys"] = get_hotkeys_map();
41
42 $params["csrf_token"] = $_SESSION["csrf_token"];
43 $params["widescreen"] = (int) $_COOKIE["ttrss_widescreen"];
44
45 $params['simple_update'] = defined('SIMPLE_UPDATE_MODE') && SIMPLE_UPDATE_MODE;
46
70c0a8c2
AD
47 $params["icon_alert"] = base64_img("images/alert.png");
48 $params["icon_information"] = base64_img("images/information.png");
49 $params["icon_cross"] = base64_img("images/cross.png");
50 $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
51
97b7d5c0
AD
52 return $params;
53 }
54
55 function get_hotkeys_info() {
56 $hotkeys = array(
57 __("Navigation") => array(
58 "next_feed" => __("Open next feed"),
59 "prev_feed" => __("Open previous feed"),
60 "next_article" => __("Open next article"),
61 "prev_article" => __("Open previous article"),
62 "next_article_noscroll" => __("Open next article (don't scroll long articles)"),
63 "prev_article_noscroll" => __("Open previous article (don't scroll long articles)"),
64 "next_article_noexpand" => __("Move to next article (don't expand or mark read)"),
65 "prev_article_noexpand" => __("Move to previous article (don't expand or mark read)"),
66 "search_dialog" => __("Show search dialog")),
67 __("Article") => array(
68 "toggle_mark" => __("Toggle starred"),
69 "toggle_publ" => __("Toggle published"),
70 "toggle_unread" => __("Toggle unread"),
71 "edit_tags" => __("Edit tags"),
97b7d5c0
AD
72 "open_in_new_window" => __("Open in new window"),
73 "catchup_below" => __("Mark below as read"),
74 "catchup_above" => __("Mark above as read"),
75 "article_scroll_down" => __("Scroll down"),
76 "article_scroll_up" => __("Scroll up"),
77 "select_article_cursor" => __("Select article under cursor"),
78 "email_article" => __("Email article"),
79 "close_article" => __("Close/collapse article"),
80 "toggle_expand" => __("Toggle article expansion (combined mode)"),
81 "toggle_widescreen" => __("Toggle widescreen mode"),
82 "toggle_embed_original" => __("Toggle embed original")),
83 __("Article selection") => array(
84 "select_all" => __("Select all articles"),
85 "select_unread" => __("Select unread"),
86 "select_marked" => __("Select starred"),
87 "select_published" => __("Select published"),
88 "select_invert" => __("Invert selection"),
89 "select_none" => __("Deselect everything")),
90 __("Feed") => array(
91 "feed_refresh" => __("Refresh current feed"),
92 "feed_unhide_read" => __("Un/hide read feeds"),
93 "feed_subscribe" => __("Subscribe to feed"),
94 "feed_edit" => __("Edit feed"),
95 "feed_catchup" => __("Mark as read"),
96 "feed_reverse" => __("Reverse headlines"),
64c24ecb 97 "feed_toggle_vgroup" => __("Toggle headline grouping"),
97b7d5c0 98 "feed_debug_update" => __("Debug feed update"),
6b0a17ad 99 "feed_debug_viewfeed" => __("Debug viewfeed()"),
97b7d5c0
AD
100 "catchup_all" => __("Mark all feeds as read"),
101 "cat_toggle_collapse" => __("Un/collapse current category"),
102 "toggle_combined_mode" => __("Toggle combined mode"),
103 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
104 __("Go to") => array(
105 "goto_all" => __("All articles"),
106 "goto_fresh" => __("Fresh"),
107 "goto_marked" => __("Starred"),
108 "goto_published" => __("Published"),
109 "goto_tagcloud" => __("Tag cloud"),
110 "goto_prefs" => __("Preferences")),
111 __("Other") => array(
112 "create_label" => __("Create label"),
113 "create_filter" => __("Create filter"),
114 "collapse_sidebar" => __("Un/collapse sidebar"),
115 "help_dialog" => __("Show help dialog"))
116 );
117
118 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
119 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
120 }
121
122 return $hotkeys;
123 }
124
125 function get_hotkeys_map() {
126 $hotkeys = array(
127// "navigation" => array(
128 "k" => "next_feed",
129 "j" => "prev_feed",
130 "n" => "next_article",
131 "p" => "prev_article",
132 "(38)|up" => "prev_article",
133 "(40)|down" => "next_article",
134// "^(38)|Ctrl-up" => "prev_article_noscroll",
135// "^(40)|Ctrl-down" => "next_article_noscroll",
136 "(191)|/" => "search_dialog",
137// "article" => array(
138 "s" => "toggle_mark",
139 "*s" => "toggle_publ",
140 "u" => "toggle_unread",
141 "*t" => "edit_tags",
97b7d5c0
AD
142 "o" => "open_in_new_window",
143 "c p" => "catchup_below",
144 "c n" => "catchup_above",
145 "*n" => "article_scroll_down",
146 "*p" => "article_scroll_up",
147 "*(38)|Shift+up" => "article_scroll_up",
148 "*(40)|Shift+down" => "article_scroll_down",
149 "a *w" => "toggle_widescreen",
150 "a e" => "toggle_embed_original",
151 "e" => "email_article",
152 "a q" => "close_article",
153// "article_selection" => array(
154 "a a" => "select_all",
155 "a u" => "select_unread",
156 "a *u" => "select_marked",
157 "a p" => "select_published",
158 "a i" => "select_invert",
159 "a n" => "select_none",
160// "feed" => array(
161 "f r" => "feed_refresh",
162 "f a" => "feed_unhide_read",
163 "f s" => "feed_subscribe",
164 "f e" => "feed_edit",
165 "f q" => "feed_catchup",
166 "f x" => "feed_reverse",
64c24ecb 167 "f g" => "feed_toggle_vgroup",
97b7d5c0 168 "f *d" => "feed_debug_update",
6b0a17ad 169 "f *g" => "feed_debug_viewfeed",
97b7d5c0
AD
170 "f *c" => "toggle_combined_mode",
171 "f c" => "toggle_cdm_expanded",
172 "*q" => "catchup_all",
173 "x" => "cat_toggle_collapse",
174// "goto" => array(
175 "g a" => "goto_all",
176 "g f" => "goto_fresh",
177 "g s" => "goto_marked",
178 "g p" => "goto_published",
179 "g t" => "goto_tagcloud",
180 "g *p" => "goto_prefs",
181// "other" => array(
182 "(9)|Tab" => "select_article_cursor", // tab
183 "c l" => "create_label",
184 "c f" => "create_filter",
185 "c s" => "collapse_sidebar",
186 "^(191)|Ctrl+/" => "help_dialog",
187 );
188
189 if (get_pref('COMBINED_DISPLAY_MODE')) {
190 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
191 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
192 }
193
194 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
195 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
196 }
197
198 $prefixes = array();
199
200 foreach (array_keys($hotkeys) as $hotkey) {
201 $pair = explode(" ", $hotkey, 2);
202
203 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
204 array_push($prefixes, $pair[0]);
205 }
206 }
207
208 return array($prefixes, $hotkeys);
209 }
210
efcc5d36
AD
211 function check_for_update() {
212 if (defined("GIT_VERSION_TIMESTAMP")) {
465fb16d 213 $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
efcc5d36
AD
214
215 if ($content) {
216 $content = json_decode($content, true);
217
218 if ($content && isset($content["changeset"])) {
219 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
220 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
221
222 return $content["changeset"]["id"];
223 }
224 }
225 }
226 }
227
119ba03b 228 return "";
efcc5d36
AD
229 }
230
79c891a8 231 function make_runtime_info($disable_update_check = false) {
97b7d5c0
AD
232 $data = array();
233
234 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
235 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
236
237 $max_feed_id = db_fetch_result($result, 0, "mid");
238 $num_feeds = db_fetch_result($result, 0, "nf");
239
240 $data["max_feed_id"] = (int) $max_feed_id;
241 $data["num_feeds"] = (int) $num_feeds;
242
243 $data['last_article_id'] = getLastArticleId();
244 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
245
246 $data['dep_ts'] = calculate_dep_timestamp();
247 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
248
efcc5d36 249
79c891a8 250 if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
efcc5d36
AD
251 $update_result = @check_for_update();
252
253 $data["update_result"] = $update_result;
254
255 $_SESSION["last_version_check"] = time();
256 }
257
97b7d5c0
AD
258 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
259
260 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
261
262 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
263
264 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
265
266 if ($stamp) {
267 $stamp_delta = time() - $stamp;
268
269 if ($stamp_delta > 1800) {
270 $stamp_check = 0;
271 } else {
272 $stamp_check = 1;
273 $_SESSION["daemon_stamp_check"] = time();
274 }
275
276 $data['daemon_stamp_ok'] = $stamp_check;
277
278 $stamp_fmt = date("Y.m.d, G:i", $stamp);
279
280 $data['daemon_stamp'] = $stamp_fmt;
281 }
282 }
283 }
284
97b7d5c0
AD
285 return $data;
286 }
287
60e68059 288 function search_to_sql($search, $search_language) {
97b7d5c0 289
28539f6a 290 $keywords = str_getcsv(trim($search), " ");
97b7d5c0
AD
291 $query_keywords = array();
292 $search_words = array();
e854442e 293 $search_query_leftover = array();
97b7d5c0 294
60e68059
AD
295 if ($search_language)
296 $search_language = db_escape_string(mb_strtolower($search_language));
297 else
298 $search_language = "english";
299
97b7d5c0
AD
300 foreach ($keywords as $k) {
301 if (strpos($k, "-") === 0) {
302 $k = substr($k, 1);
303 $not = "NOT";
304 } else {
305 $not = "";
306 }
307
308 $commandpair = explode(":", mb_strtolower($k), 2);
309
310 switch ($commandpair[0]) {
311 case "title":
312 if ($commandpair[1]) {
313 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
314 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
315 } else {
316 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
317 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
318 array_push($search_words, $k);
319 }
320 break;
321 case "author":
322 if ($commandpair[1]) {
323 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
324 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
325 } else {
326 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
327 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
328 array_push($search_words, $k);
329 }
330 break;
331 case "note":
332 if ($commandpair[1]) {
333 if ($commandpair[1] == "true")
334 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
335 else if ($commandpair[1] == "false")
336 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
337 else
338 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
339 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
340 } else {
341 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
342 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
343 if (!$not) array_push($search_words, $k);
344 }
345 break;
346 case "star":
347
348 if ($commandpair[1]) {
349 if ($commandpair[1] == "true")
350 array_push($query_keywords, "($not (marked = true))");
351 else
352 array_push($query_keywords, "($not (marked = false))");
353 } else {
354 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
355 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
356 if (!$not) array_push($search_words, $k);
357 }
358 break;
359 case "pub":
360 if ($commandpair[1]) {
361 if ($commandpair[1] == "true")
362 array_push($query_keywords, "($not (published = true))");
363 else
364 array_push($query_keywords, "($not (published = false))");
365
366 } else {
367 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
368 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
369 if (!$not) array_push($search_words, $k);
370 }
95a95b0a
S
371 break;
372 case "unread":
373 if ($commandpair[1]) {
374 if ($commandpair[1] == "true")
375 array_push($query_keywords, "($not (unread = true))");
376 else
377 array_push($query_keywords, "($not (unread = false))");
378
379 } else {
380 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
381 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
382 if (!$not) array_push($search_words, $k);
383 }
97b7d5c0
AD
384 break;
385 default:
386 if (strpos($k, "@") === 0) {
387
388 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
389 $orig_ts = strtotime(substr($k, 1));
390 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
391
392 //$k = date("Y-m-d", strtotime(substr($k, 1)));
393
394 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
395 } else {
97b7d5c0 396
e854442e
AD
397 if (DB_TYPE == "pgsql") {
398 $k = mb_strtolower($k);
399 array_push($search_query_leftover, $not ? "!$k" : $k);
400 } else {
401 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
402 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
403 }
b4f544d3 404
e854442e 405 if (!$not) array_push($search_words, $k);
97b7d5c0
AD
406 }
407 }
408 }
409
e854442e
AD
410 if (count($search_query_leftover) > 0) {
411 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
b4f544d3 412
e854442e
AD
413 if (DB_TYPE == "pgsql") {
414 array_push($query_keywords,
60e68059 415 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
e854442e 416 }
b4f544d3
AD
417
418 }
419
97b7d5c0
AD
420 $search_query_part = implode("AND", $query_keywords);
421
422 return array($search_query_part, $search_words);
423 }
424
425 function getParentCategories($cat, $owner_uid) {
426 $rv = array();
427
428 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
429 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
430
431 while ($line = db_fetch_assoc($result)) {
432 array_push($rv, $line["parent_cat"]);
433 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
434 }
435
436 return $rv;
437 }
438
439 function getChildCategories($cat, $owner_uid) {
440 $rv = array();
441
442 $result = db_query("SELECT id FROM ttrss_feed_categories
443 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
444
445 while ($line = db_fetch_assoc($result)) {
446 array_push($rv, $line["id"]);
447 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
448 }
449
450 return $rv;
451 }
452
f5a0fb8b
AD
453 function queryFeedHeadlines($params) {
454
455 $feed = $params["feed"];
456 $limit = isset($params["limit"]) ? $params["limit"] : 30;
457 $view_mode = $params["view_mode"];
458 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
459 $search = isset($params["search"]) ? $params["search"] : false;
60e68059 460 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
f5a0fb8b
AD
461 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
462 $offset = isset($params["offset"]) ? $params["offset"] : 0;
463 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
f5a0fb8b
AD
464 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
465 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
466 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
467 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
468 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
469 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
48fefe2f 470 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
19e47ad6 471 $skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false;
97b7d5c0
AD
472
473 $ext_tables_part = "";
83ce77a2
AD
474 $query_strategy_part = "";
475
97b7d5c0
AD
476 $search_words = array();
477
478 if ($search) {
baaf4c30
RH
479 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
480 list($search_query_part, $search_words) = $plugin->hook_search($search);
9a6456fa 481 break;
baaf4c30 482 }
97b7d5c0 483
baaf4c30
RH
484 // fall back in case of no plugins
485 if (!$search_query_part) {
60e68059 486 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
97b7d5c0 487 }
baaf4c30 488 $search_query_part .= " AND ";
97b7d5c0
AD
489 } else {
490 $search_query_part = "";
491 }
492
97b7d5c0
AD
493 if ($since_id) {
494 $since_id_part = "ttrss_entries.id > $since_id AND ";
495 } else {
496 $since_id_part = "";
497 }
498
499 $view_query_part = "";
500
501 if ($view_mode == "adaptive") {
502 if ($search) {
503 $view_query_part = " ";
504 } else if ($feed != -1) {
505
506 $unread = getFeedUnread($feed, $cat_view);
507
508 if ($cat_view && $feed > 0 && $include_children)
509 $unread += getCategoryChildrenUnread($feed);
510
0bd172cd
AD
511 if ($unread > 0) {
512 $view_query_part = " unread = true AND ";
0bd172cd 513 }
97b7d5c0
AD
514 }
515 }
516
517 if ($view_mode == "marked") {
518 $view_query_part = " marked = true AND ";
519 }
520
521 if ($view_mode == "has_note") {
522 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
523 }
524
525 if ($view_mode == "published") {
526 $view_query_part = " published = true AND ";
527 }
528
529 if ($view_mode == "unread" && $feed != -6) {
530 $view_query_part = " unread = true AND ";
531 }
532
533 if ($limit > 0) {
534 $limit_query_part = "LIMIT " . $limit;
535 }
536
537 $allow_archived = false;
538
539 $vfeed_query_part = "";
540
97b7d5c0 541 /* tags */
9090b874 542 if (!is_numeric($feed)) {
97b7d5c0
AD
543 $query_strategy_part = "true";
544 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
545 id = feed_id) as feed_title,";
97b7d5c0
AD
546 } else if ($feed > 0) {
547
548 if ($cat_view) {
549
550 if ($feed > 0) {
551 if ($include_children) {
552 # sub-cats
553 $subcats = getChildCategories($feed, $owner_uid);
554
555 array_push($subcats, $feed);
556 $query_strategy_part = "cat_id IN (".
557 implode(",", $subcats).")";
558
559 } else {
560 $query_strategy_part = "cat_id = '$feed'";
561 }
562
563 } else {
564 $query_strategy_part = "cat_id IS NULL";
565 }
566
567 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
568
569 } else {
570 $query_strategy_part = "feed_id = '$feed'";
571 }
572 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
573 $query_strategy_part = "feed_id IS NULL";
574 $allow_archived = true;
575 } else if ($feed == 0 && $cat_view) { // uncategorized
576 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
577 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
578 } else if ($feed == -1) { // starred virtual feed
579 $query_strategy_part = "marked = true";
580 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
581 $allow_archived = true;
582
583 if (!$override_order) {
584 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
585 }
586
587 } else if ($feed == -2) { // published virtual feed OR labels category
588
589 if (!$cat_view) {
590 $query_strategy_part = "published = true";
591 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
592 $allow_archived = true;
593
594 if (!$override_order) {
595 $override_order = "last_published DESC, date_entered DESC, updated DESC";
596 }
597
598 } else {
599 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
600
6ee255fb 601 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
97b7d5c0
AD
602
603 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
604 ttrss_user_labels2.article_id = ref_id";
605
606 }
607 } else if ($feed == -6) { // recently read
608 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
884ae7a9
AD
609
610 if (DB_TYPE == "pgsql") {
6b860bd9 611 $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' ";
884ae7a9 612 } else {
6b860bd9 613 $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
884ae7a9
AD
614 }
615
97b7d5c0
AD
616 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
617 $allow_archived = true;
ad593e43 618 $ignore_vfeed_group = true;
97b7d5c0
AD
619
620 if (!$override_order) $override_order = "last_read DESC";
621
97b7d5c0
AD
622 } else if ($feed == -3) { // fresh virtual feed
623 $query_strategy_part = "unread = true AND score >= 0";
624
625 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
626
627 if (DB_TYPE == "pgsql") {
628 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
629 } else {
630 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
631 }
632
633 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
634 } else if ($feed == -4) { // all articles virtual feed
635 $allow_archived = true;
636 $query_strategy_part = "true";
637 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
638 } else if ($feed <= LABEL_BASE_INDEX) { // labels
639 $label_id = feed_to_label_id($feed);
640
641 $query_strategy_part = "label_id = '$label_id' AND
642 ttrss_labels2.id = ttrss_user_labels2.label_id AND
643 ttrss_user_labels2.article_id = ref_id";
644
645 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
6ee255fb 646 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
97b7d5c0
AD
647 $allow_archived = true;
648
649 } else {
650 $query_strategy_part = "true";
651 }
652
653 $order_by = "score DESC, date_entered DESC, updated DESC";
654
97b7d5c0
AD
655 if ($override_order) {
656 $order_by = $override_order;
657 }
658
659 if ($override_strategy) {
660 $query_strategy_part = $override_strategy;
661 }
662
663 if ($override_vfeed) {
664 $vfeed_query_part = $override_vfeed;
665 }
666
667 $feed_title = "";
668
669 if ($search) {
670 $feed_title = T_sprintf("Search results: %s", $search);
671 } else {
672 if ($cat_view) {
673 $feed_title = getCategoryTitle($feed);
674 } else {
675 if (is_numeric($feed) && $feed > 0) {
676 $result = db_query("SELECT title,site_url,last_error,last_updated
677 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
678
679 $feed_title = db_fetch_result($result, 0, "title");
680 $feed_site_url = db_fetch_result($result, 0, "site_url");
681 $last_error = db_fetch_result($result, 0, "last_error");
682 $last_updated = db_fetch_result($result, 0, "last_updated");
683 } else {
684 $feed_title = getFeedTitle($feed);
685 }
686 }
687 }
688
689
690 $content_query_part = "content, ";
691
9090b874
AD
692 if ($limit_query_part) {
693 $offset_query_part = "OFFSET $offset";
694 } else {
695 $offset_query_part = "";
696 }
97b7d5c0
AD
697
698 if (is_numeric($feed)) {
97b7d5c0
AD
699 // proper override_order applied above
700 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
701 if (!$override_order) {
702 $order_by = "ttrss_feeds.title, $order_by";
703 } else {
704 $order_by = "ttrss_feeds.title, $override_order";
705 }
706 }
707
708 if (!$allow_archived) {
6ee255fb 709 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
97b7d5c0
AD
710 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
711
712 } else {
6ee255fb 713 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
97b7d5c0
AD
714 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
715 }
716
9090b874 717 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
97b7d5c0 718
d1e631f3
AD
719 if ($start_ts) {
720 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
99c19e1d 721 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
d1e631f3
AD
722 } else {
723 $start_ts_query_part = "";
724 }
725
f7fd1edb 726 $first_id = 0;
f56e5a35
AD
727 $first_id_query_strategy_part = $query_strategy_part;
728
729 if ($feed == -3)
730 $first_id_query_strategy_part = "true";
731
332ff5cf
AD
732 if (DB_TYPE == "pgsql") {
733 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
734 } else {
735 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
736 }
737
19e47ad6 738 if (!$search && !$skip_first_id_check) {
e854442e
AD
739 // if previous topmost article id changed that means our current pagination is no longer valid
740 $query = "SELECT DISTINCT
741 ttrss_feeds.title,
742 date_entered,
743 guid,
744 ttrss_entries.id,
745 ttrss_entries.title,
746 updated,
747 score,
748 marked,
749 published,
750 last_marked,
e07be79a
AD
751 last_published,
752 last_read
e854442e
AD
753 FROM
754 $from_qpart
755 WHERE
756 $feed_check_qpart
e854442e
AD
757 ttrss_user_entries.owner_uid = '$owner_uid' AND
758 $search_query_part
759 $start_ts_query_part
760 $since_id_part
332ff5cf 761 $sanity_interval_qpart
e854442e 762 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
f5a0fb8b
AD
763
764 if ($_REQUEST["debug"]) {
765 print $query;
766 }
767
768 $result = db_query($query);
f7fd1edb 769 if ($result && db_num_rows($result) > 0) {
e854442e 770 $first_id = (int)db_fetch_result($result, 0, "id");
f5a0fb8b 771
f7fd1edb 772 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
48fefe2f 773 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
f5a0fb8b
AD
774 }
775 }
e854442e 776 }
f5a0fb8b 777
97b7d5c0
AD
778 $query = "SELECT DISTINCT
779 date_entered,
780 guid,
781 ttrss_entries.id,ttrss_entries.title,
782 updated,
783 label_cache,
784 tag_cache,
785 always_display_enclosures,
786 site_url,
787 note,
788 num_comments,
789 comments,
790 int_id,
791 uuid,
792 lang,
793 hide_images,
794 unread,feed_id,marked,published,link,last_read,orig_feed_id,
795 last_marked, last_published,
796 $vfeed_query_part
797 $content_query_part
798 author,score
799 FROM
800 $from_qpart
801 WHERE
802 $feed_check_qpart
97b7d5c0
AD
803 ttrss_user_entries.owner_uid = '$owner_uid' AND
804 $search_query_part
d1e631f3 805 $start_ts_query_part
97b7d5c0
AD
806 $view_query_part
807 $since_id_part
808 $query_strategy_part ORDER BY $order_by
809 $limit_query_part $offset_query_part";
810
811 if ($_REQUEST["debug"]) print $query;
812
813 $result = db_query($query);
814
815 } else {
816 // browsing by tag
817
9090b874
AD
818 $query = "SELECT DISTINCT
819 date_entered,
820 guid,
821 note,
822 ttrss_entries.id as id,
823 title,
824 updated,
825 unread,
826 feed_id,
827 orig_feed_id,
828 marked,
829 num_comments,
830 comments,
9cc29abd 831 int_id,
9090b874
AD
832 tag_cache,
833 label_cache,
834 link,
835 lang,
836 uuid,
837 last_read,
838 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
839 last_marked, last_published,
840 $since_id_part
841 $vfeed_query_part
842 $content_query_part
843 author, score
844 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
845 WHERE
846 ref_id = ttrss_entries.id AND
847 ttrss_user_entries.owner_uid = $owner_uid AND
848 post_int_id = int_id AND
849 tag_name = '$feed' AND
850 $view_query_part
851 $search_query_part
852 $query_strategy_part ORDER BY $order_by
853 $limit_query_part $offset_query_part";
97b7d5c0 854
9090b874 855 if ($_REQUEST["debug"]) print $query;
97b7d5c0 856
9090b874 857 $result = db_query($query);
97b7d5c0
AD
858 }
859
48fefe2f 860 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
97b7d5c0
AD
861
862 }
863
59b5d5f3 864 function iframe_whitelisted($entry) {
389ae94a 865 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
59b5d5f3
AD
866
867 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
868
869 if ($src) {
870 foreach ($whitelist as $w) {
871 if ($src == $w || $src == "www.$w")
872 return true;
873 }
874 }
875
876 return false;
877 }
878
97b7d5c0
AD
879 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
880 if (!$owner) $owner = $_SESSION["uid"];
881
882 $res = trim($str); if (!$res) return '';
883
884 $charset_hack = '<head>
885 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
886 </head>';
887
888 $res = trim($res); if (!$res) return '';
889
890 libxml_use_internal_errors(true);
891
892 $doc = new DOMDocument();
893 $doc->loadHTML($charset_hack . $res);
894 $xpath = new DOMXPath($doc);
895
a536f94c 896 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https';
7818bfde
AD
897 $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH;
898
8519c68d 899 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
a536f94c 900
97b7d5c0
AD
901 foreach ($entries as $entry) {
902
7818bfde
AD
903 if ($entry->hasAttribute('href')) {
904 $entry->setAttribute('href',
905 rewrite_relative_url($rewrite_base_url, $entry->getAttribute('href')));
906
907 $entry->setAttribute('rel', 'noopener noreferrer');
908 }
909
910 if ($entry->hasAttribute('src')) {
911 $src = rewrite_relative_url($rewrite_base_url, $entry->getAttribute('src'));
48eefd8c 912 $cached_filename = CACHE_DIR . '/images/' . sha1($src);
7818bfde 913
48eefd8c 914 if (file_exists($cached_filename)) {
9594ea68
AD
915
916 // this is strictly cosmetic
917 if ($entry->tagName == 'img') {
918 $suffix = ".png";
919 } else if ($entry->parentNode && $entry->parentNode->tagName == "video") {
920 $suffix = ".mp4";
921 } else if ($entry->parentNode && $entry->parentNode->tagName == "audio") {
922 $suffix = ".ogg";
923 } else {
924 $suffix = "";
925 }
926
927 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src) . $suffix;
7818bfde 928
48eefd8c
AD
929 if ($entry->hasAttribute('srcset')) {
930 $entry->removeAttribute('srcset');
931 }
8519c68d 932
48eefd8c
AD
933 if ($entry->hasAttribute('sizes')) {
934 $entry->removeAttribute('sizes');
7818bfde 935 }
97b7d5c0
AD
936 }
937
7818bfde
AD
938 $entry->setAttribute('src', $src);
939 }
940
941 if ($entry->nodeName == 'img') {
97b7d5c0 942
7818bfde
AD
943 if ($entry->hasAttribute('src')) {
944 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
a01bfd78 945
7818bfde 946 if ($ttrss_uses_https && !$is_https_url) {
a01bfd78 947
5edd605a
AD
948 if ($entry->hasAttribute('srcset')) {
949 $entry->removeAttribute('srcset');
a01bfd78 950 }
97b7d5c0 951
5edd605a
AD
952 if ($entry->hasAttribute('sizes')) {
953 $entry->removeAttribute('sizes');
954 }
0442cbb6 955 }
5edd605a 956 }
5edd605a 957
7818bfde
AD
958 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
959 $force_remove_images || $_SESSION["bw_limit"]) {
a536f94c 960
7818bfde 961 $p = $doc->createElement('p');
a536f94c 962
7818bfde
AD
963 $a = $doc->createElement('a');
964 $a->setAttribute('href', $entry->getAttribute('src'));
a536f94c 965
7818bfde
AD
966 $a->appendChild(new DOMText($entry->getAttribute('src')));
967 $a->setAttribute('target', '_blank');
ba2853ca 968 $a->setAttribute('rel', 'noopener noreferrer');
a536f94c 969
7818bfde 970 $p->appendChild($a);
97b7d5c0 971
7818bfde 972 $entry->parentNode->replaceChild($p, $entry);
97b7d5c0
AD
973 }
974 }
975
976 if (strtolower($entry->nodeName) == "a") {
977 $entry->setAttribute("target", "_blank");
ba2853ca 978 $entry->setAttribute("rel", "noopener noreferrer");
97b7d5c0
AD
979 }
980 }
981
982 $entries = $xpath->query('//iframe');
983 foreach ($entries as $entry) {
59b5d5f3
AD
984 if (!iframe_whitelisted($entry)) {
985 $entry->setAttribute('sandbox', 'allow-scripts');
c63850fa
AD
986 } else {
987 if ($_SERVER['HTTPS'] == "on") {
988 $entry->setAttribute("src",
989 str_replace("http://", "https://",
990 $entry->getAttribute("src")));
991 }
59b5d5f3 992 }
97b7d5c0
AD
993 }
994
67268b00 995 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
97b7d5c0
AD
996 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
997 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
311cdb27 998 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
97b7d5c0 999 'dt', 'em', 'footer', 'figure', 'figcaption',
e2a3689a 1000 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
97b7d5c0
AD
1001 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
1002 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
1003 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
1004 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
50bda3fe 1005 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
97b7d5c0
AD
1006
1007 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
1008
1009 $disallowed_attributes = array('id', 'style', 'class');
1010
1011 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
1012 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1013 if (is_array($retval)) {
1014 $doc = $retval[0];
1015 $allowed_elements = $retval[1];
1016 $disallowed_attributes = $retval[2];
1017 } else {
1018 $doc = $retval;
1019 }
1020 }
1021
1022 $doc->removeChild($doc->firstChild); //remove doctype
1023 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1024
1025 if ($highlight_words) {
1026 foreach ($highlight_words as $word) {
1027
1028 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1029
1030 $elements = $xpath->query("//*/text()");
1031
1032 foreach ($elements as $child) {
1033
1034 $fragment = $doc->createDocumentFragment();
1035 $text = $child->textContent;
1036
1037 while (($pos = mb_stripos($text, $word)) !== false) {
1038 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1039 $word = mb_substr($text, $pos, mb_strlen($word));
1040 $highlight = $doc->createElement('span');
1041 $highlight->appendChild(new DomText($word));
1042 $highlight->setAttribute('class', 'highlight');
1043 $fragment->appendChild($highlight);
1044 $text = mb_substr($text, $pos + mb_strlen($word));
1045 }
1046
1047 if (!empty($text)) $fragment->appendChild(new DomText($text));
1048
1049 $child->parentNode->replaceChild($fragment, $child);
1050 }
1051 }
1052 }
1053
1054 $res = $doc->saveHTML();
1055
42f78188
AD
1056 /* strip everything outside of <body>...</body> */
1057
1058 $res_frag = array();
1059 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1060 return $res_frag[1];
1061 } else {
1062 return $res;
1063 }
97b7d5c0
AD
1064 }
1065
1066 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1067 $xpath = new DOMXPath($doc);
1068 $entries = $xpath->query('//*');
1069
1070 foreach ($entries as $entry) {
1071 if (!in_array($entry->nodeName, $allowed_elements)) {
1072 $entry->parentNode->removeChild($entry);
1073 }
1074
1075 if ($entry->hasAttributes()) {
1076 $attrs_to_remove = array();
1077
1078 foreach ($entry->attributes as $attr) {
1079
1080 if (strpos($attr->nodeName, 'on') === 0) {
1081 array_push($attrs_to_remove, $attr);
1082 }
1083
d8b0f067
J
1084 if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1085 array_push($attrs_to_remove, $attr);
1086 }
1087
97b7d5c0
AD
1088 if (in_array($attr->nodeName, $disallowed_attributes)) {
1089 array_push($attrs_to_remove, $attr);
1090 }
1091 }
1092
1093 foreach ($attrs_to_remove as $attr) {
1094 $entry->removeAttributeNode($attr);
1095 }
1096 }
1097 }
1098
1099 return $doc;
1100 }
1101
97b7d5c0
AD
1102 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1103
1104 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1105 if (count($ids) == 0) return;
1106
1107 $tmp_ids = array();
1108
1109 foreach ($ids as $id) {
1110 array_push($tmp_ids, "ref_id = '$id'");
1111 }
1112
1113 $ids_qpart = join(" OR ", $tmp_ids);
1114
1115 if ($cmode == 0) {
1116 db_query("UPDATE ttrss_user_entries SET
1117 unread = false,last_read = NOW()
1118 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1119 } else if ($cmode == 1) {
1120 db_query("UPDATE ttrss_user_entries SET
1121 unread = true
1122 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1123 } else {
1124 db_query("UPDATE ttrss_user_entries SET
1125 unread = NOT unread,last_read = NOW()
1126 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1127 }
1128
1129 /* update ccache */
1130
1131 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1132 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1133
1134 while ($line = db_fetch_assoc($result)) {
1135 ccache_update($line["feed_id"], $owner_uid);
1136 }
1137 }
1138
1139 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1140
1141 $a_id = db_escape_string($id);
1142
1143 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1144
1145 $query = "SELECT DISTINCT tag_name,
1146 owner_uid as owner FROM
1147 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1148 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1149
1150 $tags = array();
1151
1152 /* check cache first */
1153
1154 if ($tag_cache === false) {
1155 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1156 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1157
c3522486
AD
1158 if (db_num_rows($result) != 0)
1159 $tag_cache = db_fetch_result($result, 0, "tag_cache");
97b7d5c0
AD
1160 }
1161
1162 if ($tag_cache) {
1163 $tags = explode(",", $tag_cache);
1164 } else {
1165
1166 /* do it the hard way */
1167
1168 $tmp_result = db_query($query);
1169
1170 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1171 array_push($tags, $tmp_line["tag_name"]);
1172 }
1173
1174 /* update the cache */
1175
1176 $tags_str = db_escape_string(join(",", $tags));
1177
1178 db_query("UPDATE ttrss_user_entries
1179 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1180 AND owner_uid = $owner_uid");
1181 }
1182
1183 return $tags;
1184 }
1185
1186 function trim_array($array) {
1187 $tmp = $array;
1188 array_walk($tmp, 'trim');
1189 return $tmp;
1190 }
1191
1192 function tag_is_valid($tag) {
1193 if ($tag == '') return false;
85d067e8 1194 if (is_numeric($tag)) return false;
97b7d5c0
AD
1195 if (mb_strlen($tag) > 250) return false;
1196
1197 if (!$tag) return false;
1198
1199 return true;
1200 }
1201
1202 function render_login_form() {
1203 header('Cache-Control: public');
1204
1205 require_once "login_form.php";
1206 exit;
1207 }
1208
1209 function format_warning($msg, $id = "") {
c1ebb6cd 1210 return "<div class=\"alert\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1211 }
1212
1213 function format_notice($msg, $id = "") {
c1ebb6cd 1214 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1215 }
1216
1217 function format_error($msg, $id = "") {
c1ebb6cd 1218 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1219 }
1220
1221 function print_notice($msg) {
1222 return print format_notice($msg);
1223 }
1224
1225 function print_warning($msg) {
1226 return print format_warning($msg);
1227 }
1228
1229 function print_error($msg) {
1230 return print format_error($msg);
1231 }
1232
1233
1234 function T_sprintf() {
1235 $args = func_get_args();
1236 return vsprintf(__(array_shift($args)), $args);
1237 }
1238
1239 function format_inline_player($url, $ctype) {
1240
1241 $entry = "";
1242
1243 $url = htmlspecialchars($url);
1244
1245 if (strpos($ctype, "audio/") === 0) {
1246
1247 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1248 $_SESSION["hasMp3"])) {
1249
1250 $entry .= "<audio preload=\"none\" controls>
83ce77a2 1251 <source type=\"$ctype\" src=\"$url\"/>
97b7d5c0
AD
1252 </audio>";
1253
1254 } else {
1255
1256 $entry .= "<object type=\"application/x-shockwave-flash\"
1257 data=\"lib/button/musicplayer.swf?song_url=$url\"
1258 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1259 <param name=\"movie\"
1260 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1261 </object>";
1262 }
1263
ba2853ca 1264 if ($entry) $entry .= "&nbsp; <a target=\"_blank\" rel=\"noopener noreferrer\"
97b7d5c0
AD
1265 href=\"$url\">" . basename($url) . "</a>";
1266
1267 return $entry;
1268
1269 }
1270
1271 return "";
1272
1273/* $filename = substr($url, strrpos($url, "/")+1);
1274
ba2853ca 1275 $entry .= " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"" . htmlspecialchars($url) . "\">" .
97b7d5c0
AD
1276 $filename . " (" . $ctype . ")" . "</a>"; */
1277
1278 }
1279
1280 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1281 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1282
1283 $rv = array();
1284
1285 $rv['id'] = $id;
1286
1287 /* we can figure out feed_id from article id anyway, why do we
1288 * pass feed_id here? let's ignore the argument :(*/
1289
1290 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1291 WHERE ref_id = '$id'");
1292
1293 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1294
1295 $rv['feed_id'] = $feed_id;
1296
1297 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1298
1299 if ($mark_as_read) {
1300 $result = db_query("UPDATE ttrss_user_entries
1301 SET unread = false,last_read = NOW()
1302 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1303
1304 ccache_update($feed_id, $owner_uid);
1305 }
1306
1307 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1308 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1309 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1310 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1311 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1312 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1313 num_comments,
1314 tag_cache,
1315 author,
553ec3c3 1316 guid,
97b7d5c0
AD
1317 orig_feed_id,
1318 note
1319 FROM ttrss_entries,ttrss_user_entries
1320 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1321
1322 if ($result) {
1323
1324 $line = db_fetch_assoc($result);
1325
1326 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1327 unset($line["tag_cache"]);
1328
1329 $line["content"] = sanitize($line["content"],
1330 sql_bool_to_bool($line['hide_images']),
1331 $owner_uid, $line["site_url"], false, $line["id"]);
1332
1333 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1334 $line = $p->hook_render_article($line);
1335 }
1336
967f0619 1337 $num_comments = (int) $line["num_comments"];
97b7d5c0
AD
1338 $entry_comments = "";
1339
1340 if ($num_comments > 0) {
1341 if ($line["comments"]) {
1342 $comments_url = htmlspecialchars($line["comments"]);
1343 } else {
1344 $comments_url = htmlspecialchars($line["link"]);
1345 }
1346 $entry_comments = "<a class=\"postComments\"
ba2853ca 1347 target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
97b7d5c0
AD
1348 _ngettext("comment", "comments", $num_comments)."</a>";
1349
1350 } else {
1351 if ($line["comments"] && $line["link"] != $line["comments"]) {
ba2853ca 1352 $entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
97b7d5c0
AD
1353 }
1354 }
1355
1356 if ($zoom_mode) {
1357 header("Content-Type: text/html");
1358 $rv['content'] .= "<html><head>
1359 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1360 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1361 stylesheet_tag("css/tt-rss.css").
1362 stylesheet_tag("css/zoom.css").
1363 stylesheet_tag("css/dijit.css")."
1364
1365 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1366 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1367
97b7d5c0
AD
1368 </head><body id=\"ttrssZoom\">";
1369 }
1370
1371 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1372
1373 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1374
1375 $entry_author = $line["author"];
1376
1377 if ($entry_author) {
1378 $entry_author = __(" - ") . $entry_author;
1379 }
1380
1381 $parsed_updated = make_local_datetime($line["updated"], true,
1382 $owner_uid, true);
1383
1384 if (!$zoom_mode)
1385 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1386
1387 if ($line["link"]) {
ba2853ca 1388 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
97b7d5c0
AD
1389 title=\"".htmlspecialchars($line['title'])."\"
1390 href=\"" .
1391 htmlspecialchars($line["link"]) . "\">" .
1392 $line["title"] . "</a>" .
1393 "<span class='author'>$entry_author</span></div>";
1394 } else {
1395 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1396 }
1397
1398 if ($zoom_mode) {
6687cb99 1399 $feed_title = htmlspecialchars($line["feed_title"]);
97b7d5c0
AD
1400
1401 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1402
1403 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1404 }
1405
1406 $tags_str = format_tags_string($line["tags"], $id);
1407 $tags_str_full = join(", ", $line["tags"]);
1408
1409 if (!$tags_str_full) $tags_str_full = __("no tags");
1410
1411 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1412
1413 $rv['content'] .= "<div class='postTags' style='float : right'>
1414 <img src='images/tag.png'
1415 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1416
1417 if (!$zoom_mode) {
1418 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1419 <a title=\"".__('Edit tags for this article')."\"
1420 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1421
1422 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1423 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1424 position=\"below\">$tags_str_full</div>";
1425
1426 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1427 $rv['content'] .= $p->hook_article_button($line);
1428 }
1429
1430 } else {
1431 $tags_str = strip_tags($tags_str);
1432 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1433 }
1434 $rv['content'] .= "</div>";
1435 $rv['content'] .= "<div clear='both'>";
1436
1437 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1438 $rv['content'] .= $p->hook_article_left_button($line);
1439 }
1440
1441 $rv['content'] .= "$entry_comments</div>";
1442
1443 if ($line["orig_feed_id"]) {
1444
1445 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
71b75bb7 1446 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
97b7d5c0
AD
1447
1448 if (db_num_rows($tmp_result) != 0) {
1449
1450 $rv['content'] .= "<div clear='both'>";
1451 $rv['content'] .= __("Originally from:");
1452
1453 $rv['content'] .= "&nbsp;";
1454
1455 $tmp_line = db_fetch_assoc($tmp_result);
1456
ba2853ca 1457 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
97b7d5c0
AD
1458 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1459 $tmp_line['title'] . "</a>";
1460
1461 $rv['content'] .= "&nbsp;";
1462
ba2853ca 1463 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
97b7d5c0
AD
1464 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1465
1466 $rv['content'] .= "</div>";
1467 }
1468 }
1469
1470 $rv['content'] .= "</div>";
1471
1472 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1473 if ($line['note']) {
1474 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1475 }
1476 $rv['content'] .= "</div>";
1477
1478 if (!$line['lang']) $line['lang'] = 'en';
1479
1480 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1481
1482 $rv['content'] .= $line["content"];
6810a1de
AD
1483
1484 if (!$zoom_mode) {
1485 $rv['content'] .= format_article_enclosures($id,
1486 sql_bool_to_bool($line["always_display_enclosures"]),
1487 $line["content"],
1488 sql_bool_to_bool($line["hide_images"]));
1489 }
97b7d5c0
AD
1490
1491 $rv['content'] .= "</div>";
1492
1493 $rv['content'] .= "</div>";
1494
1495 }
1496
1497 if ($zoom_mode) {
1498 $rv['content'] .= "
1499 <div class='footer'>
1500 <button onclick=\"return window.close()\">".
1501 __("Close this window")."</button></div>";
1502 $rv['content'] .= "</body></html>";
1503 }
1504
1505 return $rv;
1506
1507 }
1508
1509 function print_checkpoint($n, $s) {
1510 $ts = microtime(true);
1511 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1512 return $ts;
1513 }
1514
1515 function sanitize_tag($tag) {
1516 $tag = trim($tag);
1517
1518 $tag = mb_strtolower($tag, 'utf-8');
1519
0e4da73f 1520 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
97b7d5c0 1521
35c37354
AD
1522 if (DB_TYPE == "mysql") {
1523 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1524 }
97b7d5c0
AD
1525
1526 return $tag;
1527 }
1528
1529 function get_self_url_prefix() {
1530 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1531 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1532 } else {
1533 return SELF_URL_PATH;
1534 }
1535 }
1536
1537 /**
1538 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1539 *
1540 * @return string The Mozilla Firefox feed adding URL.
1541 */
1542 function add_feed_url() {
1543 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1544
1545 $url_path = get_self_url_prefix() .
1546 "/public.php?op=subscribe&feed_url=%s";
1547 return $url_path;
1548 } // function add_feed_url
1549
1550 function encrypt_password($pass, $salt = '', $mode2 = false) {
1551 if ($salt && $mode2) {
1552 return "MODE2:" . hash('sha256', $salt . $pass);
1553 } else if ($salt) {
1554 return "SHA1X:" . sha1("$salt:$pass");
1555 } else {
1556 return "SHA1:" . sha1($pass);
1557 }
1558 } // function encrypt_password
1559
7b55001e 1560 function load_filters($feed_id, $owner_uid) {
97b7d5c0
AD
1561 $filters = array();
1562
1563 $cat_id = (int)getFeedCategory($feed_id);
1564
1565 if ($cat_id == 0)
1566 $null_cat_qpart = "cat_id IS NULL OR";
1567 else
1568 $null_cat_qpart = "";
1569
1570 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1571 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1572
1573 $check_cats = join(",", array_merge(
1574 getParentCategories($cat_id, $owner_uid),
1575 array($cat_id)));
1576
1577 while ($line = db_fetch_assoc($result)) {
1578 $filter_id = $line["id"];
1579
1580 $result2 = db_query("SELECT
1581 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1582 FROM ttrss_filters2_rules AS r,
1583 ttrss_filter_types AS t
1584 WHERE
1585 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1586 (feed_id IS NULL OR feed_id = '$feed_id') AND
1587 filter_type = t.id AND filter_id = '$filter_id'");
1588
1589 $rules = array();
1590 $actions = array();
1591
1592 while ($rule_line = db_fetch_assoc($result2)) {
1593# print_r($rule_line);
1594
1595 $rule = array();
1596 $rule["reg_exp"] = $rule_line["reg_exp"];
1597 $rule["type"] = $rule_line["type_name"];
1598 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1599
1600 array_push($rules, $rule);
1601 }
1602
1603 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1604 FROM ttrss_filters2_actions AS a,
1605 ttrss_filter_actions AS t
1606 WHERE
1607 action_id = t.id AND filter_id = '$filter_id'");
1608
1609 while ($action_line = db_fetch_assoc($result2)) {
1610# print_r($action_line);
1611
1612 $action = array();
1613 $action["type"] = $action_line["type_name"];
1614 $action["param"] = $action_line["action_param"];
1615
1616 array_push($actions, $action);
1617 }
1618
1619
1620 $filter = array();
1621 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1622 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1623 $filter["rules"] = $rules;
1624 $filter["actions"] = $actions;
1625
1626 if (count($rules) > 0 && count($actions) > 0) {
1627 array_push($filters, $filter);
1628 }
1629 }
1630
1631 return $filters;
1632 }
1633
1634 function get_score_pic($score) {
1635 if ($score > 100) {
1636 return "score_high.png";
1637 } else if ($score > 0) {
1638 return "score_half_high.png";
1639 } else if ($score < -100) {
1640 return "score_low.png";
1641 } else if ($score < 0) {
1642 return "score_half_low.png";
1643 } else {
1644 return "score_neutral.png";
1645 }
1646 }
1647
1648 function feed_has_icon($id) {
1649 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1650 }
1651
1652 function init_plugins() {
1653 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1654
1655 return true;
1656 }
1657
7b55001e 1658 function format_tags_string($tags) {
97b7d5c0
AD
1659 if (!is_array($tags) || count($tags) == 0) {
1660 return __("no tags");
1661 } else {
1662 $maxtags = min(5, count($tags));
83ce77a2 1663 $tags_str = "";
97b7d5c0
AD
1664
1665 for ($i = 0; $i < $maxtags; $i++) {
dcbe36b2 1666 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
97b7d5c0
AD
1667 }
1668
1669 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1670
1671 if (count($tags) > $maxtags)
1672 $tags_str .= ", &hellip;";
1673
1674 return $tags_str;
1675 }
1676 }
1677
7b55001e 1678 function format_article_labels($labels) {
97b7d5c0
AD
1679
1680 if (!is_array($labels)) return '';
1681
1682 $labels_str = "";
1683
1684 foreach ($labels as $l) {
1685 $labels_str .= sprintf("<span class='hlLabelRef'
1686 style='color : %s; background-color : %s'>%s</span>",
1687 $l[2], $l[3], $l[1]);
1688 }
1689
1690 return $labels_str;
1691
1692 }
1693
1694 function format_article_note($id, $note, $allow_edit = true) {
1695
1696 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1697 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1698 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1699
1700 return $str;
1701 }
1702
1703
1704 function get_feed_category($feed_cat, $parent_cat_id = false) {
1705 if ($parent_cat_id) {
1706 $parent_qpart = "parent_cat = '$parent_cat_id'";
1707 $parent_insert = "'$parent_cat_id'";
1708 } else {
1709 $parent_qpart = "parent_cat IS NULL";
1710 $parent_insert = "NULL";
1711 }
1712
1713 $result = db_query(
1714 "SELECT id FROM ttrss_feed_categories
1715 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1716
1717 if (db_num_rows($result) == 0) {
1718 return false;
1719 } else {
1720 return db_fetch_result($result, 0, "id");
1721 }
1722 }
1723
1724 function add_feed_category($feed_cat, $parent_cat_id = false) {
1725
1726 if (!$feed_cat) return false;
1727
1728 db_query("BEGIN");
1729
1730 if ($parent_cat_id) {
1731 $parent_qpart = "parent_cat = '$parent_cat_id'";
1732 $parent_insert = "'$parent_cat_id'";
1733 } else {
1734 $parent_qpart = "parent_cat IS NULL";
1735 $parent_insert = "NULL";
1736 }
1737
1738 $feed_cat = mb_substr($feed_cat, 0, 250);
1739
1740 $result = db_query(
1741 "SELECT id FROM ttrss_feed_categories
1742 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1743
1744 if (db_num_rows($result) == 0) {
1745
1746 $result = db_query(
1747 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1748 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1749
1750 db_query("COMMIT");
1751
1752 return true;
1753 }
1754
1755 return false;
1756 }
1757
1758 function getArticleFeed($id) {
1759 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1760 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1761
1762 if (db_num_rows($result) != 0) {
1763 return db_fetch_result($result, 0, "feed_id");
1764 } else {
1765 return 0;
1766 }
1767 }
1768
1769 /**
1770 * Fixes incomplete URLs by prepending "http://".
1771 * Also replaces feed:// with http://, and
1772 * prepends a trailing slash if the url is a domain name only.
1773 *
1774 * @param string $url Possibly incomplete URL
1775 *
1776 * @return string Fixed URL.
1777 */
1778 function fix_url($url) {
dd6e2386
AD
1779
1780 // support schema-less urls
1781 if (strpos($url, '//') === 0) {
1782 $url = 'https:' . $url;
1783 }
1784
97b7d5c0
AD
1785 if (strpos($url, '://') === false) {
1786 $url = 'http://' . $url;
1787 } else if (substr($url, 0, 5) == 'feed:') {
1788 $url = 'http:' . substr($url, 5);
1789 }
1790
1791 //prepend slash if the URL has no slash in it
1792 // "http://www.example" -> "http://www.example/"
1793 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1794 $url .= '/';
1795 }
1796
62958fe9
BT
1797 //convert IDNA hostname to punycode if possible
1798 if (function_exists("idn_to_ascii")) {
1799 $parts = parse_url($url);
1800 if (mb_detect_encoding($parts['host']) != 'ASCII')
1801 {
1802 $parts['host'] = idn_to_ascii($parts['host']);
1803 $url = build_url($parts);
1804 }
1805 }
1806
97b7d5c0
AD
1807 if ($url != "http:///")
1808 return $url;
1809 else
1810 return '';
1811 }
1812
1813 function validate_feed_url($url) {
1814 $parts = parse_url($url);
1815
1816 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1817
1818 }
1819
1820 function get_article_enclosures($id) {
1821
1822 $query = "SELECT * FROM ttrss_enclosures
1823 WHERE post_id = '$id' AND content_url != ''";
1824
1825 $rv = array();
1826
1827 $result = db_query($query);
1828
1829 if (db_num_rows($result) > 0) {
1830 while ($line = db_fetch_assoc($result)) {
388d4dfa
AD
1831
1832 if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) {
1833 $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]);
1834 }
1835
97b7d5c0
AD
1836 array_push($rv, $line);
1837 }
1838 }
1839
1840 return $rv;
1841 }
1842
3e0f2090 1843 /* function save_email_address($email) {
97b7d5c0
AD
1844 // FIXME: implement persistent storage of emails
1845
1846 if (!$_SESSION['stored_emails'])
1847 $_SESSION['stored_emails'] = array();
1848
1849 if (!in_array($email, $_SESSION['stored_emails']))
1850 array_push($_SESSION['stored_emails'], $email);
3e0f2090 1851 } */
97b7d5c0
AD
1852
1853
1854 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1855
1856 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1857
1858 $sql_is_cat = bool_to_sql_bool($is_cat);
1859
1860 $result = db_query("SELECT access_key FROM ttrss_access_keys
1861 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1862 AND owner_uid = " . $owner_uid);
1863
1864 if (db_num_rows($result) == 1) {
1865 return db_fetch_result($result, 0, "access_key");
1866 } else {
3ceb893f 1867 $key = db_escape_string(uniqid_short());
97b7d5c0
AD
1868
1869 $result = db_query("INSERT INTO ttrss_access_keys
1870 (access_key, feed_id, is_cat, owner_uid)
1871 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1872
1873 return $key;
1874 }
1875 return false;
1876 }
1877
1878 function get_feeds_from_html($url, $content)
1879 {
1880 $url = fix_url($url);
1881 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1882
1883 libxml_use_internal_errors(true);
1884
1885 $doc = new DOMDocument();
1886 $doc->loadHTML($content);
1887 $xpath = new DOMXPath($doc);
1888 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
16c48032 1889 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
97b7d5c0
AD
1890 $feedUrls = array();
1891 foreach ($entries as $entry) {
1892 if ($entry->hasAttribute('href')) {
1893 $title = $entry->getAttribute('title');
1894 if ($title == '') {
1895 $title = $entry->getAttribute('type');
1896 }
1897 $feedUrl = rewrite_relative_url(
1898 $baseUrl, $entry->getAttribute('href')
1899 );
1900 $feedUrls[$feedUrl] = $title;
1901 }
1902 }
1903 return $feedUrls;
1904 }
1905
1906 function is_html($content) {
5a4074a9 1907 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
97b7d5c0
AD
1908 }
1909
1910 function url_is_html($url, $login = false, $pass = false) {
1911 return is_html(fetch_file_contents($url, false, $login, $pass));
1912 }
1913
1914 function print_label_select($name, $value, $attributes = "") {
1915
1916 $result = db_query("SELECT caption FROM ttrss_labels2
1917 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1918
1919 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1bfe1d7b 1920 "\" $attributes>";
97b7d5c0
AD
1921
1922 while ($line = db_fetch_assoc($result)) {
1923
1924 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1925
1926 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1927 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1928
1929 }
1930
1931# print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1932
1933 print "</select>";
1934
1935
1936 }
1937
1938 function format_article_enclosures($id, $always_display_enclosures,
1939 $article_content, $hide_images = false) {
1940
1941 $result = get_article_enclosures($id);
1942 $rv = '';
1943
2bb11658
DZ
1944 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1945 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1946 if (is_array($retval)) {
1947 $rv = $retval[0];
1948 $result = $retval[1];
1949 } else {
1950 $rv = $retval;
1951 }
1952 }
84931635 1953 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
97b7d5c0 1954
c6ce584d 1955 if ($rv === '' && !empty($result)) {
97b7d5c0
AD
1956 $entries_html = array();
1957 $entries = array();
1958 $entries_inline = array();
1959
1960 foreach ($result as $line) {
1961
58210301 1962 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
676c7303 1963 $line = $plugin->hook_enclosure_entry($line);
58210301
AD
1964 }
1965
97b7d5c0
AD
1966 $url = $line["content_url"];
1967 $ctype = $line["content_type"];
1968 $title = $line["title"];
1e871938
FE
1969 $width = $line["width"];
1970 $height = $line["height"];
97b7d5c0
AD
1971
1972 if (!$ctype) $ctype = __("unknown type");
1973
c606bd57
AD
1974 //$filename = substr($url, strrpos($url, "/")+1);
1975 $filename = basename($url);
97b7d5c0
AD
1976
1977 $player = format_inline_player($url, $ctype);
1978
1979 if ($player) array_push($entries_inline, $player);
1980
ba2853ca 1981# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
97b7d5c0
AD
1982# $filename . " (" . $ctype . ")" . "</a>";
1983
829d478f 1984 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
97b7d5c0
AD
1985 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1986
1987 array_push($entries_html, $entry);
1988
1989 $entry = array();
1990
1991 $entry["type"] = $ctype;
1992 $entry["filename"] = $filename;
1993 $entry["url"] = $url;
1994 $entry["title"] = $title;
1e871938
FE
1995 $entry["width"] = $width;
1996 $entry["height"] = $height;
97b7d5c0
AD
1997
1998 array_push($entries, $entry);
1999 }
2000
2001 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
2002 if ($always_display_enclosures ||
2003 !preg_match("/<img/i", $article_content)) {
2004
2005 foreach ($entries as $entry) {
2006
945346cb
AD
2007 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
2008 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
97b7d5c0 2009
945346cb
AD
2010
2011 if ($retval) {
2012 $rv .= $retval;
2013 } else {
2014
dc2c4b13 2015 if (preg_match("/image/", $entry["type"])) {
945346cb
AD
2016
2017 if (!$hide_images) {
2018 $encsize = '';
2019 if ($entry['height'] > 0)
08e79cb6 2020 $encsize .= ' height="' . intval($entry['height']) . '"';
945346cb 2021 if ($entry['width'] > 0)
08e79cb6 2022 $encsize .= ' width="' . intval($entry['width']) . '"';
945346cb
AD
2023 $rv .= "<p><img
2024 alt=\"".htmlspecialchars($entry["filename"])."\"
2025 src=\"" .htmlspecialchars($entry["url"]) . "\"
2026 " . $encsize . " /></p>";
2027 } else {
ba2853ca 2028 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
945346cb
AD
2029 href=\"".htmlspecialchars($entry["url"])."\"
2030 >" .htmlspecialchars($entry["url"]) . "</a></p>";
2031 }
2032
2033 if ($entry['title']) {
2034 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
2035 }
2036 }
97b7d5c0
AD
2037 }
2038 }
2039 }
2040 }
2041
2042 if (count($entries_inline) > 0) {
2043 $rv .= "<hr clear='both'/>";
2044 foreach ($entries_inline as $entry) { $rv .= $entry; };
2045 $rv .= "<hr clear='both'/>";
2046 }
2047
6810a1de
AD
2048 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2049 "<span>" . __('Attachments')."</span>";
2050
2051 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
97b7d5c0
AD
2052
2053 foreach ($entries as $entry) {
2054 if ($entry["title"])
c606bd57 2055 $title = " &mdash; " . truncate_string($entry["title"], 30);
97b7d5c0
AD
2056 else
2057 $title = "";
2058
c606bd57
AD
2059 if ($entry["filename"])
2060 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
2061 else
2062 $filename = "";
2063
829d478f 2064 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
c606bd57 2065 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
97b7d5c0
AD
2066
2067 };
2068
6810a1de
AD
2069 $rv .= "</div>";
2070 $rv .= "</div>";
97b7d5c0
AD
2071 }
2072
2073 return $rv;
2074 }
2075
2076 function getLastArticleId() {
8458a312 2077 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2078 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
97b7d5c0
AD
2079
2080 if (db_num_rows($result) == 1) {
2081 return db_fetch_result($result, 0, "id");
2082 } else {
2083 return -1;
2084 }
2085 }
2086
2087 function build_url($parts) {
2088 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2089 }
2090
4a23031f
AD
2091 function cleanup_url_path($path) {
2092 $path = str_replace("/./", "/", $path);
2093 $path = str_replace("//", "/", $path);
2094
2095 return $path;
2096 }
2097
97b7d5c0
AD
2098 /**
2099 * Converts a (possibly) relative URL to a absolute one.
2100 *
2101 * @param string $url Base URL (i.e. from where the document is)
2102 * @param string $rel_url Possibly relative URL in the document
2103 *
2104 * @return string Absolute URL
2105 */
2106 function rewrite_relative_url($url, $rel_url) {
94d425fe 2107 if (strpos($rel_url, "://") !== false) {
97b7d5c0
AD
2108 return $rel_url;
2109 } else if (strpos($rel_url, "//") === 0) {
2110 # protocol-relative URL (rare but they exist)
2111 return $rel_url;
94d425fe
AD
2112 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2113 # magnet:, feed:, etc
2114 return $rel_url;
2115 } else if (strpos($rel_url, "/") === 0) {
97b7d5c0
AD
2116 $parts = parse_url($url);
2117 $parts['path'] = $rel_url;
4a23031f 2118 $parts['path'] = cleanup_url_path($parts['path']);
97b7d5c0
AD
2119
2120 return build_url($parts);
2121
2122 } else {
2123 $parts = parse_url($url);
2124 if (!isset($parts['path'])) {
2125 $parts['path'] = '/';
2126 }
2127 $dir = $parts['path'];
2128 if (substr($dir, -1) !== '/') {
2129 $dir = dirname($parts['path']);
2130 $dir !== '/' && $dir .= '/';
2131 }
2132 $parts['path'] = $dir . $rel_url;
4a23031f 2133 $parts['path'] = cleanup_url_path($parts['path']);
97b7d5c0
AD
2134
2135 return build_url($parts);
2136 }
2137 }
2138
97b7d5c0
AD
2139 function cleanup_tags($days = 14, $limit = 1000) {
2140
2141 if (DB_TYPE == "pgsql") {
2142 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2143 } else if (DB_TYPE == "mysql") {
2144 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2145 }
2146
2147 $tags_deleted = 0;
2148
2149 while ($limit > 0) {
2150 $limit_part = 500;
2151
2152 $query = "SELECT ttrss_tags.id AS id
2153 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2154 WHERE post_int_id = int_id AND $interval_query AND
2155 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2156
2157 $result = db_query($query);
2158
2159 $ids = array();
2160
2161 while ($line = db_fetch_assoc($result)) {
2162 array_push($ids, $line['id']);
2163 }
2164
2165 if (count($ids) > 0) {
2166 $ids = join(",", $ids);
2167
2168 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2169 $tags_deleted += db_affected_rows($tmp_result);
2170 } else {
2171 break;
2172 }
2173
2174 $limit -= $limit_part;
2175 }
2176
2177 return $tags_deleted;
2178 }
2179
2180 function print_user_stylesheet() {
2181 $value = get_pref('USER_STYLESHEET');
2182
2183 if ($value) {
2184 print "<style type=\"text/css\">";
2185 print str_replace("<br/>", "\n", $value);
2186 print "</style>";
2187 }
2188
2189 }
2190
2191 function filter_to_sql($filter, $owner_uid) {
2192 $query = array();
2193
2194 if (DB_TYPE == "pgsql")
2195 $reg_qpart = "~";
2196 else
2197 $reg_qpart = "REGEXP";
2198
2199 foreach ($filter["rules"] AS $rule) {
2200 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2201 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2202 $rule['reg_exp']) !== FALSE;
2203
2204 if ($regexp_valid) {
2205
2206 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2207
2208 switch ($rule["type"]) {
2209 case "title":
2210 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2211 $rule['reg_exp'] . "')";
2212 break;
2213 case "content":
2214 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2215 $rule['reg_exp'] . "')";
2216 break;
2217 case "both":
2218 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2219 $rule['reg_exp'] . "') OR LOWER(" .
2220 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2221 break;
2222 case "tag":
2223 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2224 $rule['reg_exp'] . "')";
2225 break;
2226 case "link":
2227 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2228 $rule['reg_exp'] . "')";
2229 break;
2230 case "author":
2231 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2232 $rule['reg_exp'] . "')";
2233 break;
2234 }
2235
2236 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2237
2238 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2239 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2240 }
2241
2242 if (isset($rule["cat_id"])) {
2243
2244 if ($rule["cat_id"] > 0) {
2245 $children = getChildCategories($rule["cat_id"], $owner_uid);
2246 array_push($children, $rule["cat_id"]);
2247
2248 $children = join(",", $children);
2249
2250 $cat_qpart = "cat_id IN ($children)";
2251 } else {
2252 $cat_qpart = "cat_id IS NULL";
2253 }
2254
2255 $qpart .= " AND $cat_qpart";
2256 }
2257
2258 $qpart .= " AND feed_id IS NOT NULL";
2259
2260 array_push($query, "($qpart)");
2261
2262 }
2263 }
2264
2265 if (count($query) > 0) {
2266 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2267 } else {
2268 $fullquery = "(false)";
2269 }
2270
2271 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2272
2273 return $fullquery;
2274 }
2275
2276 if (!function_exists('gzdecode')) {
2277 function gzdecode($string) { // no support for 2nd argument
2278 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2279 base64_encode($string));
2280 }
2281 }
2282
2283 function get_random_bytes($length) {
2284 if (function_exists('openssl_random_pseudo_bytes')) {
2285 return openssl_random_pseudo_bytes($length);
2286 } else {
2287 $output = "";
2288
2289 for ($i = 0; $i < $length; $i++)
2290 $output .= chr(mt_rand(0, 255));
2291
2292 return $output;
2293 }
2294 }
2295
2296 function read_stdin() {
2297 $fp = fopen("php://stdin", "r");
2298
2299 if ($fp) {
2300 $line = trim(fgets($fp));
2301 fclose($fp);
2302 return $line;
2303 }
2304
2305 return null;
2306 }
2307
2308 function tmpdirname($path, $prefix) {
2309 // Use PHP's tmpfile function to create a temporary
2310 // directory name. Delete the file and keep the name.
2311 $tempname = tempnam($path,$prefix);
2312 if (!$tempname)
2313 return false;
2314
2315 if (!unlink($tempname))
2316 return false;
2317
2318 return $tempname;
2319 }
2320
2321 function getFeedCategory($feed) {
2322 $result = db_query("SELECT cat_id FROM ttrss_feeds
2323 WHERE id = '$feed'");
2324
2325 if (db_num_rows($result) > 0) {
2326 return db_fetch_result($result, 0, "cat_id");
2327 } else {
2328 return false;
2329 }
2330
2331 }
2332
2333 function implements_interface($class, $interface) {
2334 return in_array($interface, class_implements($class));
2335 }
2336
97b7d5c0
AD
2337 function get_minified_js($files) {
2338 require_once 'lib/jshrink/Minifier.php';
2339
2340 $rv = '';
2341
2342 foreach ($files as $js) {
2343 if (!isset($_GET['debug'])) {
2344 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2345
aa9f7d44 2346 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
97b7d5c0 2347
aa9f7d44 2348 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
97b7d5c0 2349
aa9f7d44
AD
2350 if ($header && $contents) {
2351 list($htag, $hversion) = explode(":", $header);
2352
2353 if ($htag == "tt-rss" && $hversion == VERSION) {
2354 $rv .= $contents;
2355 continue;
2356 }
2357 }
97b7d5c0 2358 }
aa9f7d44
AD
2359
2360 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2361 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2362 $rv .= $minified;
2363
97b7d5c0 2364 } else {
aa9f7d44 2365 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
97b7d5c0
AD
2366 }
2367 }
2368
2369 return $rv;
2370 }
2371
2372 function stylesheet_tag($filename) {
2373 $timestamp = filemtime($filename);
2374
2375 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2376 }
2377
2378 function javascript_tag($filename) {
2379 $query = "";
2380
2381 if (!(strpos($filename, "?") === FALSE)) {
2382 $query = substr($filename, strpos($filename, "?")+1);
2383 $filename = substr($filename, 0, strpos($filename, "?"));
2384 }
2385
2386 $timestamp = filemtime($filename);
2387
2388 if ($query) $timestamp .= "&$query";
2389
2390 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2391 }
2392
2393 function calculate_dep_timestamp() {
2394 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2395
2396 $max_ts = -1;
2397
2398 foreach ($files as $file) {
2399 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2400 }
2401
2402 return $max_ts;
2403 }
2404
2405 function T_js_decl($s1, $s2) {
2406 if ($s1 && $s2) {
2407 $s1 = preg_replace("/\n/", "", $s1);
2408 $s2 = preg_replace("/\n/", "", $s2);
2409
2410 $s1 = preg_replace("/\"/", "\\\"", $s1);
2411 $s2 = preg_replace("/\"/", "\\\"", $s2);
2412
2413 return "T_messages[\"$s1\"] = \"$s2\";\n";
2414 }
2415 }
2416
2417 function init_js_translations() {
2418
2419 print 'var T_messages = new Object();
2420
2421 function __(msg) {
2422 if (T_messages[msg]) {
2423 return T_messages[msg];
2424 } else {
2425 return msg;
2426 }
2427 }
2428
2429 function ngettext(msg1, msg2, n) {
2430 return __((parseInt(n) > 1) ? msg2 : msg1);
2431 }';
2432
2433 $l10n = _get_reader();
2434
2435 for ($i = 0; $i < $l10n->total; $i++) {
2436 $orig = $l10n->get_original_string($i);
2437 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2438 $key = explode(chr(0), $orig);
2439 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2440 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2441 } else {
2442 $translation = __($orig);
2443 print T_js_decl($orig, $translation);
2444 }
2445 }
2446 }
2447
2448 function label_to_feed_id($label) {
2449 return LABEL_BASE_INDEX - 1 - abs($label);
2450 }
2451
2452 function feed_to_label_id($feed) {
2453 return LABEL_BASE_INDEX - 1 + abs($feed);
2454 }
2455
b9634eb8
AD
2456 function get_theme_path($theme) {
2457 $check = "themes/$theme";
2458 if (file_exists($check)) return $check;
2459
2460 $check = "themes.local/$theme";
2461 if (file_exists($check)) return $check;
2462 }
2463
2464 function theme_valid($theme) {
181c8285
AD
2465 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2466
2467 if (in_array($theme, $bundled_themes)) return true;
2468
b9634eb8
AD
2469 $file = "themes/" . basename($theme);
2470
2471 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
f6cbe9a5
AD
2472
2473 if (file_exists($file) && is_readable($file)) {
2474 $fh = fopen($file, "r");
2475
2476 if ($fh) {
2477 $header = fgets($fh);
2478 fclose($fh);
2479
2480 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2481 }
2482 }
2483
2484 return false;
2485 }
27f7b593 2486
7b55001e
AD
2487 /**
2488 * @SuppressWarnings(unused)
2489 */
27f7b593
AD
2490 function error_json($code) {
2491 require_once "errors.php";
2492
2493 @$message = $ERRORS[$code];
2494
2495 return json_encode(array("error" =>
2496 array("code" => $code, "message" => $message)));
2497
2498 }
7c0a2ab2
AD
2499
2500 function abs_to_rel_path($dir) {
2501 $tmp = str_replace(dirname(__DIR__), "", $dir);
2502
2503 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2504
2505 return $tmp;
2506 }
1a322ff3
AD
2507
2508 function get_upload_error_message($code) {
2509
2510 $errors = array(
2511 0 => __('There is no error, the file uploaded with success'),
2512 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2513 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2514 3 => __('The uploaded file was only partially uploaded'),
2515 4 => __('No file was uploaded'),
2516 6 => __('Missing a temporary folder'),
2517 7 => __('Failed to write file to disk.'),
2518 8 => __('A PHP extension stopped the file upload.'),
2519 );
2520
2521 return $errors[$code];
2522 }
70c0a8c2
AD
2523
2524 function base64_img($filename) {
2525 if (file_exists($filename)) {
2526 $ext = pathinfo($filename, PATHINFO_EXTENSION);
2527
2528 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));
2529 } else {
2530 return "";
2531 }
2532 }
97b7d5c0 2533?>