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