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