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