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