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