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