Commit 8af68477 authored by Aaron Wells's avatar Aaron Wells

Hide deleted comments unless they're needed for context

Bug 1580499. Updating comments feature to adapt to the new
behavior, and removing redundant "feedback_configuration" feature
file.

Change-Id: Ib48cbb19f6ab9cc4937f31cef504724569680e1f
parent 388980ad
......@@ -218,7 +218,7 @@ class PluginBlocktypeTaggedposts extends MaharaCoreBlocktype {
$artefact = new ArtefactTypeBlogpost($result->id);
// get comments for this post
$result->commentcount = count_records_select('artefact_comment_comment', "onartefact = {$result->id} AND private = 0 AND deletedby IS NULL");
$result->commentcount = count_records_select('artefact_comment_comment', "onartefact = {$result->id} AND private = 0 AND deletedby IS NULL AND hidden=0");
$allowcomments = $artefact->get('allowcomments');
if (empty($result->commentcount) && empty($allowcomments)) {
$result->commentcount = null;
......
......@@ -26,6 +26,7 @@
<FIELD NAME="rating" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="lastcontentupdate" TYPE="datetime" NOTNULL="false" />
<FIELD NAME="threadedposition" TYPE="int" LENGTH="4" NOTNULL="false" />
<FIELD NAME="hidden" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" UNSIGNED="true" />
</FIELDS>
<KEYS>
<KEY NAME="artefactpk" TYPE="primary" FIELDS="artefact" />
......
......@@ -136,5 +136,74 @@ function xmldb_artefact_comment_upgrade($oldversion=0) {
}
}
if ($oldversion < 2016051000) {
log_debug('Adding "hidden" column to artefact_comment_comment');
$table = new XMLDBTable('artefact_comment_comment');
$field = new XMLDBField('hidden');
if (field_exists($table, $field)) {
log_debug('... column already exists');
}
else {
$field->setAttributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null, null, 0);
$success = $success && add_field($table, $field);
log_debug('Checking for existing deleted comments that should now be hidden');
// Comments on the end of a thread are those which have a parent, but are not
// parent to anyone else.
// Top-level comments (not in a thread) are those which have the highest
// threadedposition in their context
$sql = <<<SQL
SELECT
acc.artefact
FROM
{artefact_comment_comment} acc
INNER JOIN {artefact} a
ON acc.artefact = a.id
WHERE
acc.deletedby IS NOT NULL
AND a.parent IS NOT NULL
AND a.artefacttype = 'comment'
AND acc.hidden = 0
AND NOT EXISTS (
SELECT 1
FROM {artefact} a1
WHERE
a1.parent = a.id
AND a1.artefacttype = 'comment'
)
UNION
SELECT acc.artefact
FROM
{artefact_comment_comment} acc
INNER JOIN (
SELECT
onview,
onartefact,
MAX(threadedposition) AS threadedposition
FROM {artefact_comment_comment}
GROUP BY
onview, onartefact
ORDER BY onview, onartefact
) lastcomments
ON
(acc.onview = lastcomments.onview
OR acc.onartefact = lastcomments.onartefact)
AND acc.threadedposition = lastcomments.threadedposition
WHERE
acc.deletedby IS NOT NULL
AND acc.hidden = 0
SQL;
$comments = get_records_sql_array($sql);
if ($comments) {
foreach ($comments as $c) {
$comment = new ArtefactTypeComment($c->artefact);
$comment->set('hidden', 1);
$comment->commit();
$comment->hide_deleted_parents();
}
}
}
}
return $success;
}
......@@ -87,7 +87,7 @@ class PluginArtefactComment extends PluginArtefact {
if (!$artefacts = get_column_sql("
SELECT artefact
FROM {artefact_comment_comment}
WHERE deletedby IS NULL AND onview IN (" . join(',', array_map('intval', $viewids)) . ')', array())) {
WHERE deletedby IS NULL AND hidden=0 AND onview IN (" . join(',', array_map('intval', $viewids)) . ')', array())) {
return array();
}
if ($attachments = get_column_sql('
......@@ -103,7 +103,7 @@ class PluginArtefactComment extends PluginArtefact {
if (!$artefacts = get_column_sql("
SELECT artefact
FROM {artefact_comment_comment}
WHERE deletedby IS NULL AND onartefact IN (" . join(',', $artefactids) . ')', array())) {
WHERE deletedby IS NULL AND hidden=0 AND onartefact IN (" . join(',', $artefactids) . ')', array())) {
return array();
}
if ($attachments = get_column_sql('
......@@ -182,6 +182,7 @@ class ArtefactTypeComment extends ArtefactType {
protected $rating;
protected $lastcontentupdate;
protected $threadedposition;
protected $hidden;
public function __construct($id = 0, $data = null) {
parent::__construct($id, $data);
......@@ -236,6 +237,7 @@ class ArtefactTypeComment extends ArtefactType {
'requestpublic' => $this->get('requestpublic'),
'rating' => $this->get('rating'),
'threadedposition' => $this->get('threadedposition'),
'hidden' => $this->get('hidden'),
);
if ($this->get('lastcontentupdate')) {
$data->lastcontentupdate = db_format_timestamp($this->get('lastcontentupdate'));
......@@ -412,11 +414,12 @@ class ArtefactTypeComment extends ArtefactType {
'data' => array(),
);
$where = 'c.hidden = 0';
if (!empty($artefactid)) {
$where = 'c.onartefact = ' . (int)$artefactid;
$where .= ' AND c.onartefact = ' . (int)$artefactid;
}
else {
$where = 'c.onview = ' . (int)$viewid;
$where .= ' AND c.onview = ' . (int)$viewid;
}
if (!$canedit) {
$where .= ' AND (';
......@@ -632,7 +635,7 @@ class ArtefactTypeComment extends ArtefactType {
return get_records_sql_assoc('
SELECT c.onview, COUNT(c.artefact) AS comments
FROM {artefact_comment_comment} c
WHERE c.onview IN (' . join(',', array_map('intval', $viewids)) . ') AND c.deletedby IS NULL
WHERE c.onview IN (' . join(',', array_map('intval', $viewids)) . ') AND c.deletedby IS NULL AND c.hidden=0
GROUP BY c.onview',
array()
);
......@@ -641,7 +644,7 @@ class ArtefactTypeComment extends ArtefactType {
return get_records_sql_assoc('
SELECT c.onartefact, COUNT(c.artefact) AS comments
FROM {artefact_comment_comment} c
WHERE c.onartefact IN (' . join(',', array_map('intval', $artefactids)) . ') AND c.deletedby IS NULL
WHERE c.onartefact IN (' . join(',', array_map('intval', $artefactids)) . ') AND c.deletedby IS NULL AND c.hidden=0
GROUP BY c.onartefact',
array()
);
......@@ -660,7 +663,7 @@ class ArtefactTypeComment extends ArtefactType {
$newest = get_records_sql_array('
SELECT a.id, a.ctime
FROM {artefact} a INNER JOIN {artefact_comment_comment} c ON a.id = c.artefact
WHERE c.private = 0 AND ' . $where . '
WHERE c.private = 0 AND c.hidden = 0 AND ' . $where . '
ORDER BY a.ctime DESC', $values, 0, 1
);
return $newest[0];
......@@ -1189,6 +1192,93 @@ class ArtefactTypeComment extends ArtefactType {
return array();
}
}
/**
* Determine whether there are visible comments below this one. This determines
* whether or not we need to keep a "comment deleted" placeholder to provide
* context if we delete this comment.
*
* @return boolean
*/
public function has_visible_descendants() {
$sql = 'SELECT 1 FROM {artefact_comment_comment} acc INNER JOIN {artefact} a ON acc.artefact = a.id WHERE ';
$params = array();
if ($this->get('onview')) {
$sql .= 'acc.onview = ?';
$params[] = $this->get('onview');
}
else {
$sql .= 'acc.onartefact = ?';
$params[] = $this->get('onartefact');
}
$sql .= ' AND acc.hidden = 0 AND (a.parent = ?';
$params[] = $this->get('id');
// If this is a top-level comment, we also need to check whether there are
// any other comments below it that aren't its direct children.
if (!$this->get('parent')) {
$sql .= ' OR acc.threadedposition > ?';
$params[] = $this->get('threadedposition');
}
$sql .= ')';
return record_exists_sql($sql, $params);
}
/**
* Recursively check all the ancestors of a hidden comment to see if they
* too now meet the criteria to be hidden.
*/
public function hide_deleted_parents() {
// If this comment is in a thread, first traverse up the thread.
$parentid = $this->get('parent');
while ($parentid) {
$parent = new ArtefactTypeComment($parentid);
if ($parent->get('deletedby') && !$parent->has_visible_descendants()) {
if (!$parent->get('hidden')) {
$parent->set('hidden', 1);
$parent->commit();
}
$parentid = $parent->get('parent');
unset($parent);
}
else {
break;
}
}
// Now we're at top-level (or unthreaded) comments, so we traverse up
// the threadedposition order.
$where = 'hidden = 0 AND threadedposition < ?';
$params = array($this->get('threadedposition'));
if ($this->get('onview')) {
$where .= ' AND onview = ?';
$params[] = $this->get('onview');
}
else {
$where .= ' AND onartefact = ?';
$params[] = $this->get('onartefact');
}
$prev = get_records_select_array('artefact_comment_comment', $where, $params, 'threadedposition DESC', 'artefact');
if ($prev) {
foreach ($prev as $c) {
$parent = new ArtefactTypeComment($c->artefact);
if ($parent->get('deletedby') && !$parent->has_visible_descendants()) {
// Already hidden. (Maybe a fluke?)
if ($parent->get('hidden')) {
continue;
}
$parent->set('hidden', 1);
$parent->commit();
unset($parent);
}
else {
break;
}
}
}
}
}
/* To make private comments public, both the author and the owner must agree. */
......@@ -1308,6 +1398,7 @@ function make_public_submit(Pieform $form, $values) {
redirect($url);
}
function delete_comment_submit(Pieform $form, $values) {
global $SESSION, $USER, $view;
require_once('embeddedimage.php');
......@@ -1335,8 +1426,19 @@ function delete_comment_submit(Pieform $form, $values) {
db_begin();
$comment->set('deletedby', $deletedby);
if (!$comment->has_visible_descendants()) {
$comment->set('hidden', 1);
}
$comment->commit();
// If this comment was hidden, check to see if its parent now also needs to be
// hidden (i.e. has no visible replies). And then its grandparent, etc
if ($comment->get('hidden')) {
$comment->hide_deleted_parents();
}
if ($deletedby != 'author') {
// Notify author
if ($artefact) {
......@@ -1421,7 +1523,8 @@ function add_feedback_form_validate(Pieform $form, $values) {
acc.private,
a.author,
p.author as grandparentauthor,
acc.deletedby
acc.deletedby,
acc.hidden
FROM
{artefact} a
INNER JOIN {artefact_comment_comment} acc
......@@ -1440,7 +1543,7 @@ function add_feedback_form_validate(Pieform $form, $values) {
}
// Can't reply to a deleted comment
if ($parent->deletedby) {
if ($parent->deletedby || $parent->hidden) {
$form->set_error('message', get_string('replytodeletednotallowed', 'artefact.comment'));
}
......
......@@ -11,6 +11,6 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2015100100;
$config->release = '1.0.0';
$config = new stdClass();
$config->version = 2016051000;
$config->release = '1.0.1';
......@@ -16,7 +16,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/wiki/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one. On master, use the date.
$config->version = 2016060800;
$config->version = 2016061000;
$config->series = '16.10';
$config->release = '16.10dev';
$config->minupgradefrom = 2012080604;
......
......@@ -3599,7 +3599,7 @@ class View {
case 'mostcomments':
$select .= ', COUNT(DISTINCT acc.artefact) AS commentcount';
$from .= '
LEFT OUTER JOIN {artefact_comment_comment} acc ON (v.id = acc.onview)';
LEFT OUTER JOIN {artefact_comment_comment} acc ON (v.id = acc.onview AND acc.hidden=0)';
$groupby = ' GROUP BY v.id';
$order = 'COUNT(DISTINCT acc.artefact) DESC,';
break;
......@@ -4312,7 +4312,7 @@ class View {
// We need the date of the last comment on each view
$from .= 'LEFT OUTER JOIN (
SELECT c.onview, MAX(a.mtime) AS lastcomment
FROM {artefact_comment_comment} c JOIN {artefact} a ON c.artefact = a.id AND c.deletedby IS NULL AND c.private = 0
FROM {artefact_comment_comment} c JOIN {artefact} a ON c.artefact = a.id AND c.deletedby IS NULL AND c.private = 0 AND c.hidden=0
GROUP BY c.onview
) l ON v.id = l.onview
';
......@@ -4504,7 +4504,7 @@ class View {
a.id AS commentid,
a.description AS commenttext,
acc.onview AS lastcommentviewid,
(SELECT COUNT(*) FROM {artefact_comment_comment} c WHERE c.onview = acc.onview AND c.deletedby IS NULL AND c.private=0) AS commentcount
(SELECT COUNT(*) FROM {artefact_comment_comment} c WHERE c.onview = acc.onview AND c.deletedby IS NULL AND c.private=0 AND c.hidden=0) AS commentcount
FROM
{artefact_comment_comment} acc
inner join {artefact} a
......@@ -4522,6 +4522,7 @@ class View {
acc2.onview = acc.onview
AND acc2.deletedby IS NULL
AND acc2.private = 0
AND acc2.hidden = 0
ORDER BY a3.mtime DESC, acc2.artefact ASC
LIMIT 1
)
......@@ -4556,6 +4557,7 @@ class View {
cv2.collection = cv.collection
AND c.deletedby IS NULL
AND c.private=0
AND c.hidden=0
) AS commentcount
FROM
{artefact_comment_comment} acc
......@@ -4577,6 +4579,7 @@ class View {
WHERE
acc2.deletedby IS NULL
AND acc2.private = 0
AND acc2.hidden = 0
AND cv2.collection = cv.collection
ORDER BY a3.mtime DESC, acc2.artefact ASC
LIMIT 1
......
@javascript @core_artefact @core @core_portfolio
Feature: Page comment configuration
In order to change the settings for placing comments on a page
As an admin
So I can choose how I want to display my comments
Background:
Given the following "users" exist:
| username | password | email | firstname | lastname | institution | authname | role |
| userA | Kupuhipa1 | test01@example.com | Pete | Mc | mahara | internal | member |
Scenario Outline: Turning switchboxes on and off on comment page (Bug 1431569)
Given I log in as "<log>" with password "Kupuhipa1"
And I follow "Portfolio"
And I press "Create page"
And I fill in the following:
| Page title | Sharky |
And I press "Save"
And I display the page
# Checking the default settings on the fields are correct
And the following fields match these values:
| Make public | 1 |
# Changing the switchbox to be the opposite
And I set the following fields to these values:
| Make public | 0 |
# Changing the switchbox back to default setting
And I set the following fields to these values:
| Make public | 1 |
And I press "Comment"
And I should see "There was an error with submitting this form. Please check the marked fields and try again."
And I should see "Your message is empty. Please enter a message or attach a file."
And I set the following fields to these values:
| Message | This is a comment 1 |
And I press "Comment"
And I wait "1" seconds
And I delete the "This is a comment 1" row
Then I should see "Comment removed by the author"
Examples:
| log |
| admin |
| userA |
......@@ -31,13 +31,21 @@ Scenario: Adding and deleting public comments
# Deleting
Given I delete the "Public comment by anonymous user" row
# If the comment is deleted, the comment author remains visible but the comment
# text is replaced with "Comment removed by owner"
# If a comment with replies is deleted, we hide its contents but leave a placeholder
# in place to give context to the replies
Then I should not see "Public comment by anonymous user"
And I should see "Comment removed by the owner"
And I should see "Joe Anonymous"
# Make sure only the selected comment was deleted"
And I should see "Comment by page owner"
# If a comment without replies is deleted, we hide it. And then if its parent was
# deleted and is showing a placeholder, we now hide it as well, because it is no
# longer needed for context.
Given I delete the "Comment by page owner" row
# Reload the page to get rid of the status message
And I go to portfolio page "page1"
Then I should not see "Comment by page owner"
And I should not see "Comment removed"
Scenario: Comments update the page's mtime
Given I log in as "pageowner" with password "password"
......
......@@ -29,7 +29,7 @@ Scenario: Public comment by page owner, public reply by third party
And I log out
And I log in as "pagecommenter" with password "password"
And I go to portfolio page "page1"
And I press "Reply"
And I click on "Reply" in "Public comment by pageowner" row
# I should see a preview of the reply-to comment below the feedback form
And I should see "Public comment by pageowner" in the ".commentreplyview" "css_element"
And I fill in "Public reply by pagecommenter" in editor "Message"
......@@ -46,14 +46,14 @@ Scenario: Public comment by non-owner, owner can private reply, another non-owne
And I log out
And I log in as "pageowner" with password "password"
And I go to portfolio page "page1"
And I press "Reply"
And I click on "Reply" in "Public comment by pagecommenter" row
And I disable the switch "Make public"
And I fill in "Private reply by pageowner" in editor "Message"
And I press "Comment"
And I log out
And I log in as "pagewatcher" with password "password"
And I go to portfolio page "page1"
And I press "Reply"
And I click on "Reply" in "Public comment by pagecommenter" row
# I should not be able to make a private reply to a comment by someone other than the page owner
And I should see "Public" in the "#add_feedback_form_ispublic_container" "css_element"
When I fill in "Public reply by pagewatcher" in editor "Message"
......@@ -73,7 +73,7 @@ Scenario: Private comment by commenter, private reply by page owner, private cou
And I log out
And I log in as "pageowner" with password "password"
And I go to portfolio page "page1"
And I press "Reply"
And I click on "Reply" in "Private comment by pagecommenter" row
# There should be no option to make a public reply to a private comment
And I should see "Private" in the "#add_feedback_form_ispublic_container" "css_element"
And I fill in "Private reply by pageowner" in editor "Message"
......@@ -84,7 +84,7 @@ Scenario: Private comment by commenter, private reply by page owner, private cou
# I should be able to see the pageowner's private reply to my private comment
# (An exception to the general rule that only the pageowner can see private comments)
And I should see "Private reply by pageowner"
And I press "Reply"
And I click on "Reply" in "Private reply by pageowner" row
And I fill in "Private counter-reply by pagecommenter" in editor "Message"
When I press "Comment"
Then I should see "Private comment by pagecommenter"
......@@ -98,13 +98,13 @@ Scenario: Private comment by commenter, private reply by page owner, private cou
Scenario: No private replies to anonymous comments
Given I go to portfolio page "page1"
And I fill in "Name" with "Anonymous User"
# No TinyMCE editor for anonymous users
# No WYSIWYG editor for anonymous users
And I fill in "Message" with "Public comment by anonymous user"
And I enable the switch "Make public"
And I press "Comment"
When I log in as "pagecommenter" with password "password"
And I go to portfolio page "page1"
And I press "Reply"
And I click on "Reply" in "Public comment by anonymous user" row
# I should not be able to make a private reply to a comment by someone other than the page owner
Then I should see "Public" in the "#add_feedback_form_ispublic_container" "css_element"
And I fill in "Public reply by pagecommenter" in editor "Message"
......@@ -122,3 +122,57 @@ Scenario: No replies to deleted comments
And I delete the "I will delete this comment" row
# No reply button, because I have deleted the comment
Then I should not see "Reply"
Scenario: Deleted comments
Given I log in as "pageowner" with password "password"
And I go to portfolio page "page1"
# Create a tree of comments like so:
#
# * Comment #1
# ** Comment #1/1
# *** Comment #1/2
# * Comment #2
#
And I fill in "Comment 1." in editor "Message"
And I press "Comment"
And I should see "Comment 1."
And I fill in "Comment 2." in editor "Message"
And I press "Comment"
And I should see "Comment 2."
And I click on "Reply" in "Comment 1." row
And I fill in "Comment 1-1." in editor "Message"
And I press "Comment"
And I should see "Comment 1-1."
# TODO: fix "I click on" so it automatically scrolls if needed
And I scroll to the base of id "commentreplyto20"
And I click on "Reply" in "Comment 1-1." row
And I fill in "Comment 1-2." in editor "Message"
And I press "Comment"
And I should see "Comment 1-2."
# Deleting a threaded comment that has a reply, display
# a placeholder for the reply's context
#
# * Comment #1
# ** (Deleted placeholder)
# *** Comment #1/2
# * Comment #2
#
# TODO: Fix "I delete the row" so it automatically scrolls if needed
When I scroll to the base of id "delete_comment20_delete_comment_submit"
And I delete the "Comment 1-1." row
Then I should not see "Comment 1-1."
And I should see "Comment removed by the author"
# Deleting a comment with no replies, hide the deleted
# comment. (Recursively also hide any deleted
# parents that now have no visible replies.)
#
# * Comment #1
# * Comment #2
#
# TODO: Fix "I delete the row" so it automatically scrolls if needed
When I scroll to the base of id "delete_comment21_delete_comment_submit"
And I delete the "Comment 1-2." row
Then I should not see "Comment 1-2."
And I should not see "Comment removed by the author"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment