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