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