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