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