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