You are here

modr8.module in modr8 7

Same filename and directory in other branches
  1. 5 modr8.module
  2. 6 modr8.module

Easy dedicated content moderation

File

modr8.module
View source
<?php

/**
 * @file
 * Easy dedicated content moderation
 */

/**
 * A random value that should not conflict with core or other modules.
 */
define('NODE_BUILD_MODR8_TEASER', 1242526499);

/**
 * Implements hook_help().
 */
function modr8_help($path, $arg) {
  switch ($path) {
    case 'admin/help#modr8':
      return '<p>' . t("Easy, dedicated moderation of content. Assign the 'moderate content' permission to one or mode user roles. Set up the default moderation option for each node type.") . '</p>';
  }
}

/**
 * An access function for Moderation Menu Tab
 */
function modr8_moderation_access($nid) {
  return user_access('moderate content') && db_query("SELECT COUNT(*) FROM {modr8_log} ml WHERE ml.nid = :nid", array(
    ':nid' => $nid,
  ))
    ->fetchField();
}

/**
 * Implements hook_menu().
 */
function modr8_menu() {
  $items = array();
  $items['admin/content/modr8'] = array(
    'title' => 'Moderated content',
    'description' => 'Approve or delete moderated content.',
    'access callback' => 'user_access',
    'access arguments' => array(
      'moderate content',
    ),
    'page callback' => 'modr8_page',
    'file' => 'modr8_admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/reports/modr8'] = array(
    'title' => 'Content moderation log',
    'description' => 'Show log of all actions on moderated content.',
    'access callback' => 'user_access',
    'access arguments' => array(
      'moderate content',
    ),
    'page callback' => 'modr8_log_view',
    'file' => 'modr8_admin.inc',
  );
  $items['admin/config/system/modr8'] = array(
    'title' => 'Modr8 settings',
    'description' => 'Configure content moderation.',
    'page callback' => 'modr8_settings',
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'modr8_admin.inc',
  );
  $items['node/%/modr8'] = array(
    'title' => 'Moderation',
    'page callback' => 'modr8_log_view',
    'page arguments' => array(
      'node',
      1,
    ),
    'access callback' => 'modr8_moderation_access',
    'access arguments' => array(
      1,
    ),
    'file' => 'modr8_admin.inc',
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );
  $items['node/%node/log/response/%'] = array(
    'title' => 'Moderation response',
    'page callback' => 'modr8_response_page',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'modr8_response_access',
    'access arguments' => array(
      1,
      4,
    ),
    'file' => 'modr8_admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Access callback.
 */
function modr8_response_access($node, $token) {
  return $token == modr8_response_token($node->nid, $node->uid);
}

/**
 * Generate a token for responding to a node in moderation.
 *
 * Calculates a HMAC-MD5 according to RFC2104 (http://www.ietf.org/rfc/rfc2104.txt).
 */
function modr8_response_token($nid, $uid) {
  $key = md5(drupal_get_private_key() . 'modr8token');
  return bin2hex(pack("H*", md5((str_pad($key, 64, chr(0x0)) ^ str_repeat(chr(0x5c), 64)) . pack("H*", md5((str_pad($key, 64, chr(0x0)) ^ str_repeat(chr(0x36), 64)) . $nid . ':' . $key . ':' . $uid)))));
}

/**
 * Implements hook_permission().
 */
function modr8_permission() {
  return array(
    'moderate content' => array(
      'title' => t('moderate content'),
      'description' => t('TODO Add a description for \'moderate content\''),
    ),
    'bypass moderation queue' => array(
      'title' => t('bypass moderation queue'),
      'description' => t('TODO Add a description for \'bypass moderation queue\''),
    ),
  );
}

/**
 * menu callback for settings form.
 */
function modr8_settings() {
  return drupal_get_form('modr8_settings_form');
}

/**
 * Implements hook_node_load().
 */
function modr8_node_load($nodes) {
  $result = db_query('SELECT nid, moderate FROM {node} WHERE nid IN (:nids)', array(
    ':nids' => array_keys($nodes),
  ));
  foreach ($result as $record) {
    $nodes[$record->nid]->moderate = $record->moderate;
  }
  foreach ($nodes as &$node) {
    if (!isset($node->moderate)) {
      $node->moderate = FALSE;
    }
  }
}

/**
 * Implements hook_node_prepare().
 */
function modr8_node_prepare($node) {
  if (!isset($node->nid)) {

    //a new node
    $node_options = variable_get('node_options_' . $node->type, array(
      'status',
      'promote',
    ));
    $node->moderate = in_array('moderate', $node_options) && !user_access('bypass moderation queue');
  }
}

/**
 * Implements hook_node_view().
 */
function modr8_node_view($node, $view_mode = 'full', $langcode) {
  if ($node->moderate && ($view_mode = 'full' || $view_mode == 'teaser')) {
    $node->content['modr8_message'] = array(
      '#type' => 'markup',
      '#markup' => theme('modr8_message', array(
        '0' => $view_mode,
        '1' => $node->type,
        '2' => 'view',
      )),
      '#weight' => -100,
    );
  }
}

/**
 * Implements hook_node_update().
 */
function modr8_node_update($node) {
  db_update('node')
    ->fields(array(
    'moderate' => $node->moderate,
  ))
    ->condition('nid', $node->nid)
    ->execute();
  if ($node->moderate) {

    //cut this?
    theme('modr8_message', array(
      '0' => FALSE,
      '1' => $node->type,
      '2' => 'update',
    ));
  }
}

/**
 * Implements hook_node_insert().
 */
function modr8_node_insert($node) {

  // We only put a node in moderation if explicitly set.
  if ($node->moderate) {
    db_update('node')
      ->fields(array(
      'moderate' => $node->moderate,
    ))
      ->condition('nid', $node->nid)
      ->execute();

    // Invokes hook_modr8_insert($node).
    module_invoke_all('modr8_insert', $node);

    //cut this?
    theme('modr8_message', array(
      '0' => FALSE,
      '1' => $node->type,
      '2' => 'insert',
    ));
  }
}

/**
 * Implements hook_node_delete().
 */
function modr8_node_delete($node) {

  // Delete log entries when a node is deleted, unless it's deleted while
  // in moderation.  In the latter case, we want to retain the log to see
  // which moderator deleted the node and any message they sent.
  if (!$node->moderate) {
    db_delete('modr8_log')
      ->condition('nid', $node->nid)
      ->execute();
  }
}

/**
 * Implements hook_form_alter()
 */
function modr8_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']['#value']) && $form['type']['#value'] . '_node_form' == $form_id) {
    $moderate_checkbox = array(
      '#type' => 'checkbox',
      '#title' => t('In moderation queue'),
      '#default_value' => $form['#node']->moderate,
      '#weight' => 24,
      '#description' => t('This %type will be placed in moderation if the %moderate checkbox is selected.', array(
        '%type' => node_type_get_name($form['#node']),
        '%moderate' => t('In moderation queue'),
      )),
    );
    if (user_access('administer nodes')) {
      $form['options']['moderate'] = $moderate_checkbox;
    }
    elseif (user_access('moderate content')) {
      $form['moderate'] = $moderate_checkbox;
    }
    else {
      $form['moderate'] = array(
        '#type' => 'value',
        '#value' => $form['#node']->moderate,
      );
      if ($form['#node']->moderate) {
        $form['modr8_message'] = array(
          '#type' => 'markup',
          '#markup' => theme('modr8_message', array(
            '0' => FALSE,
            '1' => $form['#node']->type,
            '2' => 'node_form',
          )),
          '#weight' => -100,
        );
      }
    }
  }
  elseif ($form_id == 'node_type_form') {
    $form['workflow']['node_options']['#options']['moderate'] = t('In moderation queue');
  }
  elseif ($form_id == 'modr8_filter_form') {

    // Unset the internal form token and id fields for this admin filter form,
    // since it is posted as a GET and doesn't need the drupal form system.
    unset($form['form_build_id'], $form['form_id'], $form['#token'], $form['#build_id']);
  }
}

/**
 * Implements hook_db_rewrite_sql().
 */
function modr8_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  $node_table = $primary_table;
  switch ($primary_field) {
    case 'nid':

      // this query deals with node objects
      $access = user_access('administer nodes') || user_access('moderate content');
      if (!$access && $query) {
        global $user;
        $return = array();
        if ($primary_table != 'n') {
          $return['join'] = "LEFT JOIN {node} modr_n ON {$primary_table}.nid = modr_n.nid";
          $node_table = 'modr_n';
        }
        if ($user->uid == 0) {
          $return['where'] = "({$node_table}.moderate != 1)";
        }
        else {
          $return['where'] = "({$node_table}.moderate != 1 OR {$node_table}.uid = " . (int) $user->uid . ")";
        }
        return $return;
      }
      break;
  }
}

/**
 * Implements hook_cron().
 *
 * Remove expired moderation log events.
 */
function modr8_cron() {
  if ($log_clear = variable_get('modr8_log_clear', 0)) {
    db_delete('modr8_log')
      ->condition('timestamp', REQUEST_TIME - $log_clear, '<')
      ->execute();
  }
}

/**
 * Implements hook_block_info().
 */
function modr8_block_info() {

  // TODO Rename block deltas (e.g. delta-0) to readable strings.
  if (TRUE) {
    $blocks['delta-0']['info'] = t("Modr8 moderator's block");
    $blocks['modr8-moderators']['info'] = t("Modr8 moderators credit list");
    $blocks['modr8-moderators']['cache'] = DRUPAL_CACHE_GLOBAL;
    return $blocks;
  }
}

/**
 * Implements hook_block_view().
 */
function modr8_block_view($delta) {
  if (TRUE) {
    if ($delta == 'modr8-moderators') {
      $block = array();
      $cutoff = strtotime('now -6 months');
      $qry = db_select('modr8_log', 'ml')
        ->condition('ml.action', 'Response', '<>')
        ->condition('ml.timestamp', $cutoff, '>');
      $qry
        ->join('users', 'u', 'u.uid = ml.uid');
      $qry
        ->fields('u');
      $qry
        ->condition('u.status', 1)
        ->addExpression('COUNT(ml.modid)', 'number');
      $accounts = $qry
        ->execute()
        ->fetchAll();
      $header = array(
        t('User'),
        t('Number of actions'),
      );
      $rows = array();
      foreach ($accounts as $key => $account) {
        $rows[] = array(
          theme('username', array(
            'account' => $account,
          )),
          $account->number,
        );
      }
      if ($rows) {
        $block['content'] = t('Last 6 months:') . '<br />' . theme('table', array(
          'header' => $header,
          'rows' => $rows,
        ));
        $block['subject'] = t('Top moderators');
      }
      return $block;
    }
    elseif (user_access('moderate content')) {
      $block['subject'] = t('Moderation queue');
      $is_published = array(
        0,
        1,
      );
      if (!user_access('administer nodes')) {

        // Users who don't have the 'administer nodes' permission can only see published nodes.
        $is_published = 1;
      }
      $qry = db_select('node', 'n')
        ->condition('n.moderate', 1)
        ->condition('n.status', $is_published)
        ->addTag('node_access')
        ->addMetaData('base_table', 'node');
      $count = $qry
        ->countQuery()
        ->execute()
        ->fetchField();
      $content = '<p>' . l(t('@items in moderation', array(
        '@items' => format_plural($count, '1 post', '@count posts'),
      )), 'admin/content/modr8') . '</p>';
      if ($count) {
        $qry = db_select('node', 'n')
          ->fields('n', array(
          'nid',
          'title',
        ))
          ->condition('n.moderate', 1)
          ->condition('n.status', $is_published)
          ->extend('PagerDefault')
          ->limit(6)
          ->addTag('node_access')
          ->addMetaData('base_table', 'node')
          ->orderBy('n.changed', 'DESC');
        $result = $qry
          ->execute()
          ->fetchAll();
        if (!empty($result)) {
          $modr8_nodes = node_title_list($result, t('Recent additions:'));
          $content .= theme('item_list', $modr8_nodes);
        }
      }
      $block['content'] = $content;
      return $block;
    }
  }
}

/**
 * Implements hook_block().
 */
function modr8_block_OLD($op = 'list', $delta = 0) {
}

/**
 * Implements hook_theme().
 */
function modr8_theme($existing, $type, $theme, $path) {
  return array(
    'modr8_message' => array(
      'variables' => array(
        'teaser',
        'nodetype',
        'op',
      ),
    ),
    'modr8_form' => array(
      'render element' => 'form',
    ),
    'moderation_event' => array(
      'variables' => array(
        'event',
      ),
    ),
    'modr8_note' => array(
      'variables' => array(
        'note',
      ),
    ),
  );
}

/**
 * Theming function for messages.
 */
function theme_modr8_message($variables) {
  $teaser = $variables['0'];
  $nodetype = $variables['1'];
  $op = $variables['2'];
  static $already_messaged = FALSE;

  // Don't add the message more than once per page load.
  if ($already_messaged) {
    return;
  }
  if ($teaser) {
    return ' <div class="marker">' . t('Pending moderation') . '</div>';
  }
  else {
    switch ($op) {
      case 'view':
        drupal_set_message(t("The post has been submitted for moderation and won't be listed publicly until it has been approved."), 'warning');
        break;
      case 'node_form':
        if (!user_access('bypass moderation queue')) {
          drupal_set_message(t('This %type will be submitted for moderation and will not be accessible to other users until it has been approved.', array(
            '%type' => node_type_get_name($nodetype),
          )));
        }
        break;
    }
  }
  $already_messaged = TRUE;
}

/**
 * Public API function to approve the given node.
 *
 * @param $node
 *   The node to approve.
 * @param $note
 *   An optional note to be used in emails being sent out.
 */
function modr8_approve_content($node, $note = NULL) {
  if (!isset($node->nid)) {
    return;
  }
  module_load_include('inc', 'modr8', 'modr8_admin');
  $message = '';
  if (user_access('administer nodes')) {
    $node->status = 1;
  }
  $node->moderate = 0;
  node_save($node);

  //To avoid pending moderation message in log preview
  $values = _modr8_build_values($node, $note);
  if (variable_get('modr8_send_approve', FALSE)) {
    $message = modr8_usermail('approve', $node->nid, $values);
  }
  cache_clear_all();
  modr8_log_action('approve', $node->nid, $values, $message);

  // Invokes hook_modr8_approve($node, $values, $message).
  module_invoke_all('modr8_approve', $node, $values, $message);
}

/**
 * Public API function to delete the given node.
 *
 * @param $node
 *   The node to delete.
 * @param $note
 *   An optional note to be used in emails being sent out.
 */
function modr8_delete_content($node, $note = NULL) {
  if (!isset($node->nid)) {
    return;
  }
  module_load_include('inc', 'modr8', 'modr8_admin');
  $values = _modr8_build_values($node, $note);
  $message = '';
  if (variable_get('modr8_send_deny', FALSE)) {
    $message = modr8_usermail('deny', $node->nid, $values);
  }

  // Invokes hook_modr8_delete($node, $values, $message).
  module_invoke_all('modr8_delete', $node, $values, $message);
  node_delete($node->nid);

  // drupal does its own message
  modr8_log_action('delete', $node->nid, $values, $message);
}

/**
 * Build the values array needed for so many internal functions.
 *
 * @param $node
 *   The node in the current context.
 * @param $note
 *   An optional note to be used in emails being sent out.
 */
function _modr8_build_values($node, $note = NULL) {
  if (!isset($node->nid)) {
    return;
  }
  return array(
    'note' => $note,
    'author_uid' => $node->uid,
    'log_link' => modr8_get_log_link($node),
    'title' => check_plain($node->title),
    'type' => node_type_get_name($node),
    'preview' => modr8_get_node_teaser($node),
  );
}

/**
 * Generates the log link for the passed node
 * @param $node
 *   The node oject
 */
function modr8_get_log_link($node) {
  $log_link = '';
  $events = db_query("SELECT modid FROM {modr8_log} WHERE nid = :nid", array(
    ':nid' => $node->nid,
  ));
  $count = db_query("SELECT COUNT(modid) FROM {modr8_log} WHERE nid = :nid", array(
    ':nid' => $node->nid,
  ))
    ->fetchField();
  if ($count) {
    if ($count == 1) {
      $url = 'admin/reports/modr8/event/' . $events
        ->fetchField();
    }
    else {
      $url = 'node/' . $node->nid . '/modr8/';
    }
    $message = format_plural($count, 'See the 1 moderation log event for this post', 'Overview of the @count moderation log events for this post');
    $log_link .= l($message, $url);
  }
  return $log_link;
}

/**
 * Returns the rendered teaser view of the node
 * To avoid E_SRTICT warnings
 * @param $node
 *   The node object
 */
function modr8_get_node_teaser($node) {
  $teaser = '';
  if (isset($node->nid)) {
    $node_view = node_view($node, 'teaser');
    $teaser = drupal_render($node_view);
  }
  return $teaser;
}

Functions

Namesort descending Description
modr8_approve_content Public API function to approve the given node.
modr8_block_info Implements hook_block_info().
modr8_block_OLD Implements hook_block().
modr8_block_view Implements hook_block_view().
modr8_cron Implements hook_cron().
modr8_db_rewrite_sql Implements hook_db_rewrite_sql().
modr8_delete_content Public API function to delete the given node.
modr8_form_alter Implements hook_form_alter()
modr8_get_log_link Generates the log link for the passed node
modr8_get_node_teaser Returns the rendered teaser view of the node To avoid E_SRTICT warnings
modr8_help Implements hook_help().
modr8_menu Implements hook_menu().
modr8_moderation_access An access function for Moderation Menu Tab
modr8_node_delete Implements hook_node_delete().
modr8_node_insert Implements hook_node_insert().
modr8_node_load Implements hook_node_load().
modr8_node_prepare Implements hook_node_prepare().
modr8_node_update Implements hook_node_update().
modr8_node_view Implements hook_node_view().
modr8_permission Implements hook_permission().
modr8_response_access Access callback.
modr8_response_token Generate a token for responding to a node in moderation.
modr8_settings menu callback for settings form.
modr8_theme Implements hook_theme().
theme_modr8_message Theming function for messages.
_modr8_build_values Build the values array needed for so many internal functions.

Constants

Namesort descending Description
NODE_BUILD_MODR8_TEASER A random value that should not conflict with core or other modules.