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