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