]> git.wh0rd.org Git - tt-rss.git/blob - include/functions2.php
d68d1ecc3b2589d4ebfecae7274807e85d5e8728
[tt-rss.git] / include / functions2.php
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;
20
21                 $theme = get_pref( "USER_CSS_THEME", false, false);
22                 $params["theme"] = theme_valid("$theme") ? $theme : "";
23
24                 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
25
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
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
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
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"),
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"),
97                                 "feed_toggle_vgroup" => __("Toggle headline grouping"),
98                                 "feed_debug_update" => __("Debug feed update"),
99                                 "feed_debug_viewfeed" => __("Debug viewfeed()"),
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",
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",
167                                 "f g" => "feed_toggle_vgroup",
168                                 "f *d" => "feed_debug_update",
169                                 "f *g" => "feed_debug_viewfeed",
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
211         function check_for_update() {
212                 if (defined("GIT_VERSION_TIMESTAMP")) {
213                         $content = @fetch_file_contents(array("url" => "http://tt-rss.org/version.json", "timeout" => 5));
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
228                 return "";
229         }
230
231         function make_runtime_info($disable_update_check = false) {
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
249
250                 if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
251                         $update_result = @check_for_update();
252
253                         $data["update_result"] = $update_result;
254
255                         $_SESSION["last_version_check"] = time();
256                 }
257
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
285                 return $data;
286         }
287
288         function search_to_sql($search, $search_language) {
289
290                 $keywords = str_getcsv(trim($search), " ");
291                 $query_keywords = array();
292                 $search_words = array();
293                 $search_query_leftover = array();
294
295                 if ($search_language)
296                         $search_language = db_escape_string(mb_strtolower($search_language));
297                 else
298                         $search_language = "english";
299
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                                 }
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                                 }
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 {
396
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                                         }
404
405                                         if (!$not) array_push($search_words, $k);
406                                 }
407                         }
408                 }
409
410                 if (count($search_query_leftover) > 0) {
411                         $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
412
413                         if (DB_TYPE == "pgsql") {
414                                 array_push($query_keywords,
415                                         "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
416                         }
417
418                 }
419
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
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;
460                 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
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"];
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;
470                 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
471                 $skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false;
472
473                 $ext_tables_part = "";
474                 $query_strategy_part = "";
475
476                 $search_words = array();
477
478                         if ($search) {
479                                 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
480                                         list($search_query_part, $search_words) = $plugin->hook_search($search);
481                                         break;
482                                 }
483
484                                 // fall back in case of no plugins
485                                 if (!$search_query_part) {
486                                         list($search_query_part, $search_words) = search_to_sql($search, $search_language);
487                                 }
488                                 $search_query_part .= " AND ";
489                         } else {
490                                 $search_query_part = "";
491                         }
492
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
511                                         if ($unread > 0) {
512                                                 $view_query_part = " unread = true AND ";
513                                         }
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
541                         /* tags */
542                         if (!is_numeric($feed)) {
543                                 $query_strategy_part = "true";
544                                 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
545                                         id = feed_id) as feed_title,";
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
601                                         $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
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";
609
610                                 if (DB_TYPE == "pgsql") {
611                                         $query_strategy_part .= " AND last_read > NOW() - INTERVAL '1 DAY' ";
612                                 } else {
613                                         $query_strategy_part .= " AND last_read > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
614                                 }
615
616                                 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
617                                 $allow_archived = true;
618                                 $ignore_vfeed_group = true;
619
620                                 if (!$override_order) $override_order = "last_read DESC";
621
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,";
646                                 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
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
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
692                         if ($limit_query_part) {
693                                 $offset_query_part = "OFFSET $offset";
694                         } else {
695                                 $offset_query_part = "";
696                         }
697
698                         if (is_numeric($feed)) {
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) {
709                                         $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
710                                         $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
711
712                                 } else {
713                                         $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
714                                                 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
715                                 }
716
717                                 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
718
719                                 if ($start_ts) {
720                                         $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
721                                         $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
722                                 } else {
723                                         $start_ts_query_part = "";
724                                 }
725
726                                 $first_id = 0;
727                                 $first_id_query_strategy_part = $query_strategy_part;
728
729                                 if ($feed == -3)
730                                         $first_id_query_strategy_part = "true";
731
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
738                                 if (!$search && !$skip_first_id_check) {
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,
751                                                         last_published,
752                                                         last_read
753                                                 FROM
754                                                         $from_qpart
755                                                 WHERE
756                                                 $feed_check_qpart
757                                                 ttrss_user_entries.owner_uid = '$owner_uid' AND
758                                                 $search_query_part
759                                                 $start_ts_query_part
760                                                 $since_id_part
761                                                 $sanity_interval_qpart
762                                                 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
763
764                                         if ($_REQUEST["debug"]) {
765                                                 print $query;
766                                         }
767
768                                         $result = db_query($query);
769                                         if ($result && db_num_rows($result) > 0) {
770                                                 $first_id = (int)db_fetch_result($result, 0, "id");
771
772                                                 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
773                                                         return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
774                                                 }
775                                         }
776                                 }
777
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
803                                         ttrss_user_entries.owner_uid = '$owner_uid' AND
804                                         $search_query_part
805                                         $start_ts_query_part
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
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,
831                                                         int_id,
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";
854
855                                 if ($_REQUEST["debug"]) print $query;
856
857                                 $result = db_query($query);
858                         }
859
860                         return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
861
862         }
863
864         function iframe_whitelisted($entry) {
865                 $whitelist = array("youtube.com", "youtu.be", "vimeo.com", "player.vimeo.com");
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
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
896                 $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https';
897                 $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH;
898
899                 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src])');
900
901                 foreach ($entries as $entry) {
902
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)) {
917                                         $src = get_self_url_prefix() . '/public.php?op=cached_image&hash=' . sha1($src) . $extension;
918
919                                         if ($entry->hasAttribute('srcset')) {
920                                                 $entry->removeAttribute('srcset');
921                                         }
922
923                                         if ($entry->hasAttribute('sizes')) {
924                                                 $entry->removeAttribute('sizes');
925                                         }
926                                 }
927
928                                 $entry->setAttribute('src', $src);
929                         }
930
931                         if ($entry->nodeName == 'img') {
932
933                                 if ($entry->hasAttribute('src')) {
934                                         $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
935
936                                         if ($ttrss_uses_https && !$is_https_url) {
937
938                                                 if ($entry->hasAttribute('srcset')) {
939                                                         $entry->removeAttribute('srcset');
940                                                 }
941
942                                                 if ($entry->hasAttribute('sizes')) {
943                                                         $entry->removeAttribute('sizes');
944                                                 }
945                                         }
946                                 }
947
948                                 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
949                                                 $force_remove_images || $_SESSION["bw_limit"]) {
950
951                                         $p = $doc->createElement('p');
952
953                                         $a = $doc->createElement('a');
954                                         $a->setAttribute('href', $entry->getAttribute('src'));
955
956                                         $a->appendChild(new DOMText($entry->getAttribute('src')));
957                                         $a->setAttribute('target', '_blank');
958                                         $a->setAttribute('rel', 'noopener noreferrer');
959
960                                         $p->appendChild($a);
961
962                                         $entry->parentNode->replaceChild($p, $entry);
963                                 }
964                         }
965
966                         if (strtolower($entry->nodeName) == "a") {
967                                 $entry->setAttribute("target", "_blank");
968                                 $entry->setAttribute("rel", "noopener noreferrer");
969                         }
970                 }
971
972                 $entries = $xpath->query('//iframe');
973                 foreach ($entries as $entry) {
974                         if (!iframe_whitelisted($entry)) {
975                                 $entry->setAttribute('sandbox', 'allow-scripts');
976                         } else {
977                                 if ($_SERVER['HTTPS'] == "on") {
978                                         $entry->setAttribute("src",
979                                                 str_replace("http://", "https://",
980                                                         $entry->getAttribute("src")));
981                                 }
982                         }
983                 }
984
985                 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
986                         'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
987                         'caption', 'cite', 'center', 'code', 'col', 'colgroup',
988                         'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
989                         'dt', 'em', 'footer', 'figure', 'figcaption',
990                         'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
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',
995                         'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
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
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                 }
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
1074                                         if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1075                                                 array_push($attrs_to_remove, $attr);
1076                                         }
1077
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
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
1148                         if (db_num_rows($result) != 0)
1149                                 $tag_cache = db_fetch_result($result, 0, "tag_cache");
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;
1184                 if (is_numeric($tag)) return false;
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 = "") {
1200                 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1201         }
1202
1203         function format_notice($msg, $id = "") {
1204                 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1205         }
1206
1207         function format_error($msg, $id = "") {
1208                 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
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>
1241                                         <source type=\"$ctype\" src=\"$url\"/>
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
1254                         if ($entry) $entry .= "&nbsp; <a target=\"_blank\" rel=\"noopener noreferrer\"
1255                                 href=\"$url\">" . basename($url) . "</a>";
1256
1257                         return $entry;
1258
1259                 }
1260
1261                 return "";
1262
1263 /*              $filename = substr($url, strrpos($url, "/")+1);
1264
1265                 $entry .= " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"" . htmlspecialchars($url) . "\">" .
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,
1306                         guid,
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
1327                         $num_comments = (int) $line["num_comments"];
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\"
1337                                         target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
1338                                         _ngettext("comment", "comments", $num_comments)."</a>";
1339
1340                         } else {
1341                                 if ($line["comments"] && $line["link"] != $line["comments"]) {
1342                                         $entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
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
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"]) {
1378                                 $rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
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) {
1389                                 $feed_title = htmlspecialchars($line["feed_title"]);
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
1436                                         WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
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
1447                                         $rv['content'] .= "<a target='_blank' rel='noopener noreferrer'
1448                                                 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1449                                                 $tmp_line['title'] . "</a>";
1450
1451                                         $rv['content'] .= "&nbsp;";
1452
1453                                         $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
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"];
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                         }
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
1510                 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1511
1512                 if (DB_TYPE == "mysql") {
1513                         $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1514                 }
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));
1653                         $tags_str = "";
1654
1655                         for ($i = 0; $i < $maxtags; $i++) {
1656                                 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
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) {
1769
1770                 // support schema-less urls
1771                 if (strpos($url, '//') === 0) {
1772                         $url = 'https:' . $url;
1773                 }
1774
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
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
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
1828         /* function save_email_address($email) {
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);
1836         } */
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 {
1852                         $key = db_escape_string(uniqid_short());
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 '.
1874                         '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
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) {
1892                 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
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>";
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
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                 }
1938                 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
1939
1940                 if ($rv === '' && !empty($result)) {
1941                         $entries_html = array();
1942                         $entries = array();
1943                         $entries_inline = array();
1944
1945                         foreach ($result as $line) {
1946
1947                                 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
1948                                         $line = $plugin->hook_enclosure_entry($line);
1949                                 }
1950                                 
1951                                 $url = $line["content_url"];
1952                                 $ctype = $line["content_type"];
1953                                 $title = $line["title"];
1954                                 $width = $line["width"];
1955                                 $height = $line["height"];
1956
1957                                 if (!$ctype) $ctype = __("unknown type");
1958
1959                                 //$filename = substr($url, strrpos($url, "/")+1);
1960                                 $filename = basename($url);
1961
1962                                 $player = format_inline_player($url, $ctype);
1963
1964                                 if ($player) array_push($entries_inline, $player);
1965
1966 #                               $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
1967 #                                       $filename . " (" . $ctype . ")" . "</a>";
1968
1969                                 $entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
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;
1980                                 $entry["width"] = $width;
1981                                 $entry["height"] = $height;
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
1992                                         foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1993                                                 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1994
1995
1996                                                 if ($retval) {
1997                                                         $rv .= $retval;
1998                                                 } else {
1999
2000                                                         if (preg_match("/image/", $entry["type"]) ||
2001                                                                         preg_match("/\.(jpe?g|png|gif|bmp)/i", $entry["filename"])) {
2002
2003                                                                         if (!$hide_images) {
2004                                                                                 $encsize = '';
2005                                                                                 if ($entry['height'] > 0)
2006                                                                                         $encsize .= ' height="' . intval($entry['height']) . '"';
2007                                                                                 if ($entry['width'] > 0)
2008                                                                                         $encsize .= ' width="' . intval($entry['width']) . '"';
2009                                                                                 $rv .= "<p><img
2010                                                                                 alt=\"".htmlspecialchars($entry["filename"])."\"
2011                                                                                 src=\"" .htmlspecialchars($entry["url"]) . "\"
2012                                                                                 " . $encsize . " /></p>";
2013                                                                         } else {
2014                                                                                 $rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
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                                                         }
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
2034                         $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2035                                 "<span>" . __('Attachments')."</span>";
2036
2037                         $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
2038
2039                         foreach ($entries as $entry) {
2040                                 if ($entry["title"])
2041                                         $title = " &mdash; " . truncate_string($entry["title"], 30);
2042                                 else
2043                                         $title = "";
2044
2045                                 if ($entry["filename"])
2046                                         $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
2047                                 else
2048                                         $filename = "";
2049
2050                                 $rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
2051                                         dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
2052
2053                         };
2054
2055                         $rv .= "</div>";
2056                         $rv .= "</div>";
2057                 }
2058
2059                 return $rv;
2060         }
2061
2062         function getLastArticleId() {
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");
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
2077         function cleanup_url_path($path) {
2078                 $path = str_replace("/./", "/", $path);
2079                 $path = str_replace("//", "/", $path);
2080
2081                 return $path;
2082         }
2083
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) {
2093                 if (strpos($rel_url, "://") !== false) {
2094                         return $rel_url;
2095                 } else if (strpos($rel_url, "//") === 0) {
2096                         # protocol-relative URL (rare but they exist)
2097                         return $rel_url;
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) {
2102                         $parts = parse_url($url);
2103                         $parts['path'] = $rel_url;
2104                         $parts['path'] = cleanup_url_path($parts['path']);
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;
2119                         $parts['path'] = cleanup_url_path($parts['path']);
2120
2121                         return build_url($parts);
2122                 }
2123         }
2124
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
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
2332                                 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2333
2334                                         list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2335
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                                         }
2344                                 }
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
2350                         } else {
2351                                 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
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
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) {
2451                 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2452                 
2453                 if (in_array($theme, $bundled_themes)) return true;
2454
2455                 $file = "themes/" . basename($theme);
2456
2457                 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
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         }
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         }
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         }
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         }
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         }
2516 ?>