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