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