]> git.wh0rd.org - tt-rss.git/blob - include/functions2.php
b4018453ebd6413a108002ec95a5d4a8e80b42ee
[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 "feed_debug_viewfeed" => __("Debug viewfeed()"),
96 "catchup_all" => __("Mark all feeds as read"),
97 "cat_toggle_collapse" => __("Un/collapse current category"),
98 "toggle_combined_mode" => __("Toggle combined mode"),
99 "toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
100 __("Go to") => array(
101 "goto_all" => __("All articles"),
102 "goto_fresh" => __("Fresh"),
103 "goto_marked" => __("Starred"),
104 "goto_published" => __("Published"),
105 "goto_tagcloud" => __("Tag cloud"),
106 "goto_prefs" => __("Preferences")),
107 __("Other") => array(
108 "create_label" => __("Create label"),
109 "create_filter" => __("Create filter"),
110 "collapse_sidebar" => __("Un/collapse sidebar"),
111 "help_dialog" => __("Show help dialog"))
112 );
113
114 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_INFO) as $plugin) {
115 $hotkeys = $plugin->hook_hotkey_info($hotkeys);
116 }
117
118 return $hotkeys;
119 }
120
121 function get_hotkeys_map() {
122 $hotkeys = array(
123 // "navigation" => array(
124 "k" => "next_feed",
125 "j" => "prev_feed",
126 "n" => "next_article",
127 "p" => "prev_article",
128 "(38)|up" => "prev_article",
129 "(40)|down" => "next_article",
130 // "^(38)|Ctrl-up" => "prev_article_noscroll",
131 // "^(40)|Ctrl-down" => "next_article_noscroll",
132 "(191)|/" => "search_dialog",
133 // "article" => array(
134 "s" => "toggle_mark",
135 "*s" => "toggle_publ",
136 "u" => "toggle_unread",
137 "*t" => "edit_tags",
138 "*d" => "dismiss_selected",
139 "*x" => "dismiss_read",
140 "o" => "open_in_new_window",
141 "c p" => "catchup_below",
142 "c n" => "catchup_above",
143 "*n" => "article_scroll_down",
144 "*p" => "article_scroll_up",
145 "*(38)|Shift+up" => "article_scroll_up",
146 "*(40)|Shift+down" => "article_scroll_down",
147 "a *w" => "toggle_widescreen",
148 "a e" => "toggle_embed_original",
149 "e" => "email_article",
150 "a q" => "close_article",
151 // "article_selection" => array(
152 "a a" => "select_all",
153 "a u" => "select_unread",
154 "a *u" => "select_marked",
155 "a p" => "select_published",
156 "a i" => "select_invert",
157 "a n" => "select_none",
158 // "feed" => array(
159 "f r" => "feed_refresh",
160 "f a" => "feed_unhide_read",
161 "f s" => "feed_subscribe",
162 "f e" => "feed_edit",
163 "f q" => "feed_catchup",
164 "f x" => "feed_reverse",
165 "f *d" => "feed_debug_update",
166 "f *g" => "feed_debug_viewfeed",
167 "f *c" => "toggle_combined_mode",
168 "f c" => "toggle_cdm_expanded",
169 "*q" => "catchup_all",
170 "x" => "cat_toggle_collapse",
171 // "goto" => array(
172 "g a" => "goto_all",
173 "g f" => "goto_fresh",
174 "g s" => "goto_marked",
175 "g p" => "goto_published",
176 "g t" => "goto_tagcloud",
177 "g *p" => "goto_prefs",
178 // "other" => array(
179 "(9)|Tab" => "select_article_cursor", // tab
180 "c l" => "create_label",
181 "c f" => "create_filter",
182 "c s" => "collapse_sidebar",
183 "^(191)|Ctrl+/" => "help_dialog",
184 );
185
186 if (get_pref('COMBINED_DISPLAY_MODE')) {
187 $hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
188 $hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
189 }
190
191 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
192 $hotkeys = $plugin->hook_hotkey_map($hotkeys);
193 }
194
195 $prefixes = array();
196
197 foreach (array_keys($hotkeys) as $hotkey) {
198 $pair = explode(" ", $hotkey, 2);
199
200 if (count($pair) > 1 && !in_array($pair[0], $prefixes)) {
201 array_push($prefixes, $pair[0]);
202 }
203 }
204
205 return array($prefixes, $hotkeys);
206 }
207
208 function check_for_update() {
209 if (defined("GIT_VERSION_TIMESTAMP")) {
210 $content = @fetch_file_contents("http://tt-rss.org/version.json");
211
212 if ($content) {
213 $content = json_decode($content, true);
214
215 if ($content && isset($content["changeset"])) {
216 if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
217 GIT_VERSION_HEAD != $content["changeset"]["id"]) {
218
219 return $content["changeset"]["id"];
220 }
221 }
222 }
223 }
224
225 return "";
226 }
227
228 function make_runtime_info() {
229 $data = array();
230
231 $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM
232 ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
233
234 $max_feed_id = db_fetch_result($result, 0, "mid");
235 $num_feeds = db_fetch_result($result, 0, "nf");
236
237 $data["max_feed_id"] = (int) $max_feed_id;
238 $data["num_feeds"] = (int) $num_feeds;
239
240 $data['last_article_id'] = getLastArticleId();
241 $data['cdm_expanded'] = get_pref('CDM_EXPANDED');
242
243 $data['dep_ts'] = calculate_dep_timestamp();
244 $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
245
246
247 if (CHECK_FOR_UPDATES && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
248 $update_result = @check_for_update();
249
250 $data["update_result"] = $update_result;
251
252 $_SESSION["last_version_check"] = time();
253 }
254
255 if (file_exists(LOCK_DIRECTORY . "/update_daemon.lock")) {
256
257 $data['daemon_is_running'] = (int) file_is_locked("update_daemon.lock");
258
259 if (time() - $_SESSION["daemon_stamp_check"] > 30) {
260
261 $stamp = (int) @file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
262
263 if ($stamp) {
264 $stamp_delta = time() - $stamp;
265
266 if ($stamp_delta > 1800) {
267 $stamp_check = 0;
268 } else {
269 $stamp_check = 1;
270 $_SESSION["daemon_stamp_check"] = time();
271 }
272
273 $data['daemon_stamp_ok'] = $stamp_check;
274
275 $stamp_fmt = date("Y.m.d, G:i", $stamp);
276
277 $data['daemon_stamp'] = $stamp_fmt;
278 }
279 }
280 }
281
282 return $data;
283 }
284
285 function search_to_sql($search, $search_language) {
286
287 $keywords = str_getcsv(trim($search), " ");
288 $query_keywords = array();
289 $search_words = array();
290 $search_query_leftover = array();
291
292 if ($search_language)
293 $search_language = db_escape_string(mb_strtolower($search_language));
294 else
295 $search_language = "english";
296
297 foreach ($keywords as $k) {
298 if (strpos($k, "-") === 0) {
299 $k = substr($k, 1);
300 $not = "NOT";
301 } else {
302 $not = "";
303 }
304
305 $commandpair = explode(":", mb_strtolower($k), 2);
306
307 switch ($commandpair[0]) {
308 case "title":
309 if ($commandpair[1]) {
310 array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%".
311 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
312 } else {
313 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
314 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
315 array_push($search_words, $k);
316 }
317 break;
318 case "author":
319 if ($commandpair[1]) {
320 array_push($query_keywords, "($not (LOWER(author) LIKE '%".
321 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
322 } else {
323 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
324 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
325 array_push($search_words, $k);
326 }
327 break;
328 case "note":
329 if ($commandpair[1]) {
330 if ($commandpair[1] == "true")
331 array_push($query_keywords, "($not (note IS NOT NULL AND note != ''))");
332 else if ($commandpair[1] == "false")
333 array_push($query_keywords, "($not (note IS NULL OR note = ''))");
334 else
335 array_push($query_keywords, "($not (LOWER(note) LIKE '%".
336 db_escape_string(mb_strtolower($commandpair[1]))."%'))");
337 } else {
338 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
339 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
340 if (!$not) array_push($search_words, $k);
341 }
342 break;
343 case "star":
344
345 if ($commandpair[1]) {
346 if ($commandpair[1] == "true")
347 array_push($query_keywords, "($not (marked = true))");
348 else
349 array_push($query_keywords, "($not (marked = false))");
350 } else {
351 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
352 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
353 if (!$not) array_push($search_words, $k);
354 }
355 break;
356 case "pub":
357 if ($commandpair[1]) {
358 if ($commandpair[1] == "true")
359 array_push($query_keywords, "($not (published = true))");
360 else
361 array_push($query_keywords, "($not (published = false))");
362
363 } else {
364 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
365 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
366 if (!$not) array_push($search_words, $k);
367 }
368 break;
369 case "unread":
370 if ($commandpair[1]) {
371 if ($commandpair[1] == "true")
372 array_push($query_keywords, "($not (unread = true))");
373 else
374 array_push($query_keywords, "($not (unread = false))");
375
376 } else {
377 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
378 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
379 if (!$not) array_push($search_words, $k);
380 }
381 break;
382 default:
383 if (strpos($k, "@") === 0) {
384
385 $user_tz_string = get_pref('USER_TIMEZONE', $_SESSION['uid']);
386 $orig_ts = strtotime(substr($k, 1));
387 $k = date("Y-m-d", convert_timestamp($orig_ts, $user_tz_string, 'UTC'));
388
389 //$k = date("Y-m-d", strtotime(substr($k, 1)));
390
391 array_push($query_keywords, "(".SUBSTRING_FOR_DATE."(updated,1,LENGTH('$k')) $not = '$k')");
392 } else {
393
394 if (DB_TYPE == "pgsql") {
395 $k = mb_strtolower($k);
396 array_push($search_query_leftover, $not ? "!$k" : $k);
397 } else {
398 array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%')
399 OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))");
400 }
401
402 if (!$not) array_push($search_words, $k);
403 }
404 }
405 }
406
407 if (count($search_query_leftover) > 0) {
408 $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover));
409
410 if (DB_TYPE == "pgsql") {
411 array_push($query_keywords,
412 "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))");
413 }
414
415 }
416
417 $search_query_part = implode("AND", $query_keywords);
418
419 return array($search_query_part, $search_words);
420 }
421
422 function getParentCategories($cat, $owner_uid) {
423 $rv = array();
424
425 $result = db_query("SELECT parent_cat FROM ttrss_feed_categories
426 WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid");
427
428 while ($line = db_fetch_assoc($result)) {
429 array_push($rv, $line["parent_cat"]);
430 $rv = array_merge($rv, getParentCategories($line["parent_cat"], $owner_uid));
431 }
432
433 return $rv;
434 }
435
436 function getChildCategories($cat, $owner_uid) {
437 $rv = array();
438
439 $result = db_query("SELECT id FROM ttrss_feed_categories
440 WHERE parent_cat = '$cat' AND owner_uid = $owner_uid");
441
442 while ($line = db_fetch_assoc($result)) {
443 array_push($rv, $line["id"]);
444 $rv = array_merge($rv, getChildCategories($line["id"], $owner_uid));
445 }
446
447 return $rv;
448 }
449
450 function queryFeedHeadlines($params) {
451
452 $feed = $params["feed"];
453 $limit = isset($params["limit"]) ? $params["limit"] : 30;
454 $view_mode = $params["view_mode"];
455 $cat_view = isset($params["cat_view"]) ? $params["cat_view"] : false;
456 $search = isset($params["search"]) ? $params["search"] : false;
457 $search_language = isset($params["search_language"]) ? $params["search_language"] : "";
458 $override_order = isset($params["override_order"]) ? $params["override_order"] : false;
459 $offset = isset($params["offset"]) ? $params["offset"] : 0;
460 $owner_uid = isset($params["owner_uid"]) ? $params["owner_uid"] : $_SESSION["uid"];
461 $since_id = isset($params["since_id"]) ? $params["since_id"] : 0;
462 $include_children = isset($params["include_children"]) ? $params["include_children"] : false;
463 $ignore_vfeed_group = isset($params["ignore_vfeed_group"]) ? $params["ignore_vfeed_group"] : false;
464 $override_strategy = isset($params["override_strategy"]) ? $params["override_strategy"] : false;
465 $override_vfeed = isset($params["override_vfeed"]) ? $params["override_vfeed"] : false;
466 $start_ts = isset($params["start_ts"]) ? $params["start_ts"] : false;
467 $check_first_id = isset($params["check_first_id"]) ? $params["check_first_id"] : false;
468 $api_request = isset($params["api_request"]) ? $params["api_request"] : false;
469
470 $ext_tables_part = "";
471 $query_strategy_part = "";
472
473 $search_words = array();
474
475 if ($search) {
476 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
477 list($search_query_part, $search_words) = $plugin->hook_search($search);
478 break;
479 }
480
481 // fall back in case of no plugins
482 if (!$search_query_part) {
483 list($search_query_part, $search_words) = search_to_sql($search, $search_language);
484 }
485 $search_query_part .= " AND ";
486 } else {
487 $search_query_part = "";
488 }
489
490 if ($since_id) {
491 $since_id_part = "ttrss_entries.id > $since_id AND ";
492 } else {
493 $since_id_part = "";
494 }
495
496 $view_query_part = "";
497 $disable_offsets = false;
498
499 if ($view_mode == "adaptive") {
500 if ($search) {
501 $view_query_part = " ";
502 } else if ($feed != -1) {
503
504 $unread = getFeedUnread($feed, $cat_view);
505
506 if ($cat_view && $feed > 0 && $include_children)
507 $unread += getCategoryChildrenUnread($feed);
508
509 if ($unread > 0) {
510 $view_query_part = " unread = true AND ";
511 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
512 }
513 }
514 }
515
516 if ($view_mode == "marked") {
517 $view_query_part = " marked = true AND ";
518 }
519
520 if ($view_mode == "has_note") {
521 $view_query_part = " (note IS NOT NULL AND note != '') AND ";
522 }
523
524 if ($view_mode == "published") {
525 $view_query_part = " published = true AND ";
526 }
527
528 if ($view_mode == "unread" && $feed != -6) {
529 $view_query_part = " unread = true AND ";
530 $disable_offsets = !$api_request && get_pref("CDM_AUTO_CATCHUP") && get_pref("CDM_EXPANDED");
531 }
532
533 if ($limit > 0) {
534 $limit_query_part = "LIMIT " . $limit;
535 }
536
537 $allow_archived = false;
538
539 $vfeed_query_part = "";
540
541 /* tags */
542 if (!is_numeric($feed)) {
543 $query_strategy_part = "true";
544 $vfeed_query_part = "(SELECT title FROM ttrss_feeds WHERE
545 id = feed_id) as feed_title,";
546 } else if ($feed > 0) {
547
548 if ($cat_view) {
549
550 if ($feed > 0) {
551 if ($include_children) {
552 # sub-cats
553 $subcats = getChildCategories($feed, $owner_uid);
554
555 array_push($subcats, $feed);
556 $query_strategy_part = "cat_id IN (".
557 implode(",", $subcats).")";
558
559 } else {
560 $query_strategy_part = "cat_id = '$feed'";
561 }
562
563 } else {
564 $query_strategy_part = "cat_id IS NULL";
565 }
566
567 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
568
569 } else {
570 $query_strategy_part = "feed_id = '$feed'";
571 }
572 } else if ($feed == 0 && !$cat_view) { // archive virtual feed
573 $query_strategy_part = "feed_id IS NULL";
574 $allow_archived = true;
575 } else if ($feed == 0 && $cat_view) { // uncategorized
576 $query_strategy_part = "cat_id IS NULL AND feed_id IS NOT NULL";
577 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
578 } else if ($feed == -1) { // starred virtual feed
579 $query_strategy_part = "marked = true";
580 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
581 $allow_archived = true;
582
583 if (!$override_order) {
584 $override_order = "last_marked DESC, date_entered DESC, updated DESC";
585 }
586
587 } else if ($feed == -2) { // published virtual feed OR labels category
588
589 if (!$cat_view) {
590 $query_strategy_part = "published = true";
591 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
592 $allow_archived = true;
593
594 if (!$override_order) {
595 $override_order = "last_published DESC, date_entered DESC, updated DESC";
596 }
597
598 } else {
599 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
600
601 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
602
603 $query_strategy_part = "ttrss_labels2.id = ttrss_user_labels2.label_id AND
604 ttrss_user_labels2.article_id = ref_id";
605
606 }
607 } else if ($feed == -6) { // recently read
608 $query_strategy_part = "unread = false AND last_read IS NOT NULL";
609
610 if (DB_TYPE == "pgsql") {
611 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '1 DAY' ";
612 } else {
613 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL 1 DAY) ";
614 }
615
616 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
617 $allow_archived = true;
618 $ignore_vfeed_group = true;
619
620 if (!$override_order) $override_order = "last_read DESC";
621
622 } else if ($feed == -3) { // fresh virtual feed
623 $query_strategy_part = "unread = true AND score >= 0";
624
625 $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid);
626
627 if (DB_TYPE == "pgsql") {
628 $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' ";
629 } else {
630 $query_strategy_part .= " AND date_entered > DATE_SUB(NOW(), INTERVAL $intl HOUR) ";
631 }
632
633 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
634 } else if ($feed == -4) { // all articles virtual feed
635 $allow_archived = true;
636 $query_strategy_part = "true";
637 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
638 } else if ($feed <= LABEL_BASE_INDEX) { // labels
639 $label_id = feed_to_label_id($feed);
640
641 $query_strategy_part = "label_id = '$label_id' AND
642 ttrss_labels2.id = ttrss_user_labels2.label_id AND
643 ttrss_user_labels2.article_id = ref_id";
644
645 $vfeed_query_part = "ttrss_feeds.title AS feed_title,";
646 $ext_tables_part = "ttrss_labels2,ttrss_user_labels2,";
647 $allow_archived = true;
648
649 } else {
650 $query_strategy_part = "true";
651 }
652
653 $order_by = "score DESC, date_entered DESC, updated DESC";
654
655 if ($override_order) {
656 $order_by = $override_order;
657 }
658
659 if ($override_strategy) {
660 $query_strategy_part = $override_strategy;
661 }
662
663 if ($override_vfeed) {
664 $vfeed_query_part = $override_vfeed;
665 }
666
667 $feed_title = "";
668
669 if ($search) {
670 $feed_title = T_sprintf("Search results: %s", $search);
671 } else {
672 if ($cat_view) {
673 $feed_title = getCategoryTitle($feed);
674 } else {
675 if (is_numeric($feed) && $feed > 0) {
676 $result = db_query("SELECT title,site_url,last_error,last_updated
677 FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid");
678
679 $feed_title = db_fetch_result($result, 0, "title");
680 $feed_site_url = db_fetch_result($result, 0, "site_url");
681 $last_error = db_fetch_result($result, 0, "last_error");
682 $last_updated = db_fetch_result($result, 0, "last_updated");
683 } else {
684 $feed_title = getFeedTitle($feed);
685 }
686 }
687 }
688
689
690 $content_query_part = "content, ";
691
692 if ($limit_query_part) {
693 $offset_query_part = "OFFSET $offset";
694 } else {
695 $offset_query_part = "";
696 }
697
698 if (is_numeric($feed)) {
699 // proper override_order applied above
700 if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) {
701 if (!$override_order) {
702 $order_by = "ttrss_feeds.title, $order_by";
703 } else {
704 $order_by = "ttrss_feeds.title, $override_order";
705 }
706 }
707
708 if (!$allow_archived) {
709 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id),ttrss_feeds";
710 $feed_check_qpart = "ttrss_user_entries.feed_id = ttrss_feeds.id AND";
711
712 } else {
713 $from_qpart = "${ext_tables_part}ttrss_entries LEFT JOIN ttrss_user_entries ON (ref_id = ttrss_entries.id)
714 LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id)";
715 }
716
717 if ($vfeed_query_part) $vfeed_query_part .= "favicon_avg_color,";
718
719 if ($start_ts) {
720 $start_ts_formatted = date("Y/m/d H:i:s", strtotime($start_ts));
721 $start_ts_query_part = "date_entered >= '$start_ts_formatted' AND";
722 } else {
723 $start_ts_query_part = "";
724 }
725
726 $first_id = 0;
727 $first_id_query_strategy_part = $query_strategy_part;
728
729 if ($feed == -3)
730 $first_id_query_strategy_part = "true";
731
732 if (DB_TYPE == "pgsql") {
733 $sanity_interval_qpart = "date_entered >= NOW() - INTERVAL '1 hour' AND";
734 } else {
735 $sanity_interval_qpart = "date_entered >= DATE_SUB(NOW(), INTERVAL 1 hour) AND";
736 }
737
738 if (!$search && !$disable_offsets) {
739 // if previous topmost article id changed that means our current pagination is no longer valid
740 $query = "SELECT DISTINCT
741 ttrss_feeds.title,
742 date_entered,
743 guid,
744 ttrss_entries.id,
745 ttrss_entries.title,
746 updated,
747 score,
748 marked,
749 published,
750 last_marked,
751 last_published,
752 last_read
753 FROM
754 $from_qpart
755 WHERE
756 $feed_check_qpart
757 ttrss_user_entries.owner_uid = '$owner_uid' AND
758 $search_query_part
759 $start_ts_query_part
760 $since_id_part
761 $sanity_interval_qpart
762 $first_id_query_strategy_part ORDER BY $order_by LIMIT 1";
763
764 if ($_REQUEST["debug"]) {
765 print $query;
766 }
767
768 $result = db_query($query);
769 if ($result && db_num_rows($result) > 0) {
770 $first_id = (int)db_fetch_result($result, 0, "id");
771
772 if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) {
773 return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
774 }
775 }
776 }
777
778 if ($disable_offsets) {
779 $offset_query_part = "";
780 }
781
782 $query = "SELECT DISTINCT
783 date_entered,
784 guid,
785 ttrss_entries.id,ttrss_entries.title,
786 updated,
787 label_cache,
788 tag_cache,
789 always_display_enclosures,
790 site_url,
791 note,
792 num_comments,
793 comments,
794 int_id,
795 uuid,
796 lang,
797 hide_images,
798 unread,feed_id,marked,published,link,last_read,orig_feed_id,
799 last_marked, last_published,
800 $vfeed_query_part
801 $content_query_part
802 author,score
803 FROM
804 $from_qpart
805 WHERE
806 $feed_check_qpart
807 ttrss_user_entries.owner_uid = '$owner_uid' AND
808 $search_query_part
809 $start_ts_query_part
810 $view_query_part
811 $since_id_part
812 $query_strategy_part ORDER BY $order_by
813 $limit_query_part $offset_query_part";
814
815 if ($_REQUEST["debug"]) print $query;
816
817 $result = db_query($query);
818
819 } else {
820 // browsing by tag
821
822 $query = "SELECT DISTINCT
823 date_entered,
824 guid,
825 note,
826 ttrss_entries.id as id,
827 title,
828 updated,
829 unread,
830 feed_id,
831 orig_feed_id,
832 marked,
833 num_comments,
834 comments,
835 tag_cache,
836 label_cache,
837 link,
838 lang,
839 uuid,
840 last_read,
841 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images,
842 last_marked, last_published,
843 $since_id_part
844 $vfeed_query_part
845 $content_query_part
846 author, score
847 FROM ttrss_entries, ttrss_user_entries, ttrss_tags
848 WHERE
849 ref_id = ttrss_entries.id AND
850 ttrss_user_entries.owner_uid = $owner_uid AND
851 post_int_id = int_id AND
852 tag_name = '$feed' AND
853 $view_query_part
854 $search_query_part
855 $query_strategy_part ORDER BY $order_by
856 $limit_query_part $offset_query_part";
857
858 if ($_REQUEST["debug"]) print $query;
859
860 $result = db_query($query);
861 }
862
863 return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id);
864
865 }
866
867 function iframe_whitelisted($entry) {
868 $whitelist = array("youtube.com", "youtu.be", "vimeo.com");
869
870 @$src = parse_url($entry->getAttribute("src"), PHP_URL_HOST);
871
872 if ($src) {
873 foreach ($whitelist as $w) {
874 if ($src == $w || $src == "www.$w")
875 return true;
876 }
877 }
878
879 return false;
880 }
881
882 function sanitize($str, $force_remove_images = false, $owner = false, $site_url = false, $highlight_words = false, $article_id = false) {
883 if (!$owner) $owner = $_SESSION["uid"];
884
885 $res = trim($str); if (!$res) return '';
886
887 $charset_hack = '<head>
888 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
889 </head>';
890
891 $res = trim($res); if (!$res) return '';
892
893 libxml_use_internal_errors(true);
894
895 $doc = new DOMDocument();
896 $doc->loadHTML($charset_hack . $res);
897 $xpath = new DOMXPath($doc);
898
899 $entries = $xpath->query('(//a[@href]|//img[@src])');
900
901 foreach ($entries as $entry) {
902
903 if ($site_url) {
904
905 if ($entry->hasAttribute('href')) {
906 $entry->setAttribute('href',
907 rewrite_relative_url($site_url, $entry->getAttribute('href')));
908
909 $entry->setAttribute('rel', 'noreferrer');
910 }
911
912 if ($entry->hasAttribute('src')) {
913 $src = rewrite_relative_url($site_url, $entry->getAttribute('src'));
914
915 $cached_filename = CACHE_DIR . '/images/' . sha1($src) . '.png';
916
917 if (file_exists($cached_filename)) {
918 $src = SELF_URL_PATH . '/public.php?op=cached_image&hash=' . sha1($src);
919 }
920
921 $entry->setAttribute('src', $src);
922 }
923
924 if ($entry->nodeName == 'img') {
925 if (($owner && get_pref("STRIP_IMAGES", $owner)) ||
926 $force_remove_images || $_SESSION["bw_limit"]) {
927
928 $p = $doc->createElement('p');
929
930 $a = $doc->createElement('a');
931 $a->setAttribute('href', $entry->getAttribute('src'));
932
933 $a->appendChild(new DOMText($entry->getAttribute('src')));
934 $a->setAttribute('target', '_blank');
935
936 $p->appendChild($a);
937
938 $entry->parentNode->replaceChild($p, $entry);
939 }
940 }
941 }
942
943 if (strtolower($entry->nodeName) == "a") {
944 $entry->setAttribute("target", "_blank");
945 }
946 }
947
948 $entries = $xpath->query('//iframe');
949 foreach ($entries as $entry) {
950 if (!iframe_whitelisted($entry)) {
951 $entry->setAttribute('sandbox', 'allow-scripts');
952 } else {
953 if ($_SERVER['HTTPS'] == "on") {
954 $entry->setAttribute("src",
955 str_replace("http://", "https://",
956 $entry->getAttribute("src")));
957 }
958 }
959 }
960
961 $allowed_elements = array('a', 'address', 'audio', 'article', 'aside',
962 'b', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br',
963 'caption', 'cite', 'center', 'code', 'col', 'colgroup',
964 'data', 'dd', 'del', 'details', 'div', 'dl', 'font',
965 'dt', 'em', 'footer', 'figure', 'figcaption',
966 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'html', 'i',
967 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'nav', 'noscript',
968 'ol', 'p', 'pre', 'q', 'ruby', 'rp', 'rt', 's', 'samp', 'section',
969 'small', 'source', 'span', 'strike', 'strong', 'sub', 'summary',
970 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time',
971 'tr', 'track', 'tt', 'u', 'ul', 'var', 'wbr', 'video' );
972
973 if ($_SESSION['hasSandbox']) $allowed_elements[] = 'iframe';
974
975 $disallowed_attributes = array('id', 'style', 'class');
976
977 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SANITIZE) as $plugin) {
978 $retval = $plugin->hook_sanitize($doc, $site_url, $allowed_elements, $disallowed_attributes, $article_id);
979 if (is_array($retval)) {
980 $doc = $retval[0];
981 $allowed_elements = $retval[1];
982 $disallowed_attributes = $retval[2];
983 } else {
984 $doc = $retval;
985 }
986 }
987
988 $doc->removeChild($doc->firstChild); //remove doctype
989 $doc = strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes);
990
991 if ($highlight_words) {
992 foreach ($highlight_words as $word) {
993
994 // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph
995
996 $elements = $xpath->query("//*/text()");
997
998 foreach ($elements as $child) {
999
1000 $fragment = $doc->createDocumentFragment();
1001 $text = $child->textContent;
1002
1003 while (($pos = mb_stripos($text, $word)) !== false) {
1004 $fragment->appendChild(new DomText(mb_substr($text, 0, $pos)));
1005 $word = mb_substr($text, $pos, mb_strlen($word));
1006 $highlight = $doc->createElement('span');
1007 $highlight->appendChild(new DomText($word));
1008 $highlight->setAttribute('class', 'highlight');
1009 $fragment->appendChild($highlight);
1010 $text = mb_substr($text, $pos + mb_strlen($word));
1011 }
1012
1013 if (!empty($text)) $fragment->appendChild(new DomText($text));
1014
1015 $child->parentNode->replaceChild($fragment, $child);
1016 }
1017 }
1018 }
1019
1020 $res = $doc->saveHTML();
1021
1022 return $res;
1023 }
1024
1025 function strip_harmful_tags($doc, $allowed_elements, $disallowed_attributes) {
1026 $xpath = new DOMXPath($doc);
1027 $entries = $xpath->query('//*');
1028
1029 foreach ($entries as $entry) {
1030 if (!in_array($entry->nodeName, $allowed_elements)) {
1031 $entry->parentNode->removeChild($entry);
1032 }
1033
1034 if ($entry->hasAttributes()) {
1035 $attrs_to_remove = array();
1036
1037 foreach ($entry->attributes as $attr) {
1038
1039 if (strpos($attr->nodeName, 'on') === 0) {
1040 array_push($attrs_to_remove, $attr);
1041 }
1042
1043 if (in_array($attr->nodeName, $disallowed_attributes)) {
1044 array_push($attrs_to_remove, $attr);
1045 }
1046 }
1047
1048 foreach ($attrs_to_remove as $attr) {
1049 $entry->removeAttributeNode($attr);
1050 }
1051 }
1052 }
1053
1054 return $doc;
1055 }
1056
1057 function catchupArticlesById($ids, $cmode, $owner_uid = false) {
1058
1059 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1060 if (count($ids) == 0) return;
1061
1062 $tmp_ids = array();
1063
1064 foreach ($ids as $id) {
1065 array_push($tmp_ids, "ref_id = '$id'");
1066 }
1067
1068 $ids_qpart = join(" OR ", $tmp_ids);
1069
1070 if ($cmode == 0) {
1071 db_query("UPDATE ttrss_user_entries SET
1072 unread = false,last_read = NOW()
1073 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1074 } else if ($cmode == 1) {
1075 db_query("UPDATE ttrss_user_entries SET
1076 unread = true
1077 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1078 } else {
1079 db_query("UPDATE ttrss_user_entries SET
1080 unread = NOT unread,last_read = NOW()
1081 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1082 }
1083
1084 /* update ccache */
1085
1086 $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries
1087 WHERE ($ids_qpart) AND owner_uid = $owner_uid");
1088
1089 while ($line = db_fetch_assoc($result)) {
1090 ccache_update($line["feed_id"], $owner_uid);
1091 }
1092 }
1093
1094 function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
1095
1096 $a_id = db_escape_string($id);
1097
1098 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1099
1100 $query = "SELECT DISTINCT tag_name,
1101 owner_uid as owner FROM
1102 ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
1103 ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name";
1104
1105 $tags = array();
1106
1107 /* check cache first */
1108
1109 if ($tag_cache === false) {
1110 $result = db_query("SELECT tag_cache FROM ttrss_user_entries
1111 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1112
1113 $tag_cache = db_fetch_result($result, 0, "tag_cache");
1114 }
1115
1116 if ($tag_cache) {
1117 $tags = explode(",", $tag_cache);
1118 } else {
1119
1120 /* do it the hard way */
1121
1122 $tmp_result = db_query($query);
1123
1124 while ($tmp_line = db_fetch_assoc($tmp_result)) {
1125 array_push($tags, $tmp_line["tag_name"]);
1126 }
1127
1128 /* update the cache */
1129
1130 $tags_str = db_escape_string(join(",", $tags));
1131
1132 db_query("UPDATE ttrss_user_entries
1133 SET tag_cache = '$tags_str' WHERE ref_id = '$id'
1134 AND owner_uid = $owner_uid");
1135 }
1136
1137 return $tags;
1138 }
1139
1140 function trim_array($array) {
1141 $tmp = $array;
1142 array_walk($tmp, 'trim');
1143 return $tmp;
1144 }
1145
1146 function tag_is_valid($tag) {
1147 if ($tag == '') return false;
1148 if (preg_match("/^[0-9]*$/", $tag)) return false;
1149 if (mb_strlen($tag) > 250) return false;
1150
1151 if (!$tag) return false;
1152
1153 return true;
1154 }
1155
1156 function render_login_form() {
1157 header('Cache-Control: public');
1158
1159 require_once "login_form.php";
1160 exit;
1161 }
1162
1163 function format_warning($msg, $id = "") {
1164 return "<div class=\"alert\" id=\"$id\">$msg</div>";
1165 }
1166
1167 function format_notice($msg, $id = "") {
1168 return "<div class=\"alert alert-info\" id=\"$id\">$msg</div>";
1169 }
1170
1171 function format_error($msg, $id = "") {
1172 return "<div class=\"alert alert-danger\" id=\"$id\">$msg</div>";
1173 }
1174
1175 function print_notice($msg) {
1176 return print format_notice($msg);
1177 }
1178
1179 function print_warning($msg) {
1180 return print format_warning($msg);
1181 }
1182
1183 function print_error($msg) {
1184 return print format_error($msg);
1185 }
1186
1187
1188 function T_sprintf() {
1189 $args = func_get_args();
1190 return vsprintf(__(array_shift($args)), $args);
1191 }
1192
1193 function format_inline_player($url, $ctype) {
1194
1195 $entry = "";
1196
1197 $url = htmlspecialchars($url);
1198
1199 if (strpos($ctype, "audio/") === 0) {
1200
1201 if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
1202 $_SESSION["hasMp3"])) {
1203
1204 $entry .= "<audio preload=\"none\" controls>
1205 <source type=\"$ctype\" src=\"$url\"/>
1206 </audio>";
1207
1208 } else {
1209
1210 $entry .= "<object type=\"application/x-shockwave-flash\"
1211 data=\"lib/button/musicplayer.swf?song_url=$url\"
1212 width=\"17\" height=\"17\" style='float : left; margin-right : 5px;'>
1213 <param name=\"movie\"
1214 value=\"lib/button/musicplayer.swf?song_url=$url\" />
1215 </object>";
1216 }
1217
1218 if ($entry) $entry .= "&nbsp; <a target=\"_blank\"
1219 href=\"$url\">" . basename($url) . "</a>";
1220
1221 return $entry;
1222
1223 }
1224
1225 return "";
1226
1227 /* $filename = substr($url, strrpos($url, "/")+1);
1228
1229 $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1230 $filename . " (" . $ctype . ")" . "</a>"; */
1231
1232 }
1233
1234 function format_article($id, $mark_as_read = true, $zoom_mode = false, $owner_uid = false) {
1235 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1236
1237 $rv = array();
1238
1239 $rv['id'] = $id;
1240
1241 /* we can figure out feed_id from article id anyway, why do we
1242 * pass feed_id here? let's ignore the argument :(*/
1243
1244 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1245 WHERE ref_id = '$id'");
1246
1247 $feed_id = (int) db_fetch_result($result, 0, "feed_id");
1248
1249 $rv['feed_id'] = $feed_id;
1250
1251 //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; };
1252
1253 if ($mark_as_read) {
1254 $result = db_query("UPDATE ttrss_user_entries
1255 SET unread = false,last_read = NOW()
1256 WHERE ref_id = '$id' AND owner_uid = $owner_uid");
1257
1258 ccache_update($feed_id, $owner_uid);
1259 }
1260
1261 $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang,
1262 ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
1263 (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
1264 (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
1265 (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
1266 (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
1267 num_comments,
1268 tag_cache,
1269 author,
1270 orig_feed_id,
1271 note
1272 FROM ttrss_entries,ttrss_user_entries
1273 WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid");
1274
1275 if ($result) {
1276
1277 $line = db_fetch_assoc($result);
1278
1279 $line["tags"] = get_article_tags($id, $owner_uid, $line["tag_cache"]);
1280 unset($line["tag_cache"]);
1281
1282 $line["content"] = sanitize($line["content"],
1283 sql_bool_to_bool($line['hide_images']),
1284 $owner_uid, $line["site_url"], false, $line["id"]);
1285
1286 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
1287 $line = $p->hook_render_article($line);
1288 }
1289
1290 $num_comments = $line["num_comments"];
1291 $entry_comments = "";
1292
1293 if ($num_comments > 0) {
1294 if ($line["comments"]) {
1295 $comments_url = htmlspecialchars($line["comments"]);
1296 } else {
1297 $comments_url = htmlspecialchars($line["link"]);
1298 }
1299 $entry_comments = "<a class=\"postComments\"
1300 target='_blank' href=\"$comments_url\">$num_comments ".
1301 _ngettext("comment", "comments", $num_comments)."</a>";
1302
1303 } else {
1304 if ($line["comments"] && $line["link"] != $line["comments"]) {
1305 $entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
1306 }
1307 }
1308
1309 if ($zoom_mode) {
1310 header("Content-Type: text/html");
1311 $rv['content'] .= "<html><head>
1312 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
1313 <title>Tiny Tiny RSS - ".$line["title"]."</title>".
1314 stylesheet_tag("css/tt-rss.css").
1315 stylesheet_tag("css/zoom.css").
1316 stylesheet_tag("css/dijit.css")."
1317
1318 <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
1319 <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
1320
1321 </head><body id=\"ttrssZoom\">";
1322 }
1323
1324 $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
1325
1326 $rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
1327
1328 $entry_author = $line["author"];
1329
1330 if ($entry_author) {
1331 $entry_author = __(" - ") . $entry_author;
1332 }
1333
1334 $parsed_updated = make_local_datetime($line["updated"], true,
1335 $owner_uid, true);
1336
1337 if (!$zoom_mode)
1338 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1339
1340 if ($line["link"]) {
1341 $rv['content'] .= "<div class='postTitle'><a target='_blank'
1342 title=\"".htmlspecialchars($line['title'])."\"
1343 href=\"" .
1344 htmlspecialchars($line["link"]) . "\">" .
1345 $line["title"] . "</a>" .
1346 "<span class='author'>$entry_author</span></div>";
1347 } else {
1348 $rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
1349 }
1350
1351 if ($zoom_mode) {
1352 $feed_title = "<a href=\"".htmlspecialchars($line["site_url"]).
1353 "\" target=\"_blank\">".
1354 htmlspecialchars($line["feed_title"])."</a>";
1355
1356 $rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
1357
1358 $rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
1359 }
1360
1361 $tags_str = format_tags_string($line["tags"], $id);
1362 $tags_str_full = join(", ", $line["tags"]);
1363
1364 if (!$tags_str_full) $tags_str_full = __("no tags");
1365
1366 if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
1367
1368 $rv['content'] .= "<div class='postTags' style='float : right'>
1369 <img src='images/tag.png'
1370 class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
1371
1372 if (!$zoom_mode) {
1373 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
1374 <a title=\"".__('Edit tags for this article')."\"
1375 href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
1376
1377 $rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
1378 id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
1379 position=\"below\">$tags_str_full</div>";
1380
1381 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
1382 $rv['content'] .= $p->hook_article_button($line);
1383 }
1384
1385 } else {
1386 $tags_str = strip_tags($tags_str);
1387 $rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
1388 }
1389 $rv['content'] .= "</div>";
1390 $rv['content'] .= "<div clear='both'>";
1391
1392 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
1393 $rv['content'] .= $p->hook_article_left_button($line);
1394 }
1395
1396 $rv['content'] .= "$entry_comments</div>";
1397
1398 if ($line["orig_feed_id"]) {
1399
1400 $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds
1401 WHERE id = ".$line["orig_feed_id"]);
1402
1403 if (db_num_rows($tmp_result) != 0) {
1404
1405 $rv['content'] .= "<div clear='both'>";
1406 $rv['content'] .= __("Originally from:");
1407
1408 $rv['content'] .= "&nbsp;";
1409
1410 $tmp_line = db_fetch_assoc($tmp_result);
1411
1412 $rv['content'] .= "<a target='_blank'
1413 href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" .
1414 $tmp_line['title'] . "</a>";
1415
1416 $rv['content'] .= "&nbsp;";
1417
1418 $rv['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
1419 $rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
1420
1421 $rv['content'] .= "</div>";
1422 }
1423 }
1424
1425 $rv['content'] .= "</div>";
1426
1427 $rv['content'] .= "<div id=\"POSTNOTE-$id\">";
1428 if ($line['note']) {
1429 $rv['content'] .= format_article_note($id, $line['note'], !$zoom_mode);
1430 }
1431 $rv['content'] .= "</div>";
1432
1433 if (!$line['lang']) $line['lang'] = 'en';
1434
1435 $rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
1436
1437 $rv['content'] .= $line["content"];
1438
1439 if (!$zoom_mode) {
1440 $rv['content'] .= format_article_enclosures($id,
1441 sql_bool_to_bool($line["always_display_enclosures"]),
1442 $line["content"],
1443 sql_bool_to_bool($line["hide_images"]));
1444 }
1445
1446 $rv['content'] .= "</div>";
1447
1448 $rv['content'] .= "</div>";
1449
1450 }
1451
1452 if ($zoom_mode) {
1453 $rv['content'] .= "
1454 <div class='footer'>
1455 <button onclick=\"return window.close()\">".
1456 __("Close this window")."</button></div>";
1457 $rv['content'] .= "</body></html>";
1458 }
1459
1460 return $rv;
1461
1462 }
1463
1464 function print_checkpoint($n, $s) {
1465 $ts = microtime(true);
1466 echo sprintf("<!-- CP[$n] %.4f seconds -->\n", $ts - $s);
1467 return $ts;
1468 }
1469
1470 function sanitize_tag($tag) {
1471 $tag = trim($tag);
1472
1473 $tag = mb_strtolower($tag, 'utf-8');
1474
1475 $tag = preg_replace('/[,\'\"\+\>\<]/', "", $tag);
1476
1477 if (DB_TYPE == "mysql") {
1478 $tag = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $tag);
1479 }
1480
1481 return $tag;
1482 }
1483
1484 function get_self_url_prefix() {
1485 if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
1486 return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
1487 } else {
1488 return SELF_URL_PATH;
1489 }
1490 }
1491
1492 /**
1493 * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
1494 *
1495 * @return string The Mozilla Firefox feed adding URL.
1496 */
1497 function add_feed_url() {
1498 //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
1499
1500 $url_path = get_self_url_prefix() .
1501 "/public.php?op=subscribe&feed_url=%s";
1502 return $url_path;
1503 } // function add_feed_url
1504
1505 function encrypt_password($pass, $salt = '', $mode2 = false) {
1506 if ($salt && $mode2) {
1507 return "MODE2:" . hash('sha256', $salt . $pass);
1508 } else if ($salt) {
1509 return "SHA1X:" . sha1("$salt:$pass");
1510 } else {
1511 return "SHA1:" . sha1($pass);
1512 }
1513 } // function encrypt_password
1514
1515 function load_filters($feed_id, $owner_uid, $action_id = false) {
1516 $filters = array();
1517
1518 $cat_id = (int)getFeedCategory($feed_id);
1519
1520 if ($cat_id == 0)
1521 $null_cat_qpart = "cat_id IS NULL OR";
1522 else
1523 $null_cat_qpart = "";
1524
1525 $result = db_query("SELECT * FROM ttrss_filters2 WHERE
1526 owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
1527
1528 $check_cats = join(",", array_merge(
1529 getParentCategories($cat_id, $owner_uid),
1530 array($cat_id)));
1531
1532 while ($line = db_fetch_assoc($result)) {
1533 $filter_id = $line["id"];
1534
1535 $result2 = db_query("SELECT
1536 r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
1537 FROM ttrss_filters2_rules AS r,
1538 ttrss_filter_types AS t
1539 WHERE
1540 ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
1541 (feed_id IS NULL OR feed_id = '$feed_id') AND
1542 filter_type = t.id AND filter_id = '$filter_id'");
1543
1544 $rules = array();
1545 $actions = array();
1546
1547 while ($rule_line = db_fetch_assoc($result2)) {
1548 # print_r($rule_line);
1549
1550 $rule = array();
1551 $rule["reg_exp"] = $rule_line["reg_exp"];
1552 $rule["type"] = $rule_line["type_name"];
1553 $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
1554
1555 array_push($rules, $rule);
1556 }
1557
1558 $result2 = db_query("SELECT a.action_param,t.name AS type_name
1559 FROM ttrss_filters2_actions AS a,
1560 ttrss_filter_actions AS t
1561 WHERE
1562 action_id = t.id AND filter_id = '$filter_id'");
1563
1564 while ($action_line = db_fetch_assoc($result2)) {
1565 # print_r($action_line);
1566
1567 $action = array();
1568 $action["type"] = $action_line["type_name"];
1569 $action["param"] = $action_line["action_param"];
1570
1571 array_push($actions, $action);
1572 }
1573
1574
1575 $filter = array();
1576 $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
1577 $filter["inverse"] = sql_bool_to_bool($line["inverse"]);
1578 $filter["rules"] = $rules;
1579 $filter["actions"] = $actions;
1580
1581 if (count($rules) > 0 && count($actions) > 0) {
1582 array_push($filters, $filter);
1583 }
1584 }
1585
1586 return $filters;
1587 }
1588
1589 function get_score_pic($score) {
1590 if ($score > 100) {
1591 return "score_high.png";
1592 } else if ($score > 0) {
1593 return "score_half_high.png";
1594 } else if ($score < -100) {
1595 return "score_low.png";
1596 } else if ($score < 0) {
1597 return "score_half_low.png";
1598 } else {
1599 return "score_neutral.png";
1600 }
1601 }
1602
1603 function feed_has_icon($id) {
1604 return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0;
1605 }
1606
1607 function init_plugins() {
1608 PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL);
1609
1610 return true;
1611 }
1612
1613 function format_tags_string($tags, $id) {
1614 if (!is_array($tags) || count($tags) == 0) {
1615 return __("no tags");
1616 } else {
1617 $maxtags = min(5, count($tags));
1618 $tags_str = "";
1619
1620 for ($i = 0; $i < $maxtags; $i++) {
1621 $tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
1622 }
1623
1624 $tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
1625
1626 if (count($tags) > $maxtags)
1627 $tags_str .= ", &hellip;";
1628
1629 return $tags_str;
1630 }
1631 }
1632
1633 function format_article_labels($labels, $id) {
1634
1635 if (!is_array($labels)) return '';
1636
1637 $labels_str = "";
1638
1639 foreach ($labels as $l) {
1640 $labels_str .= sprintf("<span class='hlLabelRef'
1641 style='color : %s; background-color : %s'>%s</span>",
1642 $l[2], $l[3], $l[1]);
1643 }
1644
1645 return $labels_str;
1646
1647 }
1648
1649 function format_article_note($id, $note, $allow_edit = true) {
1650
1651 $str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
1652 <div class='noteEdit' onclick=\"editArticleNote($id)\">".
1653 ($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
1654
1655 return $str;
1656 }
1657
1658
1659 function get_feed_category($feed_cat, $parent_cat_id = false) {
1660 if ($parent_cat_id) {
1661 $parent_qpart = "parent_cat = '$parent_cat_id'";
1662 $parent_insert = "'$parent_cat_id'";
1663 } else {
1664 $parent_qpart = "parent_cat IS NULL";
1665 $parent_insert = "NULL";
1666 }
1667
1668 $result = db_query(
1669 "SELECT id FROM ttrss_feed_categories
1670 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1671
1672 if (db_num_rows($result) == 0) {
1673 return false;
1674 } else {
1675 return db_fetch_result($result, 0, "id");
1676 }
1677 }
1678
1679 function add_feed_category($feed_cat, $parent_cat_id = false) {
1680
1681 if (!$feed_cat) return false;
1682
1683 db_query("BEGIN");
1684
1685 if ($parent_cat_id) {
1686 $parent_qpart = "parent_cat = '$parent_cat_id'";
1687 $parent_insert = "'$parent_cat_id'";
1688 } else {
1689 $parent_qpart = "parent_cat IS NULL";
1690 $parent_insert = "NULL";
1691 }
1692
1693 $feed_cat = mb_substr($feed_cat, 0, 250);
1694
1695 $result = db_query(
1696 "SELECT id FROM ttrss_feed_categories
1697 WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]);
1698
1699 if (db_num_rows($result) == 0) {
1700
1701 $result = db_query(
1702 "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat)
1703 VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)");
1704
1705 db_query("COMMIT");
1706
1707 return true;
1708 }
1709
1710 return false;
1711 }
1712
1713 function getArticleFeed($id) {
1714 $result = db_query("SELECT feed_id FROM ttrss_user_entries
1715 WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
1716
1717 if (db_num_rows($result) != 0) {
1718 return db_fetch_result($result, 0, "feed_id");
1719 } else {
1720 return 0;
1721 }
1722 }
1723
1724 /**
1725 * Fixes incomplete URLs by prepending "http://".
1726 * Also replaces feed:// with http://, and
1727 * prepends a trailing slash if the url is a domain name only.
1728 *
1729 * @param string $url Possibly incomplete URL
1730 *
1731 * @return string Fixed URL.
1732 */
1733 function fix_url($url) {
1734
1735 // support schema-less urls
1736 if (strpos($url, '//') === 0) {
1737 $url = 'https:' . $url;
1738 }
1739
1740 if (strpos($url, '://') === false) {
1741 $url = 'http://' . $url;
1742 } else if (substr($url, 0, 5) == 'feed:') {
1743 $url = 'http:' . substr($url, 5);
1744 }
1745
1746 //prepend slash if the URL has no slash in it
1747 // "http://www.example" -> "http://www.example/"
1748 if (strpos($url, '/', strpos($url, ':') + 3) === false) {
1749 $url .= '/';
1750 }
1751
1752 if ($url != "http:///")
1753 return $url;
1754 else
1755 return '';
1756 }
1757
1758 function validate_feed_url($url) {
1759 $parts = parse_url($url);
1760
1761 return ($parts['scheme'] == 'http' || $parts['scheme'] == 'feed' || $parts['scheme'] == 'https');
1762
1763 }
1764
1765 function get_article_enclosures($id) {
1766
1767 $query = "SELECT * FROM ttrss_enclosures
1768 WHERE post_id = '$id' AND content_url != ''";
1769
1770 $rv = array();
1771
1772 $result = db_query($query);
1773
1774 if (db_num_rows($result) > 0) {
1775 while ($line = db_fetch_assoc($result)) {
1776 array_push($rv, $line);
1777 }
1778 }
1779
1780 return $rv;
1781 }
1782
1783 /* function save_email_address($email) {
1784 // FIXME: implement persistent storage of emails
1785
1786 if (!$_SESSION['stored_emails'])
1787 $_SESSION['stored_emails'] = array();
1788
1789 if (!in_array($email, $_SESSION['stored_emails']))
1790 array_push($_SESSION['stored_emails'], $email);
1791 } */
1792
1793
1794 function get_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
1795
1796 if (!$owner_uid) $owner_uid = $_SESSION["uid"];
1797
1798 $sql_is_cat = bool_to_sql_bool($is_cat);
1799
1800 $result = db_query("SELECT access_key FROM ttrss_access_keys
1801 WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat
1802 AND owner_uid = " . $owner_uid);
1803
1804 if (db_num_rows($result) == 1) {
1805 return db_fetch_result($result, 0, "access_key");
1806 } else {
1807 $key = db_escape_string(uniqid_short());
1808
1809 $result = db_query("INSERT INTO ttrss_access_keys
1810 (access_key, feed_id, is_cat, owner_uid)
1811 VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')");
1812
1813 return $key;
1814 }
1815 return false;
1816 }
1817
1818 function get_feeds_from_html($url, $content)
1819 {
1820 $url = fix_url($url);
1821 $baseUrl = substr($url, 0, strrpos($url, '/') + 1);
1822
1823 libxml_use_internal_errors(true);
1824
1825 $doc = new DOMDocument();
1826 $doc->loadHTML($content);
1827 $xpath = new DOMXPath($doc);
1828 $entries = $xpath->query('/html/head/link[@rel="alternate" and '.
1829 '(contains(@type,"rss") or contains(@type,"atom"))]|/html/head/link[@rel="feed"]');
1830 $feedUrls = array();
1831 foreach ($entries as $entry) {
1832 if ($entry->hasAttribute('href')) {
1833 $title = $entry->getAttribute('title');
1834 if ($title == '') {
1835 $title = $entry->getAttribute('type');
1836 }
1837 $feedUrl = rewrite_relative_url(
1838 $baseUrl, $entry->getAttribute('href')
1839 );
1840 $feedUrls[$feedUrl] = $title;
1841 }
1842 }
1843 return $feedUrls;
1844 }
1845
1846 function is_html($content) {
1847 return preg_match("/<html|DOCTYPE html/i", substr($content, 0, 100)) !== 0;
1848 }
1849
1850 function url_is_html($url, $login = false, $pass = false) {
1851 return is_html(fetch_file_contents($url, false, $login, $pass));
1852 }
1853
1854 function print_label_select($name, $value, $attributes = "") {
1855
1856 $result = db_query("SELECT caption FROM ttrss_labels2
1857 WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption");
1858
1859 print "<select default=\"$value\" name=\"" . htmlspecialchars($name) .
1860 "\" $attributes onchange=\"labelSelectOnChange(this)\" >";
1861
1862 while ($line = db_fetch_assoc($result)) {
1863
1864 $issel = ($line["caption"] == $value) ? "selected=\"1\"" : "";
1865
1866 print "<option value=\"".htmlspecialchars($line["caption"])."\"
1867 $issel>" . htmlspecialchars($line["caption"]) . "</option>";
1868
1869 }
1870
1871 # print "<option value=\"ADD_LABEL\">" .__("Add label...") . "</option>";
1872
1873 print "</select>";
1874
1875
1876 }
1877
1878 function format_article_enclosures($id, $always_display_enclosures,
1879 $article_content, $hide_images = false) {
1880
1881 $result = get_article_enclosures($id);
1882 $rv = '';
1883
1884 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
1885 $retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
1886 if (is_array($retval)) {
1887 $rv = $retval[0];
1888 $result = $retval[1];
1889 } else {
1890 $rv = $retval;
1891 }
1892 }
1893
1894 if ($rv === '' && !empty($result)) {
1895 $entries_html = array();
1896 $entries = array();
1897 $entries_inline = array();
1898
1899 foreach ($result as $line) {
1900
1901 $url = $line["content_url"];
1902 $ctype = $line["content_type"];
1903 $title = $line["title"];
1904 $width = $line["width"];
1905 $height = $line["height"];
1906
1907 if (!$ctype) $ctype = __("unknown type");
1908
1909 $filename = substr($url, strrpos($url, "/")+1);
1910
1911 $player = format_inline_player($url, $ctype);
1912
1913 if ($player) array_push($entries_inline, $player);
1914
1915 # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\">" .
1916 # $filename . " (" . $ctype . ")" . "</a>";
1917
1918 $entry = "<div onclick=\"window.open('".htmlspecialchars($url)."')\"
1919 dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
1920
1921 array_push($entries_html, $entry);
1922
1923 $entry = array();
1924
1925 $entry["type"] = $ctype;
1926 $entry["filename"] = $filename;
1927 $entry["url"] = $url;
1928 $entry["title"] = $title;
1929 $entry["width"] = $width;
1930 $entry["height"] = $height;
1931
1932 array_push($entries, $entry);
1933 }
1934
1935 if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
1936 if ($always_display_enclosures ||
1937 !preg_match("/<img/i", $article_content)) {
1938
1939 foreach ($entries as $entry) {
1940
1941 foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
1942 $retval = $plugin->hook_render_enclosure($entry, $hide_images);
1943
1944
1945 if ($retval) {
1946 $rv .= $retval;
1947 } else {
1948
1949 if (preg_match("/image/", $entry["type"]) ||
1950 preg_match("/\.(jpg|png|gif|bmp)/i", $entry["filename"])) {
1951
1952 if (!$hide_images) {
1953 $encsize = '';
1954 if ($entry['height'] > 0)
1955 $encsize .= ' height="' . intval($entry['width']) . '"';
1956 if ($entry['width'] > 0)
1957 $encsize .= ' width="' . intval($entry['height']) . '"';
1958 $rv .= "<p><img
1959 alt=\"".htmlspecialchars($entry["filename"])."\"
1960 src=\"" .htmlspecialchars($entry["url"]) . "\"
1961 " . $encsize . " /></p>";
1962 } else {
1963 $rv .= "<p><a target=\"_blank\"
1964 href=\"".htmlspecialchars($entry["url"])."\"
1965 >" .htmlspecialchars($entry["url"]) . "</a></p>";
1966 }
1967
1968 if ($entry['title']) {
1969 $rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
1970 }
1971 }
1972 }
1973 }
1974 }
1975 }
1976
1977 if (count($entries_inline) > 0) {
1978 $rv .= "<hr clear='both'/>";
1979 foreach ($entries_inline as $entry) { $rv .= $entry; };
1980 $rv .= "<hr clear='both'/>";
1981 }
1982
1983 $rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
1984 "<span>" . __('Attachments')."</span>";
1985
1986 $rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
1987
1988 foreach ($entries as $entry) {
1989 if ($entry["title"])
1990 $title = "&mdash; " . truncate_string($entry["title"], 30);
1991 else
1992 $title = "";
1993
1994 $rv .= "<div onclick='window.open(\"".htmlspecialchars($entry["url"])."\")'
1995 dojoType=\"dijit.MenuItem\">".htmlspecialchars($entry["filename"])."$title</div>";
1996
1997 };
1998
1999 $rv .= "</div>";
2000 $rv .= "</div>";
2001 }
2002
2003 return $rv;
2004 }
2005
2006 function getLastArticleId() {
2007 $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries
2008 WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1");
2009
2010 if (db_num_rows($result) == 1) {
2011 return db_fetch_result($result, 0, "id");
2012 } else {
2013 return -1;
2014 }
2015 }
2016
2017 function build_url($parts) {
2018 return $parts['scheme'] . "://" . $parts['host'] . $parts['path'];
2019 }
2020
2021 /**
2022 * Converts a (possibly) relative URL to a absolute one.
2023 *
2024 * @param string $url Base URL (i.e. from where the document is)
2025 * @param string $rel_url Possibly relative URL in the document
2026 *
2027 * @return string Absolute URL
2028 */
2029 function rewrite_relative_url($url, $rel_url) {
2030 if (strpos($rel_url, ":") !== false) {
2031 return $rel_url;
2032 } else if (strpos($rel_url, "://") !== false) {
2033 return $rel_url;
2034 } else if (strpos($rel_url, "//") === 0) {
2035 # protocol-relative URL (rare but they exist)
2036 return $rel_url;
2037 } else if (strpos($rel_url, "/") === 0)
2038 {
2039 $parts = parse_url($url);
2040 $parts['path'] = $rel_url;
2041
2042 return build_url($parts);
2043
2044 } else {
2045 $parts = parse_url($url);
2046 if (!isset($parts['path'])) {
2047 $parts['path'] = '/';
2048 }
2049 $dir = $parts['path'];
2050 if (substr($dir, -1) !== '/') {
2051 $dir = dirname($parts['path']);
2052 $dir !== '/' && $dir .= '/';
2053 }
2054 $parts['path'] = $dir . $rel_url;
2055
2056 return build_url($parts);
2057 }
2058 }
2059
2060 function cleanup_tags($days = 14, $limit = 1000) {
2061
2062 if (DB_TYPE == "pgsql") {
2063 $interval_query = "date_updated < NOW() - INTERVAL '$days days'";
2064 } else if (DB_TYPE == "mysql") {
2065 $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)";
2066 }
2067
2068 $tags_deleted = 0;
2069
2070 while ($limit > 0) {
2071 $limit_part = 500;
2072
2073 $query = "SELECT ttrss_tags.id AS id
2074 FROM ttrss_tags, ttrss_user_entries, ttrss_entries
2075 WHERE post_int_id = int_id AND $interval_query AND
2076 ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part";
2077
2078 $result = db_query($query);
2079
2080 $ids = array();
2081
2082 while ($line = db_fetch_assoc($result)) {
2083 array_push($ids, $line['id']);
2084 }
2085
2086 if (count($ids) > 0) {
2087 $ids = join(",", $ids);
2088
2089 $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)");
2090 $tags_deleted += db_affected_rows($tmp_result);
2091 } else {
2092 break;
2093 }
2094
2095 $limit -= $limit_part;
2096 }
2097
2098 return $tags_deleted;
2099 }
2100
2101 function print_user_stylesheet() {
2102 $value = get_pref('USER_STYLESHEET');
2103
2104 if ($value) {
2105 print "<style type=\"text/css\">";
2106 print str_replace("<br/>", "\n", $value);
2107 print "</style>";
2108 }
2109
2110 }
2111
2112 function filter_to_sql($filter, $owner_uid) {
2113 $query = array();
2114
2115 if (DB_TYPE == "pgsql")
2116 $reg_qpart = "~";
2117 else
2118 $reg_qpart = "REGEXP";
2119
2120 foreach ($filter["rules"] AS $rule) {
2121 $rule['reg_exp'] = str_replace('/', '\/', $rule["reg_exp"]);
2122 $regexp_valid = preg_match('/' . $rule['reg_exp'] . '/',
2123 $rule['reg_exp']) !== FALSE;
2124
2125 if ($regexp_valid) {
2126
2127 $rule['reg_exp'] = db_escape_string($rule['reg_exp']);
2128
2129 switch ($rule["type"]) {
2130 case "title":
2131 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2132 $rule['reg_exp'] . "')";
2133 break;
2134 case "content":
2135 $qpart = "LOWER(ttrss_entries.content) $reg_qpart LOWER('".
2136 $rule['reg_exp'] . "')";
2137 break;
2138 case "both":
2139 $qpart = "LOWER(ttrss_entries.title) $reg_qpart LOWER('".
2140 $rule['reg_exp'] . "') OR LOWER(" .
2141 "ttrss_entries.content) $reg_qpart LOWER('" . $rule['reg_exp'] . "')";
2142 break;
2143 case "tag":
2144 $qpart = "LOWER(ttrss_user_entries.tag_cache) $reg_qpart LOWER('".
2145 $rule['reg_exp'] . "')";
2146 break;
2147 case "link":
2148 $qpart = "LOWER(ttrss_entries.link) $reg_qpart LOWER('".
2149 $rule['reg_exp'] . "')";
2150 break;
2151 case "author":
2152 $qpart = "LOWER(ttrss_entries.author) $reg_qpart LOWER('".
2153 $rule['reg_exp'] . "')";
2154 break;
2155 }
2156
2157 if (isset($rule['inverse'])) $qpart = "NOT ($qpart)";
2158
2159 if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) {
2160 $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]);
2161 }
2162
2163 if (isset($rule["cat_id"])) {
2164
2165 if ($rule["cat_id"] > 0) {
2166 $children = getChildCategories($rule["cat_id"], $owner_uid);
2167 array_push($children, $rule["cat_id"]);
2168
2169 $children = join(",", $children);
2170
2171 $cat_qpart = "cat_id IN ($children)";
2172 } else {
2173 $cat_qpart = "cat_id IS NULL";
2174 }
2175
2176 $qpart .= " AND $cat_qpart";
2177 }
2178
2179 $qpart .= " AND feed_id IS NOT NULL";
2180
2181 array_push($query, "($qpart)");
2182
2183 }
2184 }
2185
2186 if (count($query) > 0) {
2187 $fullquery = "(" . join($filter["match_any_rule"] ? "OR" : "AND", $query) . ")";
2188 } else {
2189 $fullquery = "(false)";
2190 }
2191
2192 if ($filter['inverse']) $fullquery = "(NOT $fullquery)";
2193
2194 return $fullquery;
2195 }
2196
2197 if (!function_exists('gzdecode')) {
2198 function gzdecode($string) { // no support for 2nd argument
2199 return file_get_contents('compress.zlib://data:who/cares;base64,'.
2200 base64_encode($string));
2201 }
2202 }
2203
2204 function get_random_bytes($length) {
2205 if (function_exists('openssl_random_pseudo_bytes')) {
2206 return openssl_random_pseudo_bytes($length);
2207 } else {
2208 $output = "";
2209
2210 for ($i = 0; $i < $length; $i++)
2211 $output .= chr(mt_rand(0, 255));
2212
2213 return $output;
2214 }
2215 }
2216
2217 function read_stdin() {
2218 $fp = fopen("php://stdin", "r");
2219
2220 if ($fp) {
2221 $line = trim(fgets($fp));
2222 fclose($fp);
2223 return $line;
2224 }
2225
2226 return null;
2227 }
2228
2229 function tmpdirname($path, $prefix) {
2230 // Use PHP's tmpfile function to create a temporary
2231 // directory name. Delete the file and keep the name.
2232 $tempname = tempnam($path,$prefix);
2233 if (!$tempname)
2234 return false;
2235
2236 if (!unlink($tempname))
2237 return false;
2238
2239 return $tempname;
2240 }
2241
2242 function getFeedCategory($feed) {
2243 $result = db_query("SELECT cat_id FROM ttrss_feeds
2244 WHERE id = '$feed'");
2245
2246 if (db_num_rows($result) > 0) {
2247 return db_fetch_result($result, 0, "cat_id");
2248 } else {
2249 return false;
2250 }
2251
2252 }
2253
2254 function implements_interface($class, $interface) {
2255 return in_array($interface, class_implements($class));
2256 }
2257
2258 function geturl($url, $depth = 0, $nobody = true){
2259
2260 if ($depth == 20) return $url;
2261
2262 if (!function_exists('curl_init'))
2263 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);
2264
2265 $curl = curl_init();
2266 $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
2267 $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
2268 $header[] = "Cache-Control: max-age=0";
2269 $header[] = "Connection: keep-alive";
2270 $header[] = "Keep-Alive: 300";
2271 $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
2272 $header[] = "Accept-Language: en-us,en;q=0.5";
2273 $header[] = "Pragma: ";
2274
2275 curl_setopt($curl, CURLOPT_URL, $url);
2276 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0');
2277 curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
2278 curl_setopt($curl, CURLOPT_HEADER, true);
2279 curl_setopt($curl, CURLOPT_NOBODY, $nobody);
2280 curl_setopt($curl, CURLOPT_REFERER, $url);
2281 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
2282 curl_setopt($curl, CURLOPT_AUTOREFERER, true);
2283 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
2284 //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); //CURLOPT_FOLLOWLOCATION Disabled...
2285 curl_setopt($curl, CURLOPT_TIMEOUT, 60);
2286 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
2287
2288 if (defined('_CURL_HTTP_PROXY')) {
2289 curl_setopt($curl, CURLOPT_PROXY, _CURL_HTTP_PROXY);
2290 }
2291
2292 $html = curl_exec($curl);
2293
2294 $status = curl_getinfo($curl);
2295
2296 if($status['http_code']!=200){
2297
2298 // idiot site not allowing http head
2299 if($status['http_code'] == 405) {
2300 curl_close($curl);
2301 return geturl($url, $depth +1, false);
2302 }
2303
2304 if($status['http_code'] == 301 || $status['http_code'] == 302) {
2305 curl_close($curl);
2306 list($header) = explode("\r\n\r\n", $html, 2);
2307 $matches = array();
2308 preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
2309 $url = trim(str_replace($matches[1],"",$matches[0]));
2310 $url_parsed = parse_url($url);
2311 return (isset($url_parsed))? geturl($url, $depth + 1):'';
2312 }
2313
2314 global $fetch_last_error;
2315
2316 $fetch_last_error = curl_errno($curl) . " " . curl_error($curl);
2317 curl_close($curl);
2318
2319 # $oline='';
2320 # foreach($status as $key=>$eline){$oline.='['.$key.']'.$eline.' ';}
2321 # $line =$oline." \r\n ".$url."\r\n-----------------\r\n";
2322 # $handle = @fopen('./curl.error.log', 'a');
2323 # fwrite($handle, $line);
2324 return FALSE;
2325 }
2326 curl_close($curl);
2327 return $url;
2328 }
2329
2330 function get_minified_js($files) {
2331 require_once 'lib/jshrink/Minifier.php';
2332
2333 $rv = '';
2334
2335 foreach ($files as $js) {
2336 if (!isset($_GET['debug'])) {
2337 $cached_file = CACHE_DIR . "/js/".basename($js).".js";
2338
2339 if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) {
2340
2341 list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
2342
2343 if ($header && $contents) {
2344 list($htag, $hversion) = explode(":", $header);
2345
2346 if ($htag == "tt-rss" && $hversion == VERSION) {
2347 $rv .= $contents;
2348 continue;
2349 }
2350 }
2351 }
2352
2353 $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js"));
2354 file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
2355 $rv .= $minified;
2356
2357 } else {
2358 $rv .= file_get_contents("js/$js.js"); // no cache in debug mode
2359 }
2360 }
2361
2362 return $rv;
2363 }
2364
2365 function stylesheet_tag($filename) {
2366 $timestamp = filemtime($filename);
2367
2368 return "<link rel=\"stylesheet\" type=\"text/css\" href=\"$filename?$timestamp\"/>\n";
2369 }
2370
2371 function javascript_tag($filename) {
2372 $query = "";
2373
2374 if (!(strpos($filename, "?") === FALSE)) {
2375 $query = substr($filename, strpos($filename, "?")+1);
2376 $filename = substr($filename, 0, strpos($filename, "?"));
2377 }
2378
2379 $timestamp = filemtime($filename);
2380
2381 if ($query) $timestamp .= "&$query";
2382
2383 return "<script type=\"text/javascript\" charset=\"utf-8\" src=\"$filename?$timestamp\"></script>\n";
2384 }
2385
2386 function calculate_dep_timestamp() {
2387 $files = array_merge(glob("js/*.js"), glob("css/*.css"));
2388
2389 $max_ts = -1;
2390
2391 foreach ($files as $file) {
2392 if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
2393 }
2394
2395 return $max_ts;
2396 }
2397
2398 function T_js_decl($s1, $s2) {
2399 if ($s1 && $s2) {
2400 $s1 = preg_replace("/\n/", "", $s1);
2401 $s2 = preg_replace("/\n/", "", $s2);
2402
2403 $s1 = preg_replace("/\"/", "\\\"", $s1);
2404 $s2 = preg_replace("/\"/", "\\\"", $s2);
2405
2406 return "T_messages[\"$s1\"] = \"$s2\";\n";
2407 }
2408 }
2409
2410 function init_js_translations() {
2411
2412 print 'var T_messages = new Object();
2413
2414 function __(msg) {
2415 if (T_messages[msg]) {
2416 return T_messages[msg];
2417 } else {
2418 return msg;
2419 }
2420 }
2421
2422 function ngettext(msg1, msg2, n) {
2423 return __((parseInt(n) > 1) ? msg2 : msg1);
2424 }';
2425
2426 $l10n = _get_reader();
2427
2428 for ($i = 0; $i < $l10n->total; $i++) {
2429 $orig = $l10n->get_original_string($i);
2430 if(strpos($orig, "\000") !== FALSE) { // Plural forms
2431 $key = explode(chr(0), $orig);
2432 print T_js_decl($key[0], _ngettext($key[0], $key[1], 1)); // Singular
2433 print T_js_decl($key[1], _ngettext($key[0], $key[1], 2)); // Plural
2434 } else {
2435 $translation = __($orig);
2436 print T_js_decl($orig, $translation);
2437 }
2438 }
2439 }
2440
2441 function label_to_feed_id($label) {
2442 return LABEL_BASE_INDEX - 1 - abs($label);
2443 }
2444
2445 function feed_to_label_id($feed) {
2446 return LABEL_BASE_INDEX - 1 + abs($feed);
2447 }
2448
2449 function get_theme_path($theme) {
2450 $check = "themes/$theme";
2451 if (file_exists($check)) return $check;
2452
2453 $check = "themes.local/$theme";
2454 if (file_exists($check)) return $check;
2455 }
2456
2457 function theme_valid($theme) {
2458 if ($theme == "default.css" || $theme == "night.css") return true; // needed for array_filter
2459 $file = "themes/" . basename($theme);
2460
2461 if (!file_exists($file)) $file = "themes.local/" . basename($theme);
2462
2463 if (file_exists($file) && is_readable($file)) {
2464 $fh = fopen($file, "r");
2465
2466 if ($fh) {
2467 $header = fgets($fh);
2468 fclose($fh);
2469
2470 return strpos($header, "supports-version:" . VERSION_STATIC) !== FALSE;
2471 }
2472 }
2473
2474 return false;
2475 }
2476
2477 function error_json($code) {
2478 require_once "errors.php";
2479
2480 @$message = $ERRORS[$code];
2481
2482 return json_encode(array("error" =>
2483 array("code" => $code, "message" => $message)));
2484
2485 }
2486
2487 function abs_to_rel_path($dir) {
2488 $tmp = str_replace(dirname(__DIR__), "", $dir);
2489
2490 if (strlen($tmp) > 0 && substr($tmp, 0, 1) == "/") $tmp = substr($tmp, 1);
2491
2492 return $tmp;
2493 }
2494 ?>