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