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