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