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