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