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