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