]> git.wh0rd.org - tt-rss.git/blame - include/functions2.php
image cache: do not try to cache data: schema urls; add caching of html5 video conten...
[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
5edd605a 891 $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src])');
97b7d5c0 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
5edd605a
AD
906 if ($entry->hasAttribute('src')) {
907 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
97b7d5c0 908
5edd605a
AD
909 $extension = $entry->tagName == 'source' ? '.mp4' : '.png';
910 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . $extension;
a01bfd78 911
5edd605a
AD
912 if (file_exists($cached_filename)) {
913 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src) . $extension;
a01bfd78 914
5edd605a
AD
915 if ($entry->hasAttribute('srcset')) {
916 $entry->removeAttribute('srcset');
a01bfd78 917 }
97b7d5c0 918
5edd605a
AD
919 if ($entry->hasAttribute('sizes')) {
920 $entry->removeAttribute('sizes');
921 }
0442cbb6 922 }
97b7d5c0 923
5edd605a
AD
924 $entry->setAttribute('src', $src);
925 }
926
927 if ($entry->nodeName == 'img') {
928
a536f94c
AD
929 if ($entry->hasAttribute('src')) {
930 $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
931
932 if ($ttrss_uses_https && !$is_https_url) {
933
934 if ($entry->hasAttribute('srcset')) {
935 $entry->removeAttribute('srcset');
936 }
937
938 if ($entry->hasAttribute('sizes')) {
939 $entry->removeAttribute('sizes');
940 }
941 }
942 }
943
97b7d5c0
AD
944 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
945 $force_remove_images || $_SESSION["bw_limit"]) {
946
947 $p = $doc->createElement('p');
948
949 $a = $doc->createElement('a');
950 $a->setAttribute('href', $entry->getAttribute('src'));
951
952 $a->appendChild(new DOMText($entry->getAttribute('src')));
953 $a->setAttribute('target', '_blank');
954
955 $p->appendChild($a);
956
957 $entry->parentNode->replaceChild($p, $entry);
958 }
959 }
960 }
961
962 if (strtolower($entry->nodeName) == "a") {
963 $entry->setAttribute("target", "_blank");
964 }
965 }
966
967 $entries = $xpath->query('//iframe');
968 foreach ($entries as $entry) {
59b5d5f3
AD
969 if (!iframe_whitelisted($entry)) {
970 $entry->setAttribute('sandbox', 'allow-scripts');
c63850fa
AD
971 } else {
972 if ($_SERVER['HTTPS'] == "on") {
973 $entry->setAttribute("src",
974 str_replace("http://", "https://",
975 $entry->getAttribute("src")));
976 }
59b5d5f3 977 }
97b7d5c0
AD
978 }
979
67268b00 980 $allowed_elements = array('a', 'address', 'acronym', 'audio', 'article', 'aside',
97b7d5c0
AD
981 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
982 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
311cdb27 983 'data', 'dd', 'del', 'details', 'description', 'dfn', 'div', 'dl', 'font',
97b7d5c0 984 'dt', 'em', 'footer', 'figure', 'figcaption',
e2a3689a 985 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
97b7d5c0
AD
986 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
987 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
988 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
989 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
50bda3fe 990 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video', 'xml:namespace' );
97b7d5c0
AD
991
992 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
993
994 $disallowed_attributes = array('id', 'style', 'class');
995
996 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
997 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
998 if (is_array($retval)) {
999 $doc = $retval[0];
1000 $allowed_elements = $retval[1];
1001 $disallowed_attributes = $retval[2];
1002 } else {
1003 $doc = $retval;
1004 }
1005 }
1006
1007 $doc->removeChild($doc->firstChild); //remove doctype
1008 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
1009
1010 if ($highlight_words) {
1011 foreach ($highlight_words as $word) {
1012
1013 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
1014
1015 $elements = $xpath->query("//*/text()");
1016
1017 foreach ($elements as $child) {
1018
1019 $fragment = $doc->createDocumentFragment();
1020 $text = $child->textContent;
1021
1022 while (($pos = mb_stripos($text, $word)) !== false) {
1023 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1024 $word = mb_substr($text, $pos, mb_strlen($word));
1025 $highlight = $doc->createElement('span');
1026 $highlight->appendChild(new DomText($word));
1027 $highlight->setAttribute('class', 'highlight');
1028 $fragment->appendChild($highlight);
1029 $text = mb_substr($text, $pos + mb_strlen($word));
1030 }
1031
1032 if (!empty($text)) $fragment->appendChild(new DomText($text));
1033
1034 $child->parentNode->replaceChild($fragment, $child);
1035 }
1036 }
1037 }
1038
1039 $res = $doc->saveHTML();
1040
42f78188
AD
1041 /* strip everything outside of <body>...</body> */
1042
1043 $res_frag = array();
1044 if (preg_match('/<body>(.*)<\/body>/is', $res, $res_frag)) {
1045 return $res_frag[1];
1046 } else {
1047 return $res;
1048 }
97b7d5c0
AD
1049 }
1050
1051 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1052 $xpath = new DOMXPath($doc);
1053 $entries = $xpath->query('//*');
1054
1055 foreach ($entries as $entry) {
1056 if (!in_array($entry->nodeName, $allowed_elements)) {
1057 $entry->parentNode->removeChild($entry);
1058 }
1059
1060 if ($entry->hasAttributes()) {
1061 $attrs_to_remove = array();
1062
1063 foreach ($entry->attributes as $attr) {
1064
1065 if (strpos($attr->nodeName, 'on') === 0) {
1066 array_push($attrs_to_remove, $attr);
1067 }
1068
d8b0f067
J
1069 if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
1070 array_push($attrs_to_remove, $attr);
1071 }
1072
97b7d5c0
AD
1073 if (in_array($attr->nodeName, $disallowed_attributes)) {
1074 array_push($attrs_to_remove, $attr);
1075 }
1076 }
1077
1078 foreach ($attrs_to_remove as $attr) {
1079 $entry->removeAttributeNode($attr);
1080 }
1081 }
1082 }
1083
1084 return $doc;
1085 }
1086
97b7d5c0
AD
1087 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1088
1089 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1090 if (count($ids) == 0) return;
1091
1092 $tmp_ids = array();
1093
1094 foreach ($ids as $id) {
1095 array_push($tmp_ids, "ref_id = '$id'");
1096 }
1097
1098 $ids_qpart = join(" OR ", $tmp_ids);
1099
1100 if ($cmode == 0) {
1101 db_query("UPDATE ttrss_user_entries SET
1102 unread = false,last_read = NOW()
1103 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1104 } else if ($cmode == 1) {
1105 db_query("UPDATE ttrss_user_entries SET
1106 unread = true
1107 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1108 } else {
1109 db_query("UPDATE ttrss_user_entries SET
1110 unread = NOT unread,last_read = NOW()
1111 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1112 }
1113
1114 /* update ccache */
1115
1116 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1117 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1118
1119 while ($line = db_fetch_assoc($result)) {
1120 ccache_update($line["feed_id"], $owner_uid);
1121 }
1122 }
1123
1124 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1125
1126 $a_id = db_escape_string($id);
1127
1128 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1129
1130 $query = "SELECT DISTINCT tag_name,
1131 owner_uid as owner FROM
1132 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1133 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1134
1135 $tags = array();
1136
1137 /* check cache first */
1138
1139 if ($tag_cache === false) {
1140 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1141 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1142
c3522486
AD
1143 if (db_num_rows($result) != 0)
1144 $tag_cache = db_fetch_result($result, 0, "tag_cache");
97b7d5c0
AD
1145 }
1146
1147 if ($tag_cache) {
1148 $tags = explode(",", $tag_cache);
1149 } else {
1150
1151 /* do it the hard way */
1152
1153 $tmp_result = db_query($query);
1154
1155 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1156 array_push($tags, $tmp_line["tag_name"]);
1157 }
1158
1159 /* update the cache */
1160
1161 $tags_str = db_escape_string(join(",", $tags));
1162
1163 db_query("UPDATE ttrss_user_entries
1164 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1165 AND owner_uid = $owner_uid");
1166 }
1167
1168 return $tags;
1169 }
1170
1171 function trim_array($array) {
1172 $tmp = $array;
1173 array_walk($tmp, 'trim');
1174 return $tmp;
1175 }
1176
1177 function tag_is_valid($tag) {
1178 if ($tag == '') return false;
85d067e8 1179 if (is_numeric($tag)) return false;
97b7d5c0
AD
1180 if (mb_strlen($tag) > 250) return false;
1181
1182 if (!$tag) return false;
1183
1184 return true;
1185 }
1186
1187 function render_login_form() {
1188 header('Cache-Control: public');
1189
1190 require_once "login_form.php";
1191 exit;
1192 }
1193
1194 function format_warning($msg, $id = "") {
c1ebb6cd 1195 return "<div class=\"alert\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1196 }
1197
1198 function format_notice($msg, $id = "") {
c1ebb6cd 1199 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1200 }
1201
1202 function format_error($msg, $id = "") {
c1ebb6cd 1203 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
97b7d5c0
AD
1204 }
1205
1206 function print_notice($msg) {
1207 return print format_notice($msg);
1208 }
1209
1210 function print_warning($msg) {
1211 return print format_warning($msg);
1212 }
1213
1214 function print_error($msg) {
1215 return print format_error($msg);
1216 }
1217
1218
1219 function T_sprintf() {
1220 $args = func_get_args();
1221 return vsprintf(__(array_shift($args)), $args);
1222 }
1223
1224 function format_inline_player($url, $ctype) {
1225
1226 $entry = "";
1227
1228 $url = htmlspecialchars($url);
1229
1230 if (strpos($ctype, "audio/") === 0) {
1231
1232 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1233 $_SESSION["hasMp3"])) {
1234
1235 $entry .= "<audio preload=\"none\" controls>
83ce77a2 1236 <source type=\"$ctype\" src=\"$url\"/>
97b7d5c0
AD
1237 </audio>";
1238
1239 } else {
1240
1241 $entry .= "<object type=\"application/x-shockwave-flash\"
1242 data=\"lib/button/musicplayer.swf?song_url=$url\"
1243 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1244 <param name=\"movie\"
1245 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1246 </object>";
1247 }
1248
1249 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
1250 href=\"$url\">" . basename($url) . "</a>";
1251
1252 return $entry;
1253
1254 }
1255
1256 return "";
1257
1258/* $filename = substr($url, strrpos($url, "/")+1);
1259
1260 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1261 $filename . " (" . $ctype . ")" . "</a>"; */
1262
1263 }
1264
1265 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1266 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1267
1268 $rv = array();
1269
1270 $rv['id'] = $id;
1271
1272 /* we can figure out feed_id from article id anyway, why do we
1273 * pass feed_id here? let's ignore the argument :(*/
1274
1275 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1276 WHERE ref_id = '$id'");
1277
1278 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1279
1280 $rv['feed_id'] = $feed_id;
1281
1282 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1283
1284 if ($mark_as_read) {
1285 $result = db_query("UPDATE ttrss_user_entries
1286 SET unread = false,last_read = NOW()
1287 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1288
1289 ccache_update($feed_id, $owner_uid);
1290 }
1291
1292 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1293 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1294 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1295 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1296 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1297 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1298 num_comments,
1299 tag_cache,
1300 author,
553ec3c3 1301 guid,
97b7d5c0
AD
1302 orig_feed_id,
1303 note
1304 FROM ttrss_entries,ttrss_user_entries
1305 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1306
1307 if ($result) {
1308
1309 $line = db_fetch_assoc($result);
1310
1311 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1312 unset($line["tag_cache"]);
1313
1314 $line["content"] = sanitize($line["content"],
1315 sql_bool_to_bool($line['hide_images']),
1316 $owner_uid, $line["site_url"], false, $line["id"]);
1317
1318 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1319 $line = $p->hook_render_article($line);
1320 }
1321
967f0619 1322 $num_comments = (int) $line["num_comments"];
97b7d5c0
AD
1323 $entry_comments = "";
1324
1325 if ($num_comments > 0) {
1326 if ($line["comments"]) {
1327 $comments_url = htmlspecialchars($line["comments"]);
1328 } else {
1329 $comments_url = htmlspecialchars($line["link"]);
1330 }
1331 $entry_comments = "<a class=\"postComments\"
1332 target='_blank' href=\"$comments_url\">$num_comments ".
1333 _ngettext("comment", "comments", $num_comments)."</a>";
1334
1335 } else {
1336 if ($line["comments"] && $line["link"] != $line["comments"]) {
1337 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1338 }
1339 }
1340
1341 if ($zoom_mode) {
1342 header("Content-Type: text/html");
1343 $rv['content'] .= "<html><head>
1344 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1345 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1346 stylesheet_tag("css/tt-rss.css").
1347 stylesheet_tag("css/zoom.css").
1348 stylesheet_tag("css/dijit.css")."
1349
1350 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1351 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1352
97b7d5c0
AD
1353 </head><body id=\"ttrssZoom\">";
1354 }
1355
1356 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1357
1358 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1359
1360 $entry_author = $line["author"];
1361
1362 if ($entry_author) {
1363 $entry_author = __(" - ") . $entry_author;
1364 }
1365
1366 $parsed_updated = make_local_datetime($line["updated"], true,
1367 $owner_uid, true);
1368
1369 if (!$zoom_mode)
1370 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1371
1372 if ($line["link"]) {
1373 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1374 title=\"".htmlspecialchars($line['title'])."\"
1375 href=\"" .
1376 htmlspecialchars($line["link"]) . "\">" .
1377 $line["title"] . "</a>" .
1378 "<span class='author'>$entry_author</span></div>";
1379 } else {
1380 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1381 }
1382
1383 if ($zoom_mode) {
6687cb99 1384 $feed_title = htmlspecialchars($line["feed_title"]);
97b7d5c0
AD
1385
1386 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1387
1388 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1389 }
1390
1391 $tags_str = format_tags_string($line["tags"], $id);
1392 $tags_str_full = join(", ", $line["tags"]);
1393
1394 if (!$tags_str_full) $tags_str_full = __("no tags");
1395
1396 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1397
1398 $rv['content'] .= "<div class='postTags' style='float : right'>
1399 <img src='images/tag.png'
1400 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1401
1402 if (!$zoom_mode) {
1403 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1404 <a title=\"".__('Edit tags for this article')."\"
1405 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1406
1407 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1408 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1409 position=\"below\">$tags_str_full</div>";
1410
1411 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1412 $rv['content'] .= $p->hook_article_button($line);
1413 }
1414
1415 } else {
1416 $tags_str = strip_tags($tags_str);
1417 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1418 }
1419 $rv['content'] .= "</div>";
1420 $rv['content'] .= "<div clear='both'>";
1421
1422 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1423 $rv['content'] .= $p->hook_article_left_button($line);
1424 }
1425
1426 $rv['content'] .= "$entry_comments</div>";
1427
1428 if ($line["orig_feed_id"]) {
1429
1430 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
71b75bb7 1431 WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]);
97b7d5c0
AD
1432
1433 if (db_num_rows($tmp_result) != 0) {
1434
1435 $rv['content'] .= "<div clear='both'>";
1436 $rv['content'] .= __("Originally from:");
1437
1438 $rv['content'] .= "&nbsp;";
1439
1440 $tmp_line = db_fetch_assoc($tmp_result);
1441
1442 $rv['content'] .= "<a target='_blank'
1443 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1444 $tmp_line['title'] . "</a>";
1445
1446 $rv['content'] .= "&nbsp;";
1447
1448 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1449 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1450
1451 $rv['content'] .= "</div>";
1452 }
1453 }
1454
1455 $rv['content'] .= "</div>";
1456
1457 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1458 if ($line['note']) {
1459 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1460 }
1461 $rv['content'] .= "</div>";
1462
1463 if (!$line['lang']) $line['lang'] = 'en';
1464
1465 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1466
1467 $rv['content'] .= $line["content"];
6810a1de
AD
1468
1469 if (!$zoom_mode) {
1470 $rv['content'] .= format_article_enclosures($id,
1471 sql_bool_to_bool($line["always_display_enclosures"]),
1472 $line["content"],
1473 sql_bool_to_bool($line["hide_images"]));
1474 }
97b7d5c0
AD
1475
1476 $rv['content'] .= "</div>";
1477
1478 $rv['content'] .= "</div>";
1479
1480 }
1481
1482 if ($zoom_mode) {
1483 $rv['content'] .= "
1484 <div class='footer'>
1485 <button onclick=\"return window.close()\">".
1486 __("Close this window")."</button></div>";
1487 $rv['content'] .= "</body></html>";
1488 }
1489
1490 return $rv;
1491
1492 }
1493
1494 function print_checkpoint($n, $s) {
1495 $ts = microtime(true);
1496 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1497 return $ts;
1498 }
1499
1500 function sanitize_tag($tag) {
1501 $tag = trim($tag);
1502
1503 $tag = mb_strtolower($tag, 'utf-8');
1504
0e4da73f 1505 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
97b7d5c0 1506
35c37354
AD
1507 if (DB_TYPE == "mysql") {
1508 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1509 }
97b7d5c0
AD
1510
1511 return $tag;
1512 }
1513
1514 function get_self_url_prefix() {
1515 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1516 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1517 } else {
1518 return SELF_URL_PATH;
1519 }
1520 }
1521
1522 /**
1523 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1524 *
1525 * @return string The Mozilla Firefox feed adding URL.
1526 */
1527 function add_feed_url() {
1528 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1529
1530 $url_path = get_self_url_prefix() .
1531 "/public.php?op=subscribe&feed_url=%s";
1532 return $url_path;
1533 } // function add_feed_url
1534
1535 function encrypt_password($pass, $salt = '', $mode2 = false) {
1536 if ($salt && $mode2) {
1537 return "MODE2:" . hash('sha256', $salt . $pass);
1538 } else if ($salt) {
1539 return "SHA1X:" . sha1("$salt:$pass");
1540 } else {
1541 return "SHA1:" . sha1($pass);
1542 }
1543 } // function encrypt_password
1544
1545 function load_filters($feed_id, $owner_uid, $action_id = false) {
1546 $filters = array();
1547
1548 $cat_id = (int)getFeedCategory($feed_id);
1549
1550 if ($cat_id == 0)
1551 $null_cat_qpart = "cat_id IS NULL OR";
1552 else
1553 $null_cat_qpart = "";
1554
1555 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1556 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1557
1558 $check_cats = join(",", array_merge(
1559 getParentCategories($cat_id, $owner_uid),
1560 array($cat_id)));
1561
1562 while ($line = db_fetch_assoc($result)) {
1563 $filter_id = $line["id"];
1564
1565 $result2 = db_query("SELECT
1566 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1567 FROM ttrss_filters2_rules AS r,
1568 ttrss_filter_types AS t
1569 WHERE
1570 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1571 (feed_id IS NULL OR feed_id = '$feed_id') AND
1572 filter_type = t.id AND filter_id = '$filter_id'");
1573
1574 $rules = array();
1575 $actions = array();
1576
1577 while ($rule_line = db_fetch_assoc($result2)) {
1578# print_r($rule_line);
1579
1580 $rule = array();
1581 $rule["reg_exp"] = $rule_line["reg_exp"];
1582 $rule["type"] = $rule_line["type_name"];
1583 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1584
1585 array_push($rules, $rule);
1586 }
1587
1588 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1589 FROM ttrss_filters2_actions AS a,
1590 ttrss_filter_actions AS t
1591 WHERE
1592 action_id = t.id AND filter_id = '$filter_id'");
1593
1594 while ($action_line = db_fetch_assoc($result2)) {
1595# print_r($action_line);
1596
1597 $action = array();
1598 $action["type"] = $action_line["type_name"];
1599 $action["param"] = $action_line["action_param"];
1600
1601 array_push($actions, $action);
1602 }
1603
1604
1605 $filter = array();
1606 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1607 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1608 $filter["rules"] = $rules;
1609 $filter["actions"] = $actions;
1610
1611 if (count($rules) > 0 && count($actions) > 0) {
1612 array_push($filters, $filter);
1613 }
1614 }
1615
1616 return $filters;
1617 }
1618
1619 function get_score_pic($score) {
1620 if ($score > 100) {
1621 return "score_high.png";
1622 } else if ($score > 0) {
1623 return "score_half_high.png";
1624 } else if ($score < -100) {
1625 return "score_low.png";
1626 } else if ($score < 0) {
1627 return "score_half_low.png";
1628 } else {
1629 return "score_neutral.png";
1630 }
1631 }
1632
1633 function feed_has_icon($id) {
1634 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1635 }
1636
1637 function init_plugins() {
1638 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1639
1640 return true;
1641 }
1642
1643 function format_tags_string($tags, $id) {
1644 if (!is_array($tags) || count($tags) == 0) {
1645 return __("no tags");
1646 } else {
1647 $maxtags = min(5, count($tags));
83ce77a2 1648 $tags_str = "";
97b7d5c0
AD
1649
1650 for ($i = 0; $i < $maxtags; $i++) {
dcbe36b2 1651 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
97b7d5c0
AD
1652 }
1653
1654 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1655
1656 if (count($tags) > $maxtags)
1657 $tags_str .= ", &hellip;";
1658
1659 return $tags_str;
1660 }
1661 }
1662
1663 function format_article_labels($labels, $id) {
1664
1665 if (!is_array($labels)) return '';
1666
1667 $labels_str = "";
1668
1669 foreach ($labels as $l) {
1670 $labels_str .= sprintf("<span class='hlLabelRef'
1671 style='color : %s; background-color : %s'>%s</span>",
1672 $l[2], $l[3], $l[1]);
1673 }
1674
1675 return $labels_str;
1676
1677 }
1678
1679 function format_article_note($id, $note, $allow_edit = true) {
1680
1681 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1682 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1683 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1684
1685 return $str;
1686 }
1687
1688
1689 function get_feed_category($feed_cat, $parent_cat_id = false) {
1690 if ($parent_cat_id) {
1691 $parent_qpart = "parent_cat = '$parent_cat_id'";
1692 $parent_insert = "'$parent_cat_id'";
1693 } else {
1694 $parent_qpart = "parent_cat IS NULL";
1695 $parent_insert = "NULL";
1696 }
1697
1698 $result = db_query(
1699 "SELECT id FROM ttrss_feed_categories
1700 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1701
1702 if (db_num_rows($result) == 0) {
1703 return false;
1704 } else {
1705 return db_fetch_result($result, 0, "id");
1706 }
1707 }
1708
1709 function add_feed_category($feed_cat, $parent_cat_id = false) {
1710
1711 if (!$feed_cat) return false;
1712
1713 db_query("BEGIN");
1714
1715 if ($parent_cat_id) {
1716 $parent_qpart = "parent_cat = '$parent_cat_id'";
1717 $parent_insert = "'$parent_cat_id'";
1718 } else {
1719 $parent_qpart = "parent_cat IS NULL";
1720 $parent_insert = "NULL";
1721 }
1722
1723 $feed_cat = mb_substr($feed_cat, 0, 250);
1724
1725 $result = db_query(
1726 "SELECT id FROM ttrss_feed_categories
1727 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1728
1729 if (db_num_rows($result) == 0) {
1730
1731 $result = db_query(
1732 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1733 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1734
1735 db_query("COMMIT");
1736
1737 return true;
1738 }
1739
1740 return false;
1741 }
1742
1743 function getArticleFeed($id) {
1744 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1745 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1746
1747 if (db_num_rows($result) != 0) {
1748 return db_fetch_result($result, 0, "feed_id");
1749 } else {
1750 return 0;
1751 }
1752 }
1753
1754 /**
1755 * Fixes incomplete URLs by prepending "http://".
1756 * Also replaces feed:// with http://, and
1757 * prepends a trailing slash if the url is a domain name only.
1758 *
1759 * @param string $url Possibly incomplete URL
1760 *
1761 * @return string Fixed URL.
1762 */
1763 function fix_url($url) {
dd6e2386
AD
1764
1765 // support schema-less urls
1766 if (strpos($url, '//') === 0) {
1767 $url = 'https:' . $url;
1768 }
1769
97b7d5c0
AD
1770 if (strpos($url, '://') === false) {
1771 $url = 'http://' . $url;
1772 } else if (substr($url, 0, 5) == 'feed:') {
1773 $url = 'http:' . substr($url, 5);
1774 }
1775
1776 //prepend slash if the URL has no slash in it
1777 // "http://www.example" -> "http://www.example/"
1778 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1779 $url .= '/';
1780 }
1781
62958fe9
BT
1782 //convert IDNA hostname to punycode if possible
1783 if (function_exists("idn_to_ascii")) {
1784 $parts = parse_url($url);
1785 if (mb_detect_encoding($parts['host']) != 'ASCII')
1786 {
1787 $parts['host'] = idn_to_ascii($parts['host']);
1788 $url = build_url($parts);
1789 }
1790 }
1791
97b7d5c0
AD
1792 if ($url != "http:///")
1793 return $url;
1794 else
1795 return '';
1796 }
1797
1798 function validate_feed_url($url) {
1799 $parts = parse_url($url);
1800
1801 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1802
1803 }
1804
1805 function get_article_enclosures($id) {
1806
1807 $query = "SELECT * FROM ttrss_enclosures
1808 WHERE post_id = '$id' AND content_url != ''";
1809
1810 $rv = array();
1811
1812 $result = db_query($query);
1813
1814 if (db_num_rows($result) > 0) {
1815 while ($line = db_fetch_assoc($result)) {
1816 array_push($rv, $line);
1817 }
1818 }
1819
1820 return $rv;
1821 }
1822
3e0f2090 1823 /* function save_email_address($email) {
97b7d5c0
AD
1824 // FIXME: implement persistent storage of emails
1825
1826 if (!$_SESSION['stored_emails'])
1827 $_SESSION['stored_emails'] = array();
1828
1829 if (!in_array($email, $_SESSION['stored_emails']))
1830 array_push($_SESSION['stored_emails'], $email);
3e0f2090 1831 } */
97b7d5c0
AD
1832
1833
1834 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1835
1836 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1837
1838 $sql_is_cat = bool_to_sql_bool($is_cat);
1839
1840 $result = db_query("SELECT access_key FROM ttrss_access_keys
1841 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1842 AND owner_uid = " . $owner_uid);
1843
1844 if (db_num_rows($result) == 1) {
1845 return db_fetch_result($result, 0, "access_key");
1846 } else {
3ceb893f 1847 $key = db_escape_string(uniqid_short());
97b7d5c0
AD
1848
1849 $result = db_query("INSERT INTO ttrss_access_keys
1850 (access_key, feed_id, is_cat, owner_uid)
1851 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1852
1853 return $key;
1854 }
1855 return false;
1856 }
1857
1858 function get_feeds_from_html($url, $content)
1859 {
1860 $url = fix_url($url);
1861 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1862
1863 libxml_use_internal_errors(true);
1864
1865 $doc = new DOMDocument();
1866 $doc->loadHTML($content);
1867 $xpath = new DOMXPath($doc);
1868 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
16c48032 1869 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
97b7d5c0
AD
1870 $feedUrls = array();
1871 foreach ($entries as $entry) {
1872 if ($entry->hasAttribute('href')) {
1873 $title = $entry->getAttribute('title');
1874 if ($title == '') {
1875 $title = $entry->getAttribute('type');
1876 }
1877 $feedUrl = rewrite_relative_url(
1878 $baseUrl, $entry->getAttribute('href')
1879 );
1880 $feedUrls[$feedUrl] = $title;
1881 }
1882 }
1883 return $feedUrls;
1884 }
1885
1886 function is_html($content) {
5a4074a9 1887 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
97b7d5c0
AD
1888 }
1889
1890 function url_is_html($url, $login = false, $pass = false) {
1891 return is_html(fetch_file_contents($url, false, $login, $pass));
1892 }
1893
1894 function print_label_select($name, $value, $attributes = "") {
1895
1896 $result = db_query("SELECT caption FROM ttrss_labels2
1897 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1898
1899 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1900 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1901
1902 while ($line = db_fetch_assoc($result)) {
1903
1904 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1905
1906 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1907 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1908
1909 }
1910
1911# print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1912
1913 print "</select>";
1914
1915
1916 }
1917
1918 function format_article_enclosures($id, $always_display_enclosures,
1919 $article_content, $hide_images = false) {
1920
1921 $result = get_article_enclosures($id);
1922 $rv = '';
1923
2bb11658
DZ
1924 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1925 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1926 if (is_array($retval)) {
1927 $rv = $retval[0];
1928 $result = $retval[1];
1929 } else {
1930 $rv = $retval;
1931 }
1932 }
84931635 1933 unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
97b7d5c0 1934
c6ce584d 1935 if ($rv === '' && !empty($result)) {
97b7d5c0
AD
1936 $entries_html = array();
1937 $entries = array();
1938 $entries_inline = array();
1939
1940 foreach ($result as $line) {
1941
1942 $url = $line["content_url"];
1943 $ctype = $line["content_type"];
1944 $title = $line["title"];
1e871938
FE
1945 $width = $line["width"];
1946 $height = $line["height"];
97b7d5c0
AD
1947
1948 if (!$ctype) $ctype = __("unknown type");
1949
c606bd57
AD
1950 //$filename = substr($url, strrpos($url, "/")+1);
1951 $filename = basename($url);
97b7d5c0
AD
1952
1953 $player = format_inline_player($url, $ctype);
1954
1955 if ($player) array_push($entries_inline, $player);
1956
1957# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1958# $filename . " (" . $ctype . ")" . "</a>";
1959
1960 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1961 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1962
1963 array_push($entries_html, $entry);
1964
1965 $entry = array();
1966
1967 $entry["type"] = $ctype;
1968 $entry["filename"] = $filename;
1969 $entry["url"] = $url;
1970 $entry["title"] = $title;
1e871938
FE
1971 $entry["width"] = $width;
1972 $entry["height"] = $height;
97b7d5c0
AD
1973
1974 array_push($entries, $entry);
1975 }
1976
1977 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1978 if ($always_display_enclosures ||
1979 !preg_match("/<img/i", $article_content)) {
1980
1981 foreach ($entries as $entry) {
1982
945346cb
AD
1983 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1984 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
97b7d5c0 1985
945346cb
AD
1986
1987 if ($retval) {
1988 $rv .= $retval;
1989 } else {
1990
1991 if (preg_match("/image/", $entry["type"]) ||
1992 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1993
1994 if (!$hide_images) {
1995 $encsize = '';
1996 if ($entry['height'] > 0)
08e79cb6 1997 $encsize .= ' height="' . intval($entry['height']) . '"';
945346cb 1998 if ($entry['width'] > 0)
08e79cb6 1999 $encsize .= ' width="' . intval($entry['width']) . '"';
945346cb
AD
2000 $rv .= "<p><img
2001 alt=\"".htmlspecialchars($entry["filename"])."\"
2002 src=\"" .htmlspecialchars($entry["url"]) . "\"
2003 " . $encsize . " /></p>";
2004 } else {
2005 $rv .= "<p><a target=\"_blank\"
2006 href=\"".htmlspecialchars($entry["url"])."\"
2007 >" .htmlspecialchars($entry["url"]) . "</a></p>";
2008 }
2009
2010 if ($entry['title']) {
2011 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
2012 }
2013 }
97b7d5c0
AD
2014 }
2015 }
2016 }
2017 }
2018
2019 if (count($entries_inline) > 0) {
2020 $rv .= "<hr clear='both'/>";
2021 foreach ($entries_inline as $entry) { $rv .= $entry; };
2022 $rv .= "<hr clear='both'/>";
2023 }
2024
6810a1de
AD
2025 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
2026 "<span>" . __('Attachments')."</span>";
2027
2028 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
97b7d5c0
AD
2029
2030 foreach ($entries as $entry) {
2031 if ($entry["title"])
c606bd57 2032 $title = " &mdash; " . truncate_string($entry["title"], 30);
97b7d5c0
AD
2033 else
2034 $title = "";
2035
c606bd57
AD
2036 if ($entry["filename"])
2037 $filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
2038 else
2039 $filename = "";
2040
6810a1de 2041 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
c606bd57 2042 dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
97b7d5c0
AD
2043
2044 };
2045
6810a1de
AD
2046 $rv .= "</div>";
2047 $rv .= "</div>";
97b7d5c0
AD
2048 }
2049
2050 return $rv;
2051 }
2052
2053 function getLastArticleId() {
8458a312 2054 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2055 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
97b7d5c0
AD
2056
2057 if (db_num_rows($result) == 1) {
2058 return db_fetch_result($result, 0, "id");
2059 } else {
2060 return -1;
2061 }
2062 }
2063
2064 function build_url($parts) {
2065 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2066 }
2067
2068 /**
2069 * Converts a (possibly) relative URL to a absolute one.
2070 *
2071 * @param string $url Base URL (i.e. from where the document is)
2072 * @param string $rel_url Possibly relative URL in the document
2073 *
2074 * @return string Absolute URL
2075 */
2076 function rewrite_relative_url($url, $rel_url) {
94d425fe 2077 if (strpos($rel_url, "://") !== false) {
97b7d5c0
AD
2078 return $rel_url;
2079 } else if (strpos($rel_url, "//") === 0) {
2080 # protocol-relative URL (rare but they exist)
2081 return $rel_url;
94d425fe
AD
2082 } else if (preg_match("/^[a-z]+:/i", $rel_url)) {
2083 # magnet:, feed:, etc
2084 return $rel_url;
2085 } else if (strpos($rel_url, "/") === 0) {
97b7d5c0
AD
2086 $parts = parse_url($url);
2087 $parts['path'] = $rel_url;
2088
2089 return build_url($parts);
2090
2091 } else {
2092 $parts = parse_url($url);
2093 if (!isset($parts['path'])) {
2094 $parts['path'] = '/';
2095 }
2096 $dir = $parts['path'];
2097 if (substr($dir, -1) !== '/') {
2098 $dir = dirname($parts['path']);
2099 $dir !== '/' && $dir .= '/';
2100 }
2101 $parts['path'] = $dir . $rel_url;
2102
2103 return build_url($parts);
2104 }
2105 }
2106
97b7d5c0
AD
2107 function cleanup_tags($days = 14, $limit = 1000) {
2108
2109 if (DB_TYPE == "pgsql") {
2110 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2111 } else if (DB_TYPE == "mysql") {
2112 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2113 }
2114
2115 $tags_deleted = 0;
2116
2117 while ($limit > 0) {
2118 $limit_part = 500;
2119
2120 $query = "SELECT ttrss_tags.id AS id
2121 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2122 WHERE post_int_id = int_id AND $interval_query AND
2123 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2124
2125 $result = db_query($query);
2126
2127 $ids = array();
2128
2129 while ($line = db_fetch_assoc($result)) {
2130 array_push($ids, $line['id']);
2131 }
2132
2133 if (count($ids) > 0) {
2134 $ids = join(",", $ids);
2135
2136 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2137 $tags_deleted += db_affected_rows($tmp_result);
2138 } else {
2139 break;
2140 }
2141
2142 $limit -= $limit_part;
2143 }
2144
2145 return $tags_deleted;
2146 }
2147
2148 function print_user_stylesheet() {
2149 $value = get_pref('USER_STYLESHEET');
2150
2151 if ($value) {
2152 print "<style type=\"text/css\">";
2153 print str_replace("<br/>", "\n", $value);
2154 print "</style>";
2155 }
2156
2157 }
2158
2159 function filter_to_sql($filter, $owner_uid) {
2160 $query = array();
2161
2162 if (DB_TYPE == "pgsql")
2163 $reg_qpart = "~";
2164 else
2165 $reg_qpart = "REGEXP";
2166
2167 foreach ($filter["rules"] AS $rule) {
2168 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2169 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2170 $rule['reg_exp']) !== FALSE;
2171
2172 if ($regexp_valid) {
2173
2174 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2175
2176 switch ($rule["type"]) {
2177 case "title":
2178 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2179 $rule['reg_exp'] . "')";
2180 break;
2181 case "content":
2182 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2183 $rule['reg_exp'] . "')";
2184 break;
2185 case "both":
2186 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2187 $rule['reg_exp'] . "') OR LOWER(" .
2188 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2189 break;
2190 case "tag":
2191 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2192 $rule['reg_exp'] . "')";
2193 break;
2194 case "link":
2195 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2196 $rule['reg_exp'] . "')";
2197 break;
2198 case "author":
2199 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2200 $rule['reg_exp'] . "')";
2201 break;
2202 }
2203
2204 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2205
2206 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2207 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2208 }
2209
2210 if (isset($rule["cat_id"])) {
2211
2212 if ($rule["cat_id"] > 0) {
2213 $children = getChildCategories($rule["cat_id"], $owner_uid);
2214 array_push($children, $rule["cat_id"]);
2215
2216 $children = join(",", $children);
2217
2218 $cat_qpart = "cat_id IN ($children)";
2219 } else {
2220 $cat_qpart = "cat_id IS NULL";
2221 }
2222
2223 $qpart .= " AND $cat_qpart";
2224 }
2225
2226 $qpart .= " AND feed_id IS NOT NULL";
2227
2228 array_push($query, "($qpart)");
2229
2230 }
2231 }
2232
2233 if (count($query) > 0) {
2234 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2235 } else {
2236 $fullquery = "(false)";
2237 }
2238
2239 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2240
2241 return $fullquery;
2242 }
2243
2244 if (!function_exists('gzdecode')) {
2245 function gzdecode($string) { // no support for 2nd argument
2246 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2247 base64_encode($string));
2248 }
2249 }
2250
2251 function get_random_bytes($length) {
2252 if (function_exists('openssl_random_pseudo_bytes')) {
2253 return openssl_random_pseudo_bytes($length);
2254 } else {
2255 $output = "";
2256
2257 for ($i = 0; $i < $length; $i++)
2258 $output .= chr(mt_rand(0, 255));
2259
2260 return $output;
2261 }
2262 }
2263
2264 function read_stdin() {
2265 $fp = fopen("php://stdin", "r");
2266
2267 if ($fp) {
2268 $line = trim(fgets($fp));
2269 fclose($fp);
2270 return $line;
2271 }
2272
2273 return null;
2274 }
2275
2276 function tmpdirname($path, $prefix) {
2277 // Use PHP's tmpfile function to create a temporary
2278 // directory name. Delete the file and keep the name.
2279 $tempname = tempnam($path,$prefix);
2280 if (!$tempname)
2281 return false;
2282
2283 if (!unlink($tempname))
2284 return false;
2285
2286 return $tempname;
2287 }
2288
2289 function getFeedCategory($feed) {
2290 $result = db_query("SELECT cat_id FROM ttrss_feeds
2291 WHERE id = '$feed'");
2292
2293 if (db_num_rows($result) > 0) {
2294 return db_fetch_result($result, 0, "cat_id");
2295 } else {
2296 return false;
2297 }
2298
2299 }
2300
2301 function implements_interface($class, $interface) {
2302 return in_array($interface, class_implements($class));
2303 }
2304
97b7d5c0
AD
2305 function get_minified_js($files) {
2306 require_once 'lib/jshrink/Minifier.php';
2307
2308 $rv = '';
2309
2310 foreach ($files as $js) {
2311 if (!isset($_GET['debug'])) {
2312 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2313
aa9f7d44 2314 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
97b7d5c0 2315
aa9f7d44 2316 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
97b7d5c0 2317
aa9f7d44
AD
2318 if ($header && $contents) {
2319 list($htag, $hversion) = explode(":", $header);
2320
2321 if ($htag == "tt-rss" && $hversion == VERSION) {
2322 $rv .= $contents;
2323 continue;
2324 }
2325 }
97b7d5c0 2326 }
aa9f7d44
AD
2327
2328 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2329 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2330 $rv .= $minified;
2331
97b7d5c0 2332 } else {
aa9f7d44 2333 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
97b7d5c0
AD
2334 }
2335 }
2336
2337 return $rv;
2338 }
2339
2340 function stylesheet_tag($filename) {
2341 $timestamp = filemtime($filename);
2342
2343 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2344 }
2345
2346 function javascript_tag($filename) {
2347 $query = "";
2348
2349 if (!(strpos($filename, "?") === FALSE)) {
2350 $query = substr($filename, strpos($filename, "?")+1);
2351 $filename = substr($filename, 0, strpos($filename, "?"));
2352 }
2353
2354 $timestamp = filemtime($filename);
2355
2356 if ($query) $timestamp .= "&$query";
2357
2358 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2359 }
2360
2361 function calculate_dep_timestamp() {
2362 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2363
2364 $max_ts = -1;
2365
2366 foreach ($files as $file) {
2367 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2368 }
2369
2370 return $max_ts;
2371 }
2372
2373 function T_js_decl($s1, $s2) {
2374 if ($s1 && $s2) {
2375 $s1 = preg_replace("/\n/", "", $s1);
2376 $s2 = preg_replace("/\n/", "", $s2);
2377
2378 $s1 = preg_replace("/\"/", "\\\"", $s1);
2379 $s2 = preg_replace("/\"/", "\\\"", $s2);
2380
2381 return "T_messages[\"$s1\"] = \"$s2\";\n";
2382 }
2383 }
2384
2385 function init_js_translations() {
2386
2387 print 'var T_messages = new Object();
2388
2389 function __(msg) {
2390 if (T_messages[msg]) {
2391 return T_messages[msg];
2392 } else {
2393 return msg;
2394 }
2395 }
2396
2397 function ngettext(msg1, msg2, n) {
2398 return __((parseInt(n) > 1) ? msg2 : msg1);
2399 }';
2400
2401 $l10n = _get_reader();
2402
2403 for ($i = 0; $i < $l10n->total; $i++) {
2404 $orig = $l10n->get_original_string($i);
2405 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2406 $key = explode(chr(0), $orig);
2407 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2408 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2409 } else {
2410 $translation = __($orig);
2411 print T_js_decl($orig, $translation);
2412 }
2413 }
2414 }
2415
2416 function label_to_feed_id($label) {
2417 return LABEL_BASE_INDEX - 1 - abs($label);
2418 }
2419
2420 function feed_to_label_id($feed) {
2421 return LABEL_BASE_INDEX - 1 + abs($feed);
2422 }
2423
b9634eb8
AD
2424 function get_theme_path($theme) {
2425 $check = "themes/$theme";
2426 if (file_exists($check)) return $check;
2427
2428 $check = "themes.local/$theme";
2429 if (file_exists($check)) return $check;
2430 }
2431
2432 function theme_valid($theme) {
181c8285
AD
2433 $bundled_themes = [ "default.php", "night.css", "compact.css" ];
2434
2435 if (in_array($theme, $bundled_themes)) return true;
2436
b9634eb8
AD
2437 $file = "themes/" . basename($theme);
2438
2439 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
f6cbe9a5
AD
2440
2441 if (file_exists($file) && is_readable($file)) {
2442 $fh = fopen($file, "r");
2443
2444 if ($fh) {
2445 $header = fgets($fh);
2446 fclose($fh);
2447
2448 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2449 }
2450 }
2451
2452 return false;
2453 }
27f7b593
AD
2454
2455 function error_json($code) {
2456 require_once "errors.php";
2457
2458 @$message = $ERRORS[$code];
2459
2460 return json_encode(array("error" =>
2461 array("code" => $code, "message" => $message)));
2462
2463 }
7c0a2ab2
AD
2464
2465 function abs_to_rel_path($dir) {
2466 $tmp = str_replace(dirname(__DIR__), "", $dir);
2467
2468 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2469
2470 return $tmp;
2471 }
1a322ff3
AD
2472
2473 function get_upload_error_message($code) {
2474
2475 $errors = array(
2476 0 => __('There is no error, the file uploaded with success'),
2477 1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2478 2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
2479 3 => __('The uploaded file was only partially uploaded'),
2480 4 => __('No file was uploaded'),
2481 6 => __('Missing a temporary folder'),
2482 7 => __('Failed to write file to disk.'),
2483 8 => __('A PHP extension stopped the file upload.'),
2484 );
2485
2486 return $errors[$code];
2487 }
97b7d5c0 2488?>