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