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