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