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