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