You are here

commentaccess.module in Comment Access 7

Provides users with permissions for comments on nodes they own.

File

commentaccess.module
View source
<?php

/**
 * @file
 *
 * Provides users with permissions for comments on nodes they own.
 */

/**
 * Implementation of hook_help().
 */
function commentaccess_help($path, $arg) {
  switch ($path) {
    case 'admin/help#commentaccess':
      return '<p>' . t("This module lets users delete comments on nodes they create without giving them full comment administration access. Permissions are on a per node type basis, so it is a great way to, e.g., allow users to administer comments on their own blogs. Additionally, you can configure this module to force comments on selected node types to be approved before they get published. As with delete rights, this is administered by users so you don't have to do it yourself.") . '</p>';
      break;
    case 'admin/modules#description':
      return t('Provides users with permissions for comments on nodes they own..');
      break;
  }
}

/**
 * Implementation of hook_menu().
 */
function commentaccess_menu() {
  $items = array();
  $items['admin/config/content/commentaccess'] = array(
    'title' => 'Comment access',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commentaccess_admin_settings',
    ),
    'file' => 'commentaccess.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer comments',
    ),
    'weight' => 20,
  );
  return $items;
}

/**
 * Implementation of hook_menu_alter()
 */
function commentaccess_menu_alter(&$items) {
  $items['comment/%/approve']['access callback'] = 'commentaccess_access_check';
  $items['comment/%/approve']['access arguments'] = array(
    1,
    'approve',
  );
  $items['comment/%/delete']['access callback'] = 'commentaccess_access_check';
  $items['comment/%/delete']['access arguments'] = array(
    1,
    'delete',
  );
}

/**
 * Implementation of hook_permission().
 * @return array $perms
 */
function commentaccess_permission() {
  $perms = array();
  foreach (node_type_get_types() as $node) {
    $type = check_plain($node->type);
    $perms += array(
      "administer comments on own {$type}" => array(
        'title' => t('%type: Administer comments on own content', array(
          '%type' => $node->name,
        )),
      ),
    );
    $perms += array(
      "approve comments on own {$type}" => array(
        'title' => t('%type: Approve comments on own content', array(
          '%type' => $node->name,
        )),
      ),
    );
    $perms += array(
      "delete comments on own {$type}" => array(
        'title' => t('%type: Delete comments on own content', array(
          '%type' => $node->name,
        )),
      ),
    );
  }
  return $perms;
}

/**
 * Implementation of hook_comment_presave()
 *
 * Enforces the approval queue, shows the approval message, and
 * optionally sends the e-mail notification to nodeauthor.
 */
function commentaccess_comment_presave($comment) {

  // Check if the comment needs approval.
  $requires_approval = commentaccess_requires_approval($comment);
  if ($requires_approval === NULL) {

    // Use the default message if admin approval is needed, see comment_form_submit().
    if (!user_access('administer comments')) {
      drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
    }
    return;
  }
  if ($requires_approval) {
    $node = node_load($comment->nid);

    // Set the comment to an unpublished state
    $comment->status = COMMENT_NOT_PUBLISHED;

    // Set the approval queue message for the comment author
    $status_msg_php = variable_get('commentaccess_approval_php');
    if ($status_msg_php != '' && module_exists('php')) {
      $status_msg = php_eval($status_msg_php);
    }
    else {
      $status_msg = variable_get('commentaccess_approval_msg', "Your comment will be posted once it's been approved.");
      $status_msg = t($status_msg, array(
        '@poster' => !empty($comment->name) ? $comment->name : variable_get('anonymous', 'anonymous'),
        '@node_owner' => $node->name,
        '@subject' => $comment->subject,
      ));
    }
    drupal_set_message($status_msg);

    // Send the mail if enabled
    $nodeauthor = user_load($node->uid);
    if (!$nodeauthor) {

      // Node author does not exist. Don't send notification e-mail.
      return;
    }
    $commentaccess_settings = _commentaccess_get_account_settings($nodeauthor);

    // Get the comment body text.
    $comment_body_items = field_get_items('comment', $comment, 'comment_body');
    if (empty($comment_body_items)) {
      $comment_body_text = '';
    }
    else {
      $comment_body_item = reset($comment_body_items);
      if (empty($comment_body_item['format']) || $comment_body_item['format'] == 'plain_text') {
        $comment_body_text = $comment_body_item['value'];
      }
      else {

        // The body is in html. For usage in the e-mail convert it to plain text.
        $comment_body_text = drupal_html_to_text($comment_body_item['value']);
      }
    }
    if ($commentaccess_settings['commentaccess_email']) {
      $replacements = array(
        '@approver' => $node->name,
        '@subject' => $comment->subject,
        '@comment' => $comment_body_text,
        '@commenter' => !empty($comment->name) ? $comment->name : variable_get('anonymous', 'anonymous'),
        '@nodelink' => url('node/' . $node->nid, array(
          'absolute' => TRUE,
        )),
        '@commentlink' => url('node/' . $node->nid, array(
          'absolute' => TRUE,
          'fragment' => 'comment-' . $comment->cid,
        )),
        '@site' => variable_get("site_name", "Drupal"),
        '@siteurl' => $GLOBALS["base_url"],
      );
      $params = array(
        'replacements' => $replacements,
      );
      drupal_mail('commentaccess', 'commentaccess_email', $nodeauthor->mail, user_preferred_language($nodeauthor), $params);
    }
  }
  else {

    // No approval necessary: post comment directly!
    $comment->status = COMMENT_PUBLISHED;
  }
}

/**
 * Implementation of hook_comment_view().
 *
 * Adds approve and delete links for node authors.
 */
function commentaccess_comment_view($comment, $view_mode, $langcode) {

  // Comment module adds these links for users with 'administer comments'
  if (!user_access('administer comments')) {
    if (commentaccess_access_check($comment, 'delete') && empty($comment->content['links']['comment']['#links']['comment-delete'])) {
      $comment->content['links']['comment']['#links']['comment-delete'] = array(
        'title' => t('delete'),
        'href' => 'comment/' . $comment->cid . '/delete',
        'html' => TRUE,
      );
    }
    if (commentaccess_access_check($comment, 'approve') && empty($comment->content['links']['comment']['#links']['comment-approve'])) {
      $comment->content['links']['comment']['#links']['comment-approve'] = array(
        'title' => t('approve'),
        'href' => 'comment/' . $comment->cid . '/approve',
        'html' => TRUE,
        'query' => array(
          'token' => drupal_get_token("comment/{$comment->cid}/approve"),
        ),
      );
    }
  }
}

/**
 * Implementation of hook_node_view().
 *
 * We duplicate code from comment_node_page_additions() to add the
 * comment thread when it might need to be added, but only when
 * the original code has not run because there are no published
 * comments. Our query alter hook is still active in this circumstance.
 *
 * @see commentaccess_query_comment_filter_alter()
 * @see comment_node_page_additions()
*/
function commentaccess_node_view($node, $view_mode) {
  global $user;

  // this first condition is copied from comment_node_view():
  // add comments only if we are on the node page, in "full" view mode
  if ($node->comment && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
    if (empty($node->content['comments']['comments']) && $node->comment_count == 0) {
      if ($node->uid == $user->uid && (user_access("approve comments on own {$node->type}") || user_access("administer comments on own {$node->type}"))) {

        // there are no published comments, but this user has permissions to
        // work with unpublished comments. So we run the same code as
        // comment_node_page_additions() to add the comment thread.
        $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED);
        $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50);
        if ($cids = comment_get_thread($node, $mode, $comments_per_page)) {
          $comments = comment_load_multiple($cids);
          comment_prepare_thread($comments);
          $build = comment_view_multiple($comments, $node);
          $build['pager']['#theme'] = 'pager';
          $node->content['comments']['comments'] = $build;
        }
      }
    }
  }
}

/**
 * Implementation of hook_query_TAG_alter()
 *
 * Remove status condition in comment query when the user has appropriate
 * permissions. This has the effect of showing unpublished comments.
 *
 * @see comment_get_thread()
 */
function commentaccess_query_comment_filter_alter(QueryAlterableInterface $query) {
  global $user;
  $node = $query
    ->getMetaData('node');
  if (isset($node) && $node->uid == $user->uid && (user_access("approve comments on own {$node->type}") || user_access("administer comments on own {$node->type}"))) {
    $conditions =& $query
      ->conditions();
    foreach ($conditions as $key => &$condition) {
      if (isset($condition['field']) && isset($condition['value']) && $condition['field'] == 'c.status' && $condition['value'] == COMMENT_PUBLISHED) {
        unset($conditions[$key]);
      }
    }
  }
}

/**
 * Implementation of hook_form_alter().
 *
 * Adds the comment administration fields to the node edit form.
 */
function commentaccess_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    global $user;
    $type = $form['type']['#value'];
    if ($type . '_node_form' == $form_id) {
      $form['comment_settings']['#access'] |= $user->uid == $form['#node']->uid && user_access("administer comments on own {$type}");
    }
  }
}

/*
 * Implementation of hook_form_FORM_ID_alter().
 *
 * Bypasses comment.module to enable the custom approval queue message.
 */
function commentaccess_form_comment_form_alter(&$form, &$form_state) {
  $form['actions']['submit']['#submit'] = array(
    'commentaccess_form_submit',
  );
}

/*
 * Implementation of hook_form_FORM_ID_alter().
 *
 * Adds options to the user edit form.
 */
function commentaccess_form_user_profile_form_alter(&$form, &$form_state) {
  if ($form['#user_category'] == 'account') {
    $account = $form['#user'];

    // Gets the current commentaccess settings for the user.
    $commentaccess_settings = _commentaccess_get_account_settings($account);

    // Iterate through node types, add checkbox if user has access
    foreach (node_type_get_types() as $node) {
      $type = $node->type;
      if (node_access('create', $type)) {
        if (user_access("approve comments on own {$type}") || user_access("administer comments on own {$type}")) {
          $form['commentaccess_settings']["commentaccess_skip_{$type}"] = array(
            '#type' => 'checkbox',
            '#title' => t("{$node->name}: skip comment approvals"),
            '#default_value' => $commentaccess_settings["commentaccess_skip_{$type}"],
            '#description' => t('Check this to allow other people to comment on your posts without approval (Administrators may always comment without approval).'),
          );
        }
      }
    }

    // If user has options, wrap them in a fieldset and provide e-mail option
    if (!empty($form['commentaccess_settings'])) {
      $commentaccess_settings_fieldset = array(
        '#type' => 'fieldset',
        '#title' => t('Comment Access Settings'),
        '#weight' => 5,
        '#collapsible' => TRUE,
      );
      $form['commentaccess_settings'] = array_merge($form['commentaccess_settings'], $commentaccess_settings_fieldset);
      $form['commentaccess_settings']['commentaccess_email'] = array(
        '#type' => 'checkbox',
        '#title' => t('Receive e-mail notifications'),
        '#default_value' => $commentaccess_settings["commentaccess_email"],
        '#description' => t('Check this to receive e-mail notifications when new comments need your approval.'),
      );
    }
  }
}

/**
 * Returns the current commentaccess settings for the specified account.
 *
 * @param stdClass $account
 *   The account to return the commentaccess settings for.
 *
 * @return array
 *   The settings in an array with the following structure:
 *   - commentaccess_email
 *   - commentaccess_skip_{NODE_TYPE}
 */
function _commentaccess_get_account_settings($account) {
  $settings = array();
  if (isset($account->data['commentaccess_email'])) {
    $settings['commentaccess_email'] = $account->data['commentaccess_email'];
  }
  else {
    $settings['commentaccess_email'] = variable_get('commentaccess_mail_default', 0);
  }
  foreach (node_type_get_types() as $node) {
    $type = $node->type;
    $skip_approval_field = "commentaccess_skip_{$type}";
    if (isset($account->data[$skip_approval_field])) {
      $settings[$skip_approval_field] = $account->data[$skip_approval_field];
    }
    else {
      $settings[$skip_approval_field] = variable_get('commentaccess_approval_default', 1);
    }
  }
  return $settings;
}

/**
 * Implements hook_user_presave().
 */
function commentaccess_user_presave(&$edit, $account, $category) {
  if (isset($edit['commentaccess_email'])) {
    $edit['data']['commentaccess_email'] = $edit['commentaccess_email'];
  }
  foreach (node_type_get_types() as $node) {
    $type = $node->type;
    if (isset($edit["commentaccess_skip_{$type}"])) {
      $edit['data']["commentaccess_skip_{$type}"] = $edit["commentaccess_skip_{$type}"];
    }
  }
}

/**
 * This function checks comment access permissions.
 * @param object $comment
 *   The object to check
 * @param string
 *   $op delete or approve
 * @return boolean
 *   TRUE or FALSE dependent from the $op
 */
function commentaccess_access_check($comment, $op = '') {
  global $user;

  // Menu system sometimes sends just cid
  if (!is_object($comment)) {
    $comment = comment_load($comment);
  }
  $node = node_load($comment->nid);
  switch ($op) {
    case 'delete':
      if (!$user->uid) {
        return FALSE;
      }
      if (user_access('administer comments')) {
        return TRUE;
      }
      elseif (user_access("administer comments on own {$node->type}") && $user->uid == $node->uid) {
        return TRUE;
      }
      elseif (user_access("delete comments on own {$node->type}") && $user->uid == $node->uid) {
        return TRUE;
      }
      break;
    case 'approve':
      if (!$user->uid) {
        return FALSE;
      }
      if ($comment->status == COMMENT_NOT_PUBLISHED) {
        if (user_access('administer comments')) {
          return TRUE;
        }
        elseif (user_access("administer comments on own {$node->type}") && $user->uid == $node->uid) {
          return TRUE;
        }
        elseif (user_access("approve comments on own {$node->type}") && $user->uid == $node->uid) {
          return TRUE;
        }
      }
      break;
    default:
      return FALSE;
  }

  // end switch $op
}

/**
 * Returns TRUE if the specified comment requires approval by the comment's node's owner.
 *
 * @param object $comment
 *   The comment.
 *
 * @return bool
 *   TRUE if the comment require approval, FALSE if not. NULL if the node's owner has
 *   no administer/approval access. In this case, the general comment access rules are
 *   used.
 */
function commentaccess_requires_approval($comment) {

  // Check if the comment is a node comment.
  if (empty($comment->nid)) {
    return NULL;
  }
  $node = node_load($comment->nid);
  if (empty($node)) {
    return NULL;
  }

  // Check for known owner.
  $owner = user_load($node->uid);
  if (empty($owner)) {

    // Owner unknown: use general comment rules.
    return NULL;
  }

  // Check if the owner has administer or approval access for this node type.
  if (!user_access('administer comments on own ' . $node->type, $owner) && !user_access('approve comments on own ' . $node->type, $owner)) {

    // No access: use general comment rules.
    return NULL;
  }
  global $user;
  if ($owner->uid == $user->uid) {

    // Node owner is comment owner: no approval needed.
    return FALSE;
  }
  elseif (user_access('skip comment approval')) {

    // User bypasses approval.
    return FALSE;
  }
  else {

    // Check if the node author wants to skip approvals.
    if (!empty($owner)) {
      $skip_approval_field = "commentaccess_skip_" . $node->type;
      $commentaccess_settings = _commentaccess_get_account_settings($owner);
      return empty($commentaccess_settings[$skip_approval_field]);
    }
    else {
      return FALSE;
    }
  }
}

/**
 * Modification of comment_form_submit()
 *
 * The only change is in the approval queue if block. If a comment saves
 * as unpublished, the message is set in commentaccess_comment_presave().
 */
function commentaccess_form_submit($form, &$form_state) {
  $node = node_load($form_state['values']['nid']);
  $comment = comment_form_submit_build_comment($form, $form_state);
  if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {

    // Save the anonymous user information to a cookie for reuse.
    if (user_is_anonymous()) {
      user_cookie_save(array_intersect_key($form_state['values'], array_flip(array(
        'name',
        'mail',
        'homepage',
      ))));
    }
    comment_save($comment);
    $form_state['values']['cid'] = $comment->cid;

    // Add an entry to the watchdog log.
    watchdog('content', 'Comment posted: %subject.', array(
      '%subject' => $comment->subject,
    ), WATCHDOG_NOTICE, l(t('view'), 'comment/' . $comment->cid, array(
      'fragment' => 'comment-' . $comment->cid,
    )));

    // Here is the modified block
    if ($comment->status == COMMENT_PUBLISHED) {
      drupal_set_message(t('Your comment has been posted.'));
    }
    $query = array();

    // Find the current display page for this comment.
    $page = comment_get_display_page($comment->cid, $node->type);
    if ($page > 0) {
      $query['page'] = $page;
    }

    // Redirect to the newly posted comment.
    $redirect = array(
      'node/' . $node->nid,
      array(
        'query' => $query,
        'fragment' => 'comment-' . $comment->cid,
      ),
    );
  }
  else {
    watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array(
      '%subject' => $comment->subject,
    ), WATCHDOG_WARNING);
    drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array(
      '%subject' => $comment->subject,
    )), 'error');

    // Redirect the user to the node they are commenting on.
    $redirect = 'node/' . $node->nid;
  }
  $form_state['redirect'] = $redirect;

  // Clear the block and page caches so that anonymous users see the comment
  // they have posted.
  cache_clear_all();
}

/**
 * Implementation of hook_mail()
 * @param string $key
 * @param array $message
 * @param array $params
 */
function commentaccess_mail($key, &$message, $params) {
  if ($key == 'commentaccess_email') {
    $subject = variable_get('commentaccess_mail_subject', '@commenter posted a new comment!');
    $body = variable_get('commentaccess_mail_message', commentaccess_mail_message_default());
    $langcode = isset($message['language']->language) ? $message['language']->language : NULL;
    $message['subject'] = t($subject, $params['replacements'], array(
      'langcode' => $langcode,
    ));
    $message['body'][] = t($body, $params['replacements'], array(
      'langcode' => $langcode,
    ));
  }
}

/**
 * This function defines the default message sent out by this module
 */
function commentaccess_mail_message_default() {
  return "Hey @approver,\n\n@commenter posted a new comment[1] that needs to be approved.\n\nComment in @nodelink\n\n@subject\n-----------------------\n@comment\n\n\n\nYou can approve or remove the comment here:\n\n[1]@commentlink\n\nRegards,\nThe @site team";
}

Functions

Namesort descending Description
commentaccess_access_check This function checks comment access permissions.
commentaccess_comment_presave Implementation of hook_comment_presave()
commentaccess_comment_view Implementation of hook_comment_view().
commentaccess_form_alter Implementation of hook_form_alter().
commentaccess_form_comment_form_alter
commentaccess_form_submit Modification of comment_form_submit()
commentaccess_form_user_profile_form_alter
commentaccess_help Implementation of hook_help().
commentaccess_mail Implementation of hook_mail()
commentaccess_mail_message_default This function defines the default message sent out by this module
commentaccess_menu Implementation of hook_menu().
commentaccess_menu_alter Implementation of hook_menu_alter()
commentaccess_node_view Implementation of hook_node_view().
commentaccess_permission Implementation of hook_permission().
commentaccess_query_comment_filter_alter Implementation of hook_query_TAG_alter()
commentaccess_requires_approval Returns TRUE if the specified comment requires approval by the comment's node's owner.
commentaccess_user_presave Implements hook_user_presave().
_commentaccess_get_account_settings Returns the current commentaccess settings for the specified account.