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