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