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