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