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