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