diff --git a/plugins/tracker/db/install.sql b/plugins/tracker/db/install.sql
index 990aa3c69f7..b65097175e4 100644
--- a/plugins/tracker/db/install.sql
+++ b/plugins/tracker/db/install.sql
@@ -289,6 +289,7 @@ CREATE TABLE tracker_changeset_comment(
body TEXT NOT NULL,
body_format varchar(16) NOT NULL default 'text',
old_artifact_history_id INT(11) NULL,
+ private TINYINT(1) DEFAULT 0 NOT NULL,
INDEX changeset_idx(changeset_id)
) ENGINE=InnoDB;
@@ -1115,4 +1116,12 @@ FROM groups
WHERE groups.status != 'D'
AND service.short_name = 'plugin_tracker';
+DROP TABLE IF EXISTS tracker_private_comment_permission;
+CREATE TABLE IF NOT EXISTS tracker_private_comment_permission (
+ id INT(11) NOT NULL AUTO_INCREMENT,
+ tracker_id INT(11) NOT NULL,
+ ugroup_id INT(11) NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB
+
INSERT INTO forgeconfig (name, value) VALUES ('feature_flag_use_list_pickers_in_trackers_and_modals', 1);
diff --git a/plugins/tracker/db/mysql/updates/2020/202011201401_add_private_col_in_tracker_changeset_comment.php b/plugins/tracker/db/mysql/updates/2020/202011201401_add_private_col_in_tracker_changeset_comment.php
new file mode 100644
index 00000000000..21411847243
--- /dev/null
+++ b/plugins/tracker/db/mysql/updates/2020/202011201401_add_private_col_in_tracker_changeset_comment.php
@@ -0,0 +1,44 @@
+db = $this->getApi('ForgeUpgrade_Bucket_Db');
+ }
+
+ public function up(): void
+ {
+ $sql = "ALTER TABLE tracker_changeset_comment ADD private TINYINT(1) DEFAULT 0 NOT NULL";
+ $res = $this->db->dbh->exec($sql);
+ if ($res === false) {
+ throw new ForgeUpgrade_Bucket_Exception_UpgradeNotComplete('Adding column did not work.');
+ }
+ }
+}
diff --git a/plugins/tracker/db/mysql/updates/2020/202011201410_add_tracker_private_comment_permission.php b/plugins/tracker/db/mysql/updates/2020/202011201410_add_tracker_private_comment_permission.php
new file mode 100644
index 00000000000..26d87c9995d
--- /dev/null
+++ b/plugins/tracker/db/mysql/updates/2020/202011201410_add_tracker_private_comment_permission.php
@@ -0,0 +1,43 @@
+db = $this->getApi('ForgeUpgrade_Bucket_Db');
+ }
+ public function up()
+ {
+ $sql = "CREATE TABLE IF NOT EXISTS tracker_private_comment_permission (
+ id INT(11) NOT NULL AUTO_INCREMENT,
+ tracker_id INT(11) NOT NULL,
+ ugroup_id INT(11) NOT NULL,
+ PRIMARY KEY (id)
+ ) ENGINE=InnoDB";
+
+ $this->db->createTable('tracker_private_comment_permission', $sql);
+ }
+}
diff --git a/plugins/tracker/db/uninstall.sql b/plugins/tracker/db/uninstall.sql
index 36bfaea6f33..857e0b5dde6 100644
--- a/plugins/tracker/db/uninstall.sql
+++ b/plugins/tracker/db/uninstall.sql
@@ -114,6 +114,7 @@ DROP TABLE IF EXISTS tracker_fileinfo_temporary;
DROP TABLE IF EXISTS tracker_reminder_notified_roles;
DROP TABLE IF EXISTS tracker_report_config;
DROP TABLE IF EXISTS tracker_widget_renderer;
+DROP TABLE IF EXISTS tracker_private_comment_permission;
DROP TABLE IF EXISTS plugin_tracker_legacy_tracker_migrated;
diff --git a/plugins/tracker/include/Tracker/Action/UpdateArtifact.class.php b/plugins/tracker/include/Tracker/Action/UpdateArtifact.class.php
index 690bd416d7e..195a6bbcebb 100644
--- a/plugins/tracker/include/Tracker/Action/UpdateArtifact.class.php
+++ b/plugins/tracker/include/Tracker/Action/UpdateArtifact.class.php
@@ -65,7 +65,7 @@ public function process(Tracker_IDisplayTrackerLayout $layout, Codendi_Request $
{
//TODO : check permissions on this action?
$comment_format = $this->artifact->validateCommentFormat($request, 'comment_formatnew');
-
+ $private = (bool) $request->get('private_comment_inputnew');
$fields_data = $request->get('artifact');
$fields_data['request_method_called'] = 'artifact-update';
$this->artifact->getTracker()->augmentDataFromRequest($fields_data);
@@ -75,7 +75,14 @@ public function process(Tracker_IDisplayTrackerLayout $layout, Codendi_Request $
if ($current_user->isAnonymous()) {
$current_user->setEmail($request->get('email'));
}
- $this->artifact->createNewChangeset($fields_data, $request->get('artifact_followup_comment'), $current_user, true, $comment_format);
+ $this->artifact->createNewChangeset(
+ $fields_data,
+ $request->get('artifact_followup_comment'),
+ $current_user,
+ true,
+ $comment_format,
+ $private
+ );
$art_link = $this->artifact->fetchDirectLinkToArtifact();
$GLOBALS['Response']->addFeedback('info', sprintf(dgettext('tuleap-tracker', 'Successfully Updated (%1$s)'), $art_link), CODENDI_PURIFIER_LIGHT);
diff --git a/plugins/tracker/include/Tracker/Artifact/Artifact.php b/plugins/tracker/include/Tracker/Artifact/Artifact.php
index 0fa59a3ce01..fdc0ddd9d12 100644
--- a/plugins/tracker/include/Tracker/Artifact/Artifact.php
+++ b/plugins/tracker/include/Tracker/Artifact/Artifact.php
@@ -801,12 +801,14 @@ public function process(Tracker_IDisplayTrackerLayout $layout, $request, $curren
case 'update-comment':
if ((int) $request->get('changeset_id') && $request->exist('content')) {
if ($changeset = $this->getChangeset($request->get('changeset_id'))) {
+ $private = $changeset->getComment()->private;
$comment_format = $this->validateCommentFormat($request, 'comment_format');
$changeset->updateComment(
$request->get('content'),
$current_user,
$comment_format,
- $_SERVER['REQUEST_TIME']
+ $_SERVER['REQUEST_TIME'],
+ $private
);
if ($request->isAjax()) {
//We assume that we can only change a comment from a followUp
@@ -1249,6 +1251,7 @@ public function getErrors()
* @param PFUser $submitter The user who is doing the update
* @param bool $send_notification true if a notification must be sent, false otherwise
* @param string $comment_format The comment (follow-up) type ("text" | "html")
+ * @param bool $private The comment (follow-up) private type
*
* @return Tracker_Artifact_Changeset|null
* @throws Tracker_NoChangeException In the validation
@@ -1259,7 +1262,8 @@ public function createNewChangeset(
$comment,
PFUser $submitter,
$send_notification = true,
- $comment_format = Tracker_Artifact_Changeset_Comment::TEXT_COMMENT
+ $comment_format = Tracker_Artifact_Changeset_Comment::TEXT_COMMENT,
+ bool $private = false
) {
$submitted_on = $_SERVER['REQUEST_TIME'];
$validator = new Tracker_Artifact_Changeset_NewChangesetFieldsValidator(
@@ -1278,7 +1282,8 @@ public function createNewChangeset(
(bool) $send_notification,
(string) $comment_format,
new \Tuleap\Tracker\FormElement\Field\File\CreatedFileURLMapping(),
- new TrackerNoXMLImportLoggedConfig()
+ new TrackerNoXMLImportLoggedConfig(),
+ (bool) $private
);
}
diff --git a/plugins/tracker/include/Tracker/Artifact/Changeset/NewChangesetCreatorBase.class.php b/plugins/tracker/include/Tracker/Artifact/Changeset/NewChangesetCreatorBase.class.php
index 9892924f294..4596cfd7239 100644
--- a/plugins/tracker/include/Tracker/Artifact/Changeset/NewChangesetCreatorBase.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/Changeset/NewChangesetCreatorBase.class.php
@@ -106,7 +106,8 @@ public function create(
bool $send_notification,
string $comment_format,
CreatedFileURLMapping $url_mapping,
- TrackerImportConfig $tracker_import_config
+ TrackerImportConfig $tracker_import_config,
+ bool $private = false
): ?Tracker_Artifact_Changeset {
$comment = trim($comment);
@@ -116,9 +117,9 @@ public function create(
}
try {
- $new_changeset = $this->transaction_executor->execute(function () use ($artifact, $fields_data, $comment, $comment_format, $submitter, $submitted_on, $email, $url_mapping, $tracker_import_config) {
+ $new_changeset = $this->transaction_executor->execute(function () use ($artifact, $fields_data, $comment, $comment_format, $submitter, $submitted_on, $email, $url_mapping, $tracker_import_config, $private) {
try {
- $this->validateNewChangeset($artifact, $fields_data, $comment, $submitter, $email);
+ $this->validateNewChangeset($artifact, $fields_data, $comment, $submitter, $email, $private);
$previous_changeset = $artifact->getLastChangeset();
@@ -161,7 +162,8 @@ public function create(
$submitted_on,
$comment_format,
$changeset_id,
- $url_mapping
+ $url_mapping,
+ $private
)
) {
throw new Tracker_CommentNotStoredException();
@@ -265,7 +267,8 @@ private function storeComment(
$submitted_on,
$comment_format,
$changeset_id,
- CreatedFileURLMapping $url_mapping
+ CreatedFileURLMapping $url_mapping,
+ bool $private = false
): bool {
$comment_format = Tracker_Artifact_Changeset_Comment::checkCommentFormat($comment_format);
@@ -280,7 +283,8 @@ private function storeComment(
$submitter->getId(),
$submitted_on,
0,
- $comment_format
+ $comment_format,
+ $private
);
if (! $comment_added) {
return false;
@@ -303,7 +307,8 @@ private function validateNewChangeset(
array $fields_data,
$comment,
PFUser $submitter,
- $email
+ $email,
+ bool $private = false
): void {
if ($submitter->isAnonymous() && ($email == null || $email == '')) {
$message = dgettext('tuleap-tracker', 'You are not logged in.');
diff --git a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset.class.php b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset.class.php
index b0aa0d57a14..28609a46f86 100644
--- a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset.class.php
@@ -23,6 +23,8 @@
use Tuleap\Tracker\Artifact\Changeset\ChangesetFromXmlDao;
use Tuleap\Tracker\Artifact\Changeset\ChangesetFromXmlDisplayer;
use Tuleap\Tracker\Artifact\Changeset\PostCreation\ActionsRunner;
+use Tuleap\Tracker\Permission\FollowUp\PrivateComments\PermissionsOnPrivateCommentChecker;
+use Tuleap\Tracker\REST\ChangesetRepresentation;
require_once __DIR__ . '/../../../../../src/www/include/utils.php';
@@ -44,6 +46,11 @@ class Tracker_Artifact_Changeset extends Tracker_Artifact_Followup_Item
*/
private $latest_comment;
+ /**
+ * @var bool
+ */
+ public $private_comment_access_denied;
+
/**
* Constructor
*
@@ -250,6 +257,7 @@ public function fetchFollowUp($diff_to_previous, PFUser $current_user)
$html .= $this->getPermalink();
$html .= $this->fetchChangesetActionButtons();
$html .= $this->fetchImportedFromXmlData();
+ $html .= $this->getPrivateBlock();
$html .= $this->getUserLink();
$html .= $this->getTimeAgo($current_user);
$html .= '';
@@ -263,6 +271,15 @@ public function fetchFollowUp($diff_to_previous, PFUser $current_user)
return $html;
}
+ private function getPrivateBlock()
+ {
+ $html = '';
+ if ($this->getComment()->private){
+ $html = ' Private';
+ }
+ return $html;
+ }
+
private function fetchChangesetActionButtons()
{
$html = '';
@@ -395,6 +412,10 @@ public function getFollowUpClassnames($diff_to_previous)
$classnames .= ' tracker_artifact_followup-with_comment ';
}
+ if ($comment && ! $comment->hasEmptyBody() && $comment->private){
+ $classnames .= ' tracker_artifact_followup-with_private_comment ';
+ }
+
if ($this->submitted_by && $this->submitted_by < 100) {
$classnames .= ' tracker_artifact_followup-by_system_user ';
}
@@ -472,14 +493,14 @@ public function userCanEdit(?PFUser $user = null)
*
* @return void
*/
- public function updateComment($body, $user, $comment_format, $timestamp)
+ public function updateComment($body, $user, $comment_format, $timestamp, ?bool $private = null)
{
- if ($this->updateCommentWithoutNotification($body, $user, $comment_format, $timestamp)) {
+ if ($this->updateCommentWithoutNotification($body, $user, $comment_format, $timestamp, $private)) {
$this->executePostCreationActions();
}
}
- public function updateCommentWithoutNotification($body, $user, $comment_format, $timestamp)
+ public function updateCommentWithoutNotification($body, $user, $comment_format, $timestamp, bool $private = false)
{
if ($this->userCanEdit($user)) {
$commentUpdated = $this->getCommentDao()->createNewVersion(
@@ -488,7 +509,8 @@ public function updateCommentWithoutNotification($body, $user, $comment_format,
$user->getId(),
$timestamp,
$this->getComment()->id,
- $comment_format
+ $comment_format,
+ $private
);
unset($this->latest_comment);
@@ -533,11 +555,16 @@ protected function getReferenceManager()
*/
public function getComment()
{
- if (isset($this->latest_comment)) {
+ if ($this->private_comment_access_denied || isset($this->latest_comment)) {
return $this->latest_comment;
}
- if ($row = $this->getCommentDao()->searchLastVersion($this->id)->getRow()) {
+ $user = $this->getUserManager()->getCurrentUser();
+ $tracker = $this->getArtifact()->getTracker();
+
+ $access_private_comments = PermissionsOnPrivateCommentChecker::getInstance()->checkPermission($user, $tracker);
+
+ if ($row = $this->getCommentDao()->searchLastVersion($this->id, $access_private_comments)->getRow()) {
$this->latest_comment = new Tracker_Artifact_Changeset_Comment(
$row['id'],
$this,
@@ -547,7 +574,8 @@ public function getComment()
$row['submitted_on'],
$row['body'],
$row['body_format'],
- $row['parent_id']
+ $row['parent_id'],
+ $row['private']
);
}
return $this->latest_comment;
@@ -562,6 +590,11 @@ public function setLatestComment($comment)
$this->latest_comment = $comment;
}
+ public function setPrivateCommentAccessDenied(bool $private_comment_access_denied): void
+ {
+ $this->private_comment_access_denied = $private_comment_access_denied;
+ }
+
/**
* Return the ChangesetDao
*
@@ -794,7 +827,7 @@ public function getArtifact()
/**
* Returns the Id of this changeset
*
- * @return string The Id of this changeset
+ * @return int The Id of this changeset
*
* @psalm-mutation-free
*/
diff --git a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetFactory.class.php b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetFactory.class.php
index 34f010ccf0d..ccc8328440c 100644
--- a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetFactory.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetFactory.class.php
@@ -1,4 +1,7 @@
changeset_value_dao->searchByArtifactId($artifact->getId());
- $comments_cache = $this->changeset_comment_dao->searchLastVersionForArtifact($artifact->getId());
+ $access_private_comments = PermissionsOnPrivateCommentChecker::getInstance()->checkPermission($user, $artifact->getTracker());
+ $changeset_values_cache = $this->changeset_value_dao->searchByArtifactId($artifact->getId());
+ $comments_cache = $this->changeset_comment_dao->searchLastVersionForArtifact($artifact->getId(), $access_private_comments);
$changesets = $this->getChangesetsForArtifact($artifact);
$previous_changeset = null;
@@ -178,9 +182,12 @@ private function setCommentsFromCache(array $cache, Tracker_Artifact_Changeset $
$row['submitted_on'],
$row['body'],
$row['body_format'],
- $row['parent_id']
+ $row['parent_id'],
+ $row['private']
);
$changeset->setLatestComment($comment);
+ } else {
+ $changeset->setPrivateCommentAccessDenied(true);
}
}
diff --git a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset_Comment.class.php b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset_Comment.class.php
index d4c93891d9e..7d758ab2ef5 100644
--- a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset_Comment.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_Changeset_Comment.class.php
@@ -64,6 +64,7 @@ class Tracker_Artifact_Changeset_Comment
*/
public $bodyFormat;
public $parent_id;
+ public $private;
/**
* @var array of purifier levels to be used when the comment is displayed in text/plain context
@@ -93,6 +94,7 @@ class Tracker_Artifact_Changeset_Comment
* @param string $body The comment (aka follow-up comment)
* @param string $bodyFormat The comment type (text or html follow-up comment)
* @param int $parent_id The id of the parent (if comment has been modified)
+ * @param bool $private This flag indicates the privacy of the comment
*/
public function __construct(
$id,
@@ -103,7 +105,8 @@ public function __construct(
$submitted_on,
$body,
$bodyFormat,
- $parent_id
+ $parent_id,
+ bool $private = false
) {
$this->id = $id;
$this->changeset = $changeset;
@@ -114,6 +117,7 @@ public function __construct(
$this->body = $body;
$this->bodyFormat = $bodyFormat;
$this->parent_id = $parent_id;
+ $this->private = $private;
}
/**
@@ -192,7 +196,11 @@ public function fetchFollowUp(PFUser $current_user)
$html .= '';
+ value="' . $this->private . '" />';
+ $html .= '';
$html .= '
';
if ($this->parent_id && ! trim($this->body)) {
$html .= '
' . dgettext('tuleap-tracker', 'Comment has been cleared') . '';
diff --git a/plugins/tracker/include/Tracker/Artifact/View/Edit.class.php b/plugins/tracker/include/Tracker/Artifact/View/Edit.class.php
index f4cfe7297d0..8025ec48558 100644
--- a/plugins/tracker/include/Tracker/Artifact/View/Edit.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/View/Edit.class.php
@@ -23,6 +23,7 @@
use Tuleap\Tracker\Artifact\MailGateway\MailGatewayConfig;
use Tuleap\Tracker\Artifact\MailGateway\MailGatewayConfigDao;
use Tuleap\Tracker\Artifact\RichTextareaProvider;
+use Tuleap\Tracker\Permission\FollowUp\PrivateComments\PermissionsOnPrivateCommentChecker;
use Tuleap\Tracker\Workflow\PostAction\FrozenFields\FrozenFieldDetector;
use Tuleap\Tracker\Workflow\PostAction\FrozenFields\FrozenFieldsRetriever;
use Tuleap\Tracker\Workflow\SimpleMode\SimpleWorkflowDao;
@@ -88,7 +89,7 @@ public function fetch()
if (! $submitted_values || ! is_array($submitted_values)) {
$submitted_values = [];
}
- $html_form = $this->renderer->fetchFields($this->artifact, $submitted_values);
+ $html_form = $this->renderer->fetchFields($this->artifact, $submitted_values);
$html_form .= $this->fetchFollowUps($this->request->get('artifact_followup_comment'));
$html .= $this->renderer->fetchArtifactForm($html_form);
@@ -119,6 +120,10 @@ private function fetchFollowUps($submitted_comment = '')
$tracker = $this->artifact->getTracker();
$invert_order = $this->user->getPreference(self::USER_PREFERENCE_INVERT_ORDER . '_' . $tracker->getId()) == false;
+ if (PermissionsOnPrivateCommentChecker::getInstance()->checkPermission($this->user, $tracker)) {
+ $html .= "";
+ }
+
$classname = 'tracker_artifact_followup_comments-display_changes';
$display_changes = $this->user->getPreference(self::USER_PREFERENCE_DISPLAY_CHANGES);
if ($display_changes !== false && $display_changes == 0) {
@@ -188,6 +193,9 @@ private function fetchSettingsButton($invert_order, $display_changes)
return $html;
}
+ /**
+ * @var Tracker_Artifact_Changeset[] $comments
+ */
private function fetchCommentContent(array $comments, $invert_comments)
{
$html = '';
@@ -199,13 +207,15 @@ private function fetchCommentContent(array $comments, $invert_comments)
foreach ($comments as $item) {
\assert($item instanceof Tracker_Artifact_Followup_Item);
if ($previous_item) {
- $diff_to_previous = $item->diffToPreviousArtifactView($this->user, $previous_item);
- $classnames = 'tracker_artifact_followup ';
- $classnames .= $item->getFollowUpClassnames($diff_to_previous);
- $comment_html = '
';
- $comment_html .= $item->fetchFollowUp($diff_to_previous, $this->user);
- $comment_html .= '';
- $comments_content[] = $comment_html;
+ $diff_to_previous = $item->diffToPreviousArtifactView($this->user, $previous_item);
+ if ($item->getComment() || $diff_to_previous) {
+ $classnames = 'tracker_artifact_followup ';
+ $classnames .= $item->getFollowUpClassnames($diff_to_previous);
+ $comment_html = '
';
+ $comment_html .= $item->fetchFollowUp($diff_to_previous, $this->user);
+ $comment_html .= '';
+ $comments_content[] = $comment_html;
+ }
}
$previous_item = $item;
}
diff --git a/plugins/tracker/include/Tracker/Artifact/dao/Tracker_Artifact_Changeset_CommentDao.class.php b/plugins/tracker/include/Tracker/Artifact/dao/Tracker_Artifact_Changeset_CommentDao.class.php
index de36856c672..e49ceafe8ad 100644
--- a/plugins/tracker/include/Tracker/Artifact/dao/Tracker_Artifact_Changeset_CommentDao.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/dao/Tracker_Artifact_Changeset_CommentDao.class.php
@@ -27,17 +27,18 @@ public function __construct()
$this->table_name = 'tracker_changeset_comment';
}
- public function searchLastVersion($changeset_id)
+ public function searchLastVersion($changeset_id, bool $access_private = false)
{
- $changeset_id = $this->da->escapeInt($changeset_id);
- $sql = "SELECT * FROM $this->table_name
- WHERE changeset_id = $changeset_id
+ $changeset_id = $this->da->escapeInt($changeset_id);
+ $private_where = $access_private ? '' : 'AND private = 0';
+ $sql = "SELECT * FROM $this->table_name
+ WHERE changeset_id = $changeset_id $private_where
ORDER BY id DESC
LIMIT 1";
return $this->retrieve($sql);
}
- public function searchLastVersionForArtifact($artifact_id)
+ public function searchLastVersionForArtifact($artifact_id, bool $private = false)
{
$artifact_id = $this->da->escapeInt($artifact_id);
$sql = "SELECT comment_v1.*
@@ -48,13 +49,16 @@ public function searchLastVersionForArtifact($artifact_id)
AND comment_v2.id IS NULL
AND comment_v1.id IS NOT NULL";
$result = [];
+ if (!$private) {
+ $sql .= " AND comment_v1.private = 0";
+ }
foreach ($this->retrieve($sql) as $row) {
$result[$row['changeset_id']] = $row;
}
return $result;
}
- public function createNewVersion($changeset_id, $body, $submitted_by, $submitted_on, $parent_id, $body_format)
+ public function createNewVersion($changeset_id, $body, $submitted_by, $submitted_on, $parent_id, $body_format, bool $private = false)
{
$stripped_body = $this->extractStrippedBody($body, $body_format);
@@ -65,14 +69,15 @@ public function createNewVersion($changeset_id, $body, $submitted_by, $submitted
$submitted_on = $this->da->escapeInt($submitted_on);
$parent_id = $this->da->escapeInt($parent_id);
$escaped_stripped_body = $this->da->quoteSmart($stripped_body);
+ $private = ($private) ? 1 : 0;
- $sql = "INSERT INTO $this->table_name (changeset_id, body, body_format, submitted_by, submitted_on, parent_id)
- VALUES ($changeset_id, $body, $body_format, $submitted_by, $submitted_on, $parent_id)";
+ $sql = "INSERT INTO $this->table_name (changeset_id, body, body_format, submitted_by, submitted_on, parent_id, private)
+ VALUES ($changeset_id, $body, $body_format, $submitted_by, $submitted_on, $parent_id, $private)";
$id = $this->updateAndGetLastId($sql);
if ($stripped_body !== "") {
$sql = "INSERT INTO tracker_changeset_comment_fulltext (comment_id, stripped_body)
- VALUES ($id, $escaped_stripped_body)";
+ VALUES ($id, $escaped_stripped_body)";
$this->update($sql);
}
diff --git a/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpController.php b/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpController.php
new file mode 100644
index 00000000000..2cb154bc203
--- /dev/null
+++ b/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpController.php
@@ -0,0 +1,138 @@
+tracker_factory = $tracker_factory;
+ $this->renderer = $renderer;
+ $this->presenter_builder = new Tracker_Permission_PermissionPresenterBuilder();
+ }
+
+ /**
+ * Is able to process a request routed by FrontRouter
+ *
+ * @param array $variables
+ * @return void
+ * @throws ForbiddenException
+ * @throws NotFoundException
+ */
+ public function process(HTTPRequest $request, BaseLayout $layout, array $variables)
+ {
+ $tracker = $this->tracker_factory->getTrackerById($variables['id']);
+ if (! $tracker || ! $tracker->isActive()) {
+ throw new NotFoundException();
+ }
+ if (! $tracker->userIsAdmin($request->getCurrentUser())) {
+ throw new ForbiddenException();
+ }
+
+ $this->display($tracker, $request);
+ }
+
+ protected function display(\Tracker $tracker, HTTPRequest $request)
+ {
+ $ugroups_permissions = $this->getUGroupList($tracker);
+
+ $tracker_manager = new \TrackerManager();
+
+ $title = dgettext('tuleap-tracker', 'Fallow Up Permission');
+
+ $tracker->displayAdminPermsHeader($tracker_manager, $title);
+
+ $this->renderer->renderToPage(
+ 'follow-up',
+ new FollowUpPresenter(
+ $tracker,
+ $ugroups_permissions
+ )
+ );
+
+ $tracker->displayFooter($tracker_manager);
+ }
+
+ private function getUGroupList(Tracker $tracker)
+ {
+ $ugroup_list = [];
+
+ $ugroups_permissions = plugin_tracker_permission_get_tracker_ugroups_permissions($tracker->getGroupId(), $tracker->getId());
+ ksort($ugroups_permissions);
+ reset($ugroups_permissions);
+ foreach ($ugroups_permissions as $ugroup_permissions) {
+ $ugroup = $ugroup_permissions['ugroup'];
+
+ if ($ugroup['id'] != ProjectUGroup::PROJECT_ADMIN) {
+ $ugroup_list[] = $ugroup;
+ }
+ }
+
+ $private_comments_ugroup_list = $this->getPrivateCommentsUGroup($tracker);
+
+ $private_comments_ugroup_list = array_column($private_comments_ugroup_list, 'ugroup_id');
+
+ foreach ($ugroup_list as $key => $ugroup)
+ {
+ if (in_array((int)$ugroup['id'], $private_comments_ugroup_list)) {
+ $ugroup_list[$key]['selected'] = true;
+ }
+ }
+
+ unset($key, $ugroup);
+
+ return $ugroup_list;
+ }
+
+ private function getPrivateCommentsUGroup(Tracker $tracker)
+ {
+ $dao = new TrackerPrivateCommentsDao();
+ $private_comments_groups = $dao->getAccessUgroupsByTrackerId($tracker->getId());
+ return $private_comments_groups;
+ }
+
+}
diff --git a/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpPresenter.php b/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpPresenter.php
new file mode 100644
index 00000000000..acf8c37250d
--- /dev/null
+++ b/plugins/tracker/include/Tracker/Permission/FollowUp/FollowUpPresenter.php
@@ -0,0 +1,44 @@
+update_url = PermissionsOnPrivateCommentsUpdateController::getUrl($tracker);
+ $this->ugroup_list = $ugroup_list;
+ }
+}
diff --git a/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentChecker.php b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentChecker.php
new file mode 100644
index 00000000000..583e6b25043
--- /dev/null
+++ b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentChecker.php
@@ -0,0 +1,106 @@
+getUgroups($tracker->getProject()->getID(), []);
+ $private_comments_groups = $this->getPrivateCommentsGroups($tracker);
+
+ if (!count($private_comments_groups) > 0) {
+ return false;
+ }
+
+ if ($user->isAdmin($tracker->getProject()->getID())) {
+ return true;
+ }
+
+ return count(array_intersect($user_ugroups, $private_comments_groups)) > 0;
+ }
+
+ private function getPrivateCommentsGroups(Tracker $tracker): array
+ {
+ if ($hashGroup = $this->getHashGroup()) {
+ return $hashGroup;
+ }
+
+ if (!$this->private_comments_dao instanceof TrackerPrivateCommentsDao) {
+ $this->private_comments_dao = $this->getTrackerPrivateCommentsDao();
+ }
+
+ $test = $this->private_comments_dao->getAccessUgroupsByTrackerId($tracker->getId());
+
+ $private_comments_groups = array_column(
+ $this->private_comments_dao->getAccessUgroupsByTrackerId($tracker->getId()),
+ 'ugroup_id'
+ );
+
+ $this->setHashGroup($private_comments_groups);
+
+ return $private_comments_groups;
+ }
+
+ private function getHashGroup(): array
+ {
+ return $this->hashPrivateAccessGroups;
+ }
+
+ private function setHashGroup($privateCommentsGroups): void
+ {
+ $this->hashPrivateAccessGroups = $privateCommentsGroups;
+ }
+
+ private function getTrackerPrivateCommentsDao(): TrackerPrivateCommentsDao
+ {
+ return new TrackerPrivateCommentsDao();
+ }
+}
diff --git a/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentsUpdateController.php b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentsUpdateController.php
new file mode 100644
index 00000000000..78bd5bad885
--- /dev/null
+++ b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentsUpdateController.php
@@ -0,0 +1,111 @@
+tracker_factory = $tracker_factory;
+ $this->tracker_private_comments_dao = $tracker_private_comments_dao;
+ }
+
+ /**
+ * Is able to process a request routed by FrontRouter
+ *
+ * @param array $variables
+ * @return void
+ * @throws ForbiddenException
+ * @throws NotFoundException
+ */
+ public function process(HTTPRequest $request, BaseLayout $layout, array $variables)
+ {
+ $tracker = $this->tracker_factory->getTrackerById($variables['id']);
+ if (! $tracker || ! $tracker->isActive()) {
+ throw new NotFoundException();
+ }
+ if (! $tracker->userIsAdmin($request->getCurrentUser())) {
+ throw new ForbiddenException();
+ }
+
+ if ($tracker->userIsAdmin($request->getCurrentUser())) {
+ if ($request->exist('update')) {
+ if ($request->exist('ugroups') && is_array($request->get('ugroups'))) {
+ $this->tracker_private_comments_dao->updateUgroupsByTrackerId(
+ $tracker->getId(),
+ $request->get('ugroups')
+ );
+ $layout->addFeedback(
+ Feedback::INFO,
+ $GLOBALS['Language']->getText('project_admin_userperms', 'perm_upd')
+ );
+ $layout->redirect('/plugins/tracker/permissions/follow-up/' . $tracker->getId());
+ } else {
+ $this->tracker_private_comments_dao->deleteUgroupsByTrackerId($tracker->getId());
+ $layout->addFeedback(
+ Feedback::INFO,
+ $GLOBALS['Language']->getText('project_admin_userperms', 'perm_upd')
+ );
+ $layout->redirect('/plugins/tracker/permissions/follow-up/' . $tracker->getId());
+ }
+ }
+ } else {
+ $layout->addFeedback(
+ Feedback::ERROR,
+ dgettext(
+ 'tuleap-tracker',
+ 'Access denied. You don\'t have permissions to perform this action.'
+ )
+ );
+ $layout->redirect(TRACKER_BASE_URL . '/?tracker=' . $tracker->getId());
+ }
+ }
+
+ public static function getUrl(Tracker $tracker)
+ {
+ return TRACKER_BASE_URL . self::URL . '/' . $tracker->getId();
+ }
+}
diff --git a/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/TrackerPrivateCommentsDao.php b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/TrackerPrivateCommentsDao.php
new file mode 100644
index 00000000000..6651b285c1a
--- /dev/null
+++ b/plugins/tracker/include/Tracker/Permission/FollowUp/PrivateComments/TrackerPrivateCommentsDao.php
@@ -0,0 +1,71 @@
+ $tracker_id,
+ 'ugroup_id' => $ugroup_id
+ ];
+ }
+ unset ($ugroup_id);
+
+ $this->getDB()->beginTransaction();
+ try {
+ $this->getDB()->delete(
+ 'tracker_private_comment_permission',
+ ['tracker_id' => $tracker_id]
+ );
+ $this->getDB()->insertMany(
+ 'tracker_private_comment_permission',
+ $data_insert
+ );
+ } catch (\PDOException $exception) {
+ $this->getDB()->rollBack();
+ return false;
+ }
+ $this->getDB()->commit();
+ return true;
+ }
+
+ public function deleteUgroupsByTrackerId($tracker_id): void
+ {
+ $this->getDB()->delete(
+ 'tracker_private_comment_permission',
+ ['tracker_id' => $tracker_id]
+ );
+ }
+
+ public function getAccessUgroupsByTrackerId($tracker_id)
+ {
+ $sql = "SELECT *
+ FROM tracker_private_comment_permission
+ WHERE tracker_id = ?";
+ return $this->getDB()->run($sql, $tracker_id);
+ }
+}
diff --git a/plugins/tracker/include/trackerPlugin.php b/plugins/tracker/include/trackerPlugin.php
index 58c12e614a7..bd8b08c2d15 100644
--- a/plugins/tracker/include/trackerPlugin.php
+++ b/plugins/tracker/include/trackerPlugin.php
@@ -189,6 +189,9 @@
use Tuleap\Tracker\Permission\Fields\ByField\ByFieldController;
use Tuleap\Tracker\Permission\Fields\ByGroup\ByGroupController;
use Tuleap\Tracker\Permission\Fields\PermissionsOnFieldsUpdateController;
+use Tuleap\Tracker\Permission\FollowUp\FollowUpController;
+use Tuleap\Tracker\Permission\FollowUp\PrivateComments\PermissionsOnPrivateCommentsUpdateController;
+use Tuleap\Tracker\Permission\FollowUp\PrivateComments\TrackerPrivateCommentsDao;
use Tuleap\Tracker\PermissionsPerGroup\ProjectAdminPermissionPerGroupPresenterBuilder;
use Tuleap\Tracker\ProjectDeletionEvent;
use Tuleap\Tracker\Reference\ReferenceCreator;
@@ -1991,6 +1994,20 @@ public function routeChangesetContentRetriever(): TextDiffRetriever
);
}
+ public function routeGetFollowUpPermissions(): FollowUpController
+ {
+ return new FollowUpController(TrackerFactory::instance(), TemplateRendererFactory::build()->getRenderer(__DIR__ . '/../templates/permission'));
+ }
+
+ public function routePostPrivateCommentsPermissions(): DispatchableWithRequest
+ {
+ return new PermissionsOnPrivateCommentsUpdateController(
+ TrackerFactory::instance(),
+ new TrackerPrivateCommentsDao()
+ );
+ }
+
+
public function collectRoutesEvent(\Tuleap\Request\CollectRoutesEvent $event)
{
$event->getRouteCollector()->addGroup(TRACKER_BASE_URL, function (FastRoute\RouteCollector $r) {
@@ -2010,6 +2027,9 @@ public function collectRoutesEvent(\Tuleap\Request\CollectRoutesEvent $event)
$r->get(ByGroupController::URL . '/{id:\d+}', $this->getRouteHandler('routeGetFieldsPermissionsByGroup'));
$r->post(PermissionsOnFieldsUpdateController::URL . '/{id:\d+}', $this->getRouteHandler('routePostFieldsPermissions'));
+ $r->get(FollowUpController::URL . '/{id:\d+}', $this->getRouteHandler('routeGetFollowUpPermissions'));
+ $r->post(PermissionsOnPrivateCommentsUpdateController::URL . '/{id:\d+}', $this->getRouteHandler('routePostPrivateCommentsPermissions'));
+
$r->post('/webhooks/delete', $this->getRouteHandler('routePostWebhooksDelete'));
$r->post('/webhooks/create', $this->getRouteHandler('routePostWebhooksCreate'));
$r->post('/webhooks/edit', $this->getRouteHandler('routePostWebhooksEdit'));
diff --git a/plugins/tracker/scripts/legacy/TrackerArtifact.js b/plugins/tracker/scripts/legacy/TrackerArtifact.js
index 1abae8be68b..f3f37f740e2 100644
--- a/plugins/tracker/scripts/legacy/TrackerArtifact.js
+++ b/plugins/tracker/scripts/legacy/TrackerArtifact.js
@@ -191,6 +191,13 @@ document.observe("dom:loaded", function () {
return { value: content, htmlFormat: htmlFormat };
}
+ function getPrivateFormat(id) {
+ if ($("tracker_artifact_followup_comment_private_" + id).value) {
+ return true;
+ }
+ return false;
+ }
+
$$(".tracker_artifact_followup_comment_controls_edit button").each(function (edit) {
var id = edit.up(".tracker_artifact_followup").id;
var data;
@@ -206,6 +213,7 @@ document.observe("dom:loaded", function () {
class: "user-mention",
});
var htmlFormat = false;
+ var privateFormat = false;
if (comment_panel.empty()) {
textarea.value = "";
@@ -213,6 +221,7 @@ document.observe("dom:loaded", function () {
data = getTextAreaValueAndHtmlFormat(comment_panel, id);
textarea.value = data.value;
htmlFormat = data.htmlFormat;
+ privateFormat = getPrivateFormat(id);
}
var rteSpan = new Element("span", { style: "text-align: left;" }).update(
@@ -223,6 +232,7 @@ document.observe("dom:loaded", function () {
);
comment_panel.insert({ before: edit_panel });
var name = "comment_format" + id;
+ // eslint-disable-next-line no-new
new tuleap.textarea.RTE(textarea, {
toggle: true,
default_in_html: false,
@@ -230,6 +240,7 @@ document.observe("dom:loaded", function () {
name: name,
htmlFormat: htmlFormat,
full_width: true,
+ privateFormat: privateFormat,
});
var nb_rows_displayed = 5;
diff --git a/plugins/tracker/templates/admin-header.mustache b/plugins/tracker/templates/admin-header.mustache
index 4b8024334d7..64db8c6b865 100644
--- a/plugins/tracker/templates/admin-header.mustache
+++ b/plugins/tracker/templates/admin-header.mustache
@@ -33,6 +33,11 @@
{{# dgettext }}tuleap-tracker | Fields permissions{{/ dgettext }}
+
+
+ {{# dgettext }}tuleap-tracker | Follow-up permissions{{/ dgettext }}
+
+
diff --git a/plugins/tracker/templates/permission/follow-up.mustache b/plugins/tracker/templates/permission/follow-up.mustache
new file mode 100644
index 00000000000..73b57d03053
--- /dev/null
+++ b/plugins/tracker/templates/permission/follow-up.mustache
@@ -0,0 +1,20 @@
+{{# dgettext }} tuleap-tracker |Fallow Up Permissions{{/ dgettext }}
+
+
diff --git a/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php b/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php
new file mode 100644
index 00000000000..ef00eaf4e61
--- /dev/null
+++ b/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php
@@ -0,0 +1,105 @@
+user = \Mockery::spy(\PFUser::class);
+ $this->user->shouldReceive('getId')->andReturns(120);
+ $this->user->shouldReceive('isMemberOfUGroup')->andReturns(false);
+ $this->user->shouldReceive('isSuperUser')->andReturns(false);
+ $this->user->shouldReceive('isMember')->with(12)->andReturns(true);
+
+ $this->project = \Mockery::spy(\Project::class);
+ $this->project->shouldReceive('getID')->andReturns(120);
+ $this->project->shouldReceive('isPublic')->andReturns(true);
+ $this->project->shouldReceive('isActive')->andReturns(true);
+
+ $this->tracker = \Mockery::spy(\Tracker::class);
+ $this->tracker->shouldReceive('getId')->andReturns(666);
+ $this->tracker->shouldReceive('getGroupId')->andReturns(222);
+ $this->tracker->shouldReceive('getProject')->andReturns($this->project);
+
+ $this->premission_private_comment_checker = \Mockery::mock(PermissionsOnPrivateCommentChecker::class);
+ $this->private_comment_dao = \Mockery::spy(TrackerPrivateCommentsDao::class);
+
+ $this->premission_private_comment_checker->shouldReceive()->andReturn($this->private_comment_dao);
+ $this->premission_private_comment_checker->shouldReceive('getInstance')
+ ->andReturn(PermissionsOnPrivateCommentChecker::getInstance());
+ }
+
+ function testItUniqSingleton(): void
+ {
+ $this->premission_private_comment_checker->shouldReceive('getInstance')
+ ->andReturn(PermissionsOnPrivateCommentChecker::getInstance());
+
+ $fCall = $this->premission_private_comment_checker::getInstance();
+ $sCall = $this->premission_private_comment_checker::getInstance();
+
+ $this->assertInstanceOf(PermissionsOnPrivateCommentChecker::class, $fCall );
+ $this->assertSame($fCall, $sCall);
+ }
+
+ function TestItgetPrivateCommentsGroups(): void
+ {
+ $this->private_comment_dao->shouldReceive('getAccessUgroupsByTrackerId')
+ ->andReturn([
+ [
+ 'id' => 125,
+ 'tracker_id' => 2,
+ 'ugroup_id' => 1
+ ],
+ [
+ 'id' => 134,
+ 'tracker_id' => 2,
+ 'ugroup_id' => 3
+ ]
+ ]);
+ }
+}
diff --git a/plugins/tracker/themes/default/css/style.scss b/plugins/tracker/themes/default/css/style.scss
index 101fe87eaa7..66c3f266f59 100644
--- a/plugins/tracker/themes/default/css/style.scss
+++ b/plugins/tracker/themes/default/css/style.scss
@@ -3354,6 +3354,38 @@ td.tracker-unsubscriber-actions {
display: none;
}
+.rte_header {
+ display: flex;
+ justify-content: space-between;
+}
+
+.rte_private {
+ input.checkbox {
+ margin: 0;
+ }
+}
+
+.label__private {
+ position: relative;
+ bottom: 2px;
+ text-transform: uppercase;
+ background: #d22;
+ color: #fff;
+ font-weight: bold;
+ padding: 0px 2px 0px 2px;
+ font-size: 60%;
+ margin-right: 2px;
+ border-radius: 2px;
+}
+
+.comment-body__private {
+ background-color: #dcede4;
+}
+
+.tracker_artifact_followup-with_private_comment {
+ background-color: #dcede4;
+}
+
.tracker-selector {
display: inline-block;
width: 250px;
diff --git a/src/scripts/tuleap/textarea_rte.js b/src/scripts/tuleap/textarea_rte.js
index caa77d91355..0be0ffe2130 100644
--- a/src/scripts/tuleap/textarea_rte.js
+++ b/src/scripts/tuleap/textarea_rte.js
@@ -22,6 +22,7 @@ import "regenerator-runtime/runtime";
/* global Class:readonly Builder:readonly $:readonly */
var tuleap = window.tuleap || {};
tuleap.textarea = tuleap.textarea || {};
+tuleap.private_access = Boolean(tuleap.private_access);
import "../codendi/RichTextEditor.js";
@@ -30,11 +31,34 @@ tuleap.textarea.RTE = Class.create(window.codendi.RTE, {
options = Object.extend({ toolbar: "tuleap" }, options || {});
this.options = Object.extend({ htmlFormat: false, id: 0 }, options || {});
$super(element, options);
- // This div contains comment format selection buttons
+ // This div contains comment format selection buttons and checkbox private comments
var div = Builder.node("div");
+ var header = Builder.node("div", { class: "rte_header" });
var select_container = Builder.node("div", { class: "rte_format" });
+
select_container.appendChild(document.createTextNode("Format : "));
- div.appendChild(select_container);
+ header.appendChild(select_container);
+ div.appendChild(header);
+
+ if (element.id === "tracker_followup_comment_new" && tuleap.private_access) {
+ const private_container = Builder.node("div", { class: "rte_private" });
+ private_container.appendChild(document.createTextNode("Private comment : "));
+ header.appendChild(private_container);
+ const checkbox = Builder.node("input", {
+ type: "checkbox",
+ id: "private_comment_input" + this.options.id,
+ name: "private_comment_input" + this.options.id,
+ class: "checkbox",
+ });
+ private_container.appendChild(checkbox);
+ checkbox.addEventListener("change", () => {
+ element.classList.toggle("comment-body__private");
+ });
+ }
+
+ if (this.options.privateFormat) {
+ element.classList.toggle("comment-body__private");
+ }
var div_clear = Builder.node("div", { class: "rte_clear" });
div.appendChild(div_clear);
@@ -133,6 +157,7 @@ tuleap.textarea.RTE = Class.create(window.codendi.RTE, {
var id = this.element.id;
$super();
+
(function recordRequiredAttribute() {
if ($(id).hasAttribute("required")) {
$(id).removeAttribute("required");
{{# dgettext }} tuleap-tracker |Fallow Up Permissions{{/ dgettext }}
+ + diff --git a/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php b/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php new file mode 100644 index 00000000000..ef00eaf4e61 --- /dev/null +++ b/plugins/tracker/tests/unit/Tracker/Permission/FollowUp/PrivateComments/PermissionsOnPrivateCommentCheckerTest.php @@ -0,0 +1,105 @@ +user = \Mockery::spy(\PFUser::class); + $this->user->shouldReceive('getId')->andReturns(120); + $this->user->shouldReceive('isMemberOfUGroup')->andReturns(false); + $this->user->shouldReceive('isSuperUser')->andReturns(false); + $this->user->shouldReceive('isMember')->with(12)->andReturns(true); + + $this->project = \Mockery::spy(\Project::class); + $this->project->shouldReceive('getID')->andReturns(120); + $this->project->shouldReceive('isPublic')->andReturns(true); + $this->project->shouldReceive('isActive')->andReturns(true); + + $this->tracker = \Mockery::spy(\Tracker::class); + $this->tracker->shouldReceive('getId')->andReturns(666); + $this->tracker->shouldReceive('getGroupId')->andReturns(222); + $this->tracker->shouldReceive('getProject')->andReturns($this->project); + + $this->premission_private_comment_checker = \Mockery::mock(PermissionsOnPrivateCommentChecker::class); + $this->private_comment_dao = \Mockery::spy(TrackerPrivateCommentsDao::class); + + $this->premission_private_comment_checker->shouldReceive()->andReturn($this->private_comment_dao); + $this->premission_private_comment_checker->shouldReceive('getInstance') + ->andReturn(PermissionsOnPrivateCommentChecker::getInstance()); + } + + function testItUniqSingleton(): void + { + $this->premission_private_comment_checker->shouldReceive('getInstance') + ->andReturn(PermissionsOnPrivateCommentChecker::getInstance()); + + $fCall = $this->premission_private_comment_checker::getInstance(); + $sCall = $this->premission_private_comment_checker::getInstance(); + + $this->assertInstanceOf(PermissionsOnPrivateCommentChecker::class, $fCall ); + $this->assertSame($fCall, $sCall); + } + + function TestItgetPrivateCommentsGroups(): void + { + $this->private_comment_dao->shouldReceive('getAccessUgroupsByTrackerId') + ->andReturn([ + [ + 'id' => 125, + 'tracker_id' => 2, + 'ugroup_id' => 1 + ], + [ + 'id' => 134, + 'tracker_id' => 2, + 'ugroup_id' => 3 + ] + ]); + } +} diff --git a/plugins/tracker/themes/default/css/style.scss b/plugins/tracker/themes/default/css/style.scss index 101fe87eaa7..66c3f266f59 100644 --- a/plugins/tracker/themes/default/css/style.scss +++ b/plugins/tracker/themes/default/css/style.scss @@ -3354,6 +3354,38 @@ td.tracker-unsubscriber-actions { display: none; } +.rte_header { + display: flex; + justify-content: space-between; +} + +.rte_private { + input.checkbox { + margin: 0; + } +} + +.label__private { + position: relative; + bottom: 2px; + text-transform: uppercase; + background: #d22; + color: #fff; + font-weight: bold; + padding: 0px 2px 0px 2px; + font-size: 60%; + margin-right: 2px; + border-radius: 2px; +} + +.comment-body__private { + background-color: #dcede4; +} + +.tracker_artifact_followup-with_private_comment { + background-color: #dcede4; +} + .tracker-selector { display: inline-block; width: 250px; diff --git a/src/scripts/tuleap/textarea_rte.js b/src/scripts/tuleap/textarea_rte.js index caa77d91355..0be0ffe2130 100644 --- a/src/scripts/tuleap/textarea_rte.js +++ b/src/scripts/tuleap/textarea_rte.js @@ -22,6 +22,7 @@ import "regenerator-runtime/runtime"; /* global Class:readonly Builder:readonly $:readonly */ var tuleap = window.tuleap || {}; tuleap.textarea = tuleap.textarea || {}; +tuleap.private_access = Boolean(tuleap.private_access); import "../codendi/RichTextEditor.js"; @@ -30,11 +31,34 @@ tuleap.textarea.RTE = Class.create(window.codendi.RTE, { options = Object.extend({ toolbar: "tuleap" }, options || {}); this.options = Object.extend({ htmlFormat: false, id: 0 }, options || {}); $super(element, options); - // This div contains comment format selection buttons + // This div contains comment format selection buttons and checkbox private comments var div = Builder.node("div"); + var header = Builder.node("div", { class: "rte_header" }); var select_container = Builder.node("div", { class: "rte_format" }); + select_container.appendChild(document.createTextNode("Format : ")); - div.appendChild(select_container); + header.appendChild(select_container); + div.appendChild(header); + + if (element.id === "tracker_followup_comment_new" && tuleap.private_access) { + const private_container = Builder.node("div", { class: "rte_private" }); + private_container.appendChild(document.createTextNode("Private comment : ")); + header.appendChild(private_container); + const checkbox = Builder.node("input", { + type: "checkbox", + id: "private_comment_input" + this.options.id, + name: "private_comment_input" + this.options.id, + class: "checkbox", + }); + private_container.appendChild(checkbox); + checkbox.addEventListener("change", () => { + element.classList.toggle("comment-body__private"); + }); + } + + if (this.options.privateFormat) { + element.classList.toggle("comment-body__private"); + } var div_clear = Builder.node("div", { class: "rte_clear" }); div.appendChild(div_clear); @@ -133,6 +157,7 @@ tuleap.textarea.RTE = Class.create(window.codendi.RTE, { var id = this.element.id; $super(); + (function recordRequiredAttribute() { if ($(id).hasAttribute("required")) { $(id).removeAttribute("required");