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