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