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