There is only one perm, now : administer comments on ALL nodes, whoever created the node, and whatever is the content-type of that node. I need more granularity in access control for a specific application.

From another forum thread, I gather that some serious comment.module hacking is in order for this... ?

Trouble is how do I know from the comment itself, say $comment passed as argument to a function, who is the author of the node that comment is attached to, in php, insde comment.module ? Not the author of the comment, but the author of the node the comment is attached to ?

So far here's what I did :

In my custom type module, short_story.module, I added a perm : 'administer comments of own short stories'.

And I am checking this for registered users.

In comment.module, I modified the function comment_form_alter to add the comment settings at the bottom of the page in edit mode :

 function comment_form_alter($form_id, &$form) {
  if (isset($form['type'])) {
    if ($form['type']['#value'] .'_node_settings' == $form_id) {
      $form['workflow']['comment_'. $form['type']['#value']] = array('#type' => 'radios', '#title' => t('Default comment setting'), '#default_value' => variable_get('comment_'. $form['type']['#value'], COMMENT_NODE_READ_WRITE), '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')), '#description' => t('Users with the <em>administer comments</em> permission will be able to override this setting.'));
    }
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $node = $form['#node'];
	 
      if (user_access('administer comments') || user_access('administer comments of own short stories')) {
        $form['comment_settings'] = array(
          '#type' => 'fieldset',
          '#title' => t('Comment settings'),
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
          '#weight' => 30,
        );
        $form['comment_settings']['comment'] = array(
          '#type' => 'radios',
          '#parents' => array('comment'),
          '#default_value' => $node->comment,
          '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
        );
      }
      else {
        $form['comment_settings']['comment'] = array(
          '#type' => 'value',
          '#value' => $node->comment,
        );
      }
    }
  }
}

The only line that is modified in the above code snippet is :

if (user_access('administer comments') || user_access('administer comments of own short stories')) {

Now I am faced with the second challenge which is to allow users to edit/delete comments in their own nodes.

The function that I now need to modify is comment_links :

function comment_links($comment, $return = 1) {
  global $user;

  $links = array();

  // If we are viewing just this comment, we link back to the node.
  if ($return) {
    $links[] = l(t('parent'), comment_node_url(), NULL, NULL, "comment-$comment->cid");
  }

  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
    if (user_access('administer comments') && user_access('post comments')) {
      $links[] = l(t('delete'), "comment/delete/$comment->cid");
      $links[] = l(t('edit'), "comment/edit/$comment->cid");
      $links[] = l(t('reply'), "comment/reply/$comment->nid/$comment->cid");
    }
    else if (user_access('post comments')) {
      if (comment_access('edit', $comment)) {
        $links[] = l(t('edit'), "comment/edit/$comment->cid");
      }
      $links[] = l(t('reply'), "comment/reply/$comment->nid/$comment->cid");
    }
    else {
      $links[] = theme('comment_post_forbidden', $comment->nid);
    }
  }

  return $links;
}

I need to add the edit/delete and reply links when :

-> user_access('administer comments of own short stories') and
-> the $user->uid is equal to the uid of the creator of the node to which the comment ($comment) is attached.

I have trouble checking for the second condition. Please help.

Comments

Chill35’s picture

Is the only route here to :

1. Use $comment->nid
2. Look up the database to find the uid associated with that nid.

Thanks

Caroline

Chill35’s picture

It seems I have to modify the comment_menu function as well in order for users to actually be able to access the edit and delete pages of comments.

Can someone help ?

I guess module development expertise is required here.

Caroline

Chill35’s picture

I moved the links stuff from comment_module to my custom type module, which gives me the same good results but is less of a hack :

/**
 * Implementation of hook_link().
 */
function short_story_link($type, $content = 0, $main = 0) {
  $links = array();
  global $user;
  if ($type == 'comment' && user_access('administer comments of own short stories')) {
      $result = db_query("SELECT uid FROM {node} WHERE type IN ('short_story') AND nid = '%d'", $content->nid); 
	  $row = db_fetch_object($result);
	  if (isset($row->uid) && ($row->uid == $user->uid) ) { 
	  	$links[] = l(t('delete'), "comment/delete/$content->cid");
                $links[] = l(t('edit'), "comment/edit/$content->cid"); 
	  }
  }
  return $links;
}

Now I am still left with removing that "denied access" for the edit and delete pages...

I modified my short_story_menu function :

/**
 * Implementation of hook_menu().
 */
function short_story_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $items[] = array('path' => 'node/add/short_story', 'title' => t('short story'),
      'access' => user_access('create short stories'));


    // Here comes my modification but it does not work... 
    $access = user_access('administer comments of own short stories');	
    $items[] = array('path' => 'comment/delete', 'title' => t('delete comment'),
      'callback' => 'comment_delete', 'access' => $access, 'type' => MENU_CALLBACK);
    $items[] = array('path' => 'comment/edit', 'title' => t('edit comment'),
      'callback' => 'comment_edit', 'access' => $access, 'type' => MENU_CALLBACK);
  }

  return $items;

}

Trouble is that I don't even understand what I am doing in the hook_menu function...

Can someone take a look at this, please ?

Caroline

Chill35’s picture

I want to modify EITHER comment.module or
short_story.module to

by checking to see if the user can edit a node, let him/her edit/delete the comments of the node as well

Caroline

htxt’s picture

Hi Caroline -- did you make any progress with this?

It seems like it would be really useful feature.

Chill35’s picture

I dropped that along the way. I did not get any help, so.

Now that I am looking at this again, I think I can figure it out. It would still be a hack, though. I really believe that we need one more perm in Drupal core : administer own nodes comments.

Maybe I can create a patch and a feature request. I will solve the puzzle and come back here to explain the hack.

Creating a module for this is overkill in my opinion (for me anyway), but it can be done, and should.

Caroline
Who am I | Where are we
11 heavens

mooffie’s picture

Instead of hacking the comment.module, why don't you write a tiny module that grants users the 'administer comments' permission at run time, at mymodule_init?

Chill35’s picture

That's a great idea!!!!! If I achieve this I give you credit... there's no way in hell I would have ever thought about going at it this way. It's really creative. I would do this for ony certain content types - maybe a minimalist admin settings page that lists current content types to check... and voilà.

Maybe I should use hook_menu instead ?

hook_init
This hook is run at the beginning of the page request. It is typically used to set up global parameters which are needed later in the request.
Only use this hook if your code must run even for cached page views. If you have code which must run once on all non cached pages, use hook_menu(!$may_cache) instead. Thats the usual case. If you implement this hook and see an error like 'Call to undefined function', it is likely that you are depending on the presence of a module which has not been loaded yet. It is not loaded because Drupal is still in bootstrap mode. The usual fix is to move your code to hook_menu(!$may_cache).

Now how do I force a perm on a user at runtime ? (based on another perm).

Thanks Mooffie.

Caroline
Who am I | Where are we
11 heavens

mooffie’s picture

Now how do I force a perm on a user at runtime ?

Well, what I have in mind is a quite ugly and tricky. I didn't test the idea, but I believe it might work.

Drupal calls user_access('administer comments') to check if we have the right permission. If you examine the source code you see that we need to use some trick to circumvent its internal cache ("static $parm" is a cahce; it's a common technique in Drupal code).

The plan, which may or may not work:

function bozo_initialize() { // no such hook; to be decided.

  0. decide, by analyzing the URL, whether the user
     should have 'administer comments' permission (for
     the duration of this page request). 

  1. add 'administer comments' to the {permission} table 
     under rid #2 (2 = "authenticated user").

  2. call user_access($uid) so it caches the permissions.
     We have to be the first ones who call this function, or 
     else its internal cache will be populated already.

  3. remove the 'administer comments' from the
     {permission} table. (But this step can be done much later,
     not necessarily here).

}
I really believe that we need one more perm in Drupal core : administer own nodes comments.

I agree. It's a feature I always wanted.

Maybe I should use hook_menu instead ?

Trial and error. Some module may call user_access() before we have a chance to do that ourselves. That's why I suggested hook_init(). But maybe if we assign a negative weight to our module we can use hook_menu():

function bozo_install() {
  db_query("UPDATE {system} SET weight = -50 WHERE name = 'bozo'");
}

Ugly. More tricks may be needed.

Maybe it would be easier to continue with your original plan. You have one small thing left to do, if I understand correctly; you have to modifiy comment_access(); you don't need to touch comment_menu().

mooffie’s picture

In comment.module, I modified the function comment_form_alter to add the comment settings at the bottom of the page in edit mode

BTW, In Drupal 5.0 you no longer need to modify a core module to fix such things.

In the past, modules had the following pattern:

function somemodule_form_alter() {
   ...
   if (user_access('administer something')) {
     $form['something'] = array(
       ...
     );
   }
   ...
}

Drupal 5.0 introduces the '#access' attribute; modules now use the following pattern:

function somemodule_form_alter() {
   ...
   $form['something'] = array(
     ...
     '#access' => user_access('administer something'),
     ...
   );
}

The advantage is that now you can implement hook_form_alter in your module and set this other module's '#access' to TRUE.

That's one less modification to 'comment.module'.

mooffie’s picture

Trouble is that I don't even understand what I am doing in the hook_menu function...

comment_menu() delegates to comment_edit(), when one asks to edit a comment. It's this function, and not comment_menu(), the one that checks the permission, and it does this by calling comment_access(). this comment_access() is quite trivial. I hope it's the last piece in your puzzle.

ricflomag’s picture

One way to let a user administer only the comments of his own nodes is to give him 'administer comments' access and filter the list of comments displayed by admin/content/comment.

I hacked comment.module, function comment_admin_overview() to achieve this, line 1180 (Drupal 5.1):

  // build a table listing the appropriate comments
  $destination = drupal_get_destination();
  while ($comment = db_fetch_object($result)) {

  // ---------------------------------------------
  // ADD THIS LINE.
    if(!node_access('update',node_load($comment->nid))) continue;
  // ---------------------------------------------

    $comments[$comment->cid] = '';
    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
    $form['subject'][$comment->cid] = array('#value' => l($comment->subject, 'node/'. $comment->nid, array('title' => truncate_utf8($comment->comment, 128)), NULL, 'comment-'. $comment->cid));
    $form['username'][$comment->cid] = array('#value' => theme('username', $comment));
    $form['timestamp'][$comment->cid] = array('#value' => format_date($comment->timestamp, 'small'));
    $form['operations'][$comment->cid] = array('#value' => l(t('edit'), 'comment/edit/'. $comment->cid, array(), $destination));
  }

That's all. Only users with 'administer comments' and 'update' access on a given node can delete or moderate the related comments. The good news is that it is compatible with Taxonomy Access module, which i use much.

ricflomag’s picture

Another little hack: now we want to prevent users who can administer comments on their own nodes to change the system-wide settings of the comment module. We only want the administrator to access the settings. To do this, we display the tab "settings" only to the administrator. Of course, if a user with 'administer comment' access knows the url, he can still go to the settings page.

function comment_menu(), line 172:

    global $user; // ADD THIS
    $items[] = array(
      'path' => 'admin/content/comment/settings',
      'title' => t('Settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('comment_admin_settings'),
      'access' => $user->uid == 1, // <-- CHANGE THIS LINE (was: 'access' => $access,)
      'weight' => 10,
      'type' => MENU_LOCAL_TASK);

You'll have to wait a while for the cache of the menus to be updated, or manually flush the {cache_menu} table of Drupal.

mooffie’s picture

filter the list of comments displayed by admin/content/comment

It's only a list. Users would be able to navigate to q=comment/edit/<comment-id> and edit any comment.

now we want to prevent users who can administer comments on their own nodes to change the system-wide settings [...] To do this, we display the tab "settings" only to the administrator. Of course, if a user with 'administer comment' access knows the url, he can still go to the settings page.

No, he won't be able to access that URL (becasue 'access'=>false).

===

Modifying core to let users edit their nodes' comments is easy. We're trying to achieve this without touching core. I've came up with another idea, but have yet to check it out.

ricflomag’s picture

You're right. I'll be happy to use a good (and bullet-proof ;) module to extend the core Comments module when it's ready.

Users would be able to navigate to q=comment/edit/ and edit any comment

That's true, and the "edit" and "delete" links are displayed next to all comments for a user who has "administer comments" access. Hacking a little bit more in comment.module, it's possible to prevent this, changing line 835:

if (user_access('administer comments') && user_access('post comments')) {

to:

    if (user_access('administer comments') && node_access('update',node_load($comment->nid)) && user_access('post comments')) {

It is not still very secure, as nothing prevents a user with "administer comments" access to edit or delete a comment if he knows the url to do it. I further hacked comment.module to fix this. The idea is to limit "administer comments" access to the nodes the user has "update" access to. So it consists on looking for the expression:

user_access('administer comments')

and replace it with something like:

(user_access('administer comments') && node_access('update',node_load($comment->nid)))

or

(user_access('administer comments') && node_access('update',node_load($edit['nid'])))

or

(user_access('administer comments') && node_access('update',node_load($nid)))

(depending on how the nid of the node is available). This is VERY dirty indeed, but will make the hack more secure.

Chill35’s picture

As long as

- a hack involves 2-3 lines of code changed maximum
- I put a comment next to my hacks with my full name (whatever word one can use to find quickly while searching)
- I add a description of each of my hacks to a list of hacks in a notebook (with file name, line number, etc.)
- I keep the number of items in that list to a minimum
- I replace the hacks with clean solutions as my expertise increases

Then I do what I have to do.

Caroline
Who am I | Where are we
11 heavens