Update wiki links to the new short URL
[aur.git] / web / lib / aur.inc.php
blobf4ad6b470068e5f8a1f29269c48ba92173934b41
1 <?php
2 set_include_path(get_include_path() . PATH_SEPARATOR . '../lib' . PATH_SEPARATOR . '../template');
3 header('Content-Type: text/html; charset=utf-8');
4 header('Cache-Control: no-cache, must-revalidate');
5 header('Expires: Tue, 11 Oct 1988 22:00:00 GMT'); // quite a special day
6 header('Pragma: no-cache');
7 header('X-Frame-Options: DENY');
9 date_default_timezone_set('UTC');
11 include_once('translator.inc.php');
12 set_lang();
14 include_once("DB.class.php");
15 include_once("routing.inc.php");
16 include_once("version.inc.php");
17 include_once("acctfuncs.inc.php");
18 include_once("cachefuncs.inc.php");
19 include_once("confparser.inc.php");
20 include_once("credentials.inc.php");
22 include_once('timezone.inc.php');
23 set_tz();
25 check_sid();
26 check_tos();
28 /**
29 * Check if a visitor is logged in
31 * Query "Sessions" table with supplied cookie. Determine if the cookie is valid
32 * or not. Unset the cookie if invalid or session timeout reached. Update the
33 * session timeout if it is still valid.
35 * @global array $_COOKIE User cookie values
37 * @return void
39 function check_sid() {
40 global $_COOKIE;
42 if (isset($_COOKIE["AURSID"])) {
43 $failed = 0;
44 $timeout = config_get_int('options', 'login_timeout');
45 # the visitor is logged in, try and update the session
47 $dbh = DB::connect();
48 $q = "SELECT LastUpdateTS, " . strval(time()) . " FROM Sessions ";
49 $q.= "WHERE SessionID = " . $dbh->quote($_COOKIE["AURSID"]);
50 $result = $dbh->query($q);
51 $row = $result->fetch(PDO::FETCH_NUM);
53 if (!$row) {
54 # Invalid SessionID - hacker alert!
56 $failed = 1;
57 } else {
58 $last_update = $row[0];
59 if ($last_update + $timeout <= $row[1]) {
60 $failed = 2;
64 if ($failed == 1) {
65 # clear out the hacker's cookie, and send them to a naughty page
66 # why do you have to be so harsh on these people!?
68 setcookie("AURSID", "", 1, "/", null, !empty($_SERVER['HTTPS']), true);
69 unset($_COOKIE['AURSID']);
70 } elseif ($failed == 2) {
71 # session id timeout was reached and they must login again.
73 delete_session_id($_COOKIE["AURSID"]);
75 setcookie("AURSID", "", 1, "/", null, !empty($_SERVER['HTTPS']), true);
76 unset($_COOKIE['AURSID']);
77 } else {
78 # still logged in and haven't reached the timeout, go ahead
79 # and update the idle timestamp
81 # Only update the timestamp if it is less than the
82 # current time plus $timeout.
84 # This keeps 'remembered' sessions from being
85 # overwritten.
86 if ($last_update < time() + $timeout) {
87 $q = "UPDATE Sessions SET LastUpdateTS = " . strval(time()) . " ";
88 $q.= "WHERE SessionID = " . $dbh->quote($_COOKIE["AURSID"]);
89 $dbh->exec($q);
93 return;
96 /**
97 * Redirect user to the Terms of Service agreement if there are updated terms.
99 * @return void
101 function check_tos() {
102 if (!isset($_COOKIE["AURSID"])) {
103 return;
106 $path = $_SERVER['PATH_INFO'];
107 $route = get_route($path);
108 if (!$route || $route == "tos.php") {
109 return;
112 if (count(fetch_updated_terms(uid_from_sid($_COOKIE["AURSID"]))) > 0) {
113 header('Location: ' . get_uri('/tos'));
114 exit();
119 * Verify the supplied CSRF token matches expected token
121 * @return bool True if the CSRF token is the same as the cookie SID, otherwise false
123 function check_token() {
124 if (isset($_POST['token']) && isset($_COOKIE['AURSID'])) {
125 return ($_POST['token'] == $_COOKIE['AURSID']);
126 } else {
127 return false;
132 * Verify a user supplied e-mail against RFC 3696 and DNS records
134 * @param string $addy E-mail address being validated in foo@example.com format
136 * @return bool True if e-mail passes validity checks, otherwise false
138 function valid_email($addy) {
139 // check against RFC 3696
140 if (filter_var($addy, FILTER_VALIDATE_EMAIL) === false) {
141 return false;
144 // check dns for mx, a, aaaa records
145 list($local, $domain) = explode('@', $addy);
146 if (!(checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A') || checkdnsrr($domain, 'AAAA'))) {
147 return false;
150 return true;
154 * Verify that a given URL is valid and uses the HTTP(s) protocol
156 * @param string $url URL of the home page to be validated
158 * @return bool True if URL passes validity checks, false otherwise
160 function valid_homepage($url) {
161 if (filter_var($url, FILTER_VALIDATE_URL) === false) {
162 return false;
165 $url_components = parse_url($url);
166 if (!in_array($url_components['scheme'], array('http', 'https'))) {
167 return false;
170 return true;
174 * Generate a unique session ID
176 * @return string MD5 hash of the concatenated user IP, random number, and current time
178 function new_sid() {
179 return md5($_SERVER['REMOTE_ADDR'] . uniqid(mt_rand(), true));
183 * Determine the user's username in the database using a user ID
185 * @param string $id User's ID
187 * @return string Username if it exists, otherwise null
189 function username_from_id($id) {
190 $id = intval($id);
192 $dbh = DB::connect();
193 $q = "SELECT Username FROM Users WHERE ID = " . $dbh->quote($id);
194 $result = $dbh->query($q);
195 if (!$result) {
196 return null;
199 $row = $result->fetch(PDO::FETCH_NUM);
200 if ($row) {
201 return $row[0];
206 * Determine the user's username in the database using a session ID
208 * @param string $sid User's session ID
210 * @return string Username of the visitor
212 function username_from_sid($sid="") {
213 if (!$sid) {
214 return "";
216 $dbh = DB::connect();
217 $q = "SELECT Username ";
218 $q.= "FROM Users, Sessions ";
219 $q.= "WHERE Users.ID = Sessions.UsersID ";
220 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
221 $result = $dbh->query($q);
222 if (!$result) {
223 return "";
225 $row = $result->fetch(PDO::FETCH_NUM);
227 if ($row) {
228 return $row[0];
233 * Format a user name for inclusion in HTML data
235 * @param string $username The user name to format
237 * @return string The generated HTML code for the account link
239 function html_format_username($username) {
240 $username_fmt = $username ? htmlspecialchars($username, ENT_QUOTES) : __("None");
242 if ($username && isset($_COOKIE["AURSID"])) {
243 $link = '<a href="' . get_uri('/account/') . $username_fmt;
244 $link .= '" title="' . __('View account information for %s', $username_fmt);
245 $link .= '">' . $username_fmt . '</a>';
246 return $link;
247 } else {
248 return $username_fmt;
253 * Format the maintainer and co-maintainers for inclusion in HTML data
255 * @param string $maintainer The user name of the maintainer
256 * @param array $comaintainers The list of co-maintainer user names
258 * @return string The generated HTML code for the account links
260 function html_format_maintainers($maintainer, $comaintainers) {
261 $code = html_format_username($maintainer);
263 if (count($comaintainers) > 0) {
264 $code .= ' (';
265 foreach ($comaintainers as $comaintainer) {
266 $code .= html_format_username($comaintainer);
267 if ($comaintainer !== end($comaintainers)) {
268 $code .= ', ';
271 $code .= ')';
274 return $code;
278 * Format a link in the package actions box
280 * @param string $uri The link target
281 * @param string $inner The HTML code to use for the link label
283 * @return string The generated HTML code for the action link
285 function html_action_link($uri, $inner) {
286 if (isset($_COOKIE["AURSID"])) {
287 $code = '<a href="' . htmlspecialchars($uri, ENT_QUOTES) . '">';
288 } else {
289 $code = '<a href="' . get_uri('/login/', true) . '?referer=';
290 $code .= urlencode(rtrim(aur_location(), '/') . $uri) . '">';
292 $code .= $inner . '</a>';
294 return $code;
298 * Format a form in the package actions box
300 * @param string $uri The link target
301 * @param string $action The action name (passed as HTTP POST parameter)
302 * @param string $inner The HTML code to use for the link label
304 * @return string The generated HTML code for the action link
306 function html_action_form($uri, $action, $inner) {
307 if (isset($_COOKIE["AURSID"])) {
308 $code = '<form action="' . htmlspecialchars($uri, ENT_QUOTES) . '" ';
309 $code .= 'method="post">';
310 $code .= '<input type="hidden" name="token" value="';
311 $code .= htmlspecialchars($_COOKIE['AURSID'], ENT_QUOTES) . '" />';
312 $code .= '<input type="submit" class="button text-button" name="';
313 $code .= htmlspecialchars($action, ENT_QUOTES) . '" ';
314 $code .= 'value="' . $inner . '" />';
315 $code .= '</form>';
316 } else {
317 $code = '<a href="' . get_uri('/login/', true) . '">';
318 $code .= $inner . '</a>';
321 return $code;
325 * Determine the user's e-mail address in the database using a session ID
327 * @param string $sid User's session ID
329 * @return string User's e-mail address as given during registration
331 function email_from_sid($sid="") {
332 if (!$sid) {
333 return "";
335 $dbh = DB::connect();
336 $q = "SELECT Email ";
337 $q.= "FROM Users, Sessions ";
338 $q.= "WHERE Users.ID = Sessions.UsersID ";
339 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
340 $result = $dbh->query($q);
341 if (!$result) {
342 return "";
344 $row = $result->fetch(PDO::FETCH_NUM);
346 if ($row) {
347 return $row[0];
352 * Determine the user's account type in the database using a session ID
354 * @param string $sid User's session ID
356 * @return string Account type of user ("User", "Trusted User", or "Developer")
358 function account_from_sid($sid="") {
359 if (!$sid) {
360 return "";
362 $dbh = DB::connect();
363 $q = "SELECT AccountType ";
364 $q.= "FROM Users, AccountTypes, Sessions ";
365 $q.= "WHERE Users.ID = Sessions.UsersID ";
366 $q.= "AND AccountTypes.ID = Users.AccountTypeID ";
367 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
368 $result = $dbh->query($q);
369 if (!$result) {
370 return "";
372 $row = $result->fetch(PDO::FETCH_NUM);
374 if ($row) {
375 return $row[0];
380 * Determine the user's ID in the database using a session ID
382 * @param string $sid User's session ID
384 * @return string|int The user's name, 0 on query failure
386 function uid_from_sid($sid="") {
387 if (!$sid) {
388 return "";
390 $dbh = DB::connect();
391 $q = "SELECT Users.ID ";
392 $q.= "FROM Users, Sessions ";
393 $q.= "WHERE Users.ID = Sessions.UsersID ";
394 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
395 $result = $dbh->query($q);
396 if (!$result) {
397 return 0;
399 $row = $result->fetch(PDO::FETCH_NUM);
401 if ($row) {
402 return $row[0];
407 * Common AUR header displayed on all pages
409 * @global string $LANG Language selected by the visitor
410 * @global array $SUPPORTED_LANGS Languages that are supported by the AUR
411 * @param string $title Name of the AUR page to be displayed on browser
413 * @return void
415 function html_header($title="", $details=array()) {
416 global $LANG;
417 global $SUPPORTED_LANGS;
419 include('header.php');
420 return;
424 * Common AUR footer displayed on all pages
426 * @param string $ver The AUR version
428 * @return void
430 function html_footer($ver="") {
431 include('footer.php');
432 return;
436 * Determine if a user has permission to submit a package
438 * @param string $name Name of the package to be submitted
439 * @param string $sid User's session ID
441 * @return int 0 if the user can't submit, 1 if the user can submit
443 function can_submit_pkgbase($name="", $sid="") {
444 if (!$name || !$sid) {return 0;}
445 $dbh = DB::connect();
446 $q = "SELECT MaintainerUID ";
447 $q.= "FROM PackageBases WHERE Name = " . $dbh->quote($name);
448 $result = $dbh->query($q);
449 $row = $result->fetch(PDO::FETCH_NUM);
451 if (!$row[0]) {
452 return 1;
454 $my_uid = uid_from_sid($sid);
456 if ($row[0] === NULL || $row[0] == $my_uid) {
457 return 1;
460 return 0;
464 * Determine if a package can be overwritten by some package base
466 * @param string $name Name of the package to be submitted
467 * @param int $base_id The ID of the package base
469 * @return bool True if the package can be overwritten, false if not
471 function can_submit_pkg($name, $base_id) {
472 $dbh = DB::connect();
473 $q = "SELECT COUNT(*) FROM Packages WHERE ";
474 $q.= "Name = " . $dbh->quote($name) . " AND ";
475 $q.= "PackageBaseID <> " . intval($base_id);
476 $result = $dbh->query($q);
478 if (!$result) return false;
479 return ($result->fetchColumn() == 0);
483 * Recursively delete a directory
485 * @param string $dirname Name of the directory to be removed
487 * @return void
489 function rm_tree($dirname) {
490 if (empty($dirname) || !is_dir($dirname)) return;
492 foreach (scandir($dirname) as $item) {
493 if ($item != '.' && $item != '..') {
494 $path = $dirname . '/' . $item;
495 if (is_file($path) || is_link($path)) {
496 unlink($path);
498 else {
499 rm_tree($path);
504 rmdir($dirname);
506 return;
510 * Determine the user's ID in the database using a username
512 * @param string $username The username of an account
514 * @return string Return user ID if exists for username, otherwise null
516 function uid_from_username($username) {
517 $dbh = DB::connect();
518 $q = "SELECT ID FROM Users WHERE Username = " . $dbh->quote($username);
519 $result = $dbh->query($q);
520 if (!$result) {
521 return null;
524 $row = $result->fetch(PDO::FETCH_NUM);
525 if ($row) {
526 return $row[0];
531 * Determine the user's ID in the database using a username or email address
533 * @param string $username The username or email address of an account
535 * @return string Return user ID if exists, otherwise null
537 function uid_from_loginname($loginname) {
538 $uid = uid_from_username($loginname);
539 if (!$uid) {
540 $uid = uid_from_email($loginname);
542 return $uid;
546 * Determine the user's ID in the database using an e-mail address
548 * @param string $email An e-mail address in foo@example.com format
550 * @return string The user's ID
552 function uid_from_email($email) {
553 $dbh = DB::connect();
554 $q = "SELECT ID FROM Users WHERE Email = " . $dbh->quote($email);
555 $result = $dbh->query($q);
556 if (!$result) {
557 return null;
560 $row = $result->fetch(PDO::FETCH_NUM);
561 if ($row) {
562 return $row[0];
567 * Generate clean url with edited/added user values
569 * Makes a clean string of variables for use in URLs based on current $_GET and
570 * list of values to edit/add to that. Any empty variables are discarded.
572 * @example print "http://example.com/test.php?" . mkurl("foo=bar&bar=baz")
574 * @param string $append string of variables and values formatted as in URLs
576 * @return string clean string of variables to append to URL, urlencoded
578 function mkurl($append) {
579 $get = $_GET;
580 $append = explode('&', $append);
581 $uservars = array();
582 $out = '';
584 foreach ($append as $i) {
585 $ex = explode('=', $i);
586 $uservars[$ex[0]] = $ex[1];
589 foreach ($uservars as $k => $v) { $get[$k] = $v; }
591 foreach ($get as $k => $v) {
592 if ($v !== '') {
593 $out .= '&amp;' . urlencode($k) . '=' . urlencode($v);
597 return substr($out, 5);
601 * Get a package comment
603 * @param int $comment_id The ID of the comment
605 * @return array The user ID and comment OR null, null in case of an error
607 function comment_by_id($comment_id) {
608 $dbh = DB::connect();
609 $q = "SELECT UsersID, Comments FROM PackageComments ";
610 $q.= "WHERE ID = " . intval($comment_id);
611 $result = $dbh->query($q);
612 if (!$result) {
613 return array(null, null);
616 return $result->fetch(PDO::FETCH_NUM);
620 * Process submitted comments so any links can be followed
622 * @param string $comment Raw user submitted package comment
624 * @return string User comment with links printed in HTML
626 function parse_comment($comment) {
627 $url_pattern = '/(\b(?:https?|ftp):\/\/[\w\/\#~:.?+=&%@!\-;,]+?' .
628 '(?=[.:?\-;,]*(?:[^\w\/\#~:.?+=&%@!\-;,]|$)))/iS';
630 $matches = preg_split($url_pattern, $comment, -1,
631 PREG_SPLIT_DELIM_CAPTURE);
633 $html = '';
634 for ($i = 0; $i < count($matches); $i++) {
635 if ($i % 2) {
636 # convert links
637 $html .= '<a href="' . htmlspecialchars($matches[$i]) .
638 '" rel="nofollow">' . htmlspecialchars($matches[$i]) . '</a>';
640 else {
641 # convert everything else
642 $html .= nl2br(htmlspecialchars($matches[$i]));
646 return $html;
650 * Wrapper for beginning a database transaction
652 function begin_atomic_commit() {
653 $dbh = DB::connect();
654 $dbh->beginTransaction();
658 * Wrapper for committing a database transaction
660 function end_atomic_commit() {
661 $dbh = DB::connect();
662 $dbh->commit();
666 * Merge pkgbase and package options
668 * Merges entries of the first and the second array. If any key appears in both
669 * arrays and the corresponding value in the second array is either a non-array
670 * type or a non-empty array, the value from the second array replaces the
671 * value from the first array. If the value from the second array is an array
672 * containing a single empty string, the value in the resulting array becomes
673 * an empty array instead. If the value in the second array is empty, the
674 * resulting array contains the value from the first array.
676 * @param array $pkgbase_info Options from the pkgbase section
677 * @param array $section_info Options from the package section
679 * @return array Merged information from both sections
681 function array_pkgbuild_merge($pkgbase_info, $section_info) {
682 $pi = $pkgbase_info;
683 foreach ($section_info as $opt_key => $opt_val) {
684 if (is_array($opt_val)) {
685 if ($opt_val == array('')) {
686 $pi[$opt_key] = array();
687 } elseif (count($opt_val) > 0) {
688 $pi[$opt_key] = $opt_val;
690 } else {
691 $pi[$opt_key] = $opt_val;
694 return $pi;
698 * Bound an integer value between two values
700 * @param int $n Integer value to bound
701 * @param int $min Lower bound
702 * @param int $max Upper bound
704 * @return int Bounded integer value
706 function bound($n, $min, $max) {
707 return min(max($n, $min), $max);
711 * Return the URL of the AUR root
713 * @return string The URL of the AUR root
715 function aur_location() {
716 $location = config_get('options', 'aur_location');
717 if (substr($location, -1) != '/') {
718 $location .= '/';
720 return $location;
724 * Calculate pagination templates
726 * @return array The array of pagination templates, per page, and offset values
728 function calculate_pagination($total_comment_count) {
729 /* Sanitize paging variables. */
730 if (isset($_GET["O"])) {
731 $_GET["O"] = max(intval($_GET["O"]), 0);
732 } else {
733 $_GET["O"] = 0;
735 $offset = $_GET["O"];
737 if (isset($_GET["PP"])) {
738 $_GET["PP"] = bound(intval($_GET["PP"]), 1, 250);
739 } else {
740 $_GET["PP"] = 10;
742 $per_page = $_GET["PP"];
744 // Page offsets start at zero, so page 2 has offset 1, which means that we
745 // need to add 1 to the offset to get the current page.
746 $current_page = ceil($offset / $per_page) + 1;
747 $num_pages = ceil($total_comment_count / $per_page);
748 $pagination_templs = array();
750 if ($current_page > 1) {
751 $previous_page = $current_page - 1;
752 $previous_offset = ($previous_page - 1) * $per_page;
753 $pagination_templs['&laquo; ' . __('First')] = 0;
754 $pagination_templs['&lsaquo; ' . __('Previous')] = $previous_offset;
757 if ($current_page - 5 > 1) {
758 $pagination_templs["..."] = false;
761 for ($i = max($current_page - 5, 1); $i <= min($num_pages, $current_page + 5); $i++) {
762 $pagination_templs[$i] = ($i - 1) * $per_page;
765 if ($current_page + 5 < $num_pages)
766 $pagination_templs["... "] = false;
768 if ($current_page < $num_pages) {
769 $pagination_templs[__('Next') . ' &rsaquo;'] = $current_page * $per_page;
770 $pagination_templs[__('Last') . ' &raquo;'] = ($num_pages - 1) * $per_page;
773 return array($pagination_templs, $per_page, $offset);