]> git.wh0rd.org - tt-rss.git/blob - include/functions2.php
6ccdc9a58b933329c606f6863c23a742e025a57c
[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, $search_language) {
284
285 $keywords = str_getcsv($search, " ");
286 $query_keywords = array();
287 $search_words = array();
288 $search_query_leftover = array();
289
290 if ($search_language)
291 $search_language = db_escape_string(mb_strtolower($search_language));
292 else
293 $search_language = "english";
294
295 foreach ($keywords as $k) {
296 if (strpos($k, "-") === 0) {
297 $k = substr($k, 1);
298 $not = "NOT";
299 } else {
300 $not = "";
301 }
302
303 $commandpair = explode(":", mb_strtolower($k), 2);
304
305 switch ($commandpair[0]) {
306 case "title":
307 if ($commandpair[1]) {
308 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
309 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
310 } else {
311 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
312 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
313 array_push($search_words, $k);
314 }
315 break;
316 case "author":
317 if ($commandpair[1]) {
318 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
319 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
320 } else {
321 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
322 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
323 array_push($search_words, $k);
324 }
325 break;
326 case "note":
327 if ($commandpair[1]) {
328 if ($commandpair[1] == "true")
329 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
330 else if ($commandpair[1] == "false")
331 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
332 else
333 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
334 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
335 } else {
336 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
337 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
338 if (!$not) array_push($search_words, $k);
339 }
340 break;
341 case "star":
342
343 if ($commandpair[1]) {
344 if ($commandpair[1] == "true")
345 array_push($query_keywords, "($not (marked = true))");
346 else
347 array_push($query_keywords, "($not (marked = false))");
348 } else {
349 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
350 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
351 if (!$not) array_push($search_words, $k);
352 }
353 break;
354 case "pub":
355 if ($commandpair[1]) {
356 if ($commandpair[1] == "true")
357 array_push($query_keywords, "($not (published = true))");
358 else
359 array_push($query_keywords, "($not (published = false))");
360
361 } else {
362 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
363 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
364 if (!$not) array_push($search_words, $k);
365 }
366 break;
367 case "unread":
368 if ($commandpair[1]) {
369 if ($commandpair[1] == "true")
370 array_push($query_keywords, "($not (unread = true))");
371 else
372 array_push($query_keywords, "($not (unread = false))");
373
374 } else {
375 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
376 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
377 if (!$not) array_push($search_words, $k);
378 }
379 break;
380 default:
381 if (strpos($k, "@") === 0) {
382
383 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
384 $orig_ts = strtotime(substr($k, 1));
385 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
386
387 //$k = date("Y-m-d", strtotime(substr($k, 1)));
388
389 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
390 } else {
391
392 if (DB_TYPE == "pgsql") {
393 $k = mb_strtolower($k);
394 array_push($search_query_leftover, $not ? "!$k" : $k);
395 } else {
396 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
397 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
398 }
399
400 if (!$not) array_push($search_words, $k);
401 }
402 }
403 }
404
405 if (count($search_query_leftover) > 0) {
406 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
407
408 if (DB_TYPE == "pgsql") {
409 array_push($query_keywords,
410 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
411 }
412
413 }
414
415 $search_query_part = implode("AND", $query_keywords);
416
417 return array($search_query_part, $search_words);
418 }
419
420 function getParentCategories($cat, $owner_uid) {
421 $rv = array();
422
423 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
424 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
425
426 while ($line = db_fetch_assoc($result)) {
427 array_push($rv, $line["parent_cat"]);
428 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
429 }
430
431 return $rv;
432 }
433
434 function getChildCategories($cat, $owner_uid) {
435 $rv = array();
436
437 $result = db_query("SELECT id FROM ttrss_feed_categories
438 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
439
440 while ($line = db_fetch_assoc($result)) {
441 array_push($rv, $line["id"]);
442 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
443 }
444
445 return $rv;
446 }
447
448 function queryFeedHeadlines($params) {
449
450 $feed = $params["feed"];
451 $limit = isset($params["limit"]) ? $params["limit"] : 30;
452 $view_mode = $params["view_mode"];
453 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
454 $search = isset($params["search"]) ? $params["search"] : false;
455 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
456 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
457 $offset = isset($params["offset"]) ? $params["offset"] : 0;
458 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
459 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
460 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
461 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
462 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
463 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
464 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
465 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
466 $api_request = isset($params["api_request"]) ? $params["api_request"] : false;
467
468 $ext_tables_part = "";
469 $query_strategy_part = "";
470
471 $search_words = array();
472
473 if ($search) {
474 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
475 list($search_query_part, $search_words) = $plugin->hook_search($search);
476 break;
477 }
478
479 // fall back in case of no plugins
480 if (!$search_query_part) {
481 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
482 }
483 $search_query_part .= " AND ";
484 } else {
485 $search_query_part = "";
486 }
487
488 if ($since_id) {
489 $since_id_part = "ttrss_entries.id > $since_id AND ";
490 } else {
491 $since_id_part = "";
492 }
493
494 $view_query_part = "";
495 $disable_offsets = false;
496
497 if ($view_mode == "adaptive") {
498 if ($search) {
499 $view_query_part = " ";
500 } else if ($feed != -1) {
501
502 $unread = getFeedUnread($feed, $cat_view);
503
504 if ($cat_view && $feed > 0 && $include_children)
505 $unread += getCategoryChildrenUnread($feed);
506
507 if ($unread > 0) {
508 $view_query_part = " unread = true AND ";
509 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
510 }
511 }
512 }
513
514 if ($view_mode == "marked") {
515 $view_query_part = " marked = true AND ";
516 }
517
518 if ($view_mode == "has_note") {
519 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
520 }
521
522 if ($view_mode == "published") {
523 $view_query_part = " published = true AND ";
524 }
525
526 if ($view_mode == "unread" && $feed != -6) {
527 $view_query_part = " unread = true AND ";
528 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
529 }
530
531 if ($limit > 0) {
532 $limit_query_part = "LIMIT " . $limit;
533 }
534
535 $allow_archived = false;
536
537 $vfeed_query_part = "";
538
539 /* tags */
540 if (!is_numeric($feed)) {
541 $query_strategy_part = "true";
542 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
543 id = feed_id) as feed_title,";
544 } else if ($feed > 0) {
545
546 if ($cat_view) {
547
548 if ($feed > 0) {
549 if ($include_children) {
550 # sub-cats
551 $subcats = getChildCategories($feed, $owner_uid);
552
553 array_push($subcats, $feed);
554 $query_strategy_part = "cat_id IN (".
555 implode(",", $subcats).")";
556
557 } else {
558 $query_strategy_part = "cat_id = '$feed'";
559 }
560
561 } else {
562 $query_strategy_part = "cat_id IS NULL";
563 }
564
565 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
566
567 } else {
568 $query_strategy_part = "feed_id = '$feed'";
569 }
570 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
571 $query_strategy_part = "feed_id IS NULL";
572 $allow_archived = true;
573 } else if ($feed == 0 && $cat_view) { // uncategorized
574 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
575 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
576 } else if ($feed == -1) { // starred virtual feed
577 $query_strategy_part = "marked = true";
578 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
579 $allow_archived = true;
580
581 if (!$override_order) {
582 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
583 }
584
585 } else if ($feed == -2) { // published virtual feed OR labels category
586
587 if (!$cat_view) {
588 $query_strategy_part = "published = true";
589 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
590 $allow_archived = true;
591
592 if (!$override_order) {
593 $override_order = "last_published DESC, date_entered DESC, updated DESC";
594 }
595
596 } else {
597 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
598
599 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
600
601 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
602 ttrss_user_labels2.article_id = ref_id";
603
604 }
605 } else if ($feed == -6) { // recently read
606 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
607 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
608 $allow_archived = true;
609 $ignore_vfeed_group = true;
610
611 if (!$override_order) $override_order = "last_read DESC";
612
613 } else if ($feed == -3) { // fresh virtual feed
614 $query_strategy_part = "unread = true AND score >= 0";
615
616 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
617
618 if (DB_TYPE == "pgsql") {
619 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
620 } else {
621 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
622 }
623
624 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
625 } else if ($feed == -4) { // all articles virtual feed
626 $allow_archived = true;
627 $query_strategy_part = "true";
628 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
629 } else if ($feed <= LABEL_BASE_INDEX) { // labels
630 $label_id = feed_to_label_id($feed);
631
632 $query_strategy_part = "label_id = '$label_id' AND
633 ttrss_labels2.id = ttrss_user_labels2.label_id AND
634 ttrss_user_labels2.article_id = ref_id";
635
636 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
637 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
638 $allow_archived = true;
639
640 } else {
641 $query_strategy_part = "true";
642 }
643
644 $order_by = "score DESC, date_entered DESC, updated DESC";
645
646 if ($override_order) {
647 $order_by = $override_order;
648 }
649
650 if ($override_strategy) {
651 $query_strategy_part = $override_strategy;
652 }
653
654 if ($override_vfeed) {
655 $vfeed_query_part = $override_vfeed;
656 }
657
658 $feed_title = "";
659
660 if ($search) {
661 $feed_title = T_sprintf("Search results: %s", $search);
662 } else {
663 if ($cat_view) {
664 $feed_title = getCategoryTitle($feed);
665 } else {
666 if (is_numeric($feed) && $feed > 0) {
667 $result = db_query("SELECT title,site_url,last_error,last_updated
668 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
669
670 $feed_title = db_fetch_result($result, 0, "title");
671 $feed_site_url = db_fetch_result($result, 0, "site_url");
672 $last_error = db_fetch_result($result, 0, "last_error");
673 $last_updated = db_fetch_result($result, 0, "last_updated");
674 } else {
675 $feed_title = getFeedTitle($feed);
676 }
677 }
678 }
679
680
681 $content_query_part = "content, ";
682
683 if ($limit_query_part) {
684 $offset_query_part = "OFFSET $offset";
685 } else {
686 $offset_query_part = "";
687 }
688
689 if (is_numeric($feed)) {
690 // proper override_order applied above
691 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
692 if (!$override_order) {
693 $order_by = "ttrss_feeds.title, $order_by";
694 } else {
695 $order_by = "ttrss_feeds.title, $override_order";
696 }
697 }
698
699 if (!$allow_archived) {
700 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
701 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
702
703 } else {
704 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
705 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
706 }
707
708 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
709
710 if ($start_ts) {
711 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
712 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
713 } else {
714 $start_ts_query_part = "";
715 }
716
717 $first_id = 0;
718 $first_id_query_strategy_part = $query_strategy_part;
719
720 if ($feed == -3)
721 $first_id_query_strategy_part = "true";
722
723 if (DB_TYPE == "pgsql") {
724 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
725 } else {
726 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
727 }
728
729 if (!$search && !$disable_offsets) {
730 // if previous topmost article id changed that means our current pagination is no longer valid
731 $query = "SELECT DISTINCT
732 ttrss_feeds.title,
733 date_entered,
734 guid,
735 ttrss_entries.id,
736 ttrss_entries.title,
737 updated,
738 score,
739 marked,
740 published,
741 last_marked,
742 last_published,
743 last_read
744 FROM
745 $from_qpart
746 WHERE
747 $feed_check_qpart
748 ttrss_user_entries.owner_uid = '$owner_uid' AND
749 $search_query_part
750 $start_ts_query_part
751 $since_id_part
752 $sanity_interval_qpart
753 $first_id_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 && db_num_rows($result) > 0) {
761 $first_id = (int)db_fetch_result($result, 0, "id");
762
763 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
764 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
765 }
766 }
767 }
768
769 if ($disable_offsets) {
770 $offset_query_part = "";
771 }
772
773 $query = "SELECT DISTINCT
774 date_entered,
775 guid,
776 ttrss_entries.id,ttrss_entries.title,
777 updated,
778 label_cache,
779 tag_cache,
780 always_display_enclosures,
781 site_url,
782 note,
783 num_comments,
784 comments,
785 int_id,
786 uuid,
787 lang,
788 hide_images,
789 unread,feed_id,marked,published,link,last_read,orig_feed_id,
790 last_marked, last_published,
791 $vfeed_query_part
792 $content_query_part
793 author,score
794 FROM
795 $from_qpart
796 WHERE
797 $feed_check_qpart
798 ttrss_user_entries.owner_uid = '$owner_uid' AND
799 $search_query_part
800 $start_ts_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, $first_id);
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=\"alert\" id=\"$id\">$msg</div>";
1156 }
1157
1158 function format_notice($msg, $id = "") {
1159 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1160 }
1161
1162 function format_error($msg, $id = "") {
1163 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1164 }
1165
1166 function print_notice($msg) {
1167 return print format_notice($msg);
1168 }
1169
1170 function print_warning($msg) {
1171 return print format_warning($msg);
1172 }
1173
1174 function print_error($msg) {
1175 return print format_error($msg);
1176 }
1177
1178
1179 function T_sprintf() {
1180 $args = func_get_args();
1181 return vsprintf(__(array_shift($args)), $args);
1182 }
1183
1184 function format_inline_player($url, $ctype) {
1185
1186 $entry = "";
1187
1188 $url = htmlspecialchars($url);
1189
1190 if (strpos($ctype, "audio/") === 0) {
1191
1192 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1193 $_SESSION["hasMp3"])) {
1194
1195 $entry .= "<audio preload=\"none\" controls>
1196 <source type=\"$ctype\" src=\"$url\"/>
1197 </audio>";
1198
1199 } else {
1200
1201 $entry .= "<object type=\"application/x-shockwave-flash\"
1202 data=\"lib/button/musicplayer.swf?song_url=$url\"
1203 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1204 <param name=\"movie\"
1205 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1206 </object>";
1207 }
1208
1209 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
1210 href=\"$url\">" . basename($url) . "</a>";
1211
1212 return $entry;
1213
1214 }
1215
1216 return "";
1217
1218 /* $filename = substr($url, strrpos($url, "/")+1);
1219
1220 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1221 $filename . " (" . $ctype . ")" . "</a>"; */
1222
1223 }
1224
1225 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1226 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1227
1228 $rv = array();
1229
1230 $rv['id'] = $id;
1231
1232 /* we can figure out feed_id from article id anyway, why do we
1233 * pass feed_id here? let's ignore the argument :(*/
1234
1235 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1236 WHERE ref_id = '$id'");
1237
1238 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1239
1240 $rv['feed_id'] = $feed_id;
1241
1242 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1243
1244 if ($mark_as_read) {
1245 $result = db_query("UPDATE ttrss_user_entries
1246 SET unread = false,last_read = NOW()
1247 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1248
1249 ccache_update($feed_id, $owner_uid);
1250 }
1251
1252 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1253 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1254 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1255 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1256 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1257 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1258 num_comments,
1259 tag_cache,
1260 author,
1261 orig_feed_id,
1262 note
1263 FROM ttrss_entries,ttrss_user_entries
1264 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1265
1266 if ($result) {
1267
1268 $line = db_fetch_assoc($result);
1269
1270 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1271 unset($line["tag_cache"]);
1272
1273 $line["content"] = sanitize($line["content"],
1274 sql_bool_to_bool($line['hide_images']),
1275 $owner_uid, $line["site_url"], false, $line["id"]);
1276
1277 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1278 $line = $p->hook_render_article($line);
1279 }
1280
1281 $num_comments = $line["num_comments"];
1282 $entry_comments = "";
1283
1284 if ($num_comments > 0) {
1285 if ($line["comments"]) {
1286 $comments_url = htmlspecialchars($line["comments"]);
1287 } else {
1288 $comments_url = htmlspecialchars($line["link"]);
1289 }
1290 $entry_comments = "<a class=\"postComments\"
1291 target='_blank' href=\"$comments_url\">$num_comments ".
1292 _ngettext("comment", "comments", $num_comments)."</a>";
1293
1294 } else {
1295 if ($line["comments"] && $line["link"] != $line["comments"]) {
1296 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1297 }
1298 }
1299
1300 if ($zoom_mode) {
1301 header("Content-Type: text/html");
1302 $rv['content'] .= "<html><head>
1303 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1304 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1305 stylesheet_tag("css/tt-rss.css").
1306 stylesheet_tag("css/zoom.css").
1307 stylesheet_tag("css/dijit.css")."
1308
1309 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1310 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1311
1312 </head><body id=\"ttrssZoom\">";
1313 }
1314
1315 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1316
1317 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1318
1319 $entry_author = $line["author"];
1320
1321 if ($entry_author) {
1322 $entry_author = __(" - ") . $entry_author;
1323 }
1324
1325 $parsed_updated = make_local_datetime($line["updated"], true,
1326 $owner_uid, true);
1327
1328 if (!$zoom_mode)
1329 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1330
1331 if ($line["link"]) {
1332 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1333 title=\"".htmlspecialchars($line['title'])."\"
1334 href=\"" .
1335 htmlspecialchars($line["link"]) . "\">" .
1336 $line["title"] . "</a>" .
1337 "<span class='author'>$entry_author</span></div>";
1338 } else {
1339 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1340 }
1341
1342 if ($zoom_mode) {
1343 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1344 "\" target=\"_blank\">".
1345 htmlspecialchars($line["feed_title"])."</a>";
1346
1347 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1348
1349 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1350 }
1351
1352 $tags_str = format_tags_string($line["tags"], $id);
1353 $tags_str_full = join(", ", $line["tags"]);
1354
1355 if (!$tags_str_full) $tags_str_full = __("no tags");
1356
1357 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1358
1359 $rv['content'] .= "<div class='postTags' style='float : right'>
1360 <img src='images/tag.png'
1361 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1362
1363 if (!$zoom_mode) {
1364 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1365 <a title=\"".__('Edit tags for this article')."\"
1366 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1367
1368 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1369 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1370 position=\"below\">$tags_str_full</div>";
1371
1372 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1373 $rv['content'] .= $p->hook_article_button($line);
1374 }
1375
1376 } else {
1377 $tags_str = strip_tags($tags_str);
1378 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1379 }
1380 $rv['content'] .= "</div>";
1381 $rv['content'] .= "<div clear='both'>";
1382
1383 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1384 $rv['content'] .= $p->hook_article_left_button($line);
1385 }
1386
1387 $rv['content'] .= "$entry_comments</div>";
1388
1389 if ($line["orig_feed_id"]) {
1390
1391 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1392 WHERE id = ".$line["orig_feed_id"]);
1393
1394 if (db_num_rows($tmp_result) != 0) {
1395
1396 $rv['content'] .= "<div clear='both'>";
1397 $rv['content'] .= __("Originally from:");
1398
1399 $rv['content'] .= "&nbsp;";
1400
1401 $tmp_line = db_fetch_assoc($tmp_result);
1402
1403 $rv['content'] .= "<a target='_blank'
1404 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1405 $tmp_line['title'] . "</a>";
1406
1407 $rv['content'] .= "&nbsp;";
1408
1409 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1410 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1411
1412 $rv['content'] .= "</div>";
1413 }
1414 }
1415
1416 $rv['content'] .= "</div>";
1417
1418 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1419 if ($line['note']) {
1420 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1421 }
1422 $rv['content'] .= "</div>";
1423
1424 if (!$line['lang']) $line['lang'] = 'en';
1425
1426 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1427
1428 $rv['content'] .= $line["content"];
1429
1430 if (!$zoom_mode) {
1431 $rv['content'] .= format_article_enclosures($id,
1432 sql_bool_to_bool($line["always_display_enclosures"]),
1433 $line["content"],
1434 sql_bool_to_bool($line["hide_images"]));
1435 }
1436
1437 $rv['content'] .= "</div>";
1438
1439 $rv['content'] .= "</div>";
1440
1441 }
1442
1443 if ($zoom_mode) {
1444 $rv['content'] .= "
1445 <div class='footer'>
1446 <button onclick=\"return window.close()\">".
1447 __("Close this window")."</button></div>";
1448 $rv['content'] .= "</body></html>";
1449 }
1450
1451 return $rv;
1452
1453 }
1454
1455 function print_checkpoint($n, $s) {
1456 $ts = microtime(true);
1457 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1458 return $ts;
1459 }
1460
1461 function sanitize_tag($tag) {
1462 $tag = trim($tag);
1463
1464 $tag = mb_strtolower($tag, 'utf-8');
1465
1466 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1467
1468 if (DB_TYPE == "mysql") {
1469 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1470 }
1471
1472 return $tag;
1473 }
1474
1475 function get_self_url_prefix() {
1476 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1477 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1478 } else {
1479 return SELF_URL_PATH;
1480 }
1481 }
1482
1483 /**
1484 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1485 *
1486 * @return string The Mozilla Firefox feed adding URL.
1487 */
1488 function add_feed_url() {
1489 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1490
1491 $url_path = get_self_url_prefix() .
1492 "/public.php?op=subscribe&feed_url=%s";
1493 return $url_path;
1494 } // function add_feed_url
1495
1496 function encrypt_password($pass, $salt = '', $mode2 = false) {
1497 if ($salt && $mode2) {
1498 return "MODE2:" . hash('sha256', $salt . $pass);
1499 } else if ($salt) {
1500 return "SHA1X:" . sha1("$salt:$pass");
1501 } else {
1502 return "SHA1:" . sha1($pass);
1503 }
1504 } // function encrypt_password
1505
1506 function load_filters($feed_id, $owner_uid, $action_id = false) {
1507 $filters = array();
1508
1509 $cat_id = (int)getFeedCategory($feed_id);
1510
1511 if ($cat_id == 0)
1512 $null_cat_qpart = "cat_id IS NULL OR";
1513 else
1514 $null_cat_qpart = "";
1515
1516 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1517 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1518
1519 $check_cats = join(",", array_merge(
1520 getParentCategories($cat_id, $owner_uid),
1521 array($cat_id)));
1522
1523 while ($line = db_fetch_assoc($result)) {
1524 $filter_id = $line["id"];
1525
1526 $result2 = db_query("SELECT
1527 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1528 FROM ttrss_filters2_rules AS r,
1529 ttrss_filter_types AS t
1530 WHERE
1531 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1532 (feed_id IS NULL OR feed_id = '$feed_id') AND
1533 filter_type = t.id AND filter_id = '$filter_id'");
1534
1535 $rules = array();
1536 $actions = array();
1537
1538 while ($rule_line = db_fetch_assoc($result2)) {
1539 # print_r($rule_line);
1540
1541 $rule = array();
1542 $rule["reg_exp"] = $rule_line["reg_exp"];
1543 $rule["type"] = $rule_line["type_name"];
1544 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1545
1546 array_push($rules, $rule);
1547 }
1548
1549 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1550 FROM ttrss_filters2_actions AS a,
1551 ttrss_filter_actions AS t
1552 WHERE
1553 action_id = t.id AND filter_id = '$filter_id'");
1554
1555 while ($action_line = db_fetch_assoc($result2)) {
1556 # print_r($action_line);
1557
1558 $action = array();
1559 $action["type"] = $action_line["type_name"];
1560 $action["param"] = $action_line["action_param"];
1561
1562 array_push($actions, $action);
1563 }
1564
1565
1566 $filter = array();
1567 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1568 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1569 $filter["rules"] = $rules;
1570 $filter["actions"] = $actions;
1571
1572 if (count($rules) > 0 && count($actions) > 0) {
1573 array_push($filters, $filter);
1574 }
1575 }
1576
1577 return $filters;
1578 }
1579
1580 function get_score_pic($score) {
1581 if ($score > 100) {
1582 return "score_high.png";
1583 } else if ($score > 0) {
1584 return "score_half_high.png";
1585 } else if ($score < -100) {
1586 return "score_low.png";
1587 } else if ($score < 0) {
1588 return "score_half_low.png";
1589 } else {
1590 return "score_neutral.png";
1591 }
1592 }
1593
1594 function feed_has_icon($id) {
1595 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1596 }
1597
1598 function init_plugins() {
1599 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1600
1601 return true;
1602 }
1603
1604 function format_tags_string($tags, $id) {
1605 if (!is_array($tags) || count($tags) == 0) {
1606 return __("no tags");
1607 } else {
1608 $maxtags = min(5, count($tags));
1609 $tags_str = "";
1610
1611 for ($i = 0; $i < $maxtags; $i++) {
1612 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed('".$tags[$i]."')\">" . $tags[$i] . "</a>, ";
1613 }
1614
1615 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1616
1617 if (count($tags) > $maxtags)
1618 $tags_str .= ", &hellip;";
1619
1620 return $tags_str;
1621 }
1622 }
1623
1624 function format_article_labels($labels, $id) {
1625
1626 if (!is_array($labels)) return '';
1627
1628 $labels_str = "";
1629
1630 foreach ($labels as $l) {
1631 $labels_str .= sprintf("<span class='hlLabelRef'
1632 style='color : %s; background-color : %s'>%s</span>",
1633 $l[2], $l[3], $l[1]);
1634 }
1635
1636 return $labels_str;
1637
1638 }
1639
1640 function format_article_note($id, $note, $allow_edit = true) {
1641
1642 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1643 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1644 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1645
1646 return $str;
1647 }
1648
1649
1650 function get_feed_category($feed_cat, $parent_cat_id = false) {
1651 if ($parent_cat_id) {
1652 $parent_qpart = "parent_cat = '$parent_cat_id'";
1653 $parent_insert = "'$parent_cat_id'";
1654 } else {
1655 $parent_qpart = "parent_cat IS NULL";
1656 $parent_insert = "NULL";
1657 }
1658
1659 $result = db_query(
1660 "SELECT id FROM ttrss_feed_categories
1661 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1662
1663 if (db_num_rows($result) == 0) {
1664 return false;
1665 } else {
1666 return db_fetch_result($result, 0, "id");
1667 }
1668 }
1669
1670 function add_feed_category($feed_cat, $parent_cat_id = false) {
1671
1672 if (!$feed_cat) return false;
1673
1674 db_query("BEGIN");
1675
1676 if ($parent_cat_id) {
1677 $parent_qpart = "parent_cat = '$parent_cat_id'";
1678 $parent_insert = "'$parent_cat_id'";
1679 } else {
1680 $parent_qpart = "parent_cat IS NULL";
1681 $parent_insert = "NULL";
1682 }
1683
1684 $feed_cat = mb_substr($feed_cat, 0, 250);
1685
1686 $result = db_query(
1687 "SELECT id FROM ttrss_feed_categories
1688 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1689
1690 if (db_num_rows($result) == 0) {
1691
1692 $result = db_query(
1693 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1694 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1695
1696 db_query("COMMIT");
1697
1698 return true;
1699 }
1700
1701 return false;
1702 }
1703
1704 function getArticleFeed($id) {
1705 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1706 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1707
1708 if (db_num_rows($result) != 0) {
1709 return db_fetch_result($result, 0, "feed_id");
1710 } else {
1711 return 0;
1712 }
1713 }
1714
1715 /**
1716 * Fixes incomplete URLs by prepending "http://".
1717 * Also replaces feed:// with http://, and
1718 * prepends a trailing slash if the url is a domain name only.
1719 *
1720 * @param string $url Possibly incomplete URL
1721 *
1722 * @return string Fixed URL.
1723 */
1724 function fix_url($url) {
1725
1726 // support schema-less urls
1727 if (strpos($url, '//') === 0) {
1728 $url = 'https:' . $url;
1729 }
1730
1731 if (strpos($url, '://') === false) {
1732 $url = 'http://' . $url;
1733 } else if (substr($url, 0, 5) == 'feed:') {
1734 $url = 'http:' . substr($url, 5);
1735 }
1736
1737 //prepend slash if the URL has no slash in it
1738 // "http://www.example" -> "http://www.example/"
1739 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1740 $url .= '/';
1741 }
1742
1743 if ($url != "http:///")
1744 return $url;
1745 else
1746 return '';
1747 }
1748
1749 function validate_feed_url($url) {
1750 $parts = parse_url($url);
1751
1752 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1753
1754 }
1755
1756 function get_article_enclosures($id) {
1757
1758 $query = "SELECT * FROM ttrss_enclosures
1759 WHERE post_id = '$id' AND content_url != ''";
1760
1761 $rv = array();
1762
1763 $result = db_query($query);
1764
1765 if (db_num_rows($result) > 0) {
1766 while ($line = db_fetch_assoc($result)) {
1767 array_push($rv, $line);
1768 }
1769 }
1770
1771 return $rv;
1772 }
1773
1774 /* function save_email_address($email) {
1775 // FIXME: implement persistent storage of emails
1776
1777 if (!$_SESSION['stored_emails'])
1778 $_SESSION['stored_emails'] = array();
1779
1780 if (!in_array($email, $_SESSION['stored_emails']))
1781 array_push($_SESSION['stored_emails'], $email);
1782 } */
1783
1784
1785 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1786
1787 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1788
1789 $sql_is_cat = bool_to_sql_bool($is_cat);
1790
1791 $result = db_query("SELECT access_key FROM ttrss_access_keys
1792 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1793 AND owner_uid = " . $owner_uid);
1794
1795 if (db_num_rows($result) == 1) {
1796 return db_fetch_result($result, 0, "access_key");
1797 } else {
1798 $key = db_escape_string(uniqid_short());
1799
1800 $result = db_query("INSERT INTO ttrss_access_keys
1801 (access_key, feed_id, is_cat, owner_uid)
1802 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1803
1804 return $key;
1805 }
1806 return false;
1807 }
1808
1809 function get_feeds_from_html($url, $content)
1810 {
1811 $url = fix_url($url);
1812 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1813
1814 libxml_use_internal_errors(true);
1815
1816 $doc = new DOMDocument();
1817 $doc->loadHTML($content);
1818 $xpath = new DOMXPath($doc);
1819 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1820 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1821 $feedUrls = array();
1822 foreach ($entries as $entry) {
1823 if ($entry->hasAttribute('href')) {
1824 $title = $entry->getAttribute('title');
1825 if ($title == '') {
1826 $title = $entry->getAttribute('type');
1827 }
1828 $feedUrl = rewrite_relative_url(
1829 $baseUrl, $entry->getAttribute('href')
1830 );
1831 $feedUrls[$feedUrl] = $title;
1832 }
1833 }
1834 return $feedUrls;
1835 }
1836
1837 function is_html($content) {
1838 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1839 }
1840
1841 function url_is_html($url, $login = false, $pass = false) {
1842 return is_html(fetch_file_contents($url, false, $login, $pass));
1843 }
1844
1845 function print_label_select($name, $value, $attributes = "") {
1846
1847 $result = db_query("SELECT caption FROM ttrss_labels2
1848 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1849
1850 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1851 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1852
1853 while ($line = db_fetch_assoc($result)) {
1854
1855 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1856
1857 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1858 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1859
1860 }
1861
1862 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1863
1864 print "</select>";
1865
1866
1867 }
1868
1869 function format_article_enclosures($id, $always_display_enclosures,
1870 $article_content, $hide_images = false) {
1871
1872 $result = get_article_enclosures($id);
1873 $rv = '';
1874
1875 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1876 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1877 if (is_array($retval)) {
1878 $rv = $retval[0];
1879 $result = $retval[1];
1880 } else {
1881 $rv = $retval;
1882 }
1883 }
1884
1885 if ($rv === '' && !empty($result)) {
1886 $entries_html = array();
1887 $entries = array();
1888 $entries_inline = array();
1889
1890 foreach ($result as $line) {
1891
1892 $url = $line["content_url"];
1893 $ctype = $line["content_type"];
1894 $title = $line["title"];
1895 $width = $line["width"];
1896 $height = $line["height"];
1897
1898 if (!$ctype) $ctype = __("unknown type");
1899
1900 $filename = substr($url, strrpos($url, "/")+1);
1901
1902 $player = format_inline_player($url, $ctype);
1903
1904 if ($player) array_push($entries_inline, $player);
1905
1906 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1907 # $filename . " (" . $ctype . ")" . "</a>";
1908
1909 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1910 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1911
1912 array_push($entries_html, $entry);
1913
1914 $entry = array();
1915
1916 $entry["type"] = $ctype;
1917 $entry["filename"] = $filename;
1918 $entry["url"] = $url;
1919 $entry["title"] = $title;
1920 $entry["width"] = $width;
1921 $entry["height"] = $height;
1922
1923 array_push($entries, $entry);
1924 }
1925
1926 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1927 if ($always_display_enclosures ||
1928 !preg_match("/<img/i", $article_content)) {
1929
1930 foreach ($entries as $entry) {
1931
1932 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1933 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1934
1935
1936 if ($retval) {
1937 $rv .= $retval;
1938 } else {
1939
1940 if (preg_match("/image/", $entry["type"]) ||
1941 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1942
1943 if (!$hide_images) {
1944 $encsize = '';
1945 if ($entry['height'] > 0)
1946 $encsize .= ' height="' . intval($entry['width']) . '"';
1947 if ($entry['width'] > 0)
1948 $encsize .= ' width="' . intval($entry['height']) . '"';
1949 $rv .= "<p><img
1950 alt=\"".htmlspecialchars($entry["filename"])."\"
1951 src=\"" .htmlspecialchars($entry["url"]) . "\"
1952 " . $encsize . " /></p>";
1953 } else {
1954 $rv .= "<p><a target=\"_blank\"
1955 href=\"".htmlspecialchars($entry["url"])."\"
1956 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1957 }
1958
1959 if ($entry['title']) {
1960 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1961 }
1962 }
1963 }
1964 }
1965 }
1966 }
1967
1968 if (count($entries_inline) > 0) {
1969 $rv .= "<hr clear='both'/>";
1970 foreach ($entries_inline as $entry) { $rv .= $entry; };
1971 $rv .= "<hr clear='both'/>";
1972 }
1973
1974 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1975 "<span>" . __('Attachments')."</span>";
1976
1977 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1978
1979 foreach ($entries as $entry) {
1980 if ($entry["title"])
1981 $title = "&mdash; " . truncate_string($entry["title"], 30);
1982 else
1983 $title = "";
1984
1985 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
1986 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
1987
1988 };
1989
1990 $rv .= "</div>";
1991 $rv .= "</div>";
1992 }
1993
1994 return $rv;
1995 }
1996
1997 function getLastArticleId() {
1998 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
1999 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2000
2001 if (db_num_rows($result) == 1) {
2002 return db_fetch_result($result, 0, "id");
2003 } else {
2004 return -1;
2005 }
2006 }
2007
2008 function build_url($parts) {
2009 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2010 }
2011
2012 /**
2013 * Converts a (possibly) relative URL to a absolute one.
2014 *
2015 * @param string $url Base URL (i.e. from where the document is)
2016 * @param string $rel_url Possibly relative URL in the document
2017 *
2018 * @return string Absolute URL
2019 */
2020 function rewrite_relative_url($url, $rel_url) {
2021 if (strpos($rel_url, ":") !== false) {
2022 return $rel_url;
2023 } else if (strpos($rel_url, "://") !== false) {
2024 return $rel_url;
2025 } else if (strpos($rel_url, "//") === 0) {
2026 # protocol-relative URL (rare but they exist)
2027 return $rel_url;
2028 } else if (strpos($rel_url, "/") === 0)
2029 {
2030 $parts = parse_url($url);
2031 $parts['path'] = $rel_url;
2032
2033 return build_url($parts);
2034
2035 } else {
2036 $parts = parse_url($url);
2037 if (!isset($parts['path'])) {
2038 $parts['path'] = '/';
2039 }
2040 $dir = $parts['path'];
2041 if (substr($dir, -1) !== '/') {
2042 $dir = dirname($parts['path']);
2043 $dir !== '/' && $dir .= '/';
2044 }
2045 $parts['path'] = $dir . $rel_url;
2046
2047 return build_url($parts);
2048 }
2049 }
2050
2051 function cleanup_tags($days = 14, $limit = 1000) {
2052
2053 if (DB_TYPE == "pgsql") {
2054 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2055 } else if (DB_TYPE == "mysql") {
2056 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2057 }
2058
2059 $tags_deleted = 0;
2060
2061 while ($limit > 0) {
2062 $limit_part = 500;
2063
2064 $query = "SELECT ttrss_tags.id AS id
2065 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2066 WHERE post_int_id = int_id AND $interval_query AND
2067 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2068
2069 $result = db_query($query);
2070
2071 $ids = array();
2072
2073 while ($line = db_fetch_assoc($result)) {
2074 array_push($ids, $line['id']);
2075 }
2076
2077 if (count($ids) > 0) {
2078 $ids = join(",", $ids);
2079
2080 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2081 $tags_deleted += db_affected_rows($tmp_result);
2082 } else {
2083 break;
2084 }
2085
2086 $limit -= $limit_part;
2087 }
2088
2089 return $tags_deleted;
2090 }
2091
2092 function print_user_stylesheet() {
2093 $value = get_pref('USER_STYLESHEET');
2094
2095 if ($value) {
2096 print "<style type=\"text/css\">";
2097 print str_replace("<br/>", "\n", $value);
2098 print "</style>";
2099 }
2100
2101 }
2102
2103 function filter_to_sql($filter, $owner_uid) {
2104 $query = array();
2105
2106 if (DB_TYPE == "pgsql")
2107 $reg_qpart = "~";
2108 else
2109 $reg_qpart = "REGEXP";
2110
2111 foreach ($filter["rules"] AS $rule) {
2112 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2113 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2114 $rule['reg_exp']) !== FALSE;
2115
2116 if ($regexp_valid) {
2117
2118 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2119
2120 switch ($rule["type"]) {
2121 case "title":
2122 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2123 $rule['reg_exp'] . "')";
2124 break;
2125 case "content":
2126 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2127 $rule['reg_exp'] . "')";
2128 break;
2129 case "both":
2130 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2131 $rule['reg_exp'] . "') OR LOWER(" .
2132 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2133 break;
2134 case "tag":
2135 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "')";
2137 break;
2138 case "link":
2139 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "')";
2141 break;
2142 case "author":
2143 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2144 $rule['reg_exp'] . "')";
2145 break;
2146 }
2147
2148 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2149
2150 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2151 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2152 }
2153
2154 if (isset($rule["cat_id"])) {
2155
2156 if ($rule["cat_id"] > 0) {
2157 $children = getChildCategories($rule["cat_id"], $owner_uid);
2158 array_push($children, $rule["cat_id"]);
2159
2160 $children = join(",", $children);
2161
2162 $cat_qpart = "cat_id IN ($children)";
2163 } else {
2164 $cat_qpart = "cat_id IS NULL";
2165 }
2166
2167 $qpart .= " AND $cat_qpart";
2168 }
2169
2170 $qpart .= " AND feed_id IS NOT NULL";
2171
2172 array_push($query, "($qpart)");
2173
2174 }
2175 }
2176
2177 if (count($query) > 0) {
2178 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2179 } else {
2180 $fullquery = "(false)";
2181 }
2182
2183 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2184
2185 return $fullquery;
2186 }
2187
2188 if (!function_exists('gzdecode')) {
2189 function gzdecode($string) { // no support for 2nd argument
2190 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2191 base64_encode($string));
2192 }
2193 }
2194
2195 function get_random_bytes($length) {
2196 if (function_exists('openssl_random_pseudo_bytes')) {
2197 return openssl_random_pseudo_bytes($length);
2198 } else {
2199 $output = "";
2200
2201 for ($i = 0; $i < $length; $i++)
2202 $output .= chr(mt_rand(0, 255));
2203
2204 return $output;
2205 }
2206 }
2207
2208 function read_stdin() {
2209 $fp = fopen("php://stdin", "r");
2210
2211 if ($fp) {
2212 $line = trim(fgets($fp));
2213 fclose($fp);
2214 return $line;
2215 }
2216
2217 return null;
2218 }
2219
2220 function tmpdirname($path, $prefix) {
2221 // Use PHP's tmpfile function to create a temporary
2222 // directory name. Delete the file and keep the name.
2223 $tempname = tempnam($path,$prefix);
2224 if (!$tempname)
2225 return false;
2226
2227 if (!unlink($tempname))
2228 return false;
2229
2230 return $tempname;
2231 }
2232
2233 function getFeedCategory($feed) {
2234 $result = db_query("SELECT cat_id FROM ttrss_feeds
2235 WHERE id = '$feed'");
2236
2237 if (db_num_rows($result) > 0) {
2238 return db_fetch_result($result, 0, "cat_id");
2239 } else {
2240 return false;
2241 }
2242
2243 }
2244
2245 function implements_interface($class, $interface) {
2246 return in_array($interface, class_implements($class));
2247 }
2248
2249 function geturl($url, $depth = 0, $nobody = true){
2250
2251 if ($depth == 20) return $url;
2252
2253 if (!function_exists('curl_init'))
2254 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);
2255
2256 $curl = curl_init();
2257 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2258 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2259 $header[] = "Cache-Control: max-age=0";
2260 $header[] = "Connection: keep-alive";
2261 $header[] = "Keep-Alive: 300";
2262 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2263 $header[] = "Accept-Language: en-us,en;q=0.5";
2264 $header[] = "Pragma: ";
2265
2266 curl_setopt($curl, CURLOPT_URL, $url);
2267 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2268 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2269 curl_setopt($curl, CURLOPT_HEADER, true);
2270 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2271 curl_setopt($curl, CURLOPT_REFERER, $url);
2272 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2273 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2274 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2275 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2276 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2277 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2278
2279 if (defined('_CURL_HTTP_PROXY')) {
2280 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2281 }
2282
2283 $html = curl_exec($curl);
2284
2285 $status = curl_getinfo($curl);
2286
2287 if($status['http_code']!=200){
2288
2289 // idiot site not allowing http head
2290 if($status['http_code'] == 405) {
2291 curl_close($curl);
2292 return geturl($url, $depth +1, false);
2293 }
2294
2295 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2296 curl_close($curl);
2297 list($header) = explode("\r\n\r\n", $html, 2);
2298 $matches = array();
2299 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2300 $url = trim(str_replace($matches[1],"",$matches[0]));
2301 $url_parsed = parse_url($url);
2302 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2303 }
2304
2305 global $fetch_last_error;
2306
2307 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2308 curl_close($curl);
2309
2310 # $oline='';
2311 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2312 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2313 # $handle = @fopen('./curl.error.log', 'a');
2314 # fwrite($handle, $line);
2315 return FALSE;
2316 }
2317 curl_close($curl);
2318 return $url;
2319 }
2320
2321 function get_minified_js($files) {
2322 require_once 'lib/jshrink/Minifier.php';
2323
2324 $rv = '';
2325
2326 foreach ($files as $js) {
2327 if (!isset($_GET['debug'])) {
2328 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2329
2330 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2331
2332 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2333
2334 if ($header && $contents) {
2335 list($htag, $hversion) = explode(":", $header);
2336
2337 if ($htag == "tt-rss" && $hversion == VERSION) {
2338 $rv .= $contents;
2339 continue;
2340 }
2341 }
2342 }
2343
2344 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2345 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2346 $rv .= $minified;
2347
2348 } else {
2349 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2350 }
2351 }
2352
2353 return $rv;
2354 }
2355
2356 function stylesheet_tag($filename) {
2357 $timestamp = filemtime($filename);
2358
2359 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2360 }
2361
2362 function javascript_tag($filename) {
2363 $query = "";
2364
2365 if (!(strpos($filename, "?") === FALSE)) {
2366 $query = substr($filename, strpos($filename, "?")+1);
2367 $filename = substr($filename, 0, strpos($filename, "?"));
2368 }
2369
2370 $timestamp = filemtime($filename);
2371
2372 if ($query) $timestamp .= "&$query";
2373
2374 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2375 }
2376
2377 function calculate_dep_timestamp() {
2378 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2379
2380 $max_ts = -1;
2381
2382 foreach ($files as $file) {
2383 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2384 }
2385
2386 return $max_ts;
2387 }
2388
2389 function T_js_decl($s1, $s2) {
2390 if ($s1 && $s2) {
2391 $s1 = preg_replace("/\n/", "", $s1);
2392 $s2 = preg_replace("/\n/", "", $s2);
2393
2394 $s1 = preg_replace("/\"/", "\\\"", $s1);
2395 $s2 = preg_replace("/\"/", "\\\"", $s2);
2396
2397 return "T_messages[\"$s1\"] = \"$s2\";\n";
2398 }
2399 }
2400
2401 function init_js_translations() {
2402
2403 print 'var T_messages = new Object();
2404
2405 function __(msg) {
2406 if (T_messages[msg]) {
2407 return T_messages[msg];
2408 } else {
2409 return msg;
2410 }
2411 }
2412
2413 function ngettext(msg1, msg2, n) {
2414 return __((parseInt(n) > 1) ? msg2 : msg1);
2415 }';
2416
2417 $l10n = _get_reader();
2418
2419 for ($i = 0; $i < $l10n->total; $i++) {
2420 $orig = $l10n->get_original_string($i);
2421 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2422 $key = explode(chr(0), $orig);
2423 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2424 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2425 } else {
2426 $translation = __($orig);
2427 print T_js_decl($orig, $translation);
2428 }
2429 }
2430 }
2431
2432 function label_to_feed_id($label) {
2433 return LABEL_BASE_INDEX - 1 - abs($label);
2434 }
2435
2436 function feed_to_label_id($feed) {
2437 return LABEL_BASE_INDEX - 1 + abs($feed);
2438 }
2439
2440 function get_theme_path($theme) {
2441 $check = "themes/$theme";
2442 if (file_exists($check)) return $check;
2443
2444 $check = "themes.local/$theme";
2445 if (file_exists($check)) return $check;
2446 }
2447
2448 function theme_valid($theme) {
2449 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2450 $file = "themes/" . basename($theme);
2451
2452 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2453
2454 if (file_exists($file) && is_readable($file)) {
2455 $fh = fopen($file, "r");
2456
2457 if ($fh) {
2458 $header = fgets($fh);
2459 fclose($fh);
2460
2461 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2462 }
2463 }
2464
2465 return false;
2466 }
2467
2468 function error_json($code) {
2469 require_once "errors.php";
2470
2471 @$message = $ERRORS[$code];
2472
2473 return json_encode(array("error" =>
2474 array("code" => $code, "message" => $message)));
2475
2476 }
2477
2478 function abs_to_rel_path($dir) {
2479 $tmp = str_replace(dirname(__DIR__), "", $dir);
2480
2481 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2482
2483 return $tmp;
2484 }
2485 ?>