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