Add database wrapper class and new connection method
[aur.git] / web / lib / pkgfuncs.inc.php
blobb29acb3745517f8cdb0ee0c18dd485d060c0ed15
1 <?php
2 include_once("config.inc.php");
4 /**
5 * Determine if the user can delete a specific package comment
7 * Only the comment submitter, Trusted Users, and Developers can delete
8 * comments. This function is used for the backend side of comment deletion.
10 * @param string $comment_id The comment ID in the database
11 * @param string $atype The account type of the user trying to delete a comment
12 * @param string|int $uid The user ID of the individual trying to delete a comment
13 * @param \PDO $dbh An already established database connection
15 * @return bool True if the user can delete the comment, otherwise false
17 function canDeleteComment($comment_id=0, $atype="", $uid=0, $dbh=NULL) {
18 if ($atype == "Trusted User" || $atype == "Developer") {
19 # A TU/Dev can delete any comment
20 return TRUE;
22 if(!$dbh) {
23 $dbh = DB::connect();
25 $q = "SELECT COUNT(ID) AS CNT ";
26 $q.= "FROM PackageComments ";
27 $q.= "WHERE ID = " . intval($comment_id);
28 $q.= " AND UsersID = " . $uid;
29 $result = $dbh->query($q);
30 if ($result != NULL) {
31 $row = $result->fetch(PDO::FETCH_ASSOC);
32 if ($row['CNT'] > 0) {
33 return TRUE;
36 return FALSE;
39 /**
40 * Determine if the user can delete a specific package comment using an array
42 * Only the comment submitter, Trusted Users, and Developers can delete
43 * comments. This function is used for the frontend side of comment deletion.
45 * @param array $comment All database information relating a specific comment
46 * @param string $atype The account type of the user trying to delete a comment
47 * @param string|int $uid The user ID of the individual trying to delete a comment
49 * @return bool True if the user can delete the comment, otherwise false
51 function canDeleteCommentArray($comment, $atype="", $uid=0) {
52 if ($atype == "Trusted User" || $atype == "Developer") {
53 # A TU/Dev can delete any comment
54 return TRUE;
55 } else if ($comment['UsersID'] == $uid) {
56 # User's own comment
57 return TRUE;
59 return FALSE;
62 /**
63 * Determine if the visitor can submit blacklisted packages.
65 * Only Trusted Users and Developers can delete blacklisted packages. Packages
66 * are blacklisted if they are include in the official repositories.
68 * @param string $atype The account type of the user
70 * @return bool True if the user can submit blacklisted packages, otherwise false
72 function canSubmitBlacklisted($atype = "") {
73 if ($atype == "Trusted User" || $atype == "Developer") {
74 # Only TUs/Devs can submit blacklisted packages.
75 return TRUE;
77 else {
78 return FALSE;
82 /**
83 * Get all package categories stored in the database
85 * @param \PDO An already established database connection
87 * @return array All package categories
89 function pkgCategories($dbh=NULL) {
90 $cats = array();
91 if(!$dbh) {
92 $dbh = DB::connect();
94 $q = "SELECT * FROM PackageCategories WHERE ID != 1 ";
95 $q.= "ORDER BY Category ASC";
96 $result = $dbh->query($q);
97 if ($result) {
98 while ($row = $result->fetch(PDO::FETCH_NUM)) {
99 $cats[$row[0]] = $row[1];
102 return $cats;
106 * Check to see if the package name already exists in the database
108 * @param string $name The package name to check
109 * @param \PDO $dbh An already established database connection
111 * @return string|void Package name if it already exists
113 function pkgid_from_name($name="", $dbh=NULL) {
114 if (!$name) {return NULL;}
115 if(!$dbh) {
116 $dbh = DB::connect();
118 $q = "SELECT ID FROM Packages ";
119 $q.= "WHERE Name = " . $dbh->quote($name);
120 $result = $dbh->query($q);
121 if (!$result) {
122 return;
124 $row = $result->fetch(PDO::FETCH_NUM);
125 return $row[0];
129 * Get package dependencies for a specific package
131 * @param int $pkgid The package to get dependencies for
132 * @param \PDO $dbh An already established database connection
134 * @return array All package dependencies for the package
136 function package_dependencies($pkgid, $dbh=NULL) {
137 $deps = array();
138 $pkgid = intval($pkgid);
139 if ($pkgid > 0) {
140 if(!$dbh) {
141 $dbh = DB::connect();
143 $q = "SELECT pd.DepName, pd.DepCondition, p.ID FROM PackageDepends pd ";
144 $q.= "LEFT JOIN Packages p ON pd.DepName = p.Name ";
145 $q.= "WHERE pd.PackageID = ". $pkgid . " ";
146 $q.= "ORDER BY pd.DepName";
147 $result = $dbh->query($q);
148 if (!$result) {
149 return array();
151 while ($row = $result->fetch(PDO::FETCH_NUM)) {
152 $deps[] = $row;
155 return $deps;
159 * Determine packages that depend on a package
161 * @param string $name The package name for the dependency search
162 * @param \PDO $dbh An already established database connection
164 * @return array All packages that depend on the specified package name
166 function package_required($name="", $dbh=NULL) {
167 $deps = array();
168 if ($name != "") {
169 if(!$dbh) {
170 $dbh = DB::connect();
172 $q = "SELECT DISTINCT p.Name, PackageID FROM PackageDepends pd ";
173 $q.= "JOIN Packages p ON pd.PackageID = p.ID ";
174 $q.= "WHERE DepName = " . $dbh->quote($name) . " ";
175 $q.= "ORDER BY p.Name";
176 $result = $dbh->query($q);
177 if (!$result) {return array();}
178 while ($row = $result->fetch(PDO::FETCH_NUM)) {
179 $deps[] = $row;
182 return $deps;
186 * Get the number of non-deleted comments for a specific package
188 * @param string $pkgid The package ID to get comment count for
189 * @param \PDO $dbh An already established database connection
191 * @return string The number of comments left for a specific package
193 function package_comments_count($pkgid, $dbh=NULL) {
194 if (!$dbh) {
195 $dbh = DB::connect();
198 $pkgid = intval($pkgid);
199 if ($pkgid > 0) {
200 if(!$dbh) {
201 $dbh = DB::connect();
203 $q = "SELECT COUNT(*) FROM PackageComments ";
204 $q.= "WHERE PackageID = " . $pkgid;
205 $q.= " AND DelUsersID IS NULL";
207 $result = $dbh->query($q);
209 if (!$result) {
210 return;
213 $row = $result->fetch(PDO::FETCH_NUM);
214 return $row[0];
218 * Get all package comment information for a specific package
220 * @param int $pkgid The package ID to get comments for
221 * @param \PDO $dbh An already established database connection
223 * @return array All package comment information for a specific package
225 function package_comments($pkgid, $dbh=NULL) {
226 $comments = array();
227 $pkgid = intval($pkgid);
228 if ($pkgid > 0) {
229 if(!$dbh) {
230 $dbh = DB::connect();
232 $q = "SELECT PackageComments.ID, UserName, UsersID, Comments, CommentTS ";
233 $q.= "FROM PackageComments, Users ";
234 $q.= "WHERE PackageComments.UsersID = Users.ID";
235 $q.= " AND PackageID = " . $pkgid;
236 $q.= " AND DelUsersID IS NULL"; # only display non-deleted comments
237 $q.= " ORDER BY CommentTS DESC";
239 if (!isset($_GET['comments'])) {
240 $q.= " LIMIT 10";
243 $result = $dbh->query($q);
245 if (!$result) {
246 return;
249 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
250 $comments[] = $row;
253 return $comments;
257 * Add a comment to a package page and send out appropriate notifications
259 * @global string $AUR_LOCATION The AUR's URL used for notification e-mails
260 * @param string $pkgid The package ID to add the comment on
261 * @param string $uid The user ID of the individual who left the comment
262 * @param string $comment The comment left on a package page
263 * @param \PDO $dbh An already established database connection
265 * @return void
267 function add_package_comment($pkgid, $uid, $comment, $dbh=NULL) {
268 global $AUR_LOCATION;
270 if(!$dbh) {
271 $dbh = DB::connect();
274 $q = "INSERT INTO PackageComments ";
275 $q.= "(PackageID, UsersID, Comments, CommentTS) VALUES (";
276 $q.= intval($pkgid) . ", " . $uid . ", ";
277 $q.= $dbh->quote($comment) . ", UNIX_TIMESTAMP())";
278 $dbh->exec($q);
280 # TODO: Move notification logic to separate function where it belongs
281 # Send email notifications
282 $q = "SELECT CommentNotify.*, Users.Email ";
283 $q.= "FROM CommentNotify, Users ";
284 $q.= "WHERE Users.ID = CommentNotify.UserID ";
285 $q.= "AND CommentNotify.UserID != " . $uid . " ";
286 $q.= "AND CommentNotify.PkgID = " . intval($pkgid);
287 $result = $dbh->query($q);
288 $bcc = array();
290 if ($result) {
291 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
292 array_push($bcc, $row['Email']);
295 $q = "SELECT Packages.* ";
296 $q.= "FROM Packages ";
297 $q.= "WHERE Packages.ID = " . intval($pkgid);
298 $result = $dbh->query($q);
299 $row = $result->fetch(PDO::FETCH_ASSOC);
301 # TODO: native language emails for users, based on their prefs
302 # Simply making these strings translatable won't work, users would be
303 # getting emails in the language that the user who posted the comment was in
304 $body =
305 'from ' . $AUR_LOCATION . get_pkg_uri($row['Name']) . "\n"
306 . username_from_sid($_COOKIE['AURSID'], $dbh) . " wrote:\n\n"
307 . $comment
308 . "\n\n---\nIf you no longer wish to receive notifications about this package, please go the the above package page and click the UnNotify button.";
309 $body = wordwrap($body, 70);
310 $bcc = implode(', ', $bcc);
311 $headers = "Bcc: $bcc\nReply-to: nobody@archlinux.org\nFrom: aur-notify@archlinux.org\nX-Mailer: AUR\n";
312 @mail('undisclosed-recipients: ;', "AUR Comment for " . $row['Name'], $body, $headers);
317 * Get all package sources for a specific package
319 * @param string $pkgid The package ID to get the sources for
320 * @param \PDO $dbh An already established database connection
322 * @return array All sources associated with a specific package
324 function package_sources($pkgid, $dbh=NULL) {
325 $sources = array();
326 $pkgid = intval($pkgid);
327 if ($pkgid > 0) {
328 if(!$dbh) {
329 $dbh = DB::connect();
331 $q = "SELECT Source FROM PackageSources ";
332 $q.= "WHERE PackageID = " . $pkgid;
333 $q.= " ORDER BY Source";
334 $result = $dbh->query($q);
335 if (!$result) {
336 return array();
338 while ($row = $result->fetch(PDO::FETCH_NUM)) {
339 $sources[] = $row[0];
342 return $sources;
346 * Get a list of all packages a logged-in user has voted for
348 * @param string $sid The session ID of the visitor
349 * @param \PDO $dbh An already established database connection
351 * @return array All packages the visitor has voted for
353 function pkgvotes_from_sid($sid="", $dbh=NULL) {
354 $pkgs = array();
355 if (!$sid) {return $pkgs;}
356 if(!$dbh) {
357 $dbh = DB::connect();
359 $q = "SELECT PackageID ";
360 $q.= "FROM PackageVotes, Users, Sessions ";
361 $q.= "WHERE Users.ID = Sessions.UsersID ";
362 $q.= "AND Users.ID = PackageVotes.UsersID ";
363 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
364 $result = $dbh->query($q);
365 if ($result) {
366 while ($row = $result->fetch(PDO::FETCH_NUM)) {
367 $pkgs[$row[0]] = 1;
370 return $pkgs;
374 * Determine package names from package IDs
376 * @param string|array $pkgids The package IDs to get names for
377 * @param \PDO $dbh An already established database connection
379 * @return array|string All names if multiple package IDs, otherwise package name
381 function pkgname_from_id($pkgids, $dbh=NULL) {
382 if (is_array($pkgids)) {
383 $pkgids = sanitize_ids($pkgids);
384 $names = array();
385 if(!$dbh) {
386 $dbh = DB::connect();
388 $q = "SELECT Name FROM Packages WHERE ID IN (";
389 $q.= implode(",", $pkgids) . ")";
390 $result = $dbh->query($q);
391 if ($result) {
392 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
393 $names[] = $row['Name'];
396 return $names;
398 elseif ($pkgids > 0) {
399 if(!$dbh) {
400 $dbh = DB::connect();
402 $q = "SELECT Name FROM Packages WHERE ID = " . $pkgids;
403 $result = $dbh->query($q);
404 if ($result) {
405 $name = $result->fetch(PDO::FETCH_NUM);
407 return $name[0];
409 else {
410 return NULL;
415 * Determine if a package name is on the database blacklist
417 * @param string $name The package name to check
418 * @param \PDO $dbh An already established database connection
420 * @return bool True if the name is blacklisted, otherwise false
422 function pkgname_is_blacklisted($name, $dbh=NULL) {
423 if(!$dbh) {
424 $dbh = DB::connect();
426 $q = "SELECT COUNT(*) FROM PackageBlacklist ";
427 $q.= "WHERE Name = " . $dbh->quote($name);
428 $result = $dbh->query($q);
430 if (!$result) return false;
431 return ($result->fetchColumn() > 0);
435 * Get the package details
437 * @param string $id The package ID to get description for
438 * @param \PDO $dbh An already established database connection
440 * @return array The package's details OR error message
442 function get_package_details($id=0, $dbh=NULL) {
443 if(!$dbh) {
444 $dbh = DB::connect();
447 $q = "SELECT Packages.*,Category ";
448 $q.= "FROM Packages,PackageCategories ";
449 $q.= "WHERE Packages.CategoryID = PackageCategories.ID ";
450 $q.= "AND Packages.ID = " . intval($id);
451 $result = $dbh->query($q);
453 $row = array();
455 if (!$result) {
456 $row['error'] = __("Error retrieving package details.");
458 else {
459 $row = $result->fetch(PDO::FETCH_ASSOC);
460 if (empty($row)) {
461 $row['error'] = __("Package details could not be found.");
465 return $row;
469 * Display the package details page
471 * @global string $AUR_LOCATION The AUR's URL used for notification e-mails
472 * @global bool $USE_VIRTUAL_URLS True if using URL rewriting, otherwise false
473 * @param string $id The package ID to get details page for
474 * @param array $row Package details retrieved by get_package_details
475 * @param string $SID The session ID of the visitor
476 * @param \PDO $dbh An already established database connection
478 * @return void
480 function display_package_details($id=0, $row, $SID="", $dbh=NULL) {
481 global $AUR_LOCATION;
482 global $USE_VIRTUAL_URLS;
484 if(!$dbh) {
485 $dbh = DB::connect();
488 if (isset($row['error'])) {
489 print "<p>" . $row['error'] . "</p>\n";
491 else {
492 include('pkg_details.php');
494 # Actions Bar
495 if ($SID) {
496 include('actions_form.php');
497 if (isset($_REQUEST['comment']) && check_token()) {
498 $uid = uid_from_sid($SID, $dbh);
499 add_package_comment($id, $uid, $_REQUEST['comment'], $dbh);
501 include('pkg_comment_form.php');
504 # Print Comments
505 $comments = package_comments($id, $dbh);
506 if (!empty($comments)) {
507 include('pkg_comments.php');
513 /* pkg_search_page(SID)
514 * outputs the body of search/search results page
516 * parameters:
517 * SID - current Session ID
518 * preconditions:
519 * package search page has been accessed
520 * request variables have not been sanitized
522 * request vars:
523 * O - starting result number
524 * PP - number of search hits per page
525 * C - package category ID number
526 * K - package search string
527 * SO - search hit sort order:
528 * values: a - ascending
529 * d - descending
530 * SB - sort search hits by:
531 * values: c - package category
532 * n - package name
533 * v - number of votes
534 * m - maintainer username
535 * SeB- property that search string (K) represents
536 * values: n - package name
537 * nd - package name & description
538 * x - package name (exact match)
539 * m - package maintainer's username
540 * s - package submitter's username
541 * do_Orphans - boolean. whether to search packages
542 * without a maintainer
545 * These two are actually handled in packages.php.
547 * IDs- integer array of ticked packages' IDs
548 * action - action to be taken on ticked packages
549 * values: do_Flag - Flag out-of-date
550 * do_UnFlag - Remove out-of-date flag
551 * do_Adopt - Adopt
552 * do_Disown - Disown
553 * do_Delete - Delete (requires confirm_Delete to be set)
554 * do_Notify - Enable notification
555 * do_UnNotify - Disable notification
557 function pkg_search_page($SID="", $dbh=NULL) {
558 if(!$dbh) {
559 $dbh = DB::connect();
562 // get commonly used variables...
563 // TODO: REDUCE DB HITS.
564 // grab info for user if they're logged in
565 if ($SID)
566 $myuid = uid_from_sid($SID, $dbh);
567 // get a list of package categories
568 $cats = pkgCategories($dbh); //meow
570 // sanitize paging variables
572 if (isset($_GET['O'])) {
573 $_GET['O'] = intval($_GET['O']);
574 if ($_GET['O'] < 0)
575 $_GET['O'] = 0;
577 else {
578 $_GET['O'] = 0;
581 if (isset($_GET["PP"])) {
582 $_GET["PP"] = intval($_GET["PP"]);
583 if ($_GET["PP"] < 50)
584 $_GET["PP"] = 50;
585 else if ($_GET["PP"] > 250)
586 $_GET["PP"] = 250;
588 else {
589 $_GET["PP"] = 50;
592 // FIXME: pull out DB-related code. all of it.
593 // this one's worth a choco-chip cookie,
594 // one of those nice big soft ones
596 // build the package search query
598 $q_select = "SELECT ";
599 if ($SID) {
600 $q_select .= "CommentNotify.UserID AS Notify,
601 PackageVotes.UsersID AS Voted, ";
603 $q_select .= "Users.Username AS Maintainer,
604 PackageCategories.Category,
605 Packages.Name, Packages.Version, Packages.Description, Packages.NumVotes,
606 Packages.ID, Packages.OutOfDateTS ";
608 $q_from = "FROM Packages
609 LEFT JOIN Users ON (Packages.MaintainerUID = Users.ID)
610 LEFT JOIN PackageCategories
611 ON (Packages.CategoryID = PackageCategories.ID) ";
612 if ($SID) {
613 # this portion is not needed for the total row count query
614 $q_from_extra = "LEFT JOIN PackageVotes
615 ON (Packages.ID = PackageVotes.PackageID AND PackageVotes.UsersID = $myuid)
616 LEFT JOIN CommentNotify
617 ON (Packages.ID = CommentNotify.PkgID AND CommentNotify.UserID = $myuid) ";
618 } else {
619 $q_from_extra = "";
622 $q_where = "WHERE 1 = 1 ";
623 // TODO: possibly do string matching on category
624 // to make request variable values more sensible
625 if (isset($_GET["C"]) && intval($_GET["C"])) {
626 $q_where .= "AND Packages.CategoryID = ".intval($_GET["C"])." ";
629 if (isset($_GET['K'])) {
630 # Search by maintainer
631 if (isset($_GET["SeB"]) && $_GET["SeB"] == "m") {
632 $q_where .= "AND Users.Username = " . $dbh->quote($_GET['K']) . " ";
634 # Search by submitter
635 elseif (isset($_GET["SeB"]) && $_GET["SeB"] == "s") {
636 $q_where .= "AND SubmitterUID = ".uid_from_username($_GET['K'], $dbh)." ";
638 # Search by name
639 elseif (isset($_GET["SeB"]) && $_GET["SeB"] == "n") {
640 $K = "%" . addcslashes($_GET['K'], '%_') . "%";
641 $q_where .= "AND (Name LIKE " . $dbh->quote($K) . ") ";
643 # Search by name (exact match)
644 elseif (isset($_GET["SeB"]) && $_GET["SeB"] == "x") {
645 $q_where .= "AND (Name = " . $dbh->quote($_GET['K']) . ") ";
647 # Search by name and description (Default)
648 else {
649 $K = "%" . addcslashes($_GET['K'], '%_') . "%";
650 $q_where .= "AND (Name LIKE " . $dbh->quote($K) . " OR ";
651 $q_where .= "Description LIKE " . $dbh->quote($K) . ") ";
655 if (isset($_GET["do_Orphans"])) {
656 $q_where .= "AND MaintainerUID IS NULL ";
659 if (isset($_GET['outdated'])) {
660 if ($_GET['outdated'] == 'on') {
661 $q_where .= "AND OutOfDateTS IS NOT NULL ";
663 elseif ($_GET['outdated'] == 'off') {
664 $q_where .= "AND OutOfDateTS IS NULL ";
668 $order = (isset($_GET["SO"]) && $_GET["SO"] == 'd') ? 'DESC' : 'ASC';
670 $q_sort = "ORDER BY Name ".$order." ";
671 $sort_by = isset($_GET["SB"]) ? $_GET["SB"] : '';
672 switch ($sort_by) {
673 case 'c':
674 $q_sort = "ORDER BY CategoryID ".$order.", Name ASC ";
675 break;
676 case 'v':
677 $q_sort = "ORDER BY NumVotes ".$order.", Name ASC ";
678 break;
679 case 'w':
680 if ($SID) {
681 $q_sort = "ORDER BY Voted ".$order.", Name ASC ";
683 break;
684 case 'o':
685 if ($SID) {
686 $q_sort = "ORDER BY Notify ".$order.", Name ASC ";
688 break;
689 case 'm':
690 $q_sort = "ORDER BY Maintainer ".$order.", Name ASC ";
691 break;
692 case 'a':
693 $q_sort = "ORDER BY ModifiedTS ".$order.", Name ASC ";
694 break;
695 default:
696 break;
699 $q_limit = "LIMIT ".$_GET["PP"]." OFFSET ".$_GET["O"];
701 $q = $q_select . $q_from . $q_from_extra . $q_where . $q_sort . $q_limit;
702 $q_total = "SELECT COUNT(*) " . $q_from . $q_where;
704 $result = $dbh->query($q);
705 $result_t = $dbh->query($q_total);
706 if ($result_t) {
707 $row = $result_t->fetch(PDO::FETCH_NUM);
708 $total = $row[0];
710 else {
711 $total = 0;
714 if ($result && $total > 0) {
715 if (isset($_GET["SO"]) && $_GET["SO"] == "d"){
716 $SO_next = "a";
718 else {
719 $SO_next = "d";
723 // figure out the results to use
724 $first = $_GET['O'] + 1;
726 # calculation of pagination links
727 $per_page = ($_GET['PP'] > 0) ? $_GET['PP'] : 50;
728 $current = ceil($first / $per_page);
729 $pages = ceil($total / $per_page);
730 $templ_pages = array();
732 if ($current > 1) {
733 $templ_pages['&laquo; ' . __('First')] = 0;
734 $templ_pages['&lsaquo; ' . __('Previous')] = ($current - 2) * $per_page;
737 if ($current - 5 > 1)
738 $templ_pages["..."] = false;
740 for ($i = max($current - 5, 1); $i <= min($pages, $current + 5); $i++) {
741 $templ_pages[$i] = ($i - 1) * $per_page;
744 if ($current + 5 < $pages)
745 $templ_pages["... "] = false;
747 if ($current < $pages) {
748 $templ_pages[__('Next') . ' &rsaquo;'] = $current * $per_page;
749 $templ_pages[__('Last') . ' &raquo;'] = ($pages - 1) * $per_page;
752 include('pkg_search_form.php');
754 if ($result) {
755 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
756 $searchresults[] = $row;
760 include('pkg_search_results.php');
762 return;
766 * Determine if a POST string has been sent by a visitor
768 * @param string $action String to check has been sent via POST
770 * @return bool True if the POST string was used, otherwise false
772 function current_action($action) {
773 return (isset($_POST['action']) && $_POST['action'] == $action) ||
774 isset($_POST[$action]);
778 * Determine if sent IDs are valid integers
780 * @param array $ids IDs to validate
782 * @return array All sent IDs that are valid integers
784 function sanitize_ids($ids) {
785 $new_ids = array();
786 foreach ($ids as $id) {
787 $id = intval($id);
788 if ($id > 0) {
789 $new_ids[] = $id;
792 return $new_ids;
796 * Flag package(s) as out-of-date
798 * @global string $AUR_LOCATION The AUR's URL used for notification e-mails
799 * @param string $atype Account type, output of account_from_sid
800 * @param array $ids Array of package IDs to flag/unflag
802 * @return string Translated success or error messages
804 function pkg_flag($atype, $ids, $dbh=NULL) {
805 global $AUR_LOCATION;
807 if (!$atype) {
808 return __("You must be logged in before you can flag packages.");
811 $ids = sanitize_ids($ids);
812 if (empty($ids)) {
813 return __("You did not select any packages to flag.");
816 if(!$dbh) {
817 $dbh = DB::connect();
820 $q = "UPDATE Packages SET";
821 $q.= " OutOfDateTS = UNIX_TIMESTAMP()";
822 $q.= " WHERE ID IN (" . implode(",", $ids) . ")";
823 $q.= " AND OutOfDateTS IS NULL";
825 $affected_pkgs = $dbh->exec($q);
827 if ($affected_pkgs > 0) {
828 # Notify of flagging by email
829 $f_name = username_from_sid($_COOKIE['AURSID'], $dbh);
830 $f_email = email_from_sid($_COOKIE['AURSID'], $dbh);
831 $f_uid = uid_from_sid($_COOKIE['AURSID'], $dbh);
832 $q = "SELECT Packages.Name, Users.Email, Packages.ID ";
833 $q.= "FROM Packages, Users ";
834 $q.= "WHERE Packages.ID IN (" . implode(",", $ids) .") ";
835 $q.= "AND Users.ID = Packages.MaintainerUID ";
836 $q.= "AND Users.ID != " . $f_uid;
837 $result = $dbh->query($q);
838 if ($result) {
839 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
840 # construct email
841 $body = "Your package " . $row['Name'] . " has been flagged out of date by " . $f_name . " [1]. You may view your package at:\n" . $AUR_LOCATION . get_pkg_uri($row['Name']) . "\n\n[1] - " . $AUR_LOCATION . get_user_uri($f_name);
842 $body = wordwrap($body, 70);
843 $headers = "Reply-to: nobody@archlinux.org\nFrom:aur-notify@archlinux.org\nX-Mailer: PHP\nX-MimeOLE: Produced By AUR\n";
844 @mail($row['Email'], "AUR Out-of-date Notification for ".$row['Name'], $body, $headers);
849 return __("The selected packages have been flagged out-of-date.");
853 * Unflag package(s) as out-of-date
855 * @param string $atype Account type, output of account_from_sid
856 * @param array $ids Array of package IDs to flag/unflag
858 * @return string Translated success or error messages
860 function pkg_unflag($atype, $ids, $dbh=NULL) {
861 if (!$atype) {
862 return __("You must be logged in before you can unflag packages.");
865 $ids = sanitize_ids($ids);
866 if (empty($ids)) {
867 return __("You did not select any packages to unflag.");
870 if(!$dbh) {
871 $dbh = DB::connect();
874 $q = "UPDATE Packages SET ";
875 $q.= "OutOfDateTS = NULL ";
876 $q.= "WHERE ID IN (" . implode(",", $ids) . ") ";
878 if ($atype != "Trusted User" && $atype != "Developer") {
879 $q.= "AND MaintainerUID = " . uid_from_sid($_COOKIE["AURSID"], $dbh);
882 $result = $dbh->exec($q);
884 if ($result) {
885 return __("The selected packages have been unflagged.");
890 * Delete packages
892 * @param string $atype Account type, output of account_from_sid
893 * @param array $ids Array of package IDs to delete
894 * @param int $mergepkgid Package to merge the deleted ones into
896 * @return string Translated error or success message
898 function pkg_delete ($atype, $ids, $mergepkgid, $dbh=NULL) {
899 if (!$atype) {
900 return __("You must be logged in before you can delete packages.");
903 # If they're a TU or dev, can delete
904 if ($atype != "Trusted User" && $atype != "Developer") {
905 return __("You do have permission to delete packages.");
908 $ids = sanitize_ids($ids);
909 if (empty($ids)) {
910 return __("You did not select any packages to delete.");
913 if(!$dbh) {
914 $dbh = DB::connect();
917 if ($mergepkgid) {
918 $mergepkgname = pkgname_from_id($mergepkgid, $dbh);
921 # Send email notifications
922 foreach ($ids as $pkgid) {
923 $q = "SELECT CommentNotify.*, Users.Email ";
924 $q.= "FROM CommentNotify, Users ";
925 $q.= "WHERE Users.ID = CommentNotify.UserID ";
926 $q.= "AND CommentNotify.UserID != " . uid_from_sid($_COOKIE['AURSID']) . " ";
927 $q.= "AND CommentNotify.PkgID = " . $pkgid;
928 $result = $dbh->query($q);
929 $bcc = array();
931 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
932 array_push($bcc, $row['Email']);
934 if (!empty($bcc)) {
935 $pkgname = pkgname_from_id($pkgid);
937 # TODO: native language emails for users, based on their prefs
938 # Simply making these strings translatable won't work, users would be
939 # getting emails in the language that the user who posted the comment was in
940 $body = "";
941 if ($mergepkgid) {
942 $body .= username_from_sid($_COOKIE['AURSID']) . " merged \"".$pkgname."\" into \"$mergepkgname\".\n\n";
943 $body .= "You will no longer receive notifications about this package, please go to https://aur.archlinux.org" . get_pkg_uri($mergepkgname) . " and click the Notify button if you wish to recieve them again.";
944 } else {
945 $body .= username_from_sid($_COOKIE['AURSID']) . " deleted \"".$pkgname."\".\n\n";
946 $body .= "You will no longer receive notifications about this package.";
948 $body = wordwrap($body, 70);
949 $bcc = implode(', ', $bcc);
950 $headers = "Bcc: $bcc\nReply-to: nobody@archlinux.org\nFrom: aur-notify@archlinux.org\nX-Mailer: AUR\n";
951 @mail('undisclosed-recipients: ;', "AUR Package deleted: " . $pkgname, $body, $headers);
955 if ($mergepkgid) {
956 /* Merge comments */
957 $q = "UPDATE PackageComments ";
958 $q.= "SET PackageID = " . intval($mergepkgid) . " ";
959 $q.= "WHERE PackageID IN (" . implode(",", $ids) . ")";
960 $dbh->exec($q);
962 /* Merge votes */
963 foreach ($ids as $pkgid) {
964 $q = "UPDATE PackageVotes ";
965 $q.= "SET PackageID = " . intval($mergepkgid) . " ";
966 $q.= "WHERE PackageID = " . $pkgid . " ";
967 $q.= "AND UsersID NOT IN (";
968 $q.= "SELECT * FROM (SELECT UsersID ";
969 $q.= "FROM PackageVotes ";
970 $q.= "WHERE PackageID = " . intval($mergepkgid);
971 $q.= ") temp)";
972 $dbh->exec($q);
975 $q = "UPDATE Packages ";
976 $q.= "SET NumVotes = (SELECT COUNT(*) FROM PackageVotes ";
977 $q.= "WHERE PackageID = " . intval($mergepkgid) . ") ";
978 $q.= "WHERE ID = " . intval($mergepkgid);
979 $dbh->exec($q);
982 $q = "DELETE FROM Packages WHERE ID IN (" . implode(",", $ids) . ")";
983 $result = $dbh->exec($q);
985 return __("The selected packages have been deleted.");
989 * Adopt or disown packages
991 * @param string $atype Account type, output of account_from_sid
992 * @param array $ids Array of package IDs to adopt/disown
993 * @param bool $action Adopts if true, disowns if false. Adopts by default
995 * @return string Translated error or success message
997 function pkg_adopt ($atype, $ids, $action=true, $dbh=NULL) {
998 if (!$atype) {
999 if ($action) {
1000 return __("You must be logged in before you can adopt packages.");
1001 } else {
1002 return __("You must be logged in before you can disown packages.");
1006 $ids = sanitize_ids($ids);
1007 if (empty($ids)) {
1008 if ($action) {
1009 return __("You did not select any packages to adopt.");
1010 } else {
1011 return __("You did not select any packages to disown.");
1015 if(!$dbh) {
1016 $dbh = DB::connect();
1019 $field = "MaintainerUID";
1020 $q = "UPDATE Packages ";
1022 if ($action) {
1023 $user = uid_from_sid($_COOKIE["AURSID"], $dbh);
1024 } else {
1025 $user = 'NULL';
1028 $q.= "SET $field = $user ";
1029 $q.= "WHERE ID IN (" . implode(",", $ids) . ") ";
1031 if ($action && $atype == "User") {
1032 # Regular users may only adopt orphan packages from unsupported
1033 $q.= "AND $field IS NULL ";
1034 } else if ($atype == "User") {
1035 $q.= "AND $field = " . uid_from_sid($_COOKIE["AURSID"], $dbh);
1038 $dbh->exec($q);
1040 if ($action) {
1041 pkg_notify(account_from_sid($_COOKIE["AURSID"], $dbh), $ids, $dbh);
1042 return __("The selected packages have been adopted.");
1043 } else {
1044 return __("The selected packages have been disowned.");
1049 * Vote and un-vote for packages
1051 * @param string $atype Account type, output of account_from_sid
1052 * @param array $ids Array of package IDs to vote/un-vote
1053 * @param bool $action Votes if true, un-votes if false. Votes by default
1055 * @return string Translated error or success message
1057 function pkg_vote ($atype, $ids, $action=true, $dbh=NULL) {
1058 if (!$atype) {
1059 if ($action) {
1060 return __("You must be logged in before you can vote for packages.");
1061 } else {
1062 return __("You must be logged in before you can un-vote for packages.");
1066 $ids = sanitize_ids($ids);
1067 if (empty($ids)) {
1068 if ($action) {
1069 return __("You did not select any packages to vote for.");
1070 } else {
1071 return __("Your votes have been removed from the selected packages.");
1075 if(!$dbh) {
1076 $dbh = DB::connect();
1078 $my_votes = pkgvotes_from_sid($_COOKIE["AURSID"], $dbh);
1079 $uid = uid_from_sid($_COOKIE["AURSID"], $dbh);
1081 $first = 1;
1082 foreach ($ids as $pid) {
1083 if ($action) {
1084 $check = !isset($my_votes[$pid]);
1085 } else {
1086 $check = isset($my_votes[$pid]);
1089 if ($check) {
1090 if ($first) {
1091 $first = 0;
1092 $vote_ids = $pid;
1093 if ($action) {
1094 $vote_clauses = "($uid, $pid)";
1096 } else {
1097 $vote_ids .= ", $pid";
1098 if ($action) {
1099 $vote_clauses .= ", ($uid, $pid)";
1105 # only vote for packages the user hasn't already voted for
1107 $op = $action ? "+" : "-";
1108 $q = "UPDATE Packages SET NumVotes = NumVotes $op 1 ";
1109 $q.= "WHERE ID IN ($vote_ids)";
1111 $dbh->exec($q);
1113 if ($action) {
1114 $q = "INSERT INTO PackageVotes (UsersID, PackageID) VALUES ";
1115 $q.= $vote_clauses;
1116 } else {
1117 $q = "DELETE FROM PackageVotes WHERE UsersID = $uid ";
1118 $q.= "AND PackageID IN ($vote_ids)";
1121 $dbh->exec($q);
1123 if ($action) {
1124 $q = "UPDATE Users SET LastVoted = UNIX_TIMESTAMP() ";
1125 $q.= "WHERE ID = $uid";
1126 $dbh->exec($q);
1129 if ($action) {
1130 return __("Your votes have been cast for the selected packages.");
1131 } else {
1132 return __("Your votes have been removed from the selected packages.");
1137 * Get all usernames and IDs that voted for a specific package
1139 * @param string $pkgid The package ID to get all votes for
1140 * @param \PDO $dbh An already established database connection
1142 * @return array User IDs and usernames that voted for a specific package
1144 function getvotes($pkgid, $dbh=NULL) {
1145 if(!$dbh) {
1146 $dbh = DB::connect();
1149 $q = "SELECT UsersID,Username FROM PackageVotes ";
1150 $q.= "LEFT JOIN Users on (UsersID = ID) ";
1151 $q.= "WHERE PackageID = ". $dbh->quote($pkgid) . " ";
1152 $q.= "ORDER BY Username";
1153 $result = $dbh->query($q);
1155 if (!$result) {
1156 return;
1159 $votes = array();
1160 while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
1161 $votes[] = $row;
1164 return $votes;
1168 * Determine if a user has already voted for a specific package
1170 * @param string $uid The user ID to check for an existing vote
1171 * @param string $pkgid The package ID to check for an existing vote
1172 * @param \PDO $dbh An already established database connection
1174 * @return bool True if the user has already voted, otherwise false
1176 function user_voted($uid, $pkgid, $dbh=NULL) {
1177 if(!$dbh) {
1178 $dbh = DB::connect();
1181 $q = "SELECT * FROM PackageVotes WHERE UsersID = ". $dbh->quote($uid);
1182 $q.= " AND PackageID = " . $dbh->quote($pkgid);
1183 $result = $dbh->query($q);
1185 if ($result->fetch(PDO::FETCH_NUM)) {
1186 return true;
1188 else {
1189 return false;
1194 * Determine if a user wants notifications for a specific package
1196 * @param string $uid User ID to check in the database
1197 * @param string $pkgid Package ID to check notifications for
1198 * @param \PDO $dbh An already established database connection
1200 * @return bool True if the user wants notifications, otherwise false
1202 function user_notify($uid, $pkgid, $dbh=NULL) {
1203 if(!$dbh) {
1204 $dbh = DB::connect();
1207 $q = "SELECT * FROM CommentNotify WHERE UserID = " . $dbh->quote($uid);
1208 $q.= " AND PkgID = " . $dbh->quote($pkgid);
1209 $result = $dbh->query($q);
1211 if ($result->fetch(PDO::FETCH_NUM)) {
1212 return true;
1214 else {
1215 return false;
1220 * Toggle notification of packages
1222 * @param string $atype Account type, output of account_from_sid
1223 * @param array $ids Array of package IDs to toggle, formatted as $package_id
1225 * @return string Translated error or success message
1227 function pkg_notify ($atype, $ids, $action=true, $dbh=NULL) {
1228 if (!$atype) {
1229 # return __("You must be logged in before you can get notifications on comments.");
1230 return;
1233 $ids = sanitize_ids($ids);
1234 if (empty($ids)) {
1235 return __("Couldn't add to notification list.");
1238 if(!$dbh) {
1239 $dbh = DB::connect();
1241 $uid = uid_from_sid($_COOKIE["AURSID"], $dbh);
1243 $output = "";
1245 $first = true;
1247 # There currently shouldn't be multiple requests here, but the
1248 # format in which it's sent requires this.
1249 foreach ($ids as $pid) {
1250 $q = "SELECT Name FROM Packages WHERE ID = $pid";
1251 $result = $dbh->query($q);
1252 if ($result) {
1253 $row = $result->fetch(PDO::FETCH_NUM);
1254 $pkgname = $row[0];
1256 else {
1257 $pkgname = '';
1260 if ($first)
1261 $first = False;
1262 else
1263 $output .= ", ";
1266 if ($action) {
1267 $q = "SELECT COUNT(*) FROM CommentNotify WHERE ";
1268 $q .= "UserID = $uid AND PkgID = $pid";
1270 # Notification already added. Don't add again.
1271 $result = $dbh->query($q);
1272 if ($result->fetchColumn() == 0) {
1273 $q = "INSERT INTO CommentNotify (PkgID, UserID) VALUES ($pid, $uid)";
1274 $dbh->exec($q);
1277 $output .= $pkgname;
1279 else {
1280 $q = "DELETE FROM CommentNotify WHERE PkgID = $pid ";
1281 $q .= "AND UserID = $uid";
1282 $dbh->exec($q);
1284 $output .= $pkgname;
1288 if ($action) {
1289 $output = __("You have been added to the comment notification list for %s.", $output);
1291 else {
1292 $output = __("You have been removed from the comment notification list for %s.", $output);
1295 return $output;
1299 * Delete a package comment
1301 * @param string $atype Account type, output of account_from_sid
1303 * @return string Translated error or success message
1305 function pkg_delete_comment($atype, $dbh=NULL) {
1306 if (!$atype) {
1307 return __("You must be logged in before you can edit package information.");
1310 # Get ID of comment to be removed
1311 if (isset($_POST["comment_id"])) {
1312 $comment_id = $_POST["comment_id"];
1313 } else {
1314 return __("Missing comment ID.");
1317 if(!$dbh) {
1318 $dbh = DB::connect();
1320 $uid = uid_from_sid($_COOKIE["AURSID"], $dbh);
1321 if (canDeleteComment($comment_id, $atype, $uid, $dbh)) {
1322 $q = "UPDATE PackageComments ";
1323 $q.= "SET DelUsersID = ".$uid." ";
1324 $q.= "WHERE ID = ".intval($comment_id);
1325 $dbh->exec($q);
1326 return __("Comment has been deleted.");
1327 } else {
1328 return __("You are not allowed to delete this comment.");
1333 * Change package category
1335 * @param string $atype Account type, output of account_from_sid
1337 * @return string Translated error or success message
1339 function pkg_change_category($pid, $atype, $dbh=NULL) {
1340 if (!$atype) {
1341 return __("You must be logged in before you can edit package information.");
1344 # Get ID of the new category
1345 if (isset($_POST["category_id"])) {
1346 $category_id = $_POST["category_id"];
1347 } else {
1348 return __("Missing category ID.");
1351 if(!$dbh) {
1352 $dbh = DB::connect();
1354 $catArray = pkgCategories($dbh);
1355 if (!array_key_exists($category_id, $catArray)) {
1356 return __("Invalid category ID.");
1359 # Verify package ownership
1360 $q = "SELECT Packages.MaintainerUID ";
1361 $q.= "FROM Packages ";
1362 $q.= "WHERE Packages.ID = ".$pid;
1363 $result = $dbh->query($q);
1364 if ($result) {
1365 $row = $result->fetch(PDO::FETCH_ASSOC);
1367 else {
1368 return __("You are not allowed to change this package category.");
1371 $uid = uid_from_sid($_COOKIE["AURSID"], $dbh);
1372 if ($uid == $row["MaintainerUID"] ||
1373 ($atype == "Developer" || $atype == "Trusted User")) {
1374 $q = "UPDATE Packages ";
1375 $q.= "SET CategoryID = ".intval($category_id)." ";
1376 $q.= "WHERE ID = ".intval($pid);
1377 $dbh->exec($q);
1378 return __("Package category changed.");
1379 } else {
1380 return __("You are not allowed to change this package category.");
1385 * Get all package information in the database for a specific package
1387 * @param string $pkgname The name of the package to get details for
1388 * @param \PDO $dbh An already established database connection
1390 * @return array All package details for a specific package
1392 function pkgdetails_by_pkgname($pkgname, $dbh=NULL) {
1393 if(!$dbh) {
1394 $dbh = DB::connect();
1396 $q = "SELECT * FROM Packages WHERE Name = " . $dbh->quote($pkgname);
1397 $result = $dbh->query($q);
1398 if ($result) {
1399 $row = $result->fetch(PDO::FETCH_ASSOC);
1401 return $row;
1405 * Add package information to the database for a specific package
1407 * @param string $pkgname Name of the new package
1408 * @param string $license License of the new package
1409 * @param string $pkgver Version of the new package
1410 * @param int $category_id Category for the new package
1411 * @param string $pkgdesc Description of the new package
1412 * @param string $pkgurl Upstream URL for the new package
1413 * @param int $uid User ID of the package uploader
1414 * @param \PDO $dbh An already established database connection
1416 * @return void
1418 function new_pkgdetails($pkgname, $license, $pkgver, $category_id, $pkgdesc, $pkgurl, $uid, $dbh=NULL) {
1419 if(!$dbh) {
1420 $dbh = DB::connect();
1422 $q = sprintf("INSERT INTO Packages (Name, License, Version, CategoryID, Description, URL, SubmittedTS, ModifiedTS, SubmitterUID, MaintainerUID) VALUES (%s, %s, %s, %d, %s, %s, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), %d, %d)",
1423 $dbh->quote($pkgname),
1424 $dbh->quote($license),
1425 $dbh->quote($pkgver),
1426 $category_id,
1427 $dbh->quote($pkgdesc),
1428 $dbh->quote($pkgurl),
1429 $uid,
1430 $uid);
1432 $dbh->exec($q);
1436 * Update all database information for a specific package
1438 * @param string $pkgname Name of the updated package
1439 * @param string $license License of the updated package
1440 * @param string $pkgver Version of the updated package
1441 * @param string $pkgdesc Description of updated package
1442 * @param string $pkgurl The upstream URL for the package
1443 * @param int $uid The user ID of the updater
1444 * @param int $pkgid The package ID of the updated package
1445 * @param \PDO $dbh An already established database connection
1447 * @return void
1449 function update_pkgdetails($pkgname, $license, $pkgver, $pkgdesc, $pkgurl, $uid, $pkgid, $dbh=NULL) {
1450 if(!$dbh) {
1451 $dbh = DB::connect();
1453 # This is an overwrite of an existing package
1454 $q = sprintf("UPDATE Packages SET ModifiedTS = UNIX_TIMESTAMP(), Name = %s, Version = %s, License = %s, Description = %s, URL = %s, OutOfDateTS = NULL, MaintainerUID = %d WHERE ID = %d",
1455 $dbh->quote($pkgname),
1456 $dbh->quote($pkgver),
1457 $dbh->quote($license),
1458 $dbh->quote($pkgdesc),
1459 $dbh->quote($pkgurl),
1460 $uid,
1461 $pkgid);
1463 $dbh->exec($q);
1467 * Add a dependency for a specific package to the database
1469 * @param int $pkgid The package ID to add the dependency for
1470 * @param string $depname The name of the dependency to add
1471 * @param string $depcondition The type of dependency for the package
1472 * @param \PDO $dbh An already established database connection
1474 * @return void
1476 function add_pkg_dep($pkgid, $depname, $depcondition, $dbh=NULL) {
1477 if(!$dbh) {
1478 $dbh = DB::connect();
1480 $q = sprintf("INSERT INTO PackageDepends (PackageID, DepName, DepCondition) VALUES (%d, %s, %s)",
1481 $pkgid,
1482 $dbh->quote($depname),
1483 $dbh->quote($depcondition));
1485 $dbh->exec($q);
1489 * Add a source for a specific package to the database
1491 * @param int $pkgid The package ID to add the source for
1492 * @param string $pkgsrc The package source to add to the database
1493 * @param \PDO $dbh An already established database connection
1495 * @return void
1497 function add_pkg_src($pkgid, $pkgsrc, $dbh=NULL) {
1498 if(!$dbh) {
1499 $dbh = DB::connect();
1501 $q = "INSERT INTO PackageSources (PackageID, Source) VALUES (";
1502 $q .= $pkgid . ", " . $dbh->quote($pkgsrc) . ")";
1504 $dbh->exec($q);
1508 * Change the category a package belongs to
1510 * @param int $pkgid The package ID to change the category for
1511 * @param int $category_id The new category ID for the package
1512 * @param \PDO $dbh An already established database connection
1514 * @return void
1516 function update_pkg_category($pkgid, $category_id, $dbh=NULL) {
1517 if(!$dbh) {
1518 $dbh = DB::connect();
1520 $q = sprintf( "UPDATE Packages SET CategoryID = %d WHERE ID = %d",
1521 $category_id,
1522 $pkgid);
1524 $dbh->exec($q);
1528 * Remove package dependencies from a specific package
1530 * @param string $pkgid The package ID to remove package dependencies from
1531 * @param \PDO $dbh An already established database connection
1533 * @return void
1535 function remove_pkg_deps($pkgid, $dbh=NULL) {
1536 if(!$dbh) {
1537 $dbh = DB::connect();
1539 $q = "DELETE FROM PackageDepends WHERE PackageID = " . $pkgid;
1541 $dbh->exec($q);
1545 * Remove package sources from a specific package
1547 * @param string $pkgid The package ID to remove package sources from
1548 * @param \PDO $dbh An already established database connection
1550 * @return void
1552 function remove_pkg_sources($pkgid, $dbh=NULL) {
1553 if(!$dbh) {
1554 $dbh = DB::connect();
1556 $q = "DELETE FROM PackageSources WHERE PackageID = " . $pkgid;
1558 $dbh->exec($q);