]> git.wh0rd.org - tt-rss.git/blob - include/functions2.php
queryfeedheadlines: only use disable_offsets hack in expanded mode
[tt-rss.git] / include / functions2.php
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;
20
21 $theme = get_pref( "USER_CSS_THEME", false, false);
22 $params["theme"] = theme_valid("$theme") ? $theme : "";
23
24 $params["plugins"] = implode(", ", PluginHost::getInstance()->get_plugin_names());
25
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
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
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
223 return "";
224 }
225
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
244
245 if (CHECK_FOR_UPDATES && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
246 $update_result = @check_for_update();
247
248 $data["update_result"] = $update_result;
249
250 $_SESSION["last_version_check"] = time();
251 }
252
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
280 return $data;
281 }
282
283 function search_to_sql($search, $search_language) {
284
285 $keywords = str_getcsv($search, " ");
286 $query_keywords = array();
287 $search_words = array();
288 $search_query_leftover = array();
289
290 if ($search_language)
291 $search_language = db_escape_string(mb_strtolower($search_language));
292 else
293 $search_language = "english";
294
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 }
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 }
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 {
391
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 }
399
400 if (!$not) array_push($search_words, $k);
401 }
402 }
403 }
404
405 if (count($search_query_leftover) > 0) {
406 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
407
408 if (DB_TYPE == "pgsql") {
409 array_push($query_keywords,
410 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
411 }
412
413 }
414
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
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;
455 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
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"];
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;
465 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
466 $api_request = isset($params["api_request"]) ? $params["api_request"] : false;
467
468 $ext_tables_part = "";
469 $query_strategy_part = "";
470
471 $search_words = array();
472
473 if ($search) {
474 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
475 list($search_query_part, $search_words) = $plugin->hook_search($search);
476 break;
477 }
478
479 // fall back in case of no plugins
480 if (!$search_query_part) {
481 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
482 }
483 $search_query_part .= " AND ";
484 } else {
485 $search_query_part = "";
486 }
487
488 if ($since_id) {
489 $since_id_part = "ttrss_entries.id > $since_id AND ";
490 } else {
491 $since_id_part = "";
492 }
493
494 $view_query_part = "";
495 $disable_offsets = false;
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
507 if ($unread > 0) {
508 $view_query_part = " unread = true AND ";
509 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
510 }
511 }
512 }
513
514 if ($view_mode == "marked") {
515 $view_query_part = " marked = true AND ";
516 }
517
518 if ($view_mode == "has_note") {
519 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
520 }
521
522 if ($view_mode == "published") {
523 $view_query_part = " published = true AND ";
524 }
525
526 if ($view_mode == "unread" && $feed != -6) {
527 $view_query_part = " unread = true AND ";
528 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
529 }
530
531 if ($limit > 0) {
532 $limit_query_part = "LIMIT " . $limit;
533 }
534
535 $allow_archived = false;
536
537 $vfeed_query_part = "";
538
539 /* tags */
540 if (!is_numeric($feed)) {
541 $query_strategy_part = "true";
542 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
543 id = feed_id) as feed_title,";
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;
609 $ignore_vfeed_group = true;
610
611 if (!$override_order) $override_order = "last_read DESC";
612
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
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
683 if ($limit_query_part) {
684 $offset_query_part = "OFFSET $offset";
685 } else {
686 $offset_query_part = "";
687 }
688
689 if (is_numeric($feed)) {
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
708 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
709
710 if ($start_ts) {
711 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
712 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
713 } else {
714 $start_ts_query_part = "";
715 }
716
717 $first_id = 0;
718 $first_id_query_strategy_part = $query_strategy_part;
719
720 if ($feed == -3)
721 $first_id_query_strategy_part = "true";
722
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
729 if (!$search && !$disable_offsets) {
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 last_read
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
753 $sanity_interval_qpart
754 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
755
756 if ($_REQUEST["debug"]) {
757 print $query;
758 }
759
760 $result = db_query($query);
761 if ($result && db_num_rows($result) > 0) {
762 $first_id = (int)db_fetch_result($result, 0, "id");
763
764 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
765 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
766 }
767 }
768 }
769
770 if ($disable_offsets) {
771 $offset_query_part = "";
772 }
773
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
802 $start_ts_query_part
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
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";
850
851 if ($_REQUEST["debug"]) print $query;
852
853 $result = db_query($query);
854 }
855
856 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
857
858 }
859
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
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)) {
911 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
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) {
943 if (!iframe_whitelisted($entry)) {
944 $entry->setAttribute('sandbox', 'allow-scripts');
945 } else {
946 if ($_SERVER['HTTPS'] == "on") {
947 $entry->setAttribute("src",
948 str_replace("http://", "https://",
949 $entry->getAttribute("src")));
950 }
951 }
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',
959 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
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
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 = "") {
1157 return "<div class=\"warning\" id=\"$id\">
1158 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1159 }
1160
1161 function format_notice($msg, $id = "") {
1162 return "<div class=\"notice\" id=\"$id\">
1163 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1164 }
1165
1166 function format_error($msg, $id = "") {
1167 return "<div class=\"error\" id=\"$id\">
1168 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1169 }
1170
1171 function print_notice($msg) {
1172 return print format_notice($msg);
1173 }
1174
1175 function print_warning($msg) {
1176 return print format_warning($msg);
1177 }
1178
1179 function print_error($msg) {
1180 return print format_error($msg);
1181 }
1182
1183
1184 function T_sprintf() {
1185 $args = func_get_args();
1186 return vsprintf(__(array_shift($args)), $args);
1187 }
1188
1189 function format_inline_player($url, $ctype) {
1190
1191 $entry = "";
1192
1193 $url = htmlspecialchars($url);
1194
1195 if (strpos($ctype, "audio/") === 0) {
1196
1197 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1198 $_SESSION["hasMp3"])) {
1199
1200 $entry .= "<audio preload=\"none\" controls>
1201 <source type=\"$ctype\" src=\"$url\"/>
1202 </audio>";
1203
1204 } else {
1205
1206 $entry .= "<object type=\"application/x-shockwave-flash\"
1207 data=\"lib/button/musicplayer.swf?song_url=$url\"
1208 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1209 <param name=\"movie\"
1210 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1211 </object>";
1212 }
1213
1214 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
1215 href=\"$url\">" . basename($url) . "</a>";
1216
1217 return $entry;
1218
1219 }
1220
1221 return "";
1222
1223 /* $filename = substr($url, strrpos($url, "/")+1);
1224
1225 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1226 $filename . " (" . $ctype . ")" . "</a>"; */
1227
1228 }
1229
1230 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1231 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1232
1233 $rv = array();
1234
1235 $rv['id'] = $id;
1236
1237 /* we can figure out feed_id from article id anyway, why do we
1238 * pass feed_id here? let's ignore the argument :(*/
1239
1240 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1241 WHERE ref_id = '$id'");
1242
1243 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1244
1245 $rv['feed_id'] = $feed_id;
1246
1247 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1248
1249 if ($mark_as_read) {
1250 $result = db_query("UPDATE ttrss_user_entries
1251 SET unread = false,last_read = NOW()
1252 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1253
1254 ccache_update($feed_id, $owner_uid);
1255 }
1256
1257 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1258 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1259 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1260 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1261 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1262 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1263 num_comments,
1264 tag_cache,
1265 author,
1266 orig_feed_id,
1267 note
1268 FROM ttrss_entries,ttrss_user_entries
1269 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1270
1271 if ($result) {
1272
1273 $line = db_fetch_assoc($result);
1274
1275 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1276 unset($line["tag_cache"]);
1277
1278 $line["content"] = sanitize($line["content"],
1279 sql_bool_to_bool($line['hide_images']),
1280 $owner_uid, $line["site_url"], false, $line["id"]);
1281
1282 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1283 $line = $p->hook_render_article($line);
1284 }
1285
1286 $num_comments = $line["num_comments"];
1287 $entry_comments = "";
1288
1289 if ($num_comments > 0) {
1290 if ($line["comments"]) {
1291 $comments_url = htmlspecialchars($line["comments"]);
1292 } else {
1293 $comments_url = htmlspecialchars($line["link"]);
1294 }
1295 $entry_comments = "<a class=\"postComments\"
1296 target='_blank' href=\"$comments_url\">$num_comments ".
1297 _ngettext("comment", "comments", $num_comments)."</a>";
1298
1299 } else {
1300 if ($line["comments"] && $line["link"] != $line["comments"]) {
1301 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1302 }
1303 }
1304
1305 if ($zoom_mode) {
1306 header("Content-Type: text/html");
1307 $rv['content'] .= "<html><head>
1308 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1309 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1310 stylesheet_tag("css/tt-rss.css").
1311 stylesheet_tag("css/zoom.css").
1312 stylesheet_tag("css/dijit.css")."
1313
1314 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1315 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1316
1317 <script type=\"text/javascript\">
1318 function openSelectedAttachment(elem) {
1319 try {
1320 var url = elem[elem.selectedIndex].value;
1321
1322 if (url) {
1323 window.open(url);
1324 elem.selectedIndex = 0;
1325 }
1326
1327 } catch (e) {
1328 exception_error(\"openSelectedAttachment\", e);
1329 }
1330 }
1331 </script>
1332 </head><body id=\"ttrssZoom\">";
1333 }
1334
1335 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1336
1337 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1338
1339 $entry_author = $line["author"];
1340
1341 if ($entry_author) {
1342 $entry_author = __(" - ") . $entry_author;
1343 }
1344
1345 $parsed_updated = make_local_datetime($line["updated"], true,
1346 $owner_uid, true);
1347
1348 if (!$zoom_mode)
1349 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1350
1351 if ($line["link"]) {
1352 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1353 title=\"".htmlspecialchars($line['title'])."\"
1354 href=\"" .
1355 htmlspecialchars($line["link"]) . "\">" .
1356 $line["title"] . "</a>" .
1357 "<span class='author'>$entry_author</span></div>";
1358 } else {
1359 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1360 }
1361
1362 if ($zoom_mode) {
1363 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1364 "\" target=\"_blank\">".
1365 htmlspecialchars($line["feed_title"])."</a>";
1366
1367 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1368
1369 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1370 }
1371
1372 $tags_str = format_tags_string($line["tags"], $id);
1373 $tags_str_full = join(", ", $line["tags"]);
1374
1375 if (!$tags_str_full) $tags_str_full = __("no tags");
1376
1377 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1378
1379 $rv['content'] .= "<div class='postTags' style='float : right'>
1380 <img src='images/tag.png'
1381 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1382
1383 if (!$zoom_mode) {
1384 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1385 <a title=\"".__('Edit tags for this article')."\"
1386 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1387
1388 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1389 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1390 position=\"below\">$tags_str_full</div>";
1391
1392 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1393 $rv['content'] .= $p->hook_article_button($line);
1394 }
1395
1396 } else {
1397 $tags_str = strip_tags($tags_str);
1398 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1399 }
1400 $rv['content'] .= "</div>";
1401 $rv['content'] .= "<div clear='both'>";
1402
1403 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1404 $rv['content'] .= $p->hook_article_left_button($line);
1405 }
1406
1407 $rv['content'] .= "$entry_comments</div>";
1408
1409 if ($line["orig_feed_id"]) {
1410
1411 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1412 WHERE id = ".$line["orig_feed_id"]);
1413
1414 if (db_num_rows($tmp_result) != 0) {
1415
1416 $rv['content'] .= "<div clear='both'>";
1417 $rv['content'] .= __("Originally from:");
1418
1419 $rv['content'] .= "&nbsp;";
1420
1421 $tmp_line = db_fetch_assoc($tmp_result);
1422
1423 $rv['content'] .= "<a target='_blank'
1424 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1425 $tmp_line['title'] . "</a>";
1426
1427 $rv['content'] .= "&nbsp;";
1428
1429 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1430 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1431
1432 $rv['content'] .= "</div>";
1433 }
1434 }
1435
1436 $rv['content'] .= "</div>";
1437
1438 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1439 if ($line['note']) {
1440 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1441 }
1442 $rv['content'] .= "</div>";
1443
1444 if (!$line['lang']) $line['lang'] = 'en';
1445
1446 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1447
1448 $rv['content'] .= $line["content"];
1449 $rv['content'] .= format_article_enclosures($id,
1450 sql_bool_to_bool($line["always_display_enclosures"]),
1451 $line["content"],
1452 sql_bool_to_bool($line["hide_images"]));
1453
1454 $rv['content'] .= "</div>";
1455
1456 $rv['content'] .= "</div>";
1457
1458 }
1459
1460 if ($zoom_mode) {
1461 $rv['content'] .= "
1462 <div class='footer'>
1463 <button onclick=\"return window.close()\">".
1464 __("Close this window")."</button></div>";
1465 $rv['content'] .= "</body></html>";
1466 }
1467
1468 return $rv;
1469
1470 }
1471
1472 function print_checkpoint($n, $s) {
1473 $ts = microtime(true);
1474 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1475 return $ts;
1476 }
1477
1478 function sanitize_tag($tag) {
1479 $tag = trim($tag);
1480
1481 $tag = mb_strtolower($tag, 'utf-8');
1482
1483 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1484
1485 if (DB_TYPE == "mysql") {
1486 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1487 }
1488
1489 return $tag;
1490 }
1491
1492 function get_self_url_prefix() {
1493 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1494 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1495 } else {
1496 return SELF_URL_PATH;
1497 }
1498 }
1499
1500 /**
1501 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1502 *
1503 * @return string The Mozilla Firefox feed adding URL.
1504 */
1505 function add_feed_url() {
1506 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1507
1508 $url_path = get_self_url_prefix() .
1509 "/public.php?op=subscribe&feed_url=%s";
1510 return $url_path;
1511 } // function add_feed_url
1512
1513 function encrypt_password($pass, $salt = '', $mode2 = false) {
1514 if ($salt && $mode2) {
1515 return "MODE2:" . hash('sha256', $salt . $pass);
1516 } else if ($salt) {
1517 return "SHA1X:" . sha1("$salt:$pass");
1518 } else {
1519 return "SHA1:" . sha1($pass);
1520 }
1521 } // function encrypt_password
1522
1523 function load_filters($feed_id, $owner_uid, $action_id = false) {
1524 $filters = array();
1525
1526 $cat_id = (int)getFeedCategory($feed_id);
1527
1528 if ($cat_id == 0)
1529 $null_cat_qpart = "cat_id IS NULL OR";
1530 else
1531 $null_cat_qpart = "";
1532
1533 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1534 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1535
1536 $check_cats = join(",", array_merge(
1537 getParentCategories($cat_id, $owner_uid),
1538 array($cat_id)));
1539
1540 while ($line = db_fetch_assoc($result)) {
1541 $filter_id = $line["id"];
1542
1543 $result2 = db_query("SELECT
1544 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1545 FROM ttrss_filters2_rules AS r,
1546 ttrss_filter_types AS t
1547 WHERE
1548 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1549 (feed_id IS NULL OR feed_id = '$feed_id') AND
1550 filter_type = t.id AND filter_id = '$filter_id'");
1551
1552 $rules = array();
1553 $actions = array();
1554
1555 while ($rule_line = db_fetch_assoc($result2)) {
1556 # print_r($rule_line);
1557
1558 $rule = array();
1559 $rule["reg_exp"] = $rule_line["reg_exp"];
1560 $rule["type"] = $rule_line["type_name"];
1561 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1562
1563 array_push($rules, $rule);
1564 }
1565
1566 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1567 FROM ttrss_filters2_actions AS a,
1568 ttrss_filter_actions AS t
1569 WHERE
1570 action_id = t.id AND filter_id = '$filter_id'");
1571
1572 while ($action_line = db_fetch_assoc($result2)) {
1573 # print_r($action_line);
1574
1575 $action = array();
1576 $action["type"] = $action_line["type_name"];
1577 $action["param"] = $action_line["action_param"];
1578
1579 array_push($actions, $action);
1580 }
1581
1582
1583 $filter = array();
1584 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1585 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1586 $filter["rules"] = $rules;
1587 $filter["actions"] = $actions;
1588
1589 if (count($rules) > 0 && count($actions) > 0) {
1590 array_push($filters, $filter);
1591 }
1592 }
1593
1594 return $filters;
1595 }
1596
1597 function get_score_pic($score) {
1598 if ($score > 100) {
1599 return "score_high.png";
1600 } else if ($score > 0) {
1601 return "score_half_high.png";
1602 } else if ($score < -100) {
1603 return "score_low.png";
1604 } else if ($score < 0) {
1605 return "score_half_low.png";
1606 } else {
1607 return "score_neutral.png";
1608 }
1609 }
1610
1611 function feed_has_icon($id) {
1612 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1613 }
1614
1615 function init_plugins() {
1616 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1617
1618 return true;
1619 }
1620
1621 function format_tags_string($tags, $id) {
1622 if (!is_array($tags) || count($tags) == 0) {
1623 return __("no tags");
1624 } else {
1625 $maxtags = min(5, count($tags));
1626 $tags_str = "";
1627
1628 for ($i = 0; $i < $maxtags; $i++) {
1629 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1630 }
1631
1632 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1633
1634 if (count($tags) > $maxtags)
1635 $tags_str .= ", &hellip;";
1636
1637 return $tags_str;
1638 }
1639 }
1640
1641 function format_article_labels($labels, $id) {
1642
1643 if (!is_array($labels)) return '';
1644
1645 $labels_str = "";
1646
1647 foreach ($labels as $l) {
1648 $labels_str .= sprintf("<span class='hlLabelRef'
1649 style='color : %s; background-color : %s'>%s</span>",
1650 $l[2], $l[3], $l[1]);
1651 }
1652
1653 return $labels_str;
1654
1655 }
1656
1657 function format_article_note($id, $note, $allow_edit = true) {
1658
1659 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1660 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1661 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1662
1663 return $str;
1664 }
1665
1666
1667 function get_feed_category($feed_cat, $parent_cat_id = false) {
1668 if ($parent_cat_id) {
1669 $parent_qpart = "parent_cat = '$parent_cat_id'";
1670 $parent_insert = "'$parent_cat_id'";
1671 } else {
1672 $parent_qpart = "parent_cat IS NULL";
1673 $parent_insert = "NULL";
1674 }
1675
1676 $result = db_query(
1677 "SELECT id FROM ttrss_feed_categories
1678 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1679
1680 if (db_num_rows($result) == 0) {
1681 return false;
1682 } else {
1683 return db_fetch_result($result, 0, "id");
1684 }
1685 }
1686
1687 function add_feed_category($feed_cat, $parent_cat_id = false) {
1688
1689 if (!$feed_cat) return false;
1690
1691 db_query("BEGIN");
1692
1693 if ($parent_cat_id) {
1694 $parent_qpart = "parent_cat = '$parent_cat_id'";
1695 $parent_insert = "'$parent_cat_id'";
1696 } else {
1697 $parent_qpart = "parent_cat IS NULL";
1698 $parent_insert = "NULL";
1699 }
1700
1701 $feed_cat = mb_substr($feed_cat, 0, 250);
1702
1703 $result = db_query(
1704 "SELECT id FROM ttrss_feed_categories
1705 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1706
1707 if (db_num_rows($result) == 0) {
1708
1709 $result = db_query(
1710 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1711 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1712
1713 db_query("COMMIT");
1714
1715 return true;
1716 }
1717
1718 return false;
1719 }
1720
1721 function getArticleFeed($id) {
1722 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1723 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1724
1725 if (db_num_rows($result) != 0) {
1726 return db_fetch_result($result, 0, "feed_id");
1727 } else {
1728 return 0;
1729 }
1730 }
1731
1732 /**
1733 * Fixes incomplete URLs by prepending "http://".
1734 * Also replaces feed:// with http://, and
1735 * prepends a trailing slash if the url is a domain name only.
1736 *
1737 * @param string $url Possibly incomplete URL
1738 *
1739 * @return string Fixed URL.
1740 */
1741 function fix_url($url) {
1742
1743 // support schema-less urls
1744 if (strpos($url, '//') === 0) {
1745 $url = 'https:' . $url;
1746 }
1747
1748 if (strpos($url, '://') === false) {
1749 $url = 'http://' . $url;
1750 } else if (substr($url, 0, 5) == 'feed:') {
1751 $url = 'http:' . substr($url, 5);
1752 }
1753
1754 //prepend slash if the URL has no slash in it
1755 // "http://www.example" -> "http://www.example/"
1756 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1757 $url .= '/';
1758 }
1759
1760 if ($url != "http:///")
1761 return $url;
1762 else
1763 return '';
1764 }
1765
1766 function validate_feed_url($url) {
1767 $parts = parse_url($url);
1768
1769 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1770
1771 }
1772
1773 function get_article_enclosures($id) {
1774
1775 $query = "SELECT * FROM ttrss_enclosures
1776 WHERE post_id = '$id' AND content_url != ''";
1777
1778 $rv = array();
1779
1780 $result = db_query($query);
1781
1782 if (db_num_rows($result) > 0) {
1783 while ($line = db_fetch_assoc($result)) {
1784 array_push($rv, $line);
1785 }
1786 }
1787
1788 return $rv;
1789 }
1790
1791 /* function save_email_address($email) {
1792 // FIXME: implement persistent storage of emails
1793
1794 if (!$_SESSION['stored_emails'])
1795 $_SESSION['stored_emails'] = array();
1796
1797 if (!in_array($email, $_SESSION['stored_emails']))
1798 array_push($_SESSION['stored_emails'], $email);
1799 } */
1800
1801
1802 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1803
1804 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1805
1806 $sql_is_cat = bool_to_sql_bool($is_cat);
1807
1808 $result = db_query("SELECT access_key FROM ttrss_access_keys
1809 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1810 AND owner_uid = " . $owner_uid);
1811
1812 if (db_num_rows($result) == 1) {
1813 return db_fetch_result($result, 0, "access_key");
1814 } else {
1815 $key = db_escape_string(uniqid_short());
1816
1817 $result = db_query("INSERT INTO ttrss_access_keys
1818 (access_key, feed_id, is_cat, owner_uid)
1819 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1820
1821 return $key;
1822 }
1823 return false;
1824 }
1825
1826 function get_feeds_from_html($url, $content)
1827 {
1828 $url = fix_url($url);
1829 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1830
1831 libxml_use_internal_errors(true);
1832
1833 $doc = new DOMDocument();
1834 $doc->loadHTML($content);
1835 $xpath = new DOMXPath($doc);
1836 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1837 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1838 $feedUrls = array();
1839 foreach ($entries as $entry) {
1840 if ($entry->hasAttribute('href')) {
1841 $title = $entry->getAttribute('title');
1842 if ($title == '') {
1843 $title = $entry->getAttribute('type');
1844 }
1845 $feedUrl = rewrite_relative_url(
1846 $baseUrl, $entry->getAttribute('href')
1847 );
1848 $feedUrls[$feedUrl] = $title;
1849 }
1850 }
1851 return $feedUrls;
1852 }
1853
1854 function is_html($content) {
1855 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1856 }
1857
1858 function url_is_html($url, $login = false, $pass = false) {
1859 return is_html(fetch_file_contents($url, false, $login, $pass));
1860 }
1861
1862 function print_label_select($name, $value, $attributes = "") {
1863
1864 $result = db_query("SELECT caption FROM ttrss_labels2
1865 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1866
1867 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1868 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1869
1870 while ($line = db_fetch_assoc($result)) {
1871
1872 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1873
1874 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1875 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1876
1877 }
1878
1879 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1880
1881 print "</select>";
1882
1883
1884 }
1885
1886 function format_article_enclosures($id, $always_display_enclosures,
1887 $article_content, $hide_images = false) {
1888
1889 $result = get_article_enclosures($id);
1890 $rv = '';
1891
1892 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1893 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1894 if (is_array($retval)) {
1895 $rv = $retval[0];
1896 $result = $retval[1];
1897 } else {
1898 $rv = $retval;
1899 }
1900 }
1901
1902 if ($rv === '' && !empty($result)) {
1903 $entries_html = array();
1904 $entries = array();
1905 $entries_inline = array();
1906
1907 foreach ($result as $line) {
1908
1909 $url = $line["content_url"];
1910 $ctype = $line["content_type"];
1911 $title = $line["title"];
1912 $width = $line["width"];
1913 $height = $line["height"];
1914
1915 if (!$ctype) $ctype = __("unknown type");
1916
1917 $filename = substr($url, strrpos($url, "/")+1);
1918
1919 $player = format_inline_player($url, $ctype);
1920
1921 if ($player) array_push($entries_inline, $player);
1922
1923 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1924 # $filename . " (" . $ctype . ")" . "</a>";
1925
1926 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1927 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1928
1929 array_push($entries_html, $entry);
1930
1931 $entry = array();
1932
1933 $entry["type"] = $ctype;
1934 $entry["filename"] = $filename;
1935 $entry["url"] = $url;
1936 $entry["title"] = $title;
1937 $entry["width"] = $width;
1938 $entry["height"] = $height;
1939
1940 array_push($entries, $entry);
1941 }
1942
1943 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1944 if ($always_display_enclosures ||
1945 !preg_match("/<img/i", $article_content)) {
1946
1947 foreach ($entries as $entry) {
1948
1949 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1950 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1951
1952
1953 if ($retval) {
1954 $rv .= $retval;
1955 } else {
1956
1957 if (preg_match("/image/", $entry["type"]) ||
1958 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1959
1960 if (!$hide_images) {
1961 $encsize = '';
1962 if ($entry['height'] > 0)
1963 $encsize .= ' height="' . intval($entry['width']) . '"';
1964 if ($entry['width'] > 0)
1965 $encsize .= ' width="' . intval($entry['height']) . '"';
1966 $rv .= "<p><img
1967 alt=\"".htmlspecialchars($entry["filename"])."\"
1968 src=\"" .htmlspecialchars($entry["url"]) . "\"
1969 " . $encsize . " /></p>";
1970 } else {
1971 $rv .= "<p><a target=\"_blank\"
1972 href=\"".htmlspecialchars($entry["url"])."\"
1973 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1974 }
1975
1976 if ($entry['title']) {
1977 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1978 }
1979 }
1980 }
1981 }
1982 }
1983 }
1984
1985 if (count($entries_inline) > 0) {
1986 $rv .= "<hr clear='both'/>";
1987 foreach ($entries_inline as $entry) { $rv .= $entry; };
1988 $rv .= "<hr clear='both'/>";
1989 }
1990
1991 $rv .= "<select class=\"attachments\" onchange=\"openSelectedAttachment(this)\">".
1992 "<option value=''>" . __('Attachments')."</option>";
1993
1994 foreach ($entries as $entry) {
1995 if ($entry["title"])
1996 $title = "&mdash; " . truncate_string($entry["title"], 30);
1997 else
1998 $title = "";
1999
2000 $rv .= "<option value=\"".htmlspecialchars($entry["url"])."\">" . htmlspecialchars($entry["filename"]) . "$title</option>";
2001
2002 };
2003
2004 $rv .= "</select>";
2005 }
2006
2007 return $rv;
2008 }
2009
2010 function getLastArticleId() {
2011 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2012 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2013
2014 if (db_num_rows($result) == 1) {
2015 return db_fetch_result($result, 0, "id");
2016 } else {
2017 return -1;
2018 }
2019 }
2020
2021 function build_url($parts) {
2022 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2023 }
2024
2025 /**
2026 * Converts a (possibly) relative URL to a absolute one.
2027 *
2028 * @param string $url Base URL (i.e. from where the document is)
2029 * @param string $rel_url Possibly relative URL in the document
2030 *
2031 * @return string Absolute URL
2032 */
2033 function rewrite_relative_url($url, $rel_url) {
2034 if (strpos($rel_url, ":") !== false) {
2035 return $rel_url;
2036 } else if (strpos($rel_url, "://") !== false) {
2037 return $rel_url;
2038 } else if (strpos($rel_url, "//") === 0) {
2039 # protocol-relative URL (rare but they exist)
2040 return $rel_url;
2041 } else if (strpos($rel_url, "/") === 0)
2042 {
2043 $parts = parse_url($url);
2044 $parts['path'] = $rel_url;
2045
2046 return build_url($parts);
2047
2048 } else {
2049 $parts = parse_url($url);
2050 if (!isset($parts['path'])) {
2051 $parts['path'] = '/';
2052 }
2053 $dir = $parts['path'];
2054 if (substr($dir, -1) !== '/') {
2055 $dir = dirname($parts['path']);
2056 $dir !== '/' && $dir .= '/';
2057 }
2058 $parts['path'] = $dir . $rel_url;
2059
2060 return build_url($parts);
2061 }
2062 }
2063
2064 function cleanup_tags($days = 14, $limit = 1000) {
2065
2066 if (DB_TYPE == "pgsql") {
2067 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2068 } else if (DB_TYPE == "mysql") {
2069 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2070 }
2071
2072 $tags_deleted = 0;
2073
2074 while ($limit > 0) {
2075 $limit_part = 500;
2076
2077 $query = "SELECT ttrss_tags.id AS id
2078 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2079 WHERE post_int_id = int_id AND $interval_query AND
2080 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2081
2082 $result = db_query($query);
2083
2084 $ids = array();
2085
2086 while ($line = db_fetch_assoc($result)) {
2087 array_push($ids, $line['id']);
2088 }
2089
2090 if (count($ids) > 0) {
2091 $ids = join(",", $ids);
2092
2093 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2094 $tags_deleted += db_affected_rows($tmp_result);
2095 } else {
2096 break;
2097 }
2098
2099 $limit -= $limit_part;
2100 }
2101
2102 return $tags_deleted;
2103 }
2104
2105 function print_user_stylesheet() {
2106 $value = get_pref('USER_STYLESHEET');
2107
2108 if ($value) {
2109 print "<style type=\"text/css\">";
2110 print str_replace("<br/>", "\n", $value);
2111 print "</style>";
2112 }
2113
2114 }
2115
2116 function filter_to_sql($filter, $owner_uid) {
2117 $query = array();
2118
2119 if (DB_TYPE == "pgsql")
2120 $reg_qpart = "~";
2121 else
2122 $reg_qpart = "REGEXP";
2123
2124 foreach ($filter["rules"] AS $rule) {
2125 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2126 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2127 $rule['reg_exp']) !== FALSE;
2128
2129 if ($regexp_valid) {
2130
2131 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2132
2133 switch ($rule["type"]) {
2134 case "title":
2135 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "')";
2137 break;
2138 case "content":
2139 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "')";
2141 break;
2142 case "both":
2143 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2144 $rule['reg_exp'] . "') OR LOWER(" .
2145 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2146 break;
2147 case "tag":
2148 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2150 break;
2151 case "link":
2152 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "')";
2154 break;
2155 case "author":
2156 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2157 $rule['reg_exp'] . "')";
2158 break;
2159 }
2160
2161 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2162
2163 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2164 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2165 }
2166
2167 if (isset($rule["cat_id"])) {
2168
2169 if ($rule["cat_id"] > 0) {
2170 $children = getChildCategories($rule["cat_id"], $owner_uid);
2171 array_push($children, $rule["cat_id"]);
2172
2173 $children = join(",", $children);
2174
2175 $cat_qpart = "cat_id IN ($children)";
2176 } else {
2177 $cat_qpart = "cat_id IS NULL";
2178 }
2179
2180 $qpart .= " AND $cat_qpart";
2181 }
2182
2183 $qpart .= " AND feed_id IS NOT NULL";
2184
2185 array_push($query, "($qpart)");
2186
2187 }
2188 }
2189
2190 if (count($query) > 0) {
2191 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2192 } else {
2193 $fullquery = "(false)";
2194 }
2195
2196 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2197
2198 return $fullquery;
2199 }
2200
2201 if (!function_exists('gzdecode')) {
2202 function gzdecode($string) { // no support for 2nd argument
2203 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2204 base64_encode($string));
2205 }
2206 }
2207
2208 function get_random_bytes($length) {
2209 if (function_exists('openssl_random_pseudo_bytes')) {
2210 return openssl_random_pseudo_bytes($length);
2211 } else {
2212 $output = "";
2213
2214 for ($i = 0; $i < $length; $i++)
2215 $output .= chr(mt_rand(0, 255));
2216
2217 return $output;
2218 }
2219 }
2220
2221 function read_stdin() {
2222 $fp = fopen("php://stdin", "r");
2223
2224 if ($fp) {
2225 $line = trim(fgets($fp));
2226 fclose($fp);
2227 return $line;
2228 }
2229
2230 return null;
2231 }
2232
2233 function tmpdirname($path, $prefix) {
2234 // Use PHP's tmpfile function to create a temporary
2235 // directory name. Delete the file and keep the name.
2236 $tempname = tempnam($path,$prefix);
2237 if (!$tempname)
2238 return false;
2239
2240 if (!unlink($tempname))
2241 return false;
2242
2243 return $tempname;
2244 }
2245
2246 function getFeedCategory($feed) {
2247 $result = db_query("SELECT cat_id FROM ttrss_feeds
2248 WHERE id = '$feed'");
2249
2250 if (db_num_rows($result) > 0) {
2251 return db_fetch_result($result, 0, "cat_id");
2252 } else {
2253 return false;
2254 }
2255
2256 }
2257
2258 function implements_interface($class, $interface) {
2259 return in_array($interface, class_implements($class));
2260 }
2261
2262 function geturl($url, $depth = 0, $nobody = true){
2263
2264 if ($depth == 20) return $url;
2265
2266 if (!function_exists('curl_init'))
2267 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);
2268
2269 $curl = curl_init();
2270 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2271 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2272 $header[] = "Cache-Control: max-age=0";
2273 $header[] = "Connection: keep-alive";
2274 $header[] = "Keep-Alive: 300";
2275 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2276 $header[] = "Accept-Language: en-us,en;q=0.5";
2277 $header[] = "Pragma: ";
2278
2279 curl_setopt($curl, CURLOPT_URL, $url);
2280 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2281 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2282 curl_setopt($curl, CURLOPT_HEADER, true);
2283 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2284 curl_setopt($curl, CURLOPT_REFERER, $url);
2285 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2286 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2287 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2288 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2289 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2290 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2291
2292 if (defined('_CURL_HTTP_PROXY')) {
2293 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2294 }
2295
2296 $html = curl_exec($curl);
2297
2298 $status = curl_getinfo($curl);
2299
2300 if($status['http_code']!=200){
2301
2302 // idiot site not allowing http head
2303 if($status['http_code'] == 405) {
2304 curl_close($curl);
2305 return geturl($url, $depth +1, false);
2306 }
2307
2308 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2309 curl_close($curl);
2310 list($header) = explode("\r\n\r\n", $html, 2);
2311 $matches = array();
2312 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2313 $url = trim(str_replace($matches[1],"",$matches[0]));
2314 $url_parsed = parse_url($url);
2315 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2316 }
2317
2318 global $fetch_last_error;
2319
2320 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2321 curl_close($curl);
2322
2323 # $oline='';
2324 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2325 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2326 # $handle = @fopen('./curl.error.log', 'a');
2327 # fwrite($handle, $line);
2328 return FALSE;
2329 }
2330 curl_close($curl);
2331 return $url;
2332 }
2333
2334 function get_minified_js($files) {
2335 require_once 'lib/jshrink/Minifier.php';
2336
2337 $rv = '';
2338
2339 foreach ($files as $js) {
2340 if (!isset($_GET['debug'])) {
2341 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2342
2343 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2344
2345 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2346
2347 if ($header && $contents) {
2348 list($htag, $hversion) = explode(":", $header);
2349
2350 if ($htag == "tt-rss" && $hversion == VERSION) {
2351 $rv .= $contents;
2352 continue;
2353 }
2354 }
2355 }
2356
2357 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2358 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2359 $rv .= $minified;
2360
2361 } else {
2362 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2363 }
2364 }
2365
2366 return $rv;
2367 }
2368
2369 function stylesheet_tag($filename) {
2370 $timestamp = filemtime($filename);
2371
2372 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2373 }
2374
2375 function javascript_tag($filename) {
2376 $query = "";
2377
2378 if (!(strpos($filename, "?") === FALSE)) {
2379 $query = substr($filename, strpos($filename, "?")+1);
2380 $filename = substr($filename, 0, strpos($filename, "?"));
2381 }
2382
2383 $timestamp = filemtime($filename);
2384
2385 if ($query) $timestamp .= "&$query";
2386
2387 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2388 }
2389
2390 function calculate_dep_timestamp() {
2391 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2392
2393 $max_ts = -1;
2394
2395 foreach ($files as $file) {
2396 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2397 }
2398
2399 return $max_ts;
2400 }
2401
2402 function T_js_decl($s1, $s2) {
2403 if ($s1 && $s2) {
2404 $s1 = preg_replace("/\n/", "", $s1);
2405 $s2 = preg_replace("/\n/", "", $s2);
2406
2407 $s1 = preg_replace("/\"/", "\\\"", $s1);
2408 $s2 = preg_replace("/\"/", "\\\"", $s2);
2409
2410 return "T_messages[\"$s1\"] = \"$s2\";\n";
2411 }
2412 }
2413
2414 function init_js_translations() {
2415
2416 print 'var T_messages = new Object();
2417
2418 function __(msg) {
2419 if (T_messages[msg]) {
2420 return T_messages[msg];
2421 } else {
2422 return msg;
2423 }
2424 }
2425
2426 function ngettext(msg1, msg2, n) {
2427 return __((parseInt(n) > 1) ? msg2 : msg1);
2428 }';
2429
2430 $l10n = _get_reader();
2431
2432 for ($i = 0; $i < $l10n->total; $i++) {
2433 $orig = $l10n->get_original_string($i);
2434 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2435 $key = explode(chr(0), $orig);
2436 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2437 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2438 } else {
2439 $translation = __($orig);
2440 print T_js_decl($orig, $translation);
2441 }
2442 }
2443 }
2444
2445 function label_to_feed_id($label) {
2446 return LABEL_BASE_INDEX - 1 - abs($label);
2447 }
2448
2449 function feed_to_label_id($feed) {
2450 return LABEL_BASE_INDEX - 1 + abs($feed);
2451 }
2452
2453 function get_theme_path($theme) {
2454 $check = "themes/$theme";
2455 if (file_exists($check)) return $check;
2456
2457 $check = "themes.local/$theme";
2458 if (file_exists($check)) return $check;
2459 }
2460
2461 function theme_valid($theme) {
2462 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2463 $file = "themes/" . basename($theme);
2464
2465 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2466
2467 if (file_exists($file) && is_readable($file)) {
2468 $fh = fopen($file, "r");
2469
2470 if ($fh) {
2471 $header = fgets($fh);
2472 fclose($fh);
2473
2474 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2475 }
2476 }
2477
2478 return false;
2479 }
2480
2481 function error_json($code) {
2482 require_once "errors.php";
2483
2484 @$message = $ERRORS[$code];
2485
2486 return json_encode(array("error" =>
2487 array("code" => $code, "message" => $message)));
2488
2489 }
2490
2491 function abs_to_rel_path($dir) {
2492 $tmp = str_replace(dirname(__DIR__), "", $dir);
2493
2494 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2495
2496 return $tmp;
2497 }
2498 ?>