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