]> git.wh0rd.org - tt-rss.git/blob - include/functions2.php
minor first_id query fix
[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 // $search_mode is obsolete/unused
429 //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, $check_top_id = false) {
430 function queryFeedHeadlines($params) {
431
432 $feed = $params["feed"];
433 $limit = isset($params["limit"]) ? $params["limit"] : 30;
434 $view_mode = $params["view_mode"];
435 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
436 $search = isset($params["search"]) ? $params["search"] : false;
437 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
438 $offset = isset($params["offset"]) ? $params["offset"] : 0;
439 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
440 $filter = isset($params["filter"]) ? $params["filter"] : 0;
441 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
442 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
443 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
444 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
445 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
446 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
447 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
448
449 $ext_tables_part = "";
450 $query_strategy_part = "";
451
452 $search_words = array();
453
454 if ($search) {
455 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
456 list($search_query_part, $search_words) = $plugin->hook_search($search);
457 break;
458 }
459
460 // fall back in case of no plugins
461 if (!$search_query_part) {
462 list($search_query_part, $search_words) = search_to_sql($search);
463 }
464 $search_query_part .= " AND ";
465 } else {
466 $search_query_part = "";
467 }
468
469 if ($filter) {
470
471 if (DB_TYPE == "pgsql") {
472 $query_strategy_part .= " AND updated > NOW() - INTERVAL '14 days' ";
473 } else {
474 $query_strategy_part .= " AND updated > DATE_SUB(NOW(), INTERVAL 14 DAY) ";
475 }
476
477 $override_order = "updated DESC";
478
479 $filter_query_part = filter_to_sql($filter, $owner_uid);
480
481 // Try to check if SQL regexp implementation chokes on a valid regexp
482
483
484 $result = db_query("SELECT true AS true_val
485 FROM ttrss_entries
486 JOIN ttrss_user_entries ON ttrss_entries.id = ttrss_user_entries.ref_id
487 JOIN ttrss_feeds ON ttrss_feeds.id = ttrss_user_entries.feed_id
488 WHERE $filter_query_part LIMIT 1", false);
489
490 if ($result) {
491 $test = db_fetch_result($result, 0, "true_val");
492
493 if (!$test) {
494 $filter_query_part = "false AND";
495 } else {
496 $filter_query_part .= " AND";
497 }
498 } else {
499 $filter_query_part = "false AND";
500 }
501
502 } else {
503 $filter_query_part = "";
504 }
505
506 if ($since_id) {
507 $since_id_part = "ttrss_entries.id > $since_id AND ";
508 } else {
509 $since_id_part = "";
510 }
511
512 $view_query_part = "";
513
514 if ($view_mode == "adaptive") {
515 if ($search) {
516 $view_query_part = " ";
517 } else if ($feed != -1) {
518
519 $unread = getFeedUnread($feed, $cat_view);
520
521 if ($cat_view && $feed > 0 && $include_children)
522 $unread += getCategoryChildrenUnread($feed);
523
524 if ($unread > 0)
525 $view_query_part = " unread = true AND ";
526
527 }
528 }
529
530 if ($view_mode == "marked") {
531 $view_query_part = " marked = true AND ";
532 }
533
534 if ($view_mode == "has_note") {
535 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
536 }
537
538 if ($view_mode == "published") {
539 $view_query_part = " published = true AND ";
540 }
541
542 if ($view_mode == "unread" && $feed != -6) {
543 $view_query_part = " unread = true AND ";
544 }
545
546 if ($limit > 0) {
547 $limit_query_part = "LIMIT " . $limit;
548 }
549
550 $allow_archived = false;
551
552 $vfeed_query_part = "";
553
554 /* tags */
555 if (!is_numeric($feed)) {
556 $query_strategy_part = "true";
557 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
558 id = feed_id) as feed_title,";
559 } else if ($feed > 0) {
560
561 if ($cat_view) {
562
563 if ($feed > 0) {
564 if ($include_children) {
565 # sub-cats
566 $subcats = getChildCategories($feed, $owner_uid);
567
568 array_push($subcats, $feed);
569 $query_strategy_part = "cat_id IN (".
570 implode(",", $subcats).")";
571
572 } else {
573 $query_strategy_part = "cat_id = '$feed'";
574 }
575
576 } else {
577 $query_strategy_part = "cat_id IS NULL";
578 }
579
580 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
581
582 } else {
583 $query_strategy_part = "feed_id = '$feed'";
584 }
585 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
586 $query_strategy_part = "feed_id IS NULL";
587 $allow_archived = true;
588 } else if ($feed == 0 && $cat_view) { // uncategorized
589 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
590 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
591 } else if ($feed == -1) { // starred virtual feed
592 $query_strategy_part = "marked = true";
593 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
594 $allow_archived = true;
595
596 if (!$override_order) {
597 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
598 }
599
600 } else if ($feed == -2) { // published virtual feed OR labels category
601
602 if (!$cat_view) {
603 $query_strategy_part = "published = true";
604 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
605 $allow_archived = true;
606
607 if (!$override_order) {
608 $override_order = "last_published DESC, date_entered DESC, updated DESC";
609 }
610
611 } else {
612 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
613
614 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
615
616 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
617 ttrss_user_labels2.article_id = ref_id";
618
619 }
620 } else if ($feed == -6) { // recently read
621 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
622 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
623 $allow_archived = true;
624 $ignore_vfeed_group = true;
625
626 if (!$override_order) $override_order = "last_read DESC";
627
628 } else if ($feed == -3) { // fresh virtual feed
629 $query_strategy_part = "unread = true AND score >= 0";
630
631 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
632
633 if (DB_TYPE == "pgsql") {
634 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
635 } else {
636 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
637 }
638
639 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
640 } else if ($feed == -4) { // all articles virtual feed
641 $allow_archived = true;
642 $query_strategy_part = "true";
643 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
644 } else if ($feed <= LABEL_BASE_INDEX) { // labels
645 $label_id = feed_to_label_id($feed);
646
647 $query_strategy_part = "label_id = '$label_id' AND
648 ttrss_labels2.id = ttrss_user_labels2.label_id AND
649 ttrss_user_labels2.article_id = ref_id";
650
651 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
652 $ext_tables_part = ",ttrss_labels2,ttrss_user_labels2";
653 $allow_archived = true;
654
655 } else {
656 $query_strategy_part = "true";
657 }
658
659 $order_by = "score DESC, date_entered DESC, updated DESC";
660
661 if ($override_order) {
662 $order_by = $override_order;
663 }
664
665 if ($override_strategy) {
666 $query_strategy_part = $override_strategy;
667 }
668
669 if ($override_vfeed) {
670 $vfeed_query_part = $override_vfeed;
671 }
672
673 $feed_title = "";
674
675 if ($search) {
676 $feed_title = T_sprintf("Search results: %s", $search);
677 } else {
678 if ($cat_view) {
679 $feed_title = getCategoryTitle($feed);
680 } else {
681 if (is_numeric($feed) && $feed > 0) {
682 $result = db_query("SELECT title,site_url,last_error,last_updated
683 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
684
685 $feed_title = db_fetch_result($result, 0, "title");
686 $feed_site_url = db_fetch_result($result, 0, "site_url");
687 $last_error = db_fetch_result($result, 0, "last_error");
688 $last_updated = db_fetch_result($result, 0, "last_updated");
689 } else {
690 $feed_title = getFeedTitle($feed);
691 }
692 }
693 }
694
695
696 $content_query_part = "content, ";
697
698 if ($limit_query_part) {
699 $offset_query_part = "OFFSET $offset";
700 } else {
701 $offset_query_part = "";
702 }
703
704 if (is_numeric($feed)) {
705 // proper override_order applied above
706 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
707 if (!$override_order) {
708 $order_by = "ttrss_feeds.title, $order_by";
709 } else {
710 $order_by = "ttrss_feeds.title, $override_order";
711 }
712 }
713
714 if (!$allow_archived) {
715 $from_qpart = "ttrss_entries,ttrss_user_entries,ttrss_feeds$ext_tables_part";
716 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
717
718 } else {
719 $from_qpart = "ttrss_entries$ext_tables_part,ttrss_user_entries
720 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
721 }
722
723 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
724
725 if ($start_ts) {
726 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
727 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
728 } else {
729 $start_ts_query_part = "";
730 }
731
732 $first_id = false;
733 // if previous topmost article id changed that means our current pagination is no longer valid
734 $query = "SELECT DISTINCT
735 ttrss_feeds.title,
736 date_entered,
737 guid,
738 ttrss_entries.id,
739 ttrss_entries.title,
740 updated,
741 score
742 FROM
743 $from_qpart
744 WHERE
745 $feed_check_qpart
746 ttrss_user_entries.ref_id = ttrss_entries.id AND
747 ttrss_user_entries.owner_uid = '$owner_uid' AND
748 $search_query_part
749 $start_ts_query_part
750 $filter_query_part
751 $since_id_part
752 $query_strategy_part ORDER BY $order_by LIMIT 1";
753
754 if ($_REQUEST["debug"]) {
755 print $query;
756 }
757
758 $result = db_query($query);
759 if ($result) {
760 $first_id = (int) db_fetch_result($result, 0, "id");
761
762 if ($offset > 0 && $check_first_id && $first_id != $check_first_id) {
763 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
764 }
765 }
766
767 $query = "SELECT DISTINCT
768 date_entered,
769 guid,
770 ttrss_entries.id,ttrss_entries.title,
771 updated,
772 label_cache,
773 tag_cache,
774 always_display_enclosures,
775 site_url,
776 note,
777 num_comments,
778 comments,
779 int_id,
780 uuid,
781 lang,
782 hide_images,
783 unread,feed_id,marked,published,link,last_read,orig_feed_id,
784 last_marked, last_published,
785 $vfeed_query_part
786 $content_query_part
787 author,score
788 FROM
789 $from_qpart
790 WHERE
791 $feed_check_qpart
792 ttrss_user_entries.ref_id = ttrss_entries.id AND
793 ttrss_user_entries.owner_uid = '$owner_uid' AND
794 $search_query_part
795 $start_ts_query_part
796 $filter_query_part
797 $view_query_part
798 $since_id_part
799 $query_strategy_part ORDER BY $order_by
800 $limit_query_part $offset_query_part";
801
802 if ($_REQUEST["debug"]) print $query;
803
804 $result = db_query($query);
805
806 } else {
807 // browsing by tag
808
809 $query = "SELECT DISTINCT
810 date_entered,
811 guid,
812 note,
813 ttrss_entries.id as id,
814 title,
815 updated,
816 unread,
817 feed_id,
818 orig_feed_id,
819 marked,
820 num_comments,
821 comments,
822 tag_cache,
823 label_cache,
824 link,
825 lang,
826 uuid,
827 last_read,
828 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
829 last_marked, last_published,
830 $since_id_part
831 $vfeed_query_part
832 $content_query_part
833 author, score
834 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
835 WHERE
836 ref_id = ttrss_entries.id AND
837 ttrss_user_entries.owner_uid = $owner_uid AND
838 post_int_id = int_id AND
839 tag_name = '$feed' AND
840 $view_query_part
841 $search_query_part
842 $query_strategy_part ORDER BY $order_by
843 $limit_query_part $offset_query_part";
844
845 if ($_REQUEST["debug"]) print $query;
846
847 $result = db_query($query);
848 }
849
850 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
851
852 }
853
854 function iframe_whitelisted($entry) {
855 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
856
857 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
858
859 if ($src) {
860 foreach ($whitelist as $w) {
861 if ($src == $w || $src == "www.$w")
862 return true;
863 }
864 }
865
866 return false;
867 }
868
869 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
870 if (!$owner) $owner = $_SESSION["uid"];
871
872 $res = trim($str); if (!$res) return '';
873
874 $charset_hack = '<head>
875 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
876 </head>';
877
878 $res = trim($res); if (!$res) return '';
879
880 libxml_use_internal_errors(true);
881
882 $doc = new DOMDocument();
883 $doc->loadHTML($charset_hack . $res);
884 $xpath = new DOMXPath($doc);
885
886 $entries = $xpath->query('(//a[@href]|//img[@src])');
887
888 foreach ($entries as $entry) {
889
890 if ($site_url) {
891
892 if ($entry->hasAttribute('href')) {
893 $entry->setAttribute('href',
894 rewrite_relative_url($site_url, $entry->getAttribute('href')));
895
896 $entry->setAttribute('rel', 'noreferrer');
897 }
898
899 if ($entry->hasAttribute('src')) {
900 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
901
902 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
903
904 if (file_exists($cached_filename)) {
905 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
906 }
907
908 $entry->setAttribute('src', $src);
909 }
910
911 if ($entry->nodeName == 'img') {
912 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
913 $force_remove_images || $_SESSION["bw_limit"]) {
914
915 $p = $doc->createElement('p');
916
917 $a = $doc->createElement('a');
918 $a->setAttribute('href', $entry->getAttribute('src'));
919
920 $a->appendChild(new DOMText($entry->getAttribute('src')));
921 $a->setAttribute('target', '_blank');
922
923 $p->appendChild($a);
924
925 $entry->parentNode->replaceChild($p, $entry);
926 }
927 }
928 }
929
930 if (strtolower($entry->nodeName) == "a") {
931 $entry->setAttribute("target", "_blank");
932 }
933 }
934
935 $entries = $xpath->query('//iframe');
936 foreach ($entries as $entry) {
937 if (!iframe_whitelisted($entry)) {
938 $entry->setAttribute('sandbox', 'allow-scripts');
939 } else {
940 if ($_SERVER['HTTPS'] == "on") {
941 $entry->setAttribute("src",
942 str_replace("http://", "https://",
943 $entry->getAttribute("src")));
944 }
945 }
946 }
947
948 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
949 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
950 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
951 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
952 'dt', 'em', 'footer', 'figure', 'figcaption',
953 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
954 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
955 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
956 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
957 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
958 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
959
960 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
961
962 $disallowed_attributes = array('id', 'style', 'class');
963
964 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
965 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
966 if (is_array($retval)) {
967 $doc = $retval[0];
968 $allowed_elements = $retval[1];
969 $disallowed_attributes = $retval[2];
970 } else {
971 $doc = $retval;
972 }
973 }
974
975 $doc->removeChild($doc->firstChild); //remove doctype
976 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
977
978 if ($highlight_words) {
979 foreach ($highlight_words as $word) {
980
981 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
982
983 $elements = $xpath->query("//*/text()");
984
985 foreach ($elements as $child) {
986
987 $fragment = $doc->createDocumentFragment();
988 $text = $child->textContent;
989
990 while (($pos = mb_stripos($text, $word)) !== false) {
991 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
992 $word = mb_substr($text, $pos, mb_strlen($word));
993 $highlight = $doc->createElement('span');
994 $highlight->appendChild(new DomText($word));
995 $highlight->setAttribute('class', 'highlight');
996 $fragment->appendChild($highlight);
997 $text = mb_substr($text, $pos + mb_strlen($word));
998 }
999
1000 if (!empty($text)) $fragment->appendChild(new DomText($text));
1001
1002 $child->parentNode->replaceChild($fragment, $child);
1003 }
1004 }
1005 }
1006
1007 $res = $doc->saveHTML();
1008
1009 return $res;
1010 }
1011
1012 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1013 $xpath = new DOMXPath($doc);
1014 $entries = $xpath->query('//*');
1015
1016 foreach ($entries as $entry) {
1017 if (!in_array($entry->nodeName, $allowed_elements)) {
1018 $entry->parentNode->removeChild($entry);
1019 }
1020
1021 if ($entry->hasAttributes()) {
1022 $attrs_to_remove = array();
1023
1024 foreach ($entry->attributes as $attr) {
1025
1026 if (strpos($attr->nodeName, 'on') === 0) {
1027 array_push($attrs_to_remove, $attr);
1028 }
1029
1030 if (in_array($attr->nodeName, $disallowed_attributes)) {
1031 array_push($attrs_to_remove, $attr);
1032 }
1033 }
1034
1035 foreach ($attrs_to_remove as $attr) {
1036 $entry->removeAttributeNode($attr);
1037 }
1038 }
1039 }
1040
1041 return $doc;
1042 }
1043
1044 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1045
1046 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1047 if (count($ids) == 0) return;
1048
1049 $tmp_ids = array();
1050
1051 foreach ($ids as $id) {
1052 array_push($tmp_ids, "ref_id = '$id'");
1053 }
1054
1055 $ids_qpart = join(" OR ", $tmp_ids);
1056
1057 if ($cmode == 0) {
1058 db_query("UPDATE ttrss_user_entries SET
1059 unread = false,last_read = NOW()
1060 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1061 } else if ($cmode == 1) {
1062 db_query("UPDATE ttrss_user_entries SET
1063 unread = true
1064 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1065 } else {
1066 db_query("UPDATE ttrss_user_entries SET
1067 unread = NOT unread,last_read = NOW()
1068 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1069 }
1070
1071 /* update ccache */
1072
1073 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1074 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1075
1076 while ($line = db_fetch_assoc($result)) {
1077 ccache_update($line["feed_id"], $owner_uid);
1078 }
1079 }
1080
1081 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1082
1083 $a_id = db_escape_string($id);
1084
1085 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1086
1087 $query = "SELECT DISTINCT tag_name,
1088 owner_uid as owner FROM
1089 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1090 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1091
1092 $tags = array();
1093
1094 /* check cache first */
1095
1096 if ($tag_cache === false) {
1097 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1098 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1099
1100 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1101 }
1102
1103 if ($tag_cache) {
1104 $tags = explode(",", $tag_cache);
1105 } else {
1106
1107 /* do it the hard way */
1108
1109 $tmp_result = db_query($query);
1110
1111 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1112 array_push($tags, $tmp_line["tag_name"]);
1113 }
1114
1115 /* update the cache */
1116
1117 $tags_str = db_escape_string(join(",", $tags));
1118
1119 db_query("UPDATE ttrss_user_entries
1120 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1121 AND owner_uid = $owner_uid");
1122 }
1123
1124 return $tags;
1125 }
1126
1127 function trim_array($array) {
1128 $tmp = $array;
1129 array_walk($tmp, 'trim');
1130 return $tmp;
1131 }
1132
1133 function tag_is_valid($tag) {
1134 if ($tag == '') return false;
1135 if (preg_match("/^[0-9]*$/", $tag)) return false;
1136 if (mb_strlen($tag) > 250) return false;
1137
1138 if (!$tag) return false;
1139
1140 return true;
1141 }
1142
1143 function render_login_form() {
1144 header('Cache-Control: public');
1145
1146 require_once "login_form.php";
1147 exit;
1148 }
1149
1150 function format_warning($msg, $id = "") {
1151 return "<div class=\"warning\" id=\"$id\">
1152 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1153 }
1154
1155 function format_notice($msg, $id = "") {
1156 return "<div class=\"notice\" id=\"$id\">
1157 <span><img src=\"images/information.png\"></span><span>$msg</span></div>";
1158 }
1159
1160 function format_error($msg, $id = "") {
1161 return "<div class=\"error\" id=\"$id\">
1162 <span><img src=\"images/alert.png\"></span><span>$msg</span></div>";
1163 }
1164
1165 function print_notice($msg) {
1166 return print format_notice($msg);
1167 }
1168
1169 function print_warning($msg) {
1170 return print format_warning($msg);
1171 }
1172
1173 function print_error($msg) {
1174 return print format_error($msg);
1175 }
1176
1177
1178 function T_sprintf() {
1179 $args = func_get_args();
1180 return vsprintf(__(array_shift($args)), $args);
1181 }
1182
1183 function format_inline_player($url, $ctype) {
1184
1185 $entry = "";
1186
1187 $url = htmlspecialchars($url);
1188
1189 if (strpos($ctype, "audio/") === 0) {
1190
1191 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1192 $_SESSION["hasMp3"])) {
1193
1194 $entry .= "<audio preload=\"none\" controls>
1195 <source type=\"$ctype\" src=\"$url\"/>
1196 </audio>";
1197
1198 } else {
1199
1200 $entry .= "<object type=\"application/x-shockwave-flash\"
1201 data=\"lib/button/musicplayer.swf?song_url=$url\"
1202 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1203 <param name=\"movie\"
1204 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1205 </object>";
1206 }
1207
1208 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
1209 href=\"$url\">" . basename($url) . "</a>";
1210
1211 return $entry;
1212
1213 }
1214
1215 return "";
1216
1217 /* $filename = substr($url, strrpos($url, "/")+1);
1218
1219 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1220 $filename . " (" . $ctype . ")" . "</a>"; */
1221
1222 }
1223
1224 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1225 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1226
1227 $rv = array();
1228
1229 $rv['id'] = $id;
1230
1231 /* we can figure out feed_id from article id anyway, why do we
1232 * pass feed_id here? let's ignore the argument :(*/
1233
1234 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1235 WHERE ref_id = '$id'");
1236
1237 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1238
1239 $rv['feed_id'] = $feed_id;
1240
1241 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1242
1243 if ($mark_as_read) {
1244 $result = db_query("UPDATE ttrss_user_entries
1245 SET unread = false,last_read = NOW()
1246 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1247
1248 ccache_update($feed_id, $owner_uid);
1249 }
1250
1251 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1252 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1253 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1254 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1255 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1256 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1257 num_comments,
1258 tag_cache,
1259 author,
1260 orig_feed_id,
1261 note
1262 FROM ttrss_entries,ttrss_user_entries
1263 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1264
1265 if ($result) {
1266
1267 $line = db_fetch_assoc($result);
1268
1269 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1270 unset($line["tag_cache"]);
1271
1272 $line["content"] = sanitize($line["content"],
1273 sql_bool_to_bool($line['hide_images']),
1274 $owner_uid, $line["site_url"], false, $line["id"]);
1275
1276 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1277 $line = $p->hook_render_article($line);
1278 }
1279
1280 $num_comments = $line["num_comments"];
1281 $entry_comments = "";
1282
1283 if ($num_comments > 0) {
1284 if ($line["comments"]) {
1285 $comments_url = htmlspecialchars($line["comments"]);
1286 } else {
1287 $comments_url = htmlspecialchars($line["link"]);
1288 }
1289 $entry_comments = "<a class=\"postComments\"
1290 target='_blank' href=\"$comments_url\">$num_comments ".
1291 _ngettext("comment", "comments", $num_comments)."</a>";
1292
1293 } else {
1294 if ($line["comments"] && $line["link"] != $line["comments"]) {
1295 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1296 }
1297 }
1298
1299 if ($zoom_mode) {
1300 header("Content-Type: text/html");
1301 $rv['content'] .= "<html><head>
1302 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1303 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1304 stylesheet_tag("css/tt-rss.css").
1305 stylesheet_tag("css/zoom.css").
1306 stylesheet_tag("css/dijit.css")."
1307
1308 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1309 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1310
1311 <script type=\"text/javascript\">
1312 function openSelectedAttachment(elem) {
1313 try {
1314 var url = elem[elem.selectedIndex].value;
1315
1316 if (url) {
1317 window.open(url);
1318 elem.selectedIndex = 0;
1319 }
1320
1321 } catch (e) {
1322 exception_error(\"openSelectedAttachment\", e);
1323 }
1324 }
1325 </script>
1326 </head><body id=\"ttrssZoom\">";
1327 }
1328
1329 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1330
1331 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1332
1333 $entry_author = $line["author"];
1334
1335 if ($entry_author) {
1336 $entry_author = __(" - ") . $entry_author;
1337 }
1338
1339 $parsed_updated = make_local_datetime($line["updated"], true,
1340 $owner_uid, true);
1341
1342 if (!$zoom_mode)
1343 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1344
1345 if ($line["link"]) {
1346 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1347 title=\"".htmlspecialchars($line['title'])."\"
1348 href=\"" .
1349 htmlspecialchars($line["link"]) . "\">" .
1350 $line["title"] . "</a>" .
1351 "<span class='author'>$entry_author</span></div>";
1352 } else {
1353 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1354 }
1355
1356 if ($zoom_mode) {
1357 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1358 "\" target=\"_blank\">".
1359 htmlspecialchars($line["feed_title"])."</a>";
1360
1361 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1362
1363 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1364 }
1365
1366 $tags_str = format_tags_string($line["tags"], $id);
1367 $tags_str_full = join(", ", $line["tags"]);
1368
1369 if (!$tags_str_full) $tags_str_full = __("no tags");
1370
1371 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1372
1373 $rv['content'] .= "<div class='postTags' style='float : right'>
1374 <img src='images/tag.png'
1375 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1376
1377 if (!$zoom_mode) {
1378 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1379 <a title=\"".__('Edit tags for this article')."\"
1380 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1381
1382 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1383 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1384 position=\"below\">$tags_str_full</div>";
1385
1386 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1387 $rv['content'] .= $p->hook_article_button($line);
1388 }
1389
1390 } else {
1391 $tags_str = strip_tags($tags_str);
1392 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1393 }
1394 $rv['content'] .= "</div>";
1395 $rv['content'] .= "<div clear='both'>";
1396
1397 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1398 $rv['content'] .= $p->hook_article_left_button($line);
1399 }
1400
1401 $rv['content'] .= "$entry_comments</div>";
1402
1403 if ($line["orig_feed_id"]) {
1404
1405 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1406 WHERE id = ".$line["orig_feed_id"]);
1407
1408 if (db_num_rows($tmp_result) != 0) {
1409
1410 $rv['content'] .= "<div clear='both'>";
1411 $rv['content'] .= __("Originally from:");
1412
1413 $rv['content'] .= "&nbsp;";
1414
1415 $tmp_line = db_fetch_assoc($tmp_result);
1416
1417 $rv['content'] .= "<a target='_blank'
1418 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1419 $tmp_line['title'] . "</a>";
1420
1421 $rv['content'] .= "&nbsp;";
1422
1423 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1424 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1425
1426 $rv['content'] .= "</div>";
1427 }
1428 }
1429
1430 $rv['content'] .= "</div>";
1431
1432 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1433 if ($line['note']) {
1434 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1435 }
1436 $rv['content'] .= "</div>";
1437
1438 if (!$line['lang']) $line['lang'] = 'en';
1439
1440 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1441
1442 $rv['content'] .= $line["content"];
1443 $rv['content'] .= format_article_enclosures($id,
1444 sql_bool_to_bool($line["always_display_enclosures"]),
1445 $line["content"],
1446 sql_bool_to_bool($line["hide_images"]));
1447
1448 $rv['content'] .= "</div>";
1449
1450 $rv['content'] .= "</div>";
1451
1452 }
1453
1454 if ($zoom_mode) {
1455 $rv['content'] .= "
1456 <div class='footer'>
1457 <button onclick=\"return window.close()\">".
1458 __("Close this window")."</button></div>";
1459 $rv['content'] .= "</body></html>";
1460 }
1461
1462 return $rv;
1463
1464 }
1465
1466 function print_checkpoint($n, $s) {
1467 $ts = microtime(true);
1468 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1469 return $ts;
1470 }
1471
1472 function sanitize_tag($tag) {
1473 $tag = trim($tag);
1474
1475 $tag = mb_strtolower($tag, 'utf-8');
1476
1477 $tag = preg_replace('/[\'\"\+\>\<]/', "", $tag);
1478
1479 if (DB_TYPE == "mysql") {
1480 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1481 }
1482
1483 return $tag;
1484 }
1485
1486 function get_self_url_prefix() {
1487 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1488 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1489 } else {
1490 return SELF_URL_PATH;
1491 }
1492 }
1493
1494 /**
1495 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1496 *
1497 * @return string The Mozilla Firefox feed adding URL.
1498 */
1499 function add_feed_url() {
1500 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1501
1502 $url_path = get_self_url_prefix() .
1503 "/public.php?op=subscribe&feed_url=%s";
1504 return $url_path;
1505 } // function add_feed_url
1506
1507 function encrypt_password($pass, $salt = '', $mode2 = false) {
1508 if ($salt && $mode2) {
1509 return "MODE2:" . hash('sha256', $salt . $pass);
1510 } else if ($salt) {
1511 return "SHA1X:" . sha1("$salt:$pass");
1512 } else {
1513 return "SHA1:" . sha1($pass);
1514 }
1515 } // function encrypt_password
1516
1517 function load_filters($feed_id, $owner_uid, $action_id = false) {
1518 $filters = array();
1519
1520 $cat_id = (int)getFeedCategory($feed_id);
1521
1522 if ($cat_id == 0)
1523 $null_cat_qpart = "cat_id IS NULL OR";
1524 else
1525 $null_cat_qpart = "";
1526
1527 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1528 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1529
1530 $check_cats = join(",", array_merge(
1531 getParentCategories($cat_id, $owner_uid),
1532 array($cat_id)));
1533
1534 while ($line = db_fetch_assoc($result)) {
1535 $filter_id = $line["id"];
1536
1537 $result2 = db_query("SELECT
1538 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1539 FROM ttrss_filters2_rules AS r,
1540 ttrss_filter_types AS t
1541 WHERE
1542 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1543 (feed_id IS NULL OR feed_id = '$feed_id') AND
1544 filter_type = t.id AND filter_id = '$filter_id'");
1545
1546 $rules = array();
1547 $actions = array();
1548
1549 while ($rule_line = db_fetch_assoc($result2)) {
1550 # print_r($rule_line);
1551
1552 $rule = array();
1553 $rule["reg_exp"] = $rule_line["reg_exp"];
1554 $rule["type"] = $rule_line["type_name"];
1555 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1556
1557 array_push($rules, $rule);
1558 }
1559
1560 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1561 FROM ttrss_filters2_actions AS a,
1562 ttrss_filter_actions AS t
1563 WHERE
1564 action_id = t.id AND filter_id = '$filter_id'");
1565
1566 while ($action_line = db_fetch_assoc($result2)) {
1567 # print_r($action_line);
1568
1569 $action = array();
1570 $action["type"] = $action_line["type_name"];
1571 $action["param"] = $action_line["action_param"];
1572
1573 array_push($actions, $action);
1574 }
1575
1576
1577 $filter = array();
1578 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1579 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1580 $filter["rules"] = $rules;
1581 $filter["actions"] = $actions;
1582
1583 if (count($rules) > 0 && count($actions) > 0) {
1584 array_push($filters, $filter);
1585 }
1586 }
1587
1588 return $filters;
1589 }
1590
1591 function get_score_pic($score) {
1592 if ($score > 100) {
1593 return "score_high.png";
1594 } else if ($score > 0) {
1595 return "score_half_high.png";
1596 } else if ($score < -100) {
1597 return "score_low.png";
1598 } else if ($score < 0) {
1599 return "score_half_low.png";
1600 } else {
1601 return "score_neutral.png";
1602 }
1603 }
1604
1605 function feed_has_icon($id) {
1606 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1607 }
1608
1609 function init_plugins() {
1610 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1611
1612 return true;
1613 }
1614
1615 function format_tags_string($tags, $id) {
1616 if (!is_array($tags) || count($tags) == 0) {
1617 return __("no tags");
1618 } else {
1619 $maxtags = min(5, count($tags));
1620 $tags_str = "";
1621
1622 for ($i = 0; $i < $maxtags; $i++) {
1623 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1624 }
1625
1626 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1627
1628 if (count($tags) > $maxtags)
1629 $tags_str .= ", &hellip;";
1630
1631 return $tags_str;
1632 }
1633 }
1634
1635 function format_article_labels($labels, $id) {
1636
1637 if (!is_array($labels)) return '';
1638
1639 $labels_str = "";
1640
1641 foreach ($labels as $l) {
1642 $labels_str .= sprintf("<span class='hlLabelRef'
1643 style='color : %s; background-color : %s'>%s</span>",
1644 $l[2], $l[3], $l[1]);
1645 }
1646
1647 return $labels_str;
1648
1649 }
1650
1651 function format_article_note($id, $note, $allow_edit = true) {
1652
1653 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1654 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1655 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1656
1657 return $str;
1658 }
1659
1660
1661 function get_feed_category($feed_cat, $parent_cat_id = false) {
1662 if ($parent_cat_id) {
1663 $parent_qpart = "parent_cat = '$parent_cat_id'";
1664 $parent_insert = "'$parent_cat_id'";
1665 } else {
1666 $parent_qpart = "parent_cat IS NULL";
1667 $parent_insert = "NULL";
1668 }
1669
1670 $result = db_query(
1671 "SELECT id FROM ttrss_feed_categories
1672 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1673
1674 if (db_num_rows($result) == 0) {
1675 return false;
1676 } else {
1677 return db_fetch_result($result, 0, "id");
1678 }
1679 }
1680
1681 function add_feed_category($feed_cat, $parent_cat_id = false) {
1682
1683 if (!$feed_cat) return false;
1684
1685 db_query("BEGIN");
1686
1687 if ($parent_cat_id) {
1688 $parent_qpart = "parent_cat = '$parent_cat_id'";
1689 $parent_insert = "'$parent_cat_id'";
1690 } else {
1691 $parent_qpart = "parent_cat IS NULL";
1692 $parent_insert = "NULL";
1693 }
1694
1695 $feed_cat = mb_substr($feed_cat, 0, 250);
1696
1697 $result = db_query(
1698 "SELECT id FROM ttrss_feed_categories
1699 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1700
1701 if (db_num_rows($result) == 0) {
1702
1703 $result = db_query(
1704 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1705 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1706
1707 db_query("COMMIT");
1708
1709 return true;
1710 }
1711
1712 return false;
1713 }
1714
1715 function getArticleFeed($id) {
1716 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1717 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1718
1719 if (db_num_rows($result) != 0) {
1720 return db_fetch_result($result, 0, "feed_id");
1721 } else {
1722 return 0;
1723 }
1724 }
1725
1726 /**
1727 * Fixes incomplete URLs by prepending "http://".
1728 * Also replaces feed:// with http://, and
1729 * prepends a trailing slash if the url is a domain name only.
1730 *
1731 * @param string $url Possibly incomplete URL
1732 *
1733 * @return string Fixed URL.
1734 */
1735 function fix_url($url) {
1736
1737 // support schema-less urls
1738 if (strpos($url, '//') === 0) {
1739 $url = 'https:' . $url;
1740 }
1741
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, 100)) !== 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 ?>