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