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