]> git.wh0rd.org - tt-rss.git/blame - include/functions2.php
enable caching of media in article enclosures
[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
AD
914 if (file_exists($cached_filename)) {
915 $src = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($src);
7818bfde 916
48eefd8c
AD
917 if ($entry->hasAttribute('srcset')) {
918 $entry->removeAttribute('srcset');
919 }
8519c68d 920
48eefd8c
AD
921 if ($entry->hasAttribute('sizes')) {
922 $entry->removeAttribute('sizes');
7818bfde 923 }
97b7d5c0
AD
924 }
925
7818bfde
AD
926 $entry->setAttribute('src', $src);
927 }
928
929 if ($entry->nodeName == 'img') {
97b7d5c0 930
7818bfde
AD
931 if ($entry->hasAttribute('src')) {
932 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
a01bfd78 933
7818bfde 934 if ($ttrss_uses_https && !$is_https_url) {
a01bfd78 935
5edd605a
AD
936 if ($entry->hasAttribute('srcset')) {
937 $entry->removeAttribute('srcset');
a01bfd78 938 }
97b7d5c0 939
5edd605a
AD
940 if ($entry->hasAttribute('sizes')) {
941 $entry->removeAttribute('sizes');
942 }
0442cbb6 943 }
5edd605a 944 }
5edd605a 945
7818bfde
AD
946 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
947 $force_remove_images || $_SESSION["bw_limit"]) {
a536f94c 948
7818bfde 949 $p = $doc->createElement('p');
a536f94c 950
7818bfde
AD
951 $a = $doc->createElement('a');
952 $a->setAttribute('href', $entry->getAttribute('src'));
a536f94c 953
7818bfde
AD
954 $a->appendChild(new DOMText($entry->getAttribute('src')));
955 $a->setAttribute('target', '_blank');
ba2853ca 956 $a->setAttribute('rel', 'noopener noreferrer');
a536f94c 957
7818bfde 958 $p->appendChild($a);
97b7d5c0 959
7818bfde 960 $entry->parentNode->replaceChild($p, $entry);
97b7d5c0
AD
961 }
962 }
963
964 if (strtolower($entry->nodeName) == "a") {
965 $entry->setAttribute("target", "_blank");
ba2853ca 966 $entry->setAttribute("rel", "noopener noreferrer");
97b7d5c0
AD
967 }
968 }
969
970 $entries = $xpath->query('//iframe');
971 foreach ($entries as $entry) {
59b5d5f3
AD
972 if (!iframe_whitelisted($entry)) {
973 $entry->setAttribute('sandbox', 'allow-scripts');
c63850fa
AD
974 } else {
975 if ($_SERVER['HTTPS'] == "on") {
976 $entry->setAttribute("src",
977 str_replace("http://", "https://",
978 $entry->getAttribute("src")));
979 }
59b5d5f3 980 }
97b7d5c0
AD
981 }
982
67268b00 983 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
97b7d5c0
AD
984 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
985 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
311cdb27 986 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
97b7d5c0 987 'dt', 'em', 'footer', 'figure', 'figcaption',
e2a3689a 988 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
97b7d5c0
AD
989 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
990 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
991 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
992 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
50bda3fe 993 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
97b7d5c0
AD
994
995 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
996
997 $disallowed_attributes = array('id', 'style', 'class');
998
999 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
1000 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
1001 if (is_array($retval)) {
1002 $doc = $retval[0];
1003 $allowed_elements = $retval[1];
1004 $disallowed_attributes = $retval[2];
1005 } else {
1006 $doc = $retval;
1007 }
1008 }
1009
1010 $doc->removeChild($doc->firstChild); //remove doctype
1011 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1012
1013 if ($highlight_words) {
1014 foreach ($highlight_words as $word) {
1015
1016 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1017
1018 $elements = $xpath->query("//*/text()");
1019
1020 foreach ($elements as $child) {
1021
1022 $fragment = $doc->createDocumentFragment();
1023 $text = $child->textContent;
1024
1025 while (($pos = mb_stripos($text, $word)) !== false) {
1026 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1027 $word = mb_substr($text, $pos, mb_strlen($word));
1028 $highlight = $doc->createElement('span');
1029 $highlight->appendChild(new DomText($word));
1030 $highlight->setAttribute('class', 'highlight');
1031 $fragment->appendChild($highlight);
1032 $text = mb_substr($text, $pos + mb_strlen($word));
1033 }
1034
1035 if (!empty($text)) $fragment->appendChild(new DomText($text));
1036
1037 $child->parentNode->replaceChild($fragment, $child);
1038 }
1039 }
1040 }
1041
1042 $res = $doc->saveHTML();
1043
42f78188
AD
1044 /* strip everything outside of <body>...</body> */
1045
1046 $res_frag = array();
1047 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1048 return $res_frag[1];
1049 } else {
1050 return $res;
1051 }
97b7d5c0
AD
1052 }
1053
1054 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1055 $xpath = new DOMXPath($doc);
1056 $entries = $xpath->query('//*');
1057
1058 foreach ($entries as $entry) {
1059 if (!in_array($entry->nodeName, $allowed_elements)) {
1060 $entry->parentNode->removeChild($entry);
1061 }
1062
1063 if ($entry->hasAttributes()) {
1064 $attrs_to_remove = array();
1065
1066 foreach ($entry->attributes as $attr) {
1067
1068 if (strpos($attr->nodeName, 'on') === 0) {
1069 array_push($attrs_to_remove, $attr);
1070 }
1071
d8b0f067
J
1072 if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1073 array_push($attrs_to_remove, $attr);
1074 }
1075
97b7d5c0
AD
1076 if (in_array($attr->nodeName, $disallowed_attributes)) {
1077 array_push($attrs_to_remove, $attr);
1078 }
1079 }
1080
1081 foreach ($attrs_to_remove as $attr) {
1082 $entry->removeAttributeNode($attr);
1083 }
1084 }
1085 }
1086
1087 return $doc;
1088 }
1089
97b7d5c0
AD
1090 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1091
1092 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1093 if (count($ids) == 0) return;
1094
1095 $tmp_ids = array();
1096
1097 foreach ($ids as $id) {
1098 array_push($tmp_ids, "ref_id = '$id'");
1099 }
1100
1101 $ids_qpart = join(" OR ", $tmp_ids);
1102
1103 if ($cmode == 0) {
1104 db_query("UPDATE ttrss_user_entries SET
1105 unread = false,last_read = NOW()
1106 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1107 } else if ($cmode == 1) {
1108 db_query("UPDATE ttrss_user_entries SET
1109 unread = true
1110 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1111 } else {
1112 db_query("UPDATE ttrss_user_entries SET
1113 unread = NOT unread,last_read = NOW()
1114 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1115 }
1116
1117 /* update ccache */
1118
1119 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1120 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1121
1122 while ($line = db_fetch_assoc($result)) {
1123 ccache_update($line["feed_id"], $owner_uid);
1124 }
1125 }
1126
1127 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1128
1129 $a_id = db_escape_string($id);
1130
1131 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1132
1133 $query = "SELECT DISTINCT tag_name,
1134 owner_uid as owner FROM
1135 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1136 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1137
1138 $tags = array();
1139
1140 /* check cache first */
1141
1142 if ($tag_cache === false) {
1143 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1144 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1145
c3522486
AD
1146 if (db_num_rows($result) != 0)
1147 $tag_cache = db_fetch_result($result, 0, "tag_cache");
97b7d5c0
AD
1148 }
1149
1150 if ($tag_cache) {
1151 $tags = explode(",", $tag_cache);
1152 } else {
1153
1154 /* do it the hard way */
1155
1156 $tmp_result = db_query($query);
1157
1158 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1159 array_push($tags, $tmp_line["tag_name"]);
1160 }
1161
1162 /* update the cache */
1163
1164 $tags_str = db_escape_string(join(",", $tags));
1165
1166 db_query("UPDATE ttrss_user_entries
1167 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1168 AND owner_uid = $owner_uid");
1169 }
1170
1171 return $tags;
1172 }
1173
1174 function trim_array($array) {
1175 $tmp = $array;
1176 array_walk($tmp, 'trim');
1177 return $tmp;
1178 }
1179
1180 function tag_is_valid($tag) {
1181 if ($tag == '') return false;
85d067e8 1182 if (is_numeric($tag)) return false;
97b7d5c0
AD
1183 if (mb_strlen($tag) > 250) return false;
1184
1185 if (!$tag) return false;
1186
1187 return true;
1188 }
1189
1190 function render_login_form() {
1191 header('Cache-Control: public');
1192
1193 require_once "login_form.php";
1194 exit;
1195 }
1196
1197 function format_warning($msg, $id = "") {
c1ebb6cd 1198 return "<div class=\"alert\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1199 }
1200
1201 function format_notice($msg, $id = "") {
c1ebb6cd 1202 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1203 }
1204
1205 function format_error($msg, $id = "") {
c1ebb6cd 1206 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1207 }
1208
1209 function print_notice($msg) {
1210 return print format_notice($msg);
1211 }
1212
1213 function print_warning($msg) {
1214 return print format_warning($msg);
1215 }
1216
1217 function print_error($msg) {
1218 return print format_error($msg);
1219 }
1220
1221
1222 function T_sprintf() {
1223 $args = func_get_args();
1224 return vsprintf(__(array_shift($args)), $args);
1225 }
1226
1227 function format_inline_player($url, $ctype) {
1228
1229 $entry = "";
1230
1231 $url = htmlspecialchars($url);
1232
1233 if (strpos($ctype, "audio/") === 0) {
1234
1235 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1236 $_SESSION["hasMp3"])) {
1237
1238 $entry .= "<audio preload=\"none\" controls>
83ce77a2 1239 <source type=\"$ctype\" src=\"$url\"/>
97b7d5c0
AD
1240 </audio>";
1241
1242 } else {
1243
1244 $entry .= "<object type=\"application/x-shockwave-flash\"
1245 data=\"lib/button/musicplayer.swf?song_url=$url\"
1246 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1247 <param name=\"movie\"
1248 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1249 </object>";
1250 }
1251
ba2853ca 1252 if ($entry) $entry .= "&nbsp; <a target=\"_blank\" rel=\"noopener noreferrer\"
97b7d5c0
AD
1253 href=\"$url\">" . basename($url) . "</a>";
1254
1255 return $entry;
1256
1257 }
1258
1259 return "";
1260
1261/* $filename = substr($url, strrpos($url, "/")+1);
1262
ba2853ca 1263 $entry .= " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"" . htmlspecialchars($url) . "\">" .
97b7d5c0
AD
1264 $filename . " (" . $ctype . ")" . "</a>"; */
1265
1266 }
1267
1268 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1269 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1270
1271 $rv = array();
1272
1273 $rv['id'] = $id;
1274
1275 /* we can figure out feed_id from article id anyway, why do we
1276 * pass feed_id here? let's ignore the argument :(*/
1277
1278 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1279 WHERE ref_id = '$id'");
1280
1281 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1282
1283 $rv['feed_id'] = $feed_id;
1284
1285 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1286
1287 if ($mark_as_read) {
1288 $result = db_query("UPDATE ttrss_user_entries
1289 SET unread = false,last_read = NOW()
1290 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1291
1292 ccache_update($feed_id, $owner_uid);
1293 }
1294
1295 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1296 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1297 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1298 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1299 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1300 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1301 num_comments,
1302 tag_cache,
1303 author,
553ec3c3 1304 guid,
97b7d5c0
AD
1305 orig_feed_id,
1306 note
1307 FROM ttrss_entries,ttrss_user_entries
1308 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1309
1310 if ($result) {
1311
1312 $line = db_fetch_assoc($result);
1313
1314 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1315 unset($line["tag_cache"]);
1316
1317 $line["content"] = sanitize($line["content"],
1318 sql_bool_to_bool($line['hide_images']),
1319 $owner_uid, $line["site_url"], false, $line["id"]);
1320
1321 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1322 $line = $p->hook_render_article($line);
1323 }
1324
967f0619 1325 $num_comments = (int) $line["num_comments"];
97b7d5c0
AD
1326 $entry_comments = "";
1327
1328 if ($num_comments > 0) {
1329 if ($line["comments"]) {
1330 $comments_url = htmlspecialchars($line["comments"]);
1331 } else {
1332 $comments_url = htmlspecialchars($line["link"]);
1333 }
1334 $entry_comments = "<a class=\"postComments\"
ba2853ca 1335 target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
97b7d5c0
AD
1336 _ngettext("comment", "comments", $num_comments)."</a>";
1337
1338 } else {
1339 if ($line["comments"] && $line["link"] != $line["comments"]) {
ba2853ca 1340 $entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
97b7d5c0
AD
1341 }
1342 }
1343
1344 if ($zoom_mode) {
1345 header("Content-Type: text/html");
1346 $rv['content'] .= "<html><head>
1347 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1348 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1349 stylesheet_tag("css/tt-rss.css").
1350 stylesheet_tag("css/zoom.css").
1351 stylesheet_tag("css/dijit.css")."
1352
1353 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1354 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1355
97b7d5c0
AD
1356 </head><body id=\"ttrssZoom\">";
1357 }
1358
1359 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1360
1361 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1362
1363 $entry_author = $line["author"];
1364
1365 if ($entry_author) {
1366 $entry_author = __(" - ") . $entry_author;
1367 }
1368
1369 $parsed_updated = make_local_datetime($line["updated"], true,
1370 $owner_uid, true);
1371
1372 if (!$zoom_mode)
1373 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1374
1375 if ($line["link"]) {
ba2853ca 1376 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
97b7d5c0
AD
1377 title=\"".htmlspecialchars($line['title'])."\"
1378 href=\"" .
1379 htmlspecialchars($line["link"]) . "\">" .
1380 $line["title"] . "</a>" .
1381 "<span class='author'>$entry_author</span></div>";
1382 } else {
1383 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1384 }
1385
1386 if ($zoom_mode) {
6687cb99 1387 $feed_title = htmlspecialchars($line["feed_title"]);
97b7d5c0
AD
1388
1389 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1390
1391 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1392 }
1393
1394 $tags_str = format_tags_string($line["tags"], $id);
1395 $tags_str_full = join(", ", $line["tags"]);
1396
1397 if (!$tags_str_full) $tags_str_full = __("no tags");
1398
1399 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1400
1401 $rv['content'] .= "<div class='postTags' style='float : right'>
1402 <img src='images/tag.png'
1403 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1404
1405 if (!$zoom_mode) {
1406 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1407 <a title=\"".__('Edit tags for this article')."\"
1408 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1409
1410 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1411 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1412 position=\"below\">$tags_str_full</div>";
1413
1414 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1415 $rv['content'] .= $p->hook_article_button($line);
1416 }
1417
1418 } else {
1419 $tags_str = strip_tags($tags_str);
1420 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1421 }
1422 $rv['content'] .= "</div>";
1423 $rv['content'] .= "<div clear='both'>";
1424
1425 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1426 $rv['content'] .= $p->hook_article_left_button($line);
1427 }
1428
1429 $rv['content'] .= "$entry_comments</div>";
1430
1431 if ($line["orig_feed_id"]) {
1432
1433 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
71b75bb7 1434 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
97b7d5c0
AD
1435
1436 if (db_num_rows($tmp_result) != 0) {
1437
1438 $rv['content'] .= "<div clear='both'>";
1439 $rv['content'] .= __("Originally from:");
1440
1441 $rv['content'] .= "&nbsp;";
1442
1443 $tmp_line = db_fetch_assoc($tmp_result);
1444
ba2853ca 1445 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
97b7d5c0
AD
1446 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1447 $tmp_line['title'] . "</a>";
1448
1449 $rv['content'] .= "&nbsp;";
1450
ba2853ca 1451 $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
97b7d5c0
AD
1452 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1453
1454 $rv['content'] .= "</div>";
1455 }
1456 }
1457
1458 $rv['content'] .= "</div>";
1459
1460 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1461 if ($line['note']) {
1462 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1463 }
1464 $rv['content'] .= "</div>";
1465
1466 if (!$line['lang']) $line['lang'] = 'en';
1467
1468 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1469
1470 $rv['content'] .= $line["content"];
6810a1de
AD
1471
1472 if (!$zoom_mode) {
1473 $rv['content'] .= format_article_enclosures($id,
1474 sql_bool_to_bool($line["always_display_enclosures"]),
1475 $line["content"],
1476 sql_bool_to_bool($line["hide_images"]));
1477 }
97b7d5c0
AD
1478
1479 $rv['content'] .= "</div>";
1480
1481 $rv['content'] .= "</div>";
1482
1483 }
1484
1485 if ($zoom_mode) {
1486 $rv['content'] .= "
1487 <div class='footer'>
1488 <button onclick=\"return window.close()\">".
1489 __("Close this window")."</button></div>";
1490 $rv['content'] .= "</body></html>";
1491 }
1492
1493 return $rv;
1494
1495 }
1496
1497 function print_checkpoint($n, $s) {
1498 $ts = microtime(true);
1499 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1500 return $ts;
1501 }
1502
1503 function sanitize_tag($tag) {
1504 $tag = trim($tag);
1505
1506 $tag = mb_strtolower($tag, 'utf-8');
1507
0e4da73f 1508 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
97b7d5c0 1509
35c37354
AD
1510 if (DB_TYPE == "mysql") {
1511 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1512 }
97b7d5c0
AD
1513
1514 return $tag;
1515 }
1516
1517 function get_self_url_prefix() {
1518 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1519 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1520 } else {
1521 return SELF_URL_PATH;
1522 }
1523 }
1524
1525 /**
1526 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1527 *
1528 * @return string The Mozilla Firefox feed adding URL.
1529 */
1530 function add_feed_url() {
1531 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1532
1533 $url_path = get_self_url_prefix() .
1534 "/public.php?op=subscribe&feed_url=%s";
1535 return $url_path;
1536 } // function add_feed_url
1537
1538 function encrypt_password($pass, $salt = '', $mode2 = false) {
1539 if ($salt && $mode2) {
1540 return "MODE2:" . hash('sha256', $salt . $pass);
1541 } else if ($salt) {
1542 return "SHA1X:" . sha1("$salt:$pass");
1543 } else {
1544 return "SHA1:" . sha1($pass);
1545 }
1546 } // function encrypt_password
1547
1548 function load_filters($feed_id, $owner_uid, $action_id = false) {
1549 $filters = array();
1550
1551 $cat_id = (int)getFeedCategory($feed_id);
1552
1553 if ($cat_id == 0)
1554 $null_cat_qpart = "cat_id IS NULL OR";
1555 else
1556 $null_cat_qpart = "";
1557
1558 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1559 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1560
1561 $check_cats = join(",", array_merge(
1562 getParentCategories($cat_id, $owner_uid),
1563 array($cat_id)));
1564
1565 while ($line = db_fetch_assoc($result)) {
1566 $filter_id = $line["id"];
1567
1568 $result2 = db_query("SELECT
1569 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1570 FROM ttrss_filters2_rules AS r,
1571 ttrss_filter_types AS t
1572 WHERE
1573 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1574 (feed_id IS NULL OR feed_id = '$feed_id') AND
1575 filter_type = t.id AND filter_id = '$filter_id'");
1576
1577 $rules = array();
1578 $actions = array();
1579
1580 while ($rule_line = db_fetch_assoc($result2)) {
1581# print_r($rule_line);
1582
1583 $rule = array();
1584 $rule["reg_exp"] = $rule_line["reg_exp"];
1585 $rule["type"] = $rule_line["type_name"];
1586 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1587
1588 array_push($rules, $rule);
1589 }
1590
1591 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1592 FROM ttrss_filters2_actions AS a,
1593 ttrss_filter_actions AS t
1594 WHERE
1595 action_id = t.id AND filter_id = '$filter_id'");
1596
1597 while ($action_line = db_fetch_assoc($result2)) {
1598# print_r($action_line);
1599
1600 $action = array();
1601 $action["type"] = $action_line["type_name"];
1602 $action["param"] = $action_line["action_param"];
1603
1604 array_push($actions, $action);
1605 }
1606
1607
1608 $filter = array();
1609 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1610 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1611 $filter["rules"] = $rules;
1612 $filter["actions"] = $actions;
1613
1614 if (count($rules) > 0 && count($actions) > 0) {
1615 array_push($filters, $filter);
1616 }
1617 }
1618
1619 return $filters;
1620 }
1621
1622 function get_score_pic($score) {
1623 if ($score > 100) {
1624 return "score_high.png";
1625 } else if ($score > 0) {
1626 return "score_half_high.png";
1627 } else if ($score < -100) {
1628 return "score_low.png";
1629 } else if ($score < 0) {
1630 return "score_half_low.png";
1631 } else {
1632 return "score_neutral.png";
1633 }
1634 }
1635
1636 function feed_has_icon($id) {
1637 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1638 }
1639
1640 function init_plugins() {
1641 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1642
1643 return true;
1644 }
1645
1646 function format_tags_string($tags, $id) {
1647 if (!is_array($tags) || count($tags) == 0) {
1648 return __("no tags");
1649 } else {
1650 $maxtags = min(5, count($tags));
83ce77a2 1651 $tags_str = "";
97b7d5c0
AD
1652
1653 for ($i = 0; $i < $maxtags; $i++) {
dcbe36b2 1654 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
97b7d5c0
AD
1655 }
1656
1657 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1658
1659 if (count($tags) > $maxtags)
1660 $tags_str .= ", &hellip;";
1661
1662 return $tags_str;
1663 }
1664 }
1665
1666 function format_article_labels($labels, $id) {
1667
1668 if (!is_array($labels)) return '';
1669
1670 $labels_str = "";
1671
1672 foreach ($labels as $l) {
1673 $labels_str .= sprintf("<span class='hlLabelRef'
1674 style='color : %s; background-color : %s'>%s</span>",
1675 $l[2], $l[3], $l[1]);
1676 }
1677
1678 return $labels_str;
1679
1680 }
1681
1682 function format_article_note($id, $note, $allow_edit = true) {
1683
1684 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1685 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1686 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1687
1688 return $str;
1689 }
1690
1691
1692 function get_feed_category($feed_cat, $parent_cat_id = false) {
1693 if ($parent_cat_id) {
1694 $parent_qpart = "parent_cat = '$parent_cat_id'";
1695 $parent_insert = "'$parent_cat_id'";
1696 } else {
1697 $parent_qpart = "parent_cat IS NULL";
1698 $parent_insert = "NULL";
1699 }
1700
1701 $result = db_query(
1702 "SELECT id FROM ttrss_feed_categories
1703 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1704
1705 if (db_num_rows($result) == 0) {
1706 return false;
1707 } else {
1708 return db_fetch_result($result, 0, "id");
1709 }
1710 }
1711
1712 function add_feed_category($feed_cat, $parent_cat_id = false) {
1713
1714 if (!$feed_cat) return false;
1715
1716 db_query("BEGIN");
1717
1718 if ($parent_cat_id) {
1719 $parent_qpart = "parent_cat = '$parent_cat_id'";
1720 $parent_insert = "'$parent_cat_id'";
1721 } else {
1722 $parent_qpart = "parent_cat IS NULL";
1723 $parent_insert = "NULL";
1724 }
1725
1726 $feed_cat = mb_substr($feed_cat, 0, 250);
1727
1728 $result = db_query(
1729 "SELECT id FROM ttrss_feed_categories
1730 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1731
1732 if (db_num_rows($result) == 0) {
1733
1734 $result = db_query(
1735 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1736 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1737
1738 db_query("COMMIT");
1739
1740 return true;
1741 }
1742
1743 return false;
1744 }
1745
1746 function getArticleFeed($id) {
1747 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1748 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1749
1750 if (db_num_rows($result) != 0) {
1751 return db_fetch_result($result, 0, "feed_id");
1752 } else {
1753 return 0;
1754 }
1755 }
1756
1757 /**
1758 * Fixes incomplete URLs by prepending "http://".
1759 * Also replaces feed:// with http://, and
1760 * prepends a trailing slash if the url is a domain name only.
1761 *
1762 * @param string $url Possibly incomplete URL
1763 *
1764 * @return string Fixed URL.
1765 */
1766 function fix_url($url) {
dd6e2386
AD
1767
1768 // support schema-less urls
1769 if (strpos($url, '//') === 0) {
1770 $url = 'https:' . $url;
1771 }
1772
97b7d5c0
AD
1773 if (strpos($url, '://') === false) {
1774 $url = 'http://' . $url;
1775 } else if (substr($url, 0, 5) == 'feed:') {
1776 $url = 'http:' . substr($url, 5);
1777 }
1778
1779 //prepend slash if the URL has no slash in it
1780 // "http://www.example" -> "http://www.example/"
1781 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1782 $url .= '/';
1783 }
1784
62958fe9
BT
1785 //convert IDNA hostname to punycode if possible
1786 if (function_exists("idn_to_ascii")) {
1787 $parts = parse_url($url);
1788 if (mb_detect_encoding($parts['host']) != 'ASCII')
1789 {
1790 $parts['host'] = idn_to_ascii($parts['host']);
1791 $url = build_url($parts);
1792 }
1793 }
1794
97b7d5c0
AD
1795 if ($url != "http:///")
1796 return $url;
1797 else
1798 return '';
1799 }
1800
1801 function validate_feed_url($url) {
1802 $parts = parse_url($url);
1803
1804 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1805
1806 }
1807
1808 function get_article_enclosures($id) {
1809
1810 $query = "SELECT * FROM ttrss_enclosures
1811 WHERE post_id = '$id' AND content_url != ''";
1812
1813 $rv = array();
1814
1815 $result = db_query($query);
1816
1817 if (db_num_rows($result) > 0) {
1818 while ($line = db_fetch_assoc($result)) {
388d4dfa
AD
1819
1820 if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) {
1821 $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]);
1822 }
1823
97b7d5c0
AD
1824 array_push($rv, $line);
1825 }
1826 }
1827
1828 return $rv;
1829 }
1830
3e0f2090 1831 /* function save_email_address($email) {
97b7d5c0
AD
1832 // FIXME: implement persistent storage of emails
1833
1834 if (!$_SESSION['stored_emails'])
1835 $_SESSION['stored_emails'] = array();
1836
1837 if (!in_array($email, $_SESSION['stored_emails']))
1838 array_push($_SESSION['stored_emails'], $email);
3e0f2090 1839 } */
97b7d5c0
AD
1840
1841
1842 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1843
1844 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1845
1846 $sql_is_cat = bool_to_sql_bool($is_cat);
1847
1848 $result = db_query("SELECT access_key FROM ttrss_access_keys
1849 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1850 AND owner_uid = " . $owner_uid);
1851
1852 if (db_num_rows($result) == 1) {
1853 return db_fetch_result($result, 0, "access_key");
1854 } else {
3ceb893f 1855 $key = db_escape_string(uniqid_short());
97b7d5c0
AD
1856
1857 $result = db_query("INSERT INTO ttrss_access_keys
1858 (access_key, feed_id, is_cat, owner_uid)
1859 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1860
1861 return $key;
1862 }
1863 return false;
1864 }
1865
1866 function get_feeds_from_html($url, $content)
1867 {
1868 $url = fix_url($url);
1869 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1870
1871 libxml_use_internal_errors(true);
1872
1873 $doc = new DOMDocument();
1874 $doc->loadHTML($content);
1875 $xpath = new DOMXPath($doc);
1876 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
16c48032 1877 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
97b7d5c0
AD
1878 $feedUrls = array();
1879 foreach ($entries as $entry) {
1880 if ($entry->hasAttribute('href')) {
1881 $title = $entry->getAttribute('title');
1882 if ($title == '') {
1883 $title = $entry->getAttribute('type');
1884 }
1885 $feedUrl = rewrite_relative_url(
1886 $baseUrl, $entry->getAttribute('href')
1887 );
1888 $feedUrls[$feedUrl] = $title;
1889 }
1890 }
1891 return $feedUrls;
1892 }
1893
1894 function is_html($content) {
5a4074a9 1895 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
97b7d5c0
AD
1896 }
1897
1898 function url_is_html($url, $login = false, $pass = false) {
1899 return is_html(fetch_file_contents($url, false, $login, $pass));
1900 }
1901
1902 function print_label_select($name, $value, $attributes = "") {
1903
1904 $result = db_query("SELECT caption FROM ttrss_labels2
1905 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1906
1907 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1bfe1d7b 1908 "\" $attributes>";
97b7d5c0
AD
1909
1910 while ($line = db_fetch_assoc($result)) {
1911
1912 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1913
1914 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1915 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1916
1917 }
1918
1919# print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1920
1921 print "</select>";
1922
1923
1924 }
1925
1926 function format_article_enclosures($id, $always_display_enclosures,
1927 $article_content, $hide_images = false) {
1928
1929 $result = get_article_enclosures($id);
1930 $rv = '';
1931
2bb11658
DZ
1932 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1933 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1934 if (is_array($retval)) {
1935 $rv = $retval[0];
1936 $result = $retval[1];
1937 } else {
1938 $rv = $retval;
1939 }
1940 }
84931635 1941 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
97b7d5c0 1942
c6ce584d 1943 if ($rv === '' && !empty($result)) {
97b7d5c0
AD
1944 $entries_html = array();
1945 $entries = array();
1946 $entries_inline = array();
1947
1948 foreach ($result as $line) {
1949
58210301 1950 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
676c7303 1951 $line = $plugin->hook_enclosure_entry($line);
58210301
AD
1952 }
1953
97b7d5c0
AD
1954 $url = $line["content_url"];
1955 $ctype = $line["content_type"];
1956 $title = $line["title"];
1e871938
FE
1957 $width = $line["width"];
1958 $height = $line["height"];
97b7d5c0
AD
1959
1960 if (!$ctype) $ctype = __("unknown type");
1961
c606bd57
AD
1962 //$filename = substr($url, strrpos($url, "/")+1);
1963 $filename = basename($url);
97b7d5c0
AD
1964
1965 $player = format_inline_player($url, $ctype);
1966
1967 if ($player) array_push($entries_inline, $player);
1968
ba2853ca 1969# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
97b7d5c0
AD
1970# $filename . " (" . $ctype . ")" . "</a>";
1971
829d478f 1972 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
97b7d5c0
AD
1973 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1974
1975 array_push($entries_html, $entry);
1976
1977 $entry = array();
1978
1979 $entry["type"] = $ctype;
1980 $entry["filename"] = $filename;
1981 $entry["url"] = $url;
1982 $entry["title"] = $title;
1e871938
FE
1983 $entry["width"] = $width;
1984 $entry["height"] = $height;
97b7d5c0
AD
1985
1986 array_push($entries, $entry);
1987 }
1988
1989 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1990 if ($always_display_enclosures ||
1991 !preg_match("/<img/i", $article_content)) {
1992
1993 foreach ($entries as $entry) {
1994
945346cb
AD
1995 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1996 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
97b7d5c0 1997
945346cb
AD
1998
1999 if ($retval) {
2000 $rv .= $retval;
2001 } else {
2002
2003 if (preg_match("/image/", $entry["type"]) ||
454292b2 2004 preg_match("/\.(jpe?g|png|gif|bmp)/i", $entry["filename"])) {
945346cb
AD
2005
2006 if (!$hide_images) {
2007 $encsize = '';
2008 if ($entry['height'] > 0)
08e79cb6 2009 $encsize .= ' height="' . intval($entry['height']) . '"';
945346cb 2010 if ($entry['width'] > 0)
08e79cb6 2011 $encsize .= ' width="' . intval($entry['width']) . '"';
945346cb
AD
2012 $rv .= "<p><img
2013 alt=\"".htmlspecialchars($entry["filename"])."\"
2014 src=\"" .htmlspecialchars($entry["url"]) . "\"
2015 " . $encsize . " /></p>";
2016 } else {
ba2853ca 2017 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
945346cb
AD
2018 href=\"".htmlspecialchars($entry["url"])."\"
2019 >" .htmlspecialchars($entry["url"]) . "</a></p>";
2020 }
2021
2022 if ($entry['title']) {
2023 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
2024 }
2025 }
97b7d5c0
AD
2026 }
2027 }
2028 }
2029 }
2030
2031 if (count($entries_inline) > 0) {
2032 $rv .= "<hr clear='both'/>";
2033 foreach ($entries_inline as $entry) { $rv .= $entry; };
2034 $rv .= "<hr clear='both'/>";
2035 }
2036
6810a1de
AD
2037 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2038 "<span>" . __('Attachments')."</span>";
2039
2040 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
97b7d5c0
AD
2041
2042 foreach ($entries as $entry) {
2043 if ($entry["title"])
c606bd57 2044 $title = " &mdash; " . truncate_string($entry["title"], 30);
97b7d5c0
AD
2045 else
2046 $title = "";
2047
c606bd57
AD
2048 if ($entry["filename"])
2049 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
2050 else
2051 $filename = "";
2052
829d478f 2053 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
c606bd57 2054 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
97b7d5c0
AD
2055
2056 };
2057
6810a1de
AD
2058 $rv .= "</div>";
2059 $rv .= "</div>";
97b7d5c0
AD
2060 }
2061
2062 return $rv;
2063 }
2064
2065 function getLastArticleId() {
8458a312 2066 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2067 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
97b7d5c0
AD
2068
2069 if (db_num_rows($result) == 1) {
2070 return db_fetch_result($result, 0, "id");
2071 } else {
2072 return -1;
2073 }
2074 }
2075
2076 function build_url($parts) {
2077 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2078 }
2079
4a23031f
AD
2080 function cleanup_url_path($path) {
2081 $path = str_replace("/./", "/", $path);
2082 $path = str_replace("//", "/", $path);
2083
2084 return $path;
2085 }
2086
97b7d5c0
AD
2087 /**
2088 * Converts a (possibly) relative URL to a absolute one.
2089 *
2090 * @param string $url Base URL (i.e. from where the document is)
2091 * @param string $rel_url Possibly relative URL in the document
2092 *
2093 * @return string Absolute URL
2094 */
2095 function rewrite_relative_url($url, $rel_url) {
94d425fe 2096 if (strpos($rel_url, "://") !== false) {
97b7d5c0
AD
2097 return $rel_url;
2098 } else if (strpos($rel_url, "//") === 0) {
2099 # protocol-relative URL (rare but they exist)
2100 return $rel_url;
94d425fe
AD
2101 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2102 # magnet:, feed:, etc
2103 return $rel_url;
2104 } else if (strpos($rel_url, "/") === 0) {
97b7d5c0
AD
2105 $parts = parse_url($url);
2106 $parts['path'] = $rel_url;
4a23031f 2107 $parts['path'] = cleanup_url_path($parts['path']);
97b7d5c0
AD
2108
2109 return build_url($parts);
2110
2111 } else {
2112 $parts = parse_url($url);
2113 if (!isset($parts['path'])) {
2114 $parts['path'] = '/';
2115 }
2116 $dir = $parts['path'];
2117 if (substr($dir, -1) !== '/') {
2118 $dir = dirname($parts['path']);
2119 $dir !== '/' && $dir .= '/';
2120 }
2121 $parts['path'] = $dir . $rel_url;
4a23031f 2122 $parts['path'] = cleanup_url_path($parts['path']);
97b7d5c0
AD
2123
2124 return build_url($parts);
2125 }
2126 }
2127
97b7d5c0
AD
2128 function cleanup_tags($days = 14, $limit = 1000) {
2129
2130 if (DB_TYPE == "pgsql") {
2131 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2132 } else if (DB_TYPE == "mysql") {
2133 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2134 }
2135
2136 $tags_deleted = 0;
2137
2138 while ($limit > 0) {
2139 $limit_part = 500;
2140
2141 $query = "SELECT ttrss_tags.id AS id
2142 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2143 WHERE post_int_id = int_id AND $interval_query AND
2144 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2145
2146 $result = db_query($query);
2147
2148 $ids = array();
2149
2150 while ($line = db_fetch_assoc($result)) {
2151 array_push($ids, $line['id']);
2152 }
2153
2154 if (count($ids) > 0) {
2155 $ids = join(",", $ids);
2156
2157 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2158 $tags_deleted += db_affected_rows($tmp_result);
2159 } else {
2160 break;
2161 }
2162
2163 $limit -= $limit_part;
2164 }
2165
2166 return $tags_deleted;
2167 }
2168
2169 function print_user_stylesheet() {
2170 $value = get_pref('USER_STYLESHEET');
2171
2172 if ($value) {
2173 print "<style type=\"text/css\">";
2174 print str_replace("<br/>", "\n", $value);
2175 print "</style>";
2176 }
2177
2178 }
2179
2180 function filter_to_sql($filter, $owner_uid) {
2181 $query = array();
2182
2183 if (DB_TYPE == "pgsql")
2184 $reg_qpart = "~";
2185 else
2186 $reg_qpart = "REGEXP";
2187
2188 foreach ($filter["rules"] AS $rule) {
2189 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2190 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2191 $rule['reg_exp']) !== FALSE;
2192
2193 if ($regexp_valid) {
2194
2195 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2196
2197 switch ($rule["type"]) {
2198 case "title":
2199 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2200 $rule['reg_exp'] . "')";
2201 break;
2202 case "content":
2203 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2204 $rule['reg_exp'] . "')";
2205 break;
2206 case "both":
2207 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2208 $rule['reg_exp'] . "') OR LOWER(" .
2209 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2210 break;
2211 case "tag":
2212 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2213 $rule['reg_exp'] . "')";
2214 break;
2215 case "link":
2216 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2217 $rule['reg_exp'] . "')";
2218 break;
2219 case "author":
2220 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2221 $rule['reg_exp'] . "')";
2222 break;
2223 }
2224
2225 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2226
2227 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2228 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2229 }
2230
2231 if (isset($rule["cat_id"])) {
2232
2233 if ($rule["cat_id"] > 0) {
2234 $children = getChildCategories($rule["cat_id"], $owner_uid);
2235 array_push($children, $rule["cat_id"]);
2236
2237 $children = join(",", $children);
2238
2239 $cat_qpart = "cat_id IN ($children)";
2240 } else {
2241 $cat_qpart = "cat_id IS NULL";
2242 }
2243
2244 $qpart .= " AND $cat_qpart";
2245 }
2246
2247 $qpart .= " AND feed_id IS NOT NULL";
2248
2249 array_push($query, "($qpart)");
2250
2251 }
2252 }
2253
2254 if (count($query) > 0) {
2255 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2256 } else {
2257 $fullquery = "(false)";
2258 }
2259
2260 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2261
2262 return $fullquery;
2263 }
2264
2265 if (!function_exists('gzdecode')) {
2266 function gzdecode($string) { // no support for 2nd argument
2267 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2268 base64_encode($string));
2269 }
2270 }
2271
2272 function get_random_bytes($length) {
2273 if (function_exists('openssl_random_pseudo_bytes')) {
2274 return openssl_random_pseudo_bytes($length);
2275 } else {
2276 $output = "";
2277
2278 for ($i = 0; $i < $length; $i++)
2279 $output .= chr(mt_rand(0, 255));
2280
2281 return $output;
2282 }
2283 }
2284
2285 function read_stdin() {
2286 $fp = fopen("php://stdin", "r");
2287
2288 if ($fp) {
2289 $line = trim(fgets($fp));
2290 fclose($fp);
2291 return $line;
2292 }
2293
2294 return null;
2295 }
2296
2297 function tmpdirname($path, $prefix) {
2298 // Use PHP's tmpfile function to create a temporary
2299 // directory name. Delete the file and keep the name.
2300 $tempname = tempnam($path,$prefix);
2301 if (!$tempname)
2302 return false;
2303
2304 if (!unlink($tempname))
2305 return false;
2306
2307 return $tempname;
2308 }
2309
2310 function getFeedCategory($feed) {
2311 $result = db_query("SELECT cat_id FROM ttrss_feeds
2312 WHERE id = '$feed'");
2313
2314 if (db_num_rows($result) > 0) {
2315 return db_fetch_result($result, 0, "cat_id");
2316 } else {
2317 return false;
2318 }
2319
2320 }
2321
2322 function implements_interface($class, $interface) {
2323 return in_array($interface, class_implements($class));
2324 }
2325
97b7d5c0
AD
2326 function get_minified_js($files) {
2327 require_once 'lib/jshrink/Minifier.php';
2328
2329 $rv = '';
2330
2331 foreach ($files as $js) {
2332 if (!isset($_GET['debug'])) {
2333 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2334
aa9f7d44 2335 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
97b7d5c0 2336
aa9f7d44 2337 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
97b7d5c0 2338
aa9f7d44
AD
2339 if ($header && $contents) {
2340 list($htag, $hversion) = explode(":", $header);
2341
2342 if ($htag == "tt-rss" && $hversion == VERSION) {
2343 $rv .= $contents;
2344 continue;
2345 }
2346 }
97b7d5c0 2347 }
aa9f7d44
AD
2348
2349 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2350 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2351 $rv .= $minified;
2352
97b7d5c0 2353 } else {
aa9f7d44 2354 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
97b7d5c0
AD
2355 }
2356 }
2357
2358 return $rv;
2359 }
2360
2361 function stylesheet_tag($filename) {
2362 $timestamp = filemtime($filename);
2363
2364 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2365 }
2366
2367 function javascript_tag($filename) {
2368 $query = "";
2369
2370 if (!(strpos($filename, "?") === FALSE)) {
2371 $query = substr($filename, strpos($filename, "?")+1);
2372 $filename = substr($filename, 0, strpos($filename, "?"));
2373 }
2374
2375 $timestamp = filemtime($filename);
2376
2377 if ($query) $timestamp .= "&$query";
2378
2379 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2380 }
2381
2382 function calculate_dep_timestamp() {
2383 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2384
2385 $max_ts = -1;
2386
2387 foreach ($files as $file) {
2388 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2389 }
2390
2391 return $max_ts;
2392 }
2393
2394 function T_js_decl($s1, $s2) {
2395 if ($s1 && $s2) {
2396 $s1 = preg_replace("/\n/", "", $s1);
2397 $s2 = preg_replace("/\n/", "", $s2);
2398
2399 $s1 = preg_replace("/\"/", "\\\"", $s1);
2400 $s2 = preg_replace("/\"/", "\\\"", $s2);
2401
2402 return "T_messages[\"$s1\"] = \"$s2\";\n";
2403 }
2404 }
2405
2406 function init_js_translations() {
2407
2408 print 'var T_messages = new Object();
2409
2410 function __(msg) {
2411 if (T_messages[msg]) {
2412 return T_messages[msg];
2413 } else {
2414 return msg;
2415 }
2416 }
2417
2418 function ngettext(msg1, msg2, n) {
2419 return __((parseInt(n) > 1) ? msg2 : msg1);
2420 }';
2421
2422 $l10n = _get_reader();
2423
2424 for ($i = 0; $i < $l10n->total; $i++) {
2425 $orig = $l10n->get_original_string($i);
2426 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2427 $key = explode(chr(0), $orig);
2428 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2429 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2430 } else {
2431 $translation = __($orig);
2432 print T_js_decl($orig, $translation);
2433 }
2434 }
2435 }
2436
2437 function label_to_feed_id($label) {
2438 return LABEL_BASE_INDEX - 1 - abs($label);
2439 }
2440
2441 function feed_to_label_id($feed) {
2442 return LABEL_BASE_INDEX - 1 + abs($feed);
2443 }
2444
b9634eb8
AD
2445 function get_theme_path($theme) {
2446 $check = "themes/$theme";
2447 if (file_exists($check)) return $check;
2448
2449 $check = "themes.local/$theme";
2450 if (file_exists($check)) return $check;
2451 }
2452
2453 function theme_valid($theme) {
181c8285
AD
2454 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2455
2456 if (in_array($theme, $bundled_themes)) return true;
2457
b9634eb8
AD
2458 $file = "themes/" . basename($theme);
2459
2460 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
f6cbe9a5
AD
2461
2462 if (file_exists($file) && is_readable($file)) {
2463 $fh = fopen($file, "r");
2464
2465 if ($fh) {
2466 $header = fgets($fh);
2467 fclose($fh);
2468
2469 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2470 }
2471 }
2472
2473 return false;
2474 }
27f7b593
AD
2475
2476 function error_json($code) {
2477 require_once "errors.php";
2478
2479 @$message = $ERRORS[$code];
2480
2481 return json_encode(array("error" =>
2482 array("code" => $code, "message" => $message)));
2483
2484 }
7c0a2ab2
AD
2485
2486 function abs_to_rel_path($dir) {
2487 $tmp = str_replace(dirname(__DIR__), "", $dir);
2488
2489 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2490
2491 return $tmp;
2492 }
1a322ff3
AD
2493
2494 function get_upload_error_message($code) {
2495
2496 $errors = array(
2497 0 => __('There is no error, the file uploaded with success'),
2498 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2499 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2500 3 => __('The uploaded file was only partially uploaded'),
2501 4 => __('No file was uploaded'),
2502 6 => __('Missing a temporary folder'),
2503 7 => __('Failed to write file to disk.'),
2504 8 => __('A PHP extension stopped the file upload.'),
2505 );
2506
2507 return $errors[$code];
2508 }
70c0a8c2
AD
2509
2510 function base64_img($filename) {
2511 if (file_exists($filename)) {
2512 $ext = pathinfo($filename, PATHINFO_EXTENSION);
2513
2514 return "data:image/$ext;base64," . base64_encode(file_get_contents($filename));
2515 } else {
2516 return "";
2517 }
2518 }
97b7d5c0 2519?>