Implement capability to pin comments above others

Adds capability to pin comments before others.

Implements FS#10863.

Signed-off-by: Mark Weiman <mark.weiman@markzz.com>
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
This commit is contained in:
Mark Weiman 2015-12-11 19:01:31 -05:00 committed by Lukas Fleischer
parent 3088fd0f38
commit 7d4c0c9ffa
13 changed files with 187 additions and 14 deletions

View file

@ -261,6 +261,7 @@ CREATE TABLE PackageComments (
EditedTS BIGINT UNSIGNED NULL DEFAULT NULL, EditedTS BIGINT UNSIGNED NULL DEFAULT NULL,
EditedUsersID INTEGER UNSIGNED NULL DEFAULT NULL, EditedUsersID INTEGER UNSIGNED NULL DEFAULT NULL,
DelUsersID INTEGER UNSIGNED NULL DEFAULT NULL, DelUsersID INTEGER UNSIGNED NULL DEFAULT NULL,
PinnedTS BIGINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (ID), PRIMARY KEY (ID),
INDEX (UsersID), INDEX (UsersID),
INDEX (PackageBaseID), INDEX (PackageBaseID),

View file

@ -15,3 +15,9 @@ CREATE UNIQUE INDEX ProviderNameProvides ON OfficialProviders (Name, Provides);
---- ----
ALTER TABLE Users MODIFY Email VARCHAR(254) NOT NULL; ALTER TABLE Users MODIFY Email VARCHAR(254) NOT NULL;
---- ----
3. Add new column in PackageComments for pinning system.
----
ALTER TABLE PackageComments ADD COLUMN PinnedTS BIGINT UNSIGNED NOT NULL DEFAULT 0;
----

View file

@ -101,7 +101,7 @@
color: #999; color: #999;
} }
.delete-comment-form, .edit-comment { .delete-comment-form, .pin-comment-form, .edit-comment {
float: right; float: right;
margin-left: 8px; margin-left: 8px;
} }
@ -112,13 +112,13 @@
top: 1px; top: 1px;
} }
.delete-comment, .edit-comment { .delete-comment, .edit-comment, .pin-comment {
-webkit-filter: grayscale(100%); -webkit-filter: grayscale(100%);
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.6; opacity: 0.6;
} }
.delete-comment:hover, .edit-comment:hover { .delete-comment:hover, .edit-comment:hover, .pin-comment:hover {
-webkit-filter: none; -webkit-filter: none;
filter: none; filter: none;
opacity: 1; opacity: 1;

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path style="fill:#3366aa;fill-opacity:1" d="M1.34 0a.5.5 0 0 0 .16 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.44 1 .56-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.09 0 .5.5 0 0 0-.06 0z" /></svg>

After

Width:  |  Height:  |  Size: 283 B

3
web/html/images/pin.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path style="fill:#3366aa;fill-opacity:1" d="M1.34 0a.5.5 0 0 0 .16 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.44 1 .56-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.09 0 .5.5 0 0 0-.06 0z" />
</svg>

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path style="fill:#3366aa;fill-opacity:1" d="M1.34 0a.5.5 0 0 0 .16 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.44 1 .56-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.09 0 .5.5 0 0 0-.06 0z" /><path style="fill:#3366aa;fill-opacity:1" d="m6.4 0l1 1-6.4 6.8-1-1z" /></svg>

After

Width:  |  Height:  |  Size: 355 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path style="fill:#3366aa;fill-opacity:1" d="M1.34 0a.5.5 0 0 0 .16 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.44 1 .56-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.09 0 .5.5 0 0 0-.06 0z" />
<path style="fill:#3366aa;fill-opacity:1" d="m6.4 0l1 1 -6.4 6.8 -1 -1z" />
</svg>

After

Width:  |  Height:  |  Size: 365 B

View file

@ -182,6 +182,8 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) {
break; break;
case "/images/x.min.svg": case "/images/x.min.svg":
case "/images/pencil.min.svg": case "/images/pencil.min.svg":
case "/images/pin.min.svg":
case "/images/unpin.min.svg":
header("Content-Type: image/svg+xml"); header("Content-Type: image/svg+xml");
readfile("./$path"); readfile("./$path");
break; break;

View file

@ -99,6 +99,10 @@ if (check_token()) {
list($ret, $output) = pkgbase_notify($ids, false); list($ret, $output) = pkgbase_notify($ids, false);
} elseif (current_action("do_DeleteComment")) { } elseif (current_action("do_DeleteComment")) {
list($ret, $output) = pkgbase_delete_comment(); list($ret, $output) = pkgbase_delete_comment();
} elseif (current_action("do_PinComment")) {
list($ret, $output) = pkgbase_pin_comment();
} elseif (current_action("do_UnpinComment")) {
list($ret, $output) = pkgbase_pin_comment(true);
} elseif (current_action("do_SetKeywords")) { } elseif (current_action("do_SetKeywords")) {
list($ret, $output) = pkgbase_set_keywords($base_id, preg_split("/[\s,;]+/", $_POST['keywords'], -1, PREG_SPLIT_NO_EMPTY)); list($ret, $output) = pkgbase_set_keywords($base_id, preg_split("/[\s,;]+/", $_POST['keywords'], -1, PREG_SPLIT_NO_EMPTY));
} elseif (current_action("do_FileRequest")) { } elseif (current_action("do_FileRequest")) {

View file

@ -8,6 +8,7 @@ define("CRED_ACCOUNT_SEARCH", 5);
define("CRED_COMMENT_DELETE", 6); define("CRED_COMMENT_DELETE", 6);
define("CRED_COMMENT_VIEW_DELETED", 22); define("CRED_COMMENT_VIEW_DELETED", 22);
define("CRED_COMMENT_EDIT", 25); define("CRED_COMMENT_EDIT", 25);
define("CRED_COMMENT_PIN", 26);
define("CRED_PKGBASE_ADOPT", 7); define("CRED_PKGBASE_ADOPT", 7);
define("CRED_PKGBASE_SET_KEYWORDS", 8); define("CRED_PKGBASE_SET_KEYWORDS", 8);
define("CRED_PKGBASE_DELETE", 9); define("CRED_PKGBASE_DELETE", 9);
@ -60,6 +61,7 @@ function has_credential($credential, $approved_users=array()) {
case CRED_COMMENT_DELETE: case CRED_COMMENT_DELETE:
case CRED_COMMENT_VIEW_DELETED: case CRED_COMMENT_VIEW_DELETED:
case CRED_COMMENT_EDIT: case CRED_COMMENT_EDIT:
case CRED_COMMENT_PIN:
case CRED_PKGBASE_ADOPT: case CRED_PKGBASE_ADOPT:
case CRED_PKGBASE_SET_KEYWORDS: case CRED_PKGBASE_SET_KEYWORDS:
case CRED_PKGBASE_DELETE: case CRED_PKGBASE_DELETE:

View file

@ -7,10 +7,11 @@ include_once("pkgreqfuncs.inc.php");
* *
* @param string $base_id The package base ID to get comment count for * @param string $base_id The package base ID to get comment count for
* @param bool $include_deleted True if deleted comments should be included * @param bool $include_deleted True if deleted comments should be included
* @param bool $only_pinned True if only pinned comments should be included
* *
* @return string The number of comments left for a specific package * @return string The number of comments left for a specific package
*/ */
function pkgbase_comments_count($base_id, $include_deleted) { function pkgbase_comments_count($base_id, $include_deleted, $only_pinned=false) {
$base_id = intval($base_id); $base_id = intval($base_id);
if (!$base_id) { if (!$base_id) {
return null; return null;
@ -22,6 +23,9 @@ function pkgbase_comments_count($base_id, $include_deleted) {
if (!$include_deleted) { if (!$include_deleted) {
$q.= "AND DelUsersID IS NULL"; $q.= "AND DelUsersID IS NULL";
} }
if ($only_pinned) {
$q.= "AND NOT PinnedTS = 0";
}
$result = $dbh->query($q); $result = $dbh->query($q);
if (!$result) { if (!$result) {
return null; return null;
@ -36,10 +40,11 @@ function pkgbase_comments_count($base_id, $include_deleted) {
* @param int $base_id The package base ID to get comments for * @param int $base_id The package base ID to get comments for
* @param int $limit Maximum number of comments to return (0 means unlimited) * @param int $limit Maximum number of comments to return (0 means unlimited)
* @param bool $include_deleted True if deleted comments should be included * @param bool $include_deleted True if deleted comments should be included
* @param bool $only_pinned True when only pinned comments are to be included
* *
* @return array All package comment information for a specific package base * @return array All package comment information for a specific package base
*/ */
function pkgbase_comments($base_id, $limit, $include_deleted) { function pkgbase_comments($base_id, $limit, $include_deleted, $only_pinned=false) {
$base_id = intval($base_id); $base_id = intval($base_id);
$limit = intval($limit); $limit = intval($limit);
if (!$base_id) { if (!$base_id) {
@ -48,15 +53,20 @@ function pkgbase_comments($base_id, $limit, $include_deleted) {
$dbh = DB::connect(); $dbh = DB::connect();
$q = "SELECT PackageComments.ID, A.UserName AS UserName, UsersID, Comments, "; $q = "SELECT PackageComments.ID, A.UserName AS UserName, UsersID, Comments, ";
$q.= "CommentTS, EditedTS, B.UserName AS EditUserName, "; $q.= "PackageBaseID, CommentTS, EditedTS, B.UserName AS EditUserName, ";
$q.= "DelUsersID, C.UserName AS DelUserName FROM PackageComments "; $q.= "DelUsersID, C.UserName AS DelUserName, ";
$q.= "PinnedTS FROM PackageComments ";
$q.= "LEFT JOIN Users A ON PackageComments.UsersID = A.ID "; $q.= "LEFT JOIN Users A ON PackageComments.UsersID = A.ID ";
$q.= "LEFT JOIN Users B ON PackageComments.EditedUsersID = B.ID "; $q.= "LEFT JOIN Users B ON PackageComments.EditedUsersID = B.ID ";
$q.= "LEFT JOIN Users C ON PackageComments.DelUsersID = C.ID "; $q.= "LEFT JOIN Users C ON PackageComments.DelUsersID = C.ID ";
$q.= "WHERE PackageBaseID = " . $base_id . " "; $q.= "WHERE PackageBaseID = " . $base_id . " ";
if (!$include_deleted) { if (!$include_deleted) {
$q.= "AND DelUsersID IS NULL "; $q.= "AND DelUsersID IS NULL ";
} }
if ($only_pinned) {
$q.= "AND NOT PinnedTS = 0 ";
}
$q.= "ORDER BY CommentTS DESC"; $q.= "ORDER BY CommentTS DESC";
if ($limit > 0) { if ($limit > 0) {
$q.=" LIMIT " . $limit; $q.=" LIMIT " . $limit;
@ -97,6 +107,58 @@ function pkgbase_add_comment($base_id, $uid, $comment) {
} }
/** /**
* Pin/unpin a package comment
*
* @param bool $unpin True if unpinning rather than pinning
*
* @return array Tuple of success/failure indicator and error message
*/
function pkgbase_pin_comment($unpin=false) {
$uid = uid_from_sid($_COOKIE["AURSID"]);
if (!$uid) {
return array(false, __("You must be logged in before you can edit package information."));
}
if (isset($_POST["comment_id"])) {
$comment_id = $_POST["comment_id"];
} else {
return array(false, __("Missing comment ID."));
}
if (!$unpin) {
if (pkgbase_comments_count($_POST['package_base'], false, true) >= 5){
return array(false, __("No more than 5 comments can be pinned."));
}
}
if (!can_pin_comment($comment_id)) {
if (!$unpin) {
return array(false, __("You are not allowed to pin this comment."));
} else {
return array(false, __("You are not allowed to unpin this comment."));
}
}
$dbh = DB::connect();
$q = "UPDATE PackageComments ";
if (!$unpin) {
$q.= "SET PinnedTS = UNIX_TIMESTAMP() ";
} else {
$q.= "SET PinnedTS = 0 ";
}
$q.= "WHERE ID = " . intval($comment_id);
$dbh->exec($q);
if (!$unpin) {
return array(true, __("Comment has been pinned."));
} else {
return array(true, __("Comment has been unpinned."));
}
}
/**
* Get a list of all packages a logged-in user has voted for * Get a list of all packages a logged-in user has voted for
* *
* @param string $sid The session ID of the visitor * @param string $sid The session ID of the visitor
@ -183,8 +245,16 @@ function pkgbase_display_details($base_id, $row, $SID="") {
include('pkg_comment_box.php'); include('pkg_comment_box.php');
} }
$limit = isset($_GET['comments']) ? 0 : 10;
$include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED); $include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED);
$limit_pinned = isset($_GET['pinned']) ? 0 : 5;
$pinned = pkgbase_comments($base_id, $limit_pinned, false, true);
if (!empty($pinned)) {
include('pkg_comments.php');
unset($pinned);
}
$limit = isset($_GET['comments']) ? 0 : 10;
$comments = pkgbase_comments($base_id, $limit, $include_deleted); $comments = pkgbase_comments($base_id, $limit, $include_deleted);
if (!empty($comments)) { if (!empty($comments)) {
include('pkg_comments.php'); include('pkg_comments.php');

View file

@ -82,6 +82,47 @@ function can_edit_comment_array($comment) {
return has_credential(CRED_COMMENT_EDIT, array($comment['UsersID'])); return has_credential(CRED_COMMENT_EDIT, array($comment['UsersID']));
} }
/**
* Determine if the user can pin a specific package comment
*
* Only the Package Maintainer, Trusted Users, and Developers can pin
* comments. This function is used for the backend side of comment pinning.
*
* @param string $comment_id The comment ID in the database
*
* @return bool True if the user can pin the comment, otherwise false
*/
function can_pin_comment($comment_id=0) {
$dbh = DB::connect();
$q = "SELECT MaintainerUID FROM PackageBases AS pb ";
$q.= "LEFT JOIN PackageComments AS pc ON pb.ID = pc.PackageBaseID ";
$q.= "WHERE pc.ID = " . intval($comment_id);
$result = $dbh->query($q);
if (!$result) {
return false;
}
$uid = $result->fetch(PDO::FETCH_COLUMN, 0);
return has_credential(CRED_COMMENT_PIN, array($uid));
}
/**
* Determine if the user can edit a specific package comment using an array
*
* Only the Package Maintainer, Trusted Users, and Developers can pin
* comments. This function is used for the frontend side of comment pinning.
*
* @param array $comment All database information relating a specific comment
*
* @return bool True if the user can edit the comment, otherwise false
*/
function can_pin_comment_array($comment) {
return can_pin_comment($comment['ID']);
}
/** /**
* Check to see if the package name already exists in the database * Check to see if the package name already exists in the database
* *
@ -582,8 +623,16 @@ function pkg_display_details($id=0, $row, $SID="") {
include('pkg_comment_box.php'); include('pkg_comment_box.php');
} }
$limit = isset($_GET['comments']) ? 0 : 10;
$include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED); $include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED);
$limit_pinned = isset($_GET['pinned']) ? 0 : 5;
$pinned = pkgbase_comments($base_id, $limit_pinned, false, true);
if (!empty($pinned)) {
include('pkg_comments.php');
unset($pinned);
}
$limit = isset($_GET['comments']) ? 0 : 10;
$comments = pkgbase_comments($base_id, $limit, $include_deleted); $comments = pkgbase_comments($base_id, $limit, $include_deleted);
if (!empty($comments)) { if (!empty($comments)) {
include('pkg_comments.php'); include('pkg_comments.php');

View file

@ -1,11 +1,18 @@
<?php <?php
$include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED); if (!isset($count)) {
$count = pkgbase_comments_count($base_id, $include_deleted); $count = pkgbase_comments_count($base_id, $include_deleted);
}
?> ?>
<div id="news"> <div id="news">
<h3> <h3>
<a href="<?= htmlentities(get_pkgbase_uri($pkgbase_name), ENT_QUOTES) . '?' . mkurl('comments=all') ?>" title="<?= __('View all comments' , $count) ?> (<?= $count ?>)"><?= __('Latest Comments') ?></a> <?php if (!isset($comments)): ?>
<span class="arrow"></span> <?php $comments = $pinned ?>
<a href="<?= htmlentities(get_pkgbase_uri($pkgbase_name), ENT_QUOTES) . '?' . mkurl('comments=all') ?>" title="<?= __('View all comments' , $count) ?> (<?= $count ?>)"><?= __('Pinned Comments') ?></a>
<span class="arrow"></span>
<?php else: ?>
<a href="<?= htmlentities(get_pkgbase_uri($pkgbase_name), ENT_QUOTES) . '?' . mkurl('comments=all') ?>" title="<?= __('View all comments' , $count) ?> (<?= $count ?>)"><?= __('Latest Comments') ?></a>
<span class="arrow"></span>
<?php endif; ?>
</h3> </h3>
<?php while (list($indx, $row) = each($comments)): ?> <?php while (list($indx, $row) = each($comments)): ?>
@ -49,6 +56,29 @@ $count = pkgbase_comments_count($base_id, $include_deleted);
<?php if (!$row['DelUsersID'] && can_edit_comment_array($row)): ?> <?php if (!$row['DelUsersID'] && can_edit_comment_array($row)): ?>
<a href="<?= htmlspecialchars(get_pkgbase_uri($pkgbase_name) . 'edit-comment/?comment_id=' . $row['ID'], ENT_QUOTES) ?>" class="edit-comment" title="<?= __('Edit comment') ?>"><img src="/images/pencil.min.svg" alt="<?= __('Edit comment') ?>" width="11" height="11"></a> <a href="<?= htmlspecialchars(get_pkgbase_uri($pkgbase_name) . 'edit-comment/?comment_id=' . $row['ID'], ENT_QUOTES) ?>" class="edit-comment" title="<?= __('Edit comment') ?>"><img src="/images/pencil.min.svg" alt="<?= __('Edit comment') ?>" width="11" height="11"></a>
<?php endif; ?> <?php endif; ?>
<?php if (!$row['DelUsersID'] && !$row['PinnedTS'] && can_pin_comment_array($row) && !(pkgbase_comments_count($base_id, false, true) >= 5)): ?>
<form class="pin-comment-form" method="post" action="<?= htmlspecialchars(get_pkgbase_uri($pkgbase_name), ENT_QUOTES); ?>">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_PinComment" />
<input type="hidden" name="comment_id" value="<?= $row['ID'] ?>" />
<input type="hidden" name="package_base" value="<?= $base_id ?>" />
<input type="hidden" name="token" value="<?= htmlspecialchars($_COOKIE['AURSID']) ?>" />
<input type="image" class="pin-comment" src="/images/pin.min.svg" width="11" height="11" alt="<?= __('Pin comment') ?>" title="<?= __('Pin comment') ?>" name="submit" value="1" />
</fieldset>
</form>
<?php endif; ?>
<?php if (!$row['DelUsersID'] && $row['PinnedTS'] && can_pin_comment_array($row)): ?>
<form class="pin-comment-form" method="post" action="<?= htmlspecialchars(get_pkgbase_uri($pkgbase_name), ENT_QUOTES); ?>">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_UnpinComment" />
<input type="hidden" name="comment_id" value="<?= $row['ID'] ?>" />
<input type="hidden" name="token" value="<?= htmlspecialchars($_COOKIE['AURSID']) ?>" />
<input type="image" class="pin-comment" src="/images/unpin.min.svg" width="11" height="11" alt="<?= __('Unpin comment') ?>" title="<?= __('Unpin comment') ?>" name="submit" value="1" />
</fieldset>
</form>
<?php endif; ?>
</h4> </h4>
<div class="article-content<?php if ($row['DelUsersID']): ?> comment-deleted<?php endif; ?>"> <div class="article-content<?php if ($row['DelUsersID']): ?> comment-deleted<?php endif; ?>">
<p> <p>
@ -57,7 +87,7 @@ $count = pkgbase_comments_count($base_id, $include_deleted);
</div> </div>
<?php endwhile; ?> <?php endwhile; ?>
<?php if ($count > 10 && !isset($_GET['comments'])): ?> <?php if ($count > 10 && !isset($_GET['comments']) && !isset($pinned)): ?>
<h3> <h3>
<a href="<?= htmlentities(get_pkgbase_uri($pkgbase_name), ENT_QUOTES) . '?' . mkurl('comments=all') ?>" title="<?= __('View all comments') ?> (<?= $count ?>)"><?= __('All comments', $count) ?></a> <a href="<?= htmlentities(get_pkgbase_uri($pkgbase_name), ENT_QUOTES) . '?' . mkurl('comments=all') ?>" title="<?= __('View all comments') ?> (<?= $count ?>)"><?= __('All comments', $count) ?></a>
</h3> </h3>