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