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