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