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