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