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