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