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