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