You are here

node_limit.module in Node Limit 6

Same filename and directory in other branches
  1. 8 node_limit.module
  2. 7 node_limit.module

Module to restrict the number of nodes a user or role may create.

File

node_limit.module
View source
<?php

/**
 * @file
 * Module to restrict the number of nodes a user or role may create.
 */
define("NODE_LIMIT_NO_LIMIT", -1);
define("NODE_LIMIT_PERM_ADMIN", "administer node limits");
define("NODE_LIMIT_LIMIT_DOESNT_APPLY", 0);
define("NODE_LIMIT_LIMIT_DOES_APPLY", 1);
define("NODE_LIMIT_LIMIT_NEUTRAL", 2);

/**
 * Implementation of hook_perm().
 */
function node_limit_perm() {
  return array(
    NODE_LIMIT_PERM_ADMIN,
  );
}

/**
 * Implementation of hook_menu().
 */
function node_limit_menu() {
  $items = array();
  $items['admin/user/node_limit'] = array(
    'title' => t('Node Limits'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_limit_list_limits',
    ),
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
  );
  $items['admin/user/node_limit/list'] = array(
    'title' => t('List'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
    'weight' => -10,
  );
  $items['admin/user/node_limit/add'] = array(
    'title' => t('Add Node Limit'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_limit_limit_form',
      '0',
    ),
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/user/node_limit/%node_limit'] = array(
    'title' => t('Edit Node Limit'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_limit_limit_form',
      3,
    ),
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/user/node_limit/%node_limit/delete'] = array(
    'title' => t('Delete Node Limit'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_limit_delete_form',
      3,
    ),
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/node_limit/%node_limit/clone'] = array(
    'title' => t('Clone Node Limit'),
    'page callback' => 'node_limit_clone_limit',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      NODE_LIMIT_PERM_ADMIN,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_theme().
 * Register the two forms that require custom rendering.
 */
function node_limit_theme() {
  return array(
    'node_limit_limit_form' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'node_limit_list_limits' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_nodeapi().
 *
 * This is where we'll determine if the user may create new nodes or not.
 * We'll key off $op == 'prepare', which is sent before the edit/add form
 * is constructed.
 */
function node_limit_nodeapi($node, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'prepare' || $op == 'validate') {
    if ($node->nid == 0 && _node_limit_violates_limit($node)) {

      //we have a violation!

      //and this is a new node
      $nodetypename = node_get_types('name', $node);
      drupal_set_message(t("You can't create more content of type !type", array(
        '!type' => $nodetypename,
      )), 'error');
      drupal_goto('node/add');
    }
  }
}

/**
 * Helper function to check limit violations for this node.
 * Always returns FALSE for user 1.
 *
 * @param $node
 *   The node to check.
 */
function _node_limit_violates_limit($node) {
  if ($node->uid == 1) {
    return FALSE;
  }
  $limits = node_limit_limits($node);
  foreach ($limits as $idx => $lid) {
    $limit = node_limit_load($lid);
    if ($limit['nlimit'] == NODE_LIMIT_NO_LIMIT) {
      continue;
    }
    $sql = _node_limit_sql($limit['lid']);
    $count = db_result(db_query($sql));
    if ($count >= $limit['nlimit']) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Generates the sql statement to find the nodes that apply to a particular limit.
 * Modules that implement hook_node_limit_sql() should sprintf their arguments
 * into the returned array.
 * This will be changed in Drupal 7, which will be able to accept an array of
 * arguments to db_query().
 *
 * @param $lid
 *   Identifier of limit rule.
 */
function _node_limit_sql($lid) {
  $sql = "SELECT COUNT(n.nid) AS number FROM {node} AS n";
  $components = module_invoke_all('node_limit_sql', $lid);
  $limit = node_limit_load($lid);
  $joins = isset($components['join']) ? implode(' ', $components['join']) : '';
  $wheres = isset($components['where']) ? 'WHERE ' . implode(' AND ', $components['where']) : '';
  return $sql . ' ' . $joins . ' ' . $wheres;
}

/**
 * Returns all the limits that can be applied to a specific node.
 *
 * @param $node
 *   The node object that may be limited.
 */
function node_limit_limits($node) {
  $user = user_load($node->uid);

  //get all the limits:
  $res = db_query("SELECT lid FROM {node_limit} ORDER BY weight ASC");
  $applicable_limits = array();
  while ($l = db_fetch_array($res)) {

    // This particular limit id.
    $lid = $l['lid'];
    $applies = TRUE;
    $submodule_applies = module_invoke_all('node_limit_applies_in_context', $lid, $node, $user);
    foreach ($submodule_applies as $module => $module_applies) {

      // A submodule returns DOESNT_APPLY if it requires a specific user or role, etc,
      // and the context given does not satisfy that.
      if ($module_applies == NODE_LIMIT_DOESNT_APPLY) {
        $applies = FALSE;
      }
    }
    if ($applies == TRUE) {
      $applicable_limits[] = $lid;
    }
  }
  return $applicable_limits;
}

/**
 * Theme the node limit list form.
 */
function theme_node_limit_list_limits($form) {
  $header = array(
    t('Title'),
    t('Limit'),
    t('Weight'),
    t('Actions'),
  );
  $rows = array();
  foreach (element_children($form['limits']) as $lid) {
    $row = array();
    $form['limits'][$lid]['weight']['#attributes']['class'] = 'node_limit-weight';
    $row[] = drupal_render($form['limits'][$lid]['title']);
    $row[] = drupal_render($form['limits'][$lid]['nlimit']);
    $row[] = drupal_render($form['limits'][$lid]['weight']);
    $row[] = drupal_render($form['limits'][$lid]['actions']);
    $rows[] = array(
      'data' => $row,
      'class' => 'draggable',
    );
  }
  if (count($rows) > 0) {
    $out = theme('table', $header, $rows, array(
      'id' => 'node_limit-table',
    ));
  }
  $out .= drupal_render($form);
  drupal_add_tabledrag('node_limit-table', 'order', 'sibling', 'node_limit-weight');
  return $out;
}

/**
 * Form for listing the created limits.
 * Created as a form so that the user can adjust the weight.
 */
function node_limit_list_limits() {
  $weights = array();
  for ($i = -10; $i <= 10; $i++) {
    $weights[$i] = $i;
  }
  $form = array(
    '#tree' => TRUE,
  );
  $form['limits'] = array();
  $sql = "SELECT * FROM {node_limit} ORDER BY weight ASC";
  $res = db_query($sql);
  $limit_count = 0;
  while ($rec = db_fetch_array($res)) {
    $limit_count++;
    $form['limits'][$rec['lid']]['weight'] = array(
      '#type' => 'select',
      '#options' => $weights,
      '#default_value' => $rec['weight'],
    );
    $form['limits'][$rec['lid']]['title'] = array(
      '#value' => $rec['title'],
    );
    $form['limits'][$rec['lid']]['nlimit'] = array(
      '#value' => $rec['nlimit'],
    );
    $form['limits'][$rec['lid']]['actions'] = array(
      '#value' => l(t('Edit'), 'admin/user/node_limit/' . $rec['lid']) . ' ' . l(t('Delete'), 'admin/user/node_limit/' . $rec['lid'] . '/delete') . ' ' . l(t('Clone'), 'admin/user/node_limit/' . $rec['lid'] . '/clone'),
    );
  }
  if ($limit_count > 0) {
    $form['save'] = array(
      '#type' => 'submit',
      '#value' => t('Save Limits'),
    );
  }
  else {
    $form['create'] = array(
      '#value' => l(t('Add a new node limit'), 'admin/user/node_limit/add'),
    );
  }
  return $form;
}

/**
 * Save the module weights.
 */
function node_limit_list_limits_submit($form_id, &$form_state) {
  $sql = "UPDATE {node_limit} SET `weight`='%d' WHERE `lid`='%d'";
  foreach ($form_state['values']['limits'] as $lid => $info) {
    db_query($sql, $info['weight'], $lid);
  }
  drupal_set_message(t('Limits saved!'));
}

/**
 * Theme the admin settings form so that we have tables inside fieldsets.
 */
function theme_node_limit_limit_form($form) {
  if (!isset($form['info'])) {
    return;
  }
  $rows = array();
  foreach ($form['node_limit_elements'] as $module => &$element) {
    if (is_array($element['applies']) && is_array($element['element'])) {
      if ($element['element']['#custom_render'] === TRUE && module_hook($module, 'node_limit_render_element')) {

        // We can't use module_invoke because form elements must be passed byref.
        $func = $module . '_node_limit_render_element';
        $rendered_element = $func($element['element']);
      }
      else {
        $rendered_element = drupal_render($element['element']);
      }
      $rows[] = array(
        drupal_render($element['applies']),
        $rendered_element,
      );
    }
  }
  $output = drupal_render($form['info']);
  if (count($rows) > 0) {
    $output .= theme('table', array(
      t('Applies to:'),
      '',
    ), $rows);
  }
  $output .= drupal_render($form);
  return $output;
}

/**
 * The node_limit settings form.
 */
function node_limit_limit_form(&$form_state, $limit) {
  if ($limit == FALSE) {
    $limit = array();
  }
  $form = array();
  $form['#tree'] = TRUE;
  if ($limit['lid'] > 0) {
    $form['lid'] = array(
      '#type' => 'hidden',
      '#value' => $limit['lid'],
    );
  }
  $form['info']['title'] = array(
    '#title' => t('Description'),
    '#type' => 'textfield',
    '#default_value' => $limit['title'],
    '#required' => TRUE,
    '#description' => t('The description for this Node Limit'),
  );
  $form['info']['nlimit'] = array(
    '#title' => t('Limit'),
    '#type' => 'textfield',
    '#default_value' => isset($limit['nlimit']) ? $limit['nlimit'] : NODE_LIMIT_NO_LIMIT,
    '#size' => 10,
    '#required' => TRUE,
    '#description' => t('The number of nodes for this limit.  Must be an integer greater than 0 or %nolimit for no limit', array(
      '%nolimit' => NODE_LIMIT_NO_LIMIT,
    )),
  );
  $form['info']['weight'] = array(
    '#type' => 'hidden',
    '#value' => $limit['weight'],
  );
  $elements = module_invoke_all('node_limit_element', $limit['lid']);
  $form['node_limit_elements'] = array();
  foreach ($elements as $module => $element) {
    $form['node_limit_elements'][$module]['applies'] = array(
      '#type' => 'checkbox',
      '#title' => $element['#title'],
      '#default_value' => isset($limit[$module]),
    );
    $element['#title'] = '';
    $form['node_limit_elements'][$module]['element'] = $element;
  }
  $form['save'] = array(
    '#type' => 'submit',
    '#value' => $limit['lid'] > 0 ? t('Edit Node Limit') : t('Add Node Limit'),
  );
  return $form;
}

/**
 * Validation hook for node_limit_limit_form.
 *
 * Allows submodules that are applied to validate their own input.
 */
function node_limit_limit_form_validate($form_id, &$form_state) {
  if (!is_numeric($form_state['values']['info']['nlimit'])) {
    form_set_error('info][limit', t('Node limits must be an integer'));
  }
  else {
    if (intval($form_state['values']['info']['nlimit']) != floatval($form_state['values']['info']['nlimit'])) {

      // Can't use is_int because is_int("2") == FALSE.
      form_set_error('info][limit', t('Node limits must be an integer'));
    }
    else {
      if (intval($form_state['values']['info']['nlimit']) < NODE_LIMIT_NO_LIMIT) {
        form_set_error('info][limit', t('Node limits cannot be less that %nolimit', array(
          '%nolimit' => NODE_LIMIT_NO_LIMIT,
        )));
      }
    }
  }
  if (trim($form_state['values']['info']['title']) == '') {
    form_set_error('info][title', t('Invalid Node Limit title'));
  }
  foreach ($form_state['values']['node_limit_elements'] as $module => $element) {
    if ($element['applies'] === 1) {

      // They checked the box!
      $result = module_invoke($module, 'node_limit_element_validate', $element['element']);
      if (is_array($result) && isset($result['error'])) {
        $path = $module . '][element';
        if (isset($result['element'])) {
          $path .= '][' . $result['element'];
        }
        form_set_error('node_limit_elements][' . $path, $result['error']);
      }
    }
  }
}

/**
 * Submission hook for node_limit_limit_form.
 *
 * Calls the submission hook on applied submodules to allow them to save their data.
 */
function node_limit_limit_form_submit($form_id, &$form_state) {
  if (isset($form_state['values']['lid'])) {
    $lid = $form_state['values']['lid'];
  }
  else {
    $lid = _node_limit_next_limit_id();
  }
  $limit = array();
  $limit['lid'] = $lid;
  $limit['nlimit'] = intval($form_state['values']['info']['nlimit']);
  $limit['title'] = $form_state['values']['info']['title'];
  $limit['weight'] = $form_state['values']['info']['weight'];
  foreach ($form_state['values']['node_limit_elements'] as $module => $element) {
    if ($element['applies']) {
      $limit[$module] = $element['element'];
    }
  }
  node_limit_save($limit);
  $form_state['redirect'] = 'admin/user/node_limit';
  drupal_set_message(t('Saved limit "%limit"', array(
    '%limit' => $limit['title'],
  )));
}

/**
 * Confirmation form to delete a node limit.
 */
function node_limit_delete_form(&$form_state, $limit) {
  if ($limit == FALSE) {
    drupal_goto('admin/user/node_limit');
  }
  $form = array(
    'lid' => array(
      '#type' => 'hidden',
      '#value' => $limit['lid'],
    ),
  );
  return confirm_form($form, t('Are you sure you want to delete %name?', array(
    '%name' => $limit['title'],
  )), 'admin/user/node_limit');
}

/**
 * Submission hook for node limit deletion.
 */
function node_limit_delete_form_submit($form_id, &$form_state) {
  $lid = $form_state['values']['lid'];
  node_limit_delete($lid);
  $form_state['redirect'] = 'admin/user/node_limit';
}

/**
 * Callback to clone a limit.
 */
function node_limit_clone_limit($limit) {
  $old_title = $limit['title'];
  $limit['lid'] = _node_limit_next_limit_id();
  $limit['title'] = t('Clone of !title', array(
    '!title' => $old_title,
  ));
  node_limit_save($limit);
  drupal_set_message(t('Cloned limit "%limit"', array(
    '%limit' => $old_title,
  )));
  drupal_goto('admin/user/node_limit');
}

/**
 * Helper function to get the next available node limit id.
 */
function _node_limit_next_limit_id() {
  $next_lid = db_result(db_query("SELECT (MAX(lid)+1) AS lid FROM {node_limit}"));
  return max($next_lid, 1);
}

/**
 * Loads a node limit.
 *
 * @param $lid
 *   The limit id.
 * @return
 *   FALSE if the limit couldn't be loaded; otherwise the limit rule.
 */
function node_limit_load($lid) {
  if (!is_numeric($lid)) {
    return FALSE;
  }
  $info = db_fetch_array(db_query("SELECT * FROM {node_limit} WHERE `lid`='%d'", $lid));
  if ($info['lid'] == $lid && intval($lid) >= 0) {

    //load up the information from the other modules

    //perhaps this isn't necessary.  does node_limit ever use the other modules info?

    //yes (for setting the default state of the "applies" checkbox when editing a limit)
    $res = module_invoke_all('node_limit_load', $lid);
    return array_merge($info, $res);
  }
  else {
    return FALSE;
  }
}
function node_limit_delete($lid) {
  db_query("DELETE FROM {node_limit} WHERE `lid`='%d'", $lid);
  module_invoke_all('node_limit_delete', $lid);
}

/**
 * Callback to save a node limit back to the database.
 */
function node_limit_save($limit) {
  node_limit_delete($limit['lid']);
  db_query("INSERT INTO {node_limit} VALUES('%d', '%d', '%s', '%d')", $limit['lid'], $limit['nlimit'], $limit['title'], $limit['weight']);
  drupal_set_message(print_r($limit, true));
  $modules = module_implements('node_limit_save');
  foreach ($modules as $module) {
    module_invoke($module, 'node_limit_save', $limit['lid'], isset($limit[$module]), $limit[$module]);
  }
}

Functions

Namesort descending Description
node_limit_clone_limit Callback to clone a limit.
node_limit_delete
node_limit_delete_form Confirmation form to delete a node limit.
node_limit_delete_form_submit Submission hook for node limit deletion.
node_limit_limits Returns all the limits that can be applied to a specific node.
node_limit_limit_form The node_limit settings form.
node_limit_limit_form_submit Submission hook for node_limit_limit_form.
node_limit_limit_form_validate Validation hook for node_limit_limit_form.
node_limit_list_limits Form for listing the created limits. Created as a form so that the user can adjust the weight.
node_limit_list_limits_submit Save the module weights.
node_limit_load Loads a node limit.
node_limit_menu Implementation of hook_menu().
node_limit_nodeapi Implementation of hook_nodeapi().
node_limit_perm Implementation of hook_perm().
node_limit_save Callback to save a node limit back to the database.
node_limit_theme Implementation of hook_theme(). Register the two forms that require custom rendering.
theme_node_limit_limit_form Theme the admin settings form so that we have tables inside fieldsets.
theme_node_limit_list_limits Theme the node limit list form.
_node_limit_next_limit_id Helper function to get the next available node limit id.
_node_limit_sql Generates the sql statement to find the nodes that apply to a particular limit. Modules that implement hook_node_limit_sql() should sprintf their arguments into the returned array. This will be changed in Drupal 7, which will be able to accept an…
_node_limit_violates_limit Helper function to check limit violations for this node. Always returns FALSE for user 1.

Constants

Namesort descending Description
NODE_LIMIT_LIMIT_DOESNT_APPLY
NODE_LIMIT_LIMIT_DOES_APPLY
NODE_LIMIT_LIMIT_NEUTRAL
NODE_LIMIT_NO_LIMIT @file Module to restrict the number of nodes a user or role may create.
NODE_LIMIT_PERM_ADMIN