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