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