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