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