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