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