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