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