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