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 }} + +