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