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