I would like to request the following feature:

Similar to the delete-button for Heartbeat messages, there should be a delete-button for Heartbeat comments, so that a) the message owner, b) the commenting user, c) the admin, or d) a person with sufficient rights can delete Heartbeat comments. If these are node comments then the node comments should be deleted as well, of course, and vice versa.

Comments

Stalski’s picture

true. I will implement this asap. For the message deleting buttons, i was thinking more and more about moving them to the attachments section and get rid of the buttons part. With this feature request in mind, i would certainly prefer a common system to use at both levels.

jaochoo’s picture

Since I needed this quite urgently, I implemented an own solution. I think you can re-use most of the code since it is almost exactly the same as for deleting a whole message. In brief:

  1. On node-comments, I display a link to the standard Drupal /comment/delete/% page; for Heartbeat-comments, I display a link to the path /heartbeat/comment/delete/% which I then create
  2. Using hook_menu() I register a path for aforementioned /heartbeat/comment/delete/% page
  3. The page under /heartbeat/comment/delete/% uses the Drupal function confirm_form() to display a standard confirmation form
  4. The submit-handler for the aforementioned confirmation form then deletes the actual comment

TODO: I am currently using user_access('administer')) as permission, i.e. only the administrators are allowed to delete comments. You might want to change this to other permissions (e.g. also the message or comment owner is allowed to delete a comment). See altered code below in #4

Code:

(1) in mymodule_heartbeat_comment() (displaying links to the comment deletion page)

function mymodule_heartbeat_comment($comment, $node_comment = FALSE, $last = FALSE) {
  // your code here
  $output = ...;

  // links to the standard Drupal deletion form
  if($node_comment && user_access('administer')) {
    $output .= 
      l(
        t('Delete'), 
        $base_url . '/comment/delete/' . $comment->cid, 
        array( 
          'query' => drupal_get_destination()
        )
      );
  // links to an own deletion form
  } else if(user_access('administer')) {
    $output .= 
      l(
        t('Delete'), 
        $base_url . '/heartbeat/comment/delete/' . $comment->hcid, 
        array( 
          'query' => drupal_get_destination()
        )
      );
  }
  return $output;
}

(2) in hook_menu() (registering a path for an own Heartbeat comment deletion page in addition to the already existing Drupal comment deletion page)

function mymodule_menu() {
  $items = array();

  // your code here...

  $items['heartbeat/comment/delete/%'] = array(
    'title' => t('Delete comment'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_delete_confirm', 3),
    'access callback' => 'user_access',
    'access arguments' => array('administer'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

(3) in mymodule_delete_confirm() (code to generate the actual page conents for the aforementioned path)

function mymodule_delete_confirm(&$form_state, $hcid) { 
  $form = array(
    'hcid' => array(
      '#type' => 'hidden',
      '#value' => $hcid
    ),
    'redirect_path' => array(
      '#type' => 'hidden',
      '#value' => isset($_GET['destination']) ? $_GET['destination'] : $_SERVER['HTTP_REFERER']
    ),
  );

	return confirm_form($form,
    	t('Are you sure you want to delete this comment?'),
    	$_GET['destination'],
    	t('This action cannot be undone.'),
    	t('Delete'),
    	t('Cancel'));
}

(4) in mymodule_delete_confirm_submit() (submit-handler for the form generated in (3) actually deleting the comment from the database)

function mymodule_delete_confirm_submit($form, &$form_state) {
  db_query("DELETE FROM {heartbeat_comments} WHERE hcid = %d", $form_state['values']['hcid']);
  $form_state['redirect'] = isset($_GET['destination']) ? $_GET['destination'] : $form_state['values']['redirect_path'];
  drupal_set_message('Comment deleted.');
}

,

Stalski’s picture

Status: Active » Needs review

Cool thx, i can indeed reuse most of it, great job.

jaochoo’s picture

I altered the code to meet the aforementioned TODO (not only admins can delete comments, but also the commenter himself, or a person with "administer comments"-permissions (for node comments) or "administer Heartbeat comments"-permissions (for Heartbeat comments) respectively). It is worth to note that I had to install the "Comment Delete" module, because Drupal core does not allow a commenting person to delete his own comments (furthermore, I did not want to give the right "administer comments" to normal users, because it also exposes the possibility to administer general comment configurations like "read/write/read+write"; the "Comment Delete" module provides more detailled permissions only allowing a person to delete comments but not to change general comment settings).

A workaround would be to not use the general /comment/delete/ form (which needs one of the aforementioned permissions, either "administer comments" from Drupal core or those provided by "Comment Delete" module), but to register an own path (same way like for Heartbeat comments), display a confirmation form, and then delete the comment directly from the Drupal DB.

Code:

(1) in mymodule_heartbeat_comment() (displaying links to the comment deletion page)

function mymodule_heartbeat_comment($comment, $node_comment = FALSE, $last = FALSE) {
  // your code here
  $output = ...;

  // For node comments link to the standard Drupal comment deletion form under comment/delete/%
  // Only users who have the right permissions should see the delete link.
  // Permissions are provided by the "Comment Delete" module.
  if ($node_comment && (user_access('delete any comment') || ($user && ($user->uid==$comment->uid) && user_access('delete own comments')))) {
    $output .= 
      " · " .
      l(
        t('Delete'), 
        $base_url . '/comment/delete/' . $comment->cid, 
        array( 
          'query' => drupal_get_destination()
        )
      );
  // For Heartbeat comments link to an own deletion form.
  // Only users who have the right permissions or are the commenting person should see the delete link.
  // Permissions are provided by Heartbeat itself ("administer heartbeat comments').
  } else if(user_access('administer heartbeat comments') || ($comment->uid && $user->uid && ($comment->uid==$user->uid))) {
    $output .= 
      " · " .
      l(
        t('Delete'), 
        $base_url . '/heartbeat/comment/delete/' . $comment->hcid, 
        array( 
          'query' => drupal_get_destination()
        )
      );
  }
  return $output;
}

(2) in hook_menu() (registering a path for an own Heartbeat comment deletion page in addition to the already existing Drupal comment deletion page)

function mymodule_menu() {
  $items = array();

  // your code here...

  // we register an own path to display our delete form
  $items['heartbeat/comment/delete/%'] = array(
    // title of the page
    'title' => t('Delete comment'),
    // our page consists of a form only, so we can use the Drupal function drupal_get_form()
    'page callback' => 'drupal_get_form',
    // we pass two arguments to drupal_get_form(): The name of our function which creates the form,
    // and the 3rd URL argument (same like arg(3)), i.e. the %hcid from the URL
    'page arguments' => array('mymodule_delete_confirm', 3),
    // since we want to have full control over who is allowed to delete comments,
    // we implement an own access function
    'access callback' => '_mymodule_heartbeat_comment_delete_access',
    // again, we pass the 3rd URL argument (i.e. %hcid) to our access function
    'access arguments' => array(3),
    // a MENU_CALLBACK only registers the plain path, but does not display any tab or menu entry
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

(2b) in _mymodule_heartbeat_comment_delete_access (if our custom access function returns TRUE the user is granted access to our deletion form, otherwise he will not be granted access)

function _mymodule_heartbeat_comment_delete_access($hcid) {
  // users with the administer permission should always be allowed to access our deletion form
  if(user_access('administer heartbeat comments')) {
    return TRUE;
  // otherweise we need to check whether they are the commenter (i.e. they are allowed to delete their own comments)
  } else {
    global $user;
    $uid = db_result(db_query("SELECT uid FROM {heartbeat_comments} WHERE hcid = %d", $hcid));
    return $uid == $user->uid;
  } // TODO: Allow message owner to delete comment as well
  return FALSE;
}

(3) in mymodule_delete_confirm() (code to generate the actual page conents for the aforementioned path)

// same as above...

(4) in mymodule_delete_confirm_submit() (submit-handler for the form generated in (3) actually deleting the comment from the database)

// same as above...
Stalski’s picture

Status: Needs review » Fixed

This will be available in next snapshot

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

jaochoo’s picture

Thanks stalski! :-) Just one questions: How did you implement that a user can delete his own comments? As mentioned above in #4 I had to install the "Comment Delete" module to achieve this. Did you go the other way mentioned, i. e. registering an own path for deletion of Drupal comments and then deleting it from the Drupal DB directly?

Stalski’s picture

I implemented all your code (merged the #2 and #4). Should i do something more for this? I understood that providing a own menu callback for heartbeat was enough, no? did not test this fully yet.

jaochoo’s picture

The point is that by default behaviour Drupal core does not provide a way for the author of a node-comment to delete his own comments. There is nothing like an "administer own comments" or "delete own comments" permission provided by Drupal core. There is only an "administer comments" permission, which, however, enables the corresponding user to delete any (!) comment (i. e. even those not posted himself) as well as touching the overall comment configuration.

To get a "delete own comments" permission (which I am using in my code posted in #4) I had to install the "Comment Delete" module (as described in #4) which provides exactly that functionality. Of course, this would mean that Heartbeat would have a dependency to that module, which might be just overhead for such a small issue, so I proposed an alternative solution:

You could just register an own path (just the same like for deleting a Heartbeat comment, e. g. heartbeat/nodecomment/delete/%cid), display a submission form on that path, which, when submitted, deletes the comment from the Drupal database directly (pseudo: DELETE FROM {comments} WHERE cid = %d). You could then even implement a "delete own comment" permission yourself which protects that path (which would be only 1-2 Drupal hooks more). Probably it would be even enough to implement a "delete own comment" permission and then hook_menu_alter() the path for comment-deletion provided by Drupal itself, i. e. comment/delete/%cid

If you implemented the code as-is in #2 and #4 it will most certainly not work correctly for node comments (yet it should be executed without a PHP error), because the if-clause user_access('delete own comments') will always evaluate to FALSE (for every use except the admin) simply because there is no such permission available in the system (as far as the user did not have the aforementioned "Comment Delete" module installed before anyways, of course).

Sorry if I did not point that out in #4 clearly enough.

jaochoo’s picture

Status: Closed (fixed) » Needs work

Sorry, I don't have a test machine here, so the following code is not tested at all yet, but mainly c/p from above:

1. Implement additional permissions for deleting own or any comments (so we don't have to use the "Comment Delete" module)

function mymodule_perm() {
  return array(
    'delete own comments',
    'delete any comment',
  );
}

2. Register an own path for the deletion of a node comment

function mymodule_menu() {
  $items = array();

  // your code here...

  $items['heartbeat/nodecomment/delete/%'] = array(
    'title' => t('Delete comment'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_nodecommentdelete_confirm', 3),
    'access callback' => '_mymodule_heartbeat_nodecomment_delete_access',
    'access arguments' => array(3),
    'type' => MENU_CALLBACK,
  );

  return $items;
}

3. Allow only user with appropriate rights to access our delete-form

function _mymodule_heartbeat_nodecomment_delete_access($cid) {
  if(user_access('administer comments') || user_access('delete any comment')) {
    return TRUE;
  } else {
    global $user;
    $uid = db_result(db_query("SELECT uid FROM {comments} WHERE cid = %d", $cid));
    return ($uid == $user->uid && user_access('delete own comments'));
  } 
  return FALSE;
}

4. Implement a confirmation form so the use has to confirm the deletion of a comment before it finally gets deleted from the DB

function mymodule_nodecommentdelete_confirm(&$form_state, $cid) {
  $form = array(
    'cid' => array(
      '#type' => 'hidden',
      '#value' => $cid
    ),
    'redirect_path' => array(
      '#type' => 'hidden',
      '#value' => isset($_GET['destination']) ? $_GET['destination'] : $_SERVER['HTTP_REFERER']
    ),
  );

    return confirm_form($form,
        t('Are you sure you want to delete this comment?'),
        $_GET['destination'],
        t('This action cannot be undone.'),
        t('Delete'),
        t('Cancel'));
}

5. Delete the data from the database

function mymodule_nodecommentdelete_confirm_submit($form, &$form_state) {
  db_query("DELETE FROM {comments} WHERE cid = %d", $form_state['values']['cid']);
  $form_state['redirect'] = isset($_GET['destination']) ? $_GET['destination'] : $form_state['values']['redirect_path'];
  drupal_set_message('Comment deleted.');
}
Stalski’s picture

Ok, i will do a totoal overview.
Just a couple of remarks:
- altering the existing path is always my preferrence but then you have the possibility that your module does not come last and another module already had overridden it. So maybe own custom callback to deal with this comments. I would have to check where it could be a problem, since i think i will need most things that "comment delete" module does.
- My preference would in fact go to a setting that handles this. (no node comment delete from stream; you can delete node comments from stream). This would implicate that my widgit system for attachments is ready to refactor.

Stalski’s picture

Status: Needs work » Fixed

Hey,

@Jaochoo: Could you update and test this on your site? You have lots of modules and features enabled, thus would be great if you gave your blessing on this?

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

jaochoo’s picture

@ Stalski: What should I test? I think I don't understand ;-)

Stalski’s picture

Well i fixed it, but i would like you to tell me if it works for you as well. It's in heartbeat core now, your feature request and patch code (just modified it a bit here and there)