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