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