You are here

protected_node.module in Protected Node 6

File

protected_node.module
View source
<?php

/*
 * @file
 * Protected node module
 */

/**
 * The password is required on all nodes unless the node type defines
 * a default password.
 *
 * Defined in: protected_node_use_global_password
 */
define('PROTECTED_NODE_PER_NODE_PASSWORD', 0);

/**
 * The password is not required. The system uses the global password
 * if the node does not define a password.
 *
 * Defined in: protected_node_use_global_password
 */
define('PROTECTED_NODE_PER_NODE_AND_GLOBAL_PASSWORD', 1);

/**
 * Use the global password only. Ignore the node specific password
 * and don't ask for one when editing the node.
 *
 * Defined in: protected_node_use_global_password
 */
define('PROTECTED_NODE_GLOBAL_PASSWORD', 2);

/**
 * Never protect these types of nodes.
 *
 * Defined in: protected_node_protection_<node type name>
 */
define('PROTECTED_NODE_PROTECTION_NEVER', 0);

/**
 * The author can choose whether the node is protected or not.
 * By default, the node is not protected.
 *
 * Defined in: protected_node_protection_<node type name>
 */
define('PROTECTED_NODE_PROTECTION_PROTECTABLE', 1);

/**
 * The author can choose whether the node is protected or not.
 * By default, the node is protected.
 *
 * Defined in: protected_node_protection_<node type name>
 */
define('PROTECTED_NODE_PROTECTION_PROTECTED', 2);

/**
 * The nodes of this type will always be protected.
 *
 * Defined in: protected_node_protection_<node type name>
 */
define('PROTECTED_NODE_PROTECTION_ALWAYS', 3);

/**
 * Implementation of hook_help().
 * @link http://api.drupal.org/api/function/hook_help/6
 */
function protected_node_help($path, $arg) {
  switch ($path) {
    case 'admin/modules#description':
      return t('With this module anybody who has edit protected node permission can password protect his or her own node.');
  }
}

/**
 * Implementation of hook_perm().
 * @link http://api.drupal.org/api/function/hook_perm/6
 */
function protected_node_perm() {
  $perms = array(
    'access protected content',
    'bypass password protection',
    'edit any password',
    'view protected content',
  );
  foreach (array_keys(node_get_types()) as $type) {
    $perms[] = 'edit ' . $type . ' password';
  }
  return $perms;
}

/**
 * Implementation of hook_menu().
 * @link http://api.drupal.org/api/function/hook_menu/6
 */
function protected_node_menu() {
  module_load_include('settings.inc', 'protected_node');
  return protected_node_menu_array();
}

/**
 * Callback function to determine who can enter a password.
 */
function protected_node_access_callback() {
  global $user;

  // super user?
  if ($user->uid == 1) {
    return TRUE;
  }
  if (!user_access('access protected content')) {
    return FALSE;
  }

  // is $nid properly defined?
  if (empty($_GET['protected_page']) || !is_numeric($_GET['protected_page'])) {
    return FALSE;
  }

  // valid node?
  $node = node_load($_GET['protected_page']);
  if (!$node) {
    return FALSE;
  }

  // editing/deleting? user has edit right?
  if (substr($_GET['destination'], 0, 5) == 'node/') {
    if (substr($_GET['destination'], -5) == '/edit') {
      if (!node_access('update', $node)) {
        return FALSE;
      }
    }
    elseif (substr($_GET['destination'], -7) == '/delete') {
      if (!node_access('delete', $node)) {
        return FALSE;
      }
    }
  }
  return TRUE;
}

/**
 * Implementation of hook_init().
 * @link http://api.drupal.org/api/function/hook_init/6
 */
function protected_node_init() {
  global $user;

  // are we about to display a node?
  // can user see all nodes anyway?
  if (user_access('bypass password protection')) {
    return;
  }
  $nid = FALSE;
  $param2 = arg(2);
  if (arg(0) == 'node' && is_numeric(arg(1))) {
    if ($param2 === NULL) {
      $nid = protected_node_is_locked(arg(1), 'view');
      if ($nid === -1) {
        return;
      }
    }
    elseif ($param2 == 'edit' || $param2 == 'delete') {
      $nid = protected_node_is_locked(arg(1), $param2);
    }
    else {

      // any access right?
      $nid = protected_node_is_locked(arg(1));
    }
    if ($nid === TRUE || $nid === -1) {
      drupal_access_denied();
      exit;
    }
  }
  elseif (arg(0) == 'system' && arg(1) == 'files') {
    if (!empty($param2)) {

      // $param2 is a filename in this case
      $nid = protected_node_and_attachment($param2);
    }
  }
  if ($nid) {
    $query = drupal_get_destination();
    if (!empty($_SERVER['HTTP_REFERER'])) {
      $query .= '&back=' . urlencode($_SERVER['HTTP_REFERER']);
    }
    $query .= '&protected_page=' . $nid;
    drupal_goto('protected-node', $query);
  }
}

/**
 * Check whether a node is protected and a password is required.
 *
 * \param[in] $nid The node identifier.
 * \param[in] $op Operation: 'access', 'view', 'edit', or 'delete'
 *
 * \return FALSE if the node is not protected for the current user.
 *         Return TRUE if it is protected and cannot be viewed by
 *         the current user. Return $nid if the user has a chance
 *         to unlock this protected node by entering the password.
 */
function protected_node_is_locked($nid, $op = 'access') {

  // get the node
  $node = node_load($nid);

  // is the node protected?
  if (!$node->protected_node_is_protected) {
    return FALSE;
  }

  // anonymous user?
  if (!$user->uid) {

    // do not cache anything for anonymous users as that could make
    // the content of the page available to people who never enter
    // the password (especially with aggressive caching.)
    if (variable_get('cache', CACHE_DISABLED)) {

      // prevent caching (do NOT use variable_set() since this is temporary for this session.)
      $GLOBALS['conf']['cache'] = CACHE_DISABLED;
    }
  }
  else {

    // author looking at his work? (if not anonymous)
    if ($node->uid === $user->uid) {
      return FALSE;
    }
  }

  // user cannot access any protected node
  // (this check avoids the rather useless drupal_goto() and thus does not
  // change the URL on the user.)
  if (!user_access('access protected content')) {
    return TRUE;
  }

  // if the user is only trying to view this node, accept
  if ($op == 'view') {
    if (user_access('view protected content') && node_access('view', $node)) {

      // user's got view permission without password
      // (password for edit/delete rights.)
      return -1;
    }
  }
  elseif ($op == 'edit') {
    if (!node_access('update', $node)) {

      // no rights to edit
      return TRUE;
    }

    // rights to edit, but password is still required in this case!
  }
  elseif ($op == 'delete') {
    if (!node_access('delete', $node)) {

      // no rights to delete
      return TRUE;
    }

    // rights to delete, but password is still required in this case!
  }
  else {
    return TRUE;
  }

  // user already entered the password?
  if (isset($_SESSION['_protected_node']['passwords'][$nid])) {
    $when = $_SESSION['_protected_node']['passwords'][$nid];
    if ($when > variable_get('protected_node_session_timelimit', 0) && $when > $node->protected_node_passwd_changed) {

      // this page reset time
      return FALSE;
    }

    // the session is out of date, we can as well get rid of it now
    unset($_SESSION['_protected_node']['passwords'][$nid]);
  }
  return $nid;
}

/**
 * If gathering an attachment, verify that it is accessible and if
 * not ask for the password.
 *
 * @param[in] $filename  The name of the attachment file.
 */
function protected_node_and_attachment($filename) {
  global $user;

  // the upload module glues the attachments and nodes together
  // without that module, we cannot test anything here
  // (it is not required anyway if the user is going to the /node/#
  // page itself.)
  if (user_access('bypass password protection') || !module_exists('upload')) {
    return FALSE;
  }

  // check whether the node linked to this file attachment is protected
  $sql = "SELECT u.nid, n.uid, pn.protected_node_passwd_changed" . " FROM {files} f, {upload} u, {protected_nodes} pn, {node} n" . " WHERE pn.nid = u.nid AND u.nid = n.nid AND f.filename = '%s' AND u.fid = f.fid" . " AND pn.protected_node_is_protected = 1";
  $file_info = db_fetch_array(db_query($sql, $filename));
  if ($file_info === FALSE || $user->uid && $user->uid == $file_info['uid']) {

    // $user is the author
    return FALSE;
  }

  // got the password?
  if (isset($_SESSION['_protected_node']['passwords'][$file_info['nid']])) {
    $when = $_SESSION['_protected_node']['passwords'][$file_info['nid']];
    if ($when > $file_info['protected_node_passwd_changed'] && $when > variable_get('protected_node_session_timelimit', 0)) {

      // global reset time
      return FALSE;
    }

    // the session is out of date, we can as well get rid of it now
    unset($_SESSION['_protected_node']['passwords'][$file_info['nid']]);
  }

  // avoid the drupal_goto() if another module anyway forbids access
  // to the file
  foreach (module_implements('file_download') as $module) {

    // skip ourself, we already know the answer!
    if ($module != 'protected_node') {
      $function = $module . '_file_download';
      $result = call_user_func_array($function, array(
        $filename,
      ));
      if (isset($result) && $result == -1) {

        // this $module forbids the file download, forget it
        // a password won't help
        return FALSE;
      }
    }
  }

  // no password, access denied
  return $file_info['nid'];
}

/**
 * Call module implemented functions with a parameter passed as reference
 * instead of copy.
 *
 * For calls that require multiple parameters, use an array or object.
 *
 * @param[in] $hook  The name of the hook to call
 * @param[in,out] $param  The one parameter to pass to the hook functions
 */
function protected_node_invoke($hook, &$param) {
  foreach (module_implements($hook) as $module) {
    call_user_func($module . '_' . $hook, $param);
  }
}

/**
 * Implementation of hook_form_alter().
 * @link http://api.drupal.org/api/function/hook_form_alter/6
 *
 * Add the protected node form widgets assuming the user editing this
 * node as permission to do so on this type of node.
 *
 * Turn off the auto-complete feature on this form since it could otherwise
 * pre-fill the password with the wrong parameter.
 *
 * @param[in,out] $form  The form to alter
 * @param[in,out] $form_state  The current state of the form
 * @param[in] $form_id  The name of the form being modified
 */
function protected_node_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    module_load_include('settings.inc', 'protected_node');
    protected_node_node_type_form_alter($form);
  }
  elseif (isset($form['type']['#value']) && $form['#id'] == 'node-form' && (user_access('edit any password') || user_access('edit ' . $form['type']['#value'] . ' password'))) {
    module_load_include('settings.inc', 'protected_node');
    protected_node_node_form_alter($form);
  }
}

/**
 * Implementation of hook_nodeapi().
 * @link http://api.drupal.org/api/function/hook_nodeapi/6
 */
function protected_node_nodeapi(&$node, $op, $arg = 0, $page = 0) {
  global $user;

  // ugly but we want to keep some variables between the validation and insert/update
  global $_protected_node_emails;
  global $_protected_node_random_passwd;
  switch ($op) {
    case 'load':
      return protected_node_load($node);
    case 'validate':
      $_protected_node_emails = '';
      $_protected_node_random_passwd = '';
      if ($node->protected_node_is_protected && (user_access('edit any password') || user_access('edit ' . $node->type . ' password'))) {
        $missing_password = FALSE;
        if (empty($node->protected_node_passwd)) {

          // password missing in database too?
          $sql = "SELECT protected_node_passwd FROM {protected_nodes} WHERE nid = %d";
          $result = trim(db_result(db_query($sql, $node->nid)));

          // getting "    " (40 spaces) when empty
          if (empty($result)) {
            $missing_password = TRUE;
          }
        }
        if (!empty($node->protected_node_emails)) {
          if ($node->status) {

            // verify each email address
            $emails = explode(',', str_replace(array(
              "\r",
              "\n",
            ), ',', $node->protected_node_emails));
            foreach ($emails as $k => $m) {
              $m = trim($m);
              if ($m) {
                if (!valid_email_address($m)) {
                  form_error($arg['protected_node']['protected_node_emails'], t('Invalid email address: @m. Please correct this mistake and try again.', array(
                    '@m' => $m,
                  )));
                  unset($emails[$k]);

                  // unset just in case; should be useless though
                }
                else {
                  $emails[$k] = $m;
                }
              }
              else {

                // ignore empty entries
                unset($emails[$k]);
              }
            }
            $_protected_node_emails = implode(', ', $emails);
            if ($_protected_node_emails && $missing_password && variable_get('protected_node_random_password', FALSE)) {

              // automatically generate a password for the email (note that means the author won't know the password!)
              $_protected_node_random_passwd = user_password();
              $missing_password = FALSE;

              // not missing anymore
              drupal_set_message(t('A random password was generated in order to send the email about this page. Remember that changing the password will prevent users you just emailed from accessing this page.'), 'warning');
            }
          }
          else {

            // the node is not published, forget about emails!
            form_error($arg['protected_node']['protected_node_emails'], t('Email addresses were specified even though the password is turned off.'));
          }
        }
        if ($missing_password) {
          if ($user->uid == 0) {

            // if anonymous user, then global password is not an option otherwise
            // all the nodes could be edited by all the anonymous users!
            $global_password = PROTECTED_NODE_PER_NODE_PASSWORD;
          }
          else {
            $global_password = variable_get('protected_node_use_global_password', PROTECTED_NODE_PER_NODE_PASSWORD);
          }
          switch ($global_password) {
            case PROTECTED_NODE_PER_NODE_PASSWORD:

              // $arg is the form in this case
              form_error($arg['protected_node']['protected_node_passwd'], t('To protect this page, please enter a password.'));
              break;
          }
        }
      }
      elseif (isset($node->protected_node_emails) && trim($node->protected_node_emails)) {
        form_error($arg['protected_node']['protected_node_emails'], t('No email can be sent by the protected node module when the node is not protected or you do not have permission to set a password.'));
      }
      break;
    case 'insert':
    case 'update':
      if (user_access('edit any password') || user_access('edit ' . $node->type . ' password')) {
        if (!empty($_protected_node_random_passwd)) {
          $node->protected_node_passwd = $_protected_node_random_passwd;
        }
        if (!empty($_protected_node_emails)) {
          $node->protected_node_emails = $_protected_node_emails;
        }
        protected_node_save($node);
        if ($node->protected_node_is_protected && !empty($node->protected_node_emails)) {
          module_load_include('mail.inc', 'protected_node');
          protected_node_send_mail($node);
        }
      }
      break;
    case 'view':
      if (!empty($node->protected_node_is_protected)) {

        // Accessed for search indexing? (usually by cron.php)
        if ($node->build_mode == NODE_BUILD_SEARCH_INDEX) {

          // "user" could see the node, but at this time, not its contents
          // (the current user is Anonymous, so that statement is not exactly true,
          // but at the time of the search index building we cannot know who will
          // be searching so we let go without the access denied error.)
          protected_node_invoke('protected_node_hide', $node);
        }
        elseif (!user_access('bypass password protection') && !user_access('view protected content')) {
          if (!$user->uid && variable_get('cache', CACHE_DISABLED)) {

            // prevent caching (do NOT use variable_set() since this is temporary for this session.)
            $GLOBALS['conf']['cache'] = CACHE_DISABLED;
          }
          if ($node->uid !== $user->uid) {

            // is there a password?
            if (isset($_SESSION['_protected_node']['passwords'][$node->nid])) {

              // is password out of date?
              $when = $_SESSION['_protected_node']['passwords'][$node->nid];
              if ($when <= variable_get('protected_node_session_timelimit', 0) || $when <= $node->protected_node_passwd_changed) {

                // this page reset time
                unset($_SESSION['_protected_node']['passwords'][$node->nid]);
              }
            }
            if (!isset($_SESSION['_protected_node']['passwords'][$node->nid])) {
              if (!user_access('access protected content')) {

                // user will never be given access (no drupal_goto() call necessary)
                drupal_access_denied();
                exit;
              }

              // user could see the node, but at this time, not its contents
              protected_node_invoke('protected_node_hide', $node);
            }
          }
        }
      }
      break;
    case 'delete':
      db_query('DELETE FROM {protected_nodes} WHERE nid = %d', $node->nid);
      break;
  }
}

/**
 * Implementation of hook_protected_node_hide().
 *
 * We implement this callback since it makes sense (I think) although
 * it makes the module a bit slower.
 *
 * This function hides the body & teaser, and if requested on that node
 * we hide the title as well.
 *
 * @param[in,out] $node  The affected node.
 */
function protected_node_protected_node_hide(&$node) {

  // Core module fields
  if (!$node->protected_node_show_title) {
    $node->title = t('Password protected page');
  }
  $node->teaser = '';
  $node->body = '';
  $node->content = array();
}

/**
 * Implementation of hook_file_download().
 * @link http://api.drupal.org/api/function/hook_file_download/6
 */
function protected_node_file_download($file) {
  global $user;

  // the upload module glues the attachments and nodes together
  // without that module, we cannot test anything here
  // (it is not required anyway if the user is going to the /node/#
  // page itself.)
  if (user_access('bypass password protection') || !module_exists('upload')) {
    return array();
  }

  // check whether the node linked to this file attachment is protected
  $sql = "SELECT u.nid, n.uid, pn.protected_node_passwd_changed" . " FROM {files} f, {upload} u, {protected_nodes} pn, {node} n" . " WHERE pn.nid = u.nid AND u.nid = n.nid AND f.filename = '%s' AND u.fid = f.fid" . " AND pn.protected_node_is_protected = 1";
  $file_info = db_fetch_array(db_query($sql, $file));
  if ($file_info === FALSE || $user->uid && $user->uid == $file_info['uid']) {

    // $user is the author
    return array();
  }

  // got the password?
  if (isset($_SESSION['_protected_node']['passwords'][$file_info['nid']])) {
    $when = $_SESSION['_protected_node']['passwords'][$file_info['nid']];
    if ($when > $file_info['protected_node_passwd_changed'] && $when > variable_get('protected_node_session_timelimit', 0)) {

      // global reset time
      return array();
    }
  }

  // no password, access denied
  return -1;
}

/*
 * Helper functions
 */

/**
 * Sets the given node to protected with the provided password.
 * The password cannot be empty.
 *
 * If the node already password protected this method changes the password
 * to the one you provided as $password parameter.
 *
 * @param[in,out] object $node The node to be saved.
 * @return boolean TRUE if the action was successful, FALSE otherwise.
 */
function protected_node_save(&$node) {

  // we first test whether a protected_nodes entry exist so we can use UPDATE
  // or INSERT accordingly (UPDATE does not always properly report working
  // with MySQL.)
  // We also retrive nid because protected_node_passwd may exist and be empty
  $result = db_fetch_array(db_query("SELECT nid, protected_node_passwd FROM {protected_nodes} WHERE nid = %d", $node->nid));
  if (!empty($result)) {

    // note: the following test prevents the user from using "0"
    //       as a password.
    if (isset($node->protected_node_passwd)) {
      $changed = $node->protected_node_passwd != $result['protected_node_passwd'];
      if (empty($node->protected_node_passwd)) {

        // keep result if it's empty...
        $node->protected_node_passwd = $result['protected_node_passwd'];
        $changed = FALSE;
      }
      else {
        $node->protected_node_clear_passwd = $node->protected_node_passwd;
        $node->protected_node_passwd = sha1($node->protected_node_passwd);
      }
    }
    else {
      $changed = FALSE;
      $node->protected_node_passwd = $result['protected_node_passwd'];
    }
    $sql = "UPDATE {protected_nodes} SET protected_node_is_protected = %d," . " protected_node_passwd = '%s', protected_node_show_title = %d," . " protected_node_hint = '%s'";
    $args = array(
      $node->protected_node_is_protected,
      $node->protected_node_passwd,
      $node->protected_node_show_title,
      isset($node->protected_node_hint) ? $node->protected_node_hint : '',
    );
    if ($changed) {
      $sql .= ", protected_node_passwd_changed = %d";
      $args[] = time();

      // last time the password was changed (i.e. invalidate all existing sessions)
    }
    $sql .= " WHERE nid = %d";
    $args[] = $node->nid;
    db_query($sql, $args);
  }
  else {
    if (!isset($node->protected_node_passwd)) {

      // this happens when the global password is to be used
      $node->protected_node_passwd = '';
    }
    elseif ($node->protected_node_passwd) {
      $node->protected_node_clear_passwd = $node->protected_node_passwd;
      $node->protected_node_passwd = sha1($node->protected_node_passwd);
    }

    // we don't need to set the protected_node_passwd_changed since no
    // one has ever entered a password for this node
    $sql = "INSERT INTO {protected_nodes} (protected_node_is_protected," . " protected_node_passwd, protected_node_show_title, nid," . " protected_node_hint)" . " VALUES (%d, '%s', %d, %d, '%s')";
    db_query($sql, $node->protected_node_is_protected, $node->protected_node_passwd, $node->protected_node_show_title, $node->nid, isset($node->protected_node_hint) ? $node->protected_node_hint : '');
  }
}

/**
 * Load the node extension fields.
 *
 * @param[in] object $node  The node to complement with the protected node parameters.
 *
 * @return An array with the node extended fields or FALSE.
 */
function protected_node_load($node) {

  // valid input parameters?
  if (!is_object($node) || !is_numeric($node->nid)) {
    return FALSE;
  }

  // default fields for protected nodes
  static $default_fields = array(
    'protected_node_is_protected' => FALSE,
    'protected_node_passwd' => '',
    'protected_node_passwd_changed' => 0,
    'protected_node_show_title' => 0,
    'protected_node_hint' => '',
  );

  // can the node be protected at all?
  $protection = variable_get('protected_node_protection_' . $node->type, PROTECTED_NODE_PROTECTION_PROTECTABLE);
  if ($protection == PROTECTED_NODE_PROTECTION_NEVER) {

    // by default the node is not protected, return that
    return $default_fields;
  }
  $sql = "SELECT protected_node_is_protected, protected_node_passwd, protected_node_passwd_changed," . " protected_node_show_title, protected_node_hint FROM {protected_nodes} WHERE nid = %d";
  $result = db_fetch_array(db_query($sql, $node->nid));
  if (!is_array($result)) {

    // the SELECT failed, use the defaults
    $result = $default_fields;
  }
  else {

    // define any missing field
    $result += $default_fields;
  }

  // the password is a CHAR(40) and when empty it's all spaces
  // (this is possible when the global password is used)
  $result['protected_node_passwd'] = trim($result['protected_node_passwd']);

  // if the user changed the mode to "always protected" then we force that here
  // it means the node may not be accessible to people without administration
  // privileges since it may not have a default password
  if ($protection == PROTECTED_NODE_PROTECTION_ALWAYS) {
    $result['protected_node_is_protected'] = TRUE;
  }
  return $result;
}

/**
 * Implementation of hook_token_list().
 *
 * This function defines some extras for the protected node (i.e. whether a
 * node is protected, title flag, last time the password was changed, etc.)
 */
function protected_node_token_list($type = 'all') {
  if ($type == 'node' || $type == 'all') {
    $tokens['node']['node-is-protected'] = t('Whether the node is protected (yes/no).');
    $tokens['node']['node-password'] = t('The password in clear (only if available, empty otherwise).');
    $tokens['node']['node-protected-title'] = t('Whether the title node of the node is protected (yes/no).');
    $tokens['node']['node-password-hint'] = t('The password hint as entered in this node.');
    return $tokens;
  }
}

/**
 * Implementation of hook_node_type().
 *
 * This function deletes the variables corresponding to the fields added
 * to the node type form.
 *
 * @param[in] $op  The operation performed on the node type.
 * @param[in] $type  The type object concerned.
 */
function protected_node_node_type($op, $type) {
  switch ($op) {
    case 'delete':
      variable_del('protected_node_fieldset_' . $type->type);
      variable_del('protected_node_protection_' . $type->type);
      variable_del('protected_node_node_type_password_' . $type->type);
      variable_del('protected_node_node_type_password_field_' . $type->type);

      // should already be deleted by the submit()
      break;
  }
}

/**
 * Implementation of hook_token_values().
 */
function protected_node_token_values($type, $object = NULL, $options = array()) {
  if ($type == 'node' && $object) {
    if (empty($object->protected_node_is_protected)) {
      $tokens['node-is-protected'] = t('no');
      $tokens['node-password'] = '';
      $tokens['node-protected-title'] = '';
      $tokens['node-password-hint'] = '';
    }
    else {
      $tokens['node-is-protected'] = t('yes');
      $tokens['node-password'] = empty($object->protected_node_clear_passwd) ? '' : $object->protected_node_clear_passwd;
      $tokens['node-protected-title'] = empty($object->protected_node_show_title) ? t('no') : t('yes');
      $tokens['node-password-hint'] = $object->protected_node_hint;
    }
    return $tokens;
  }
}

/**
 * After_build function to disable autocomplete for the password fields.
 *
 * Without this FF >= 3 will attempt to autocomplete the fields with the user's login info.
 *
 * @link http://api.drupal.org/api/file/developer/topics/forms_api_reference.html/6#after_build
 */
function protected_node_autocomplete_off($form_element, &$form_state) {
  $form_element['pass1']['#attributes']['autocomplete'] = 'off';
  $form_element['pass2']['#attributes']['autocomplete'] = 'off';
  return $form_element;
}

/**
 * Implementation of hook_db_rewrite_sql().
 *
 * This hook forbids end users from seeing a node they do not otherwise have
 * access to without a password.
 */
function protected_node_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  if ($primary_field != 'nid') {
    return;
  }
  if (user_access('access protected content')) {
    return;
  }

  // Prevent query from finding nodes the current user may not have permission to see.
  // (i.e. if the user doesn't know the password, then it shouldn't be shown)
  $join = "LEFT JOIN {protected_nodes} protected_nd ON {$primary_table}.nid = protected_nd.nid";
  $where = "(protected_nd.nid IS NULL OR protected_nd.protected_node_is_protected = 0)";
  return array(
    'join' => $join,
    'where' => $where,
  );
}

/**
 * Prevent boost from caching protected nodes.
 *
 * @todo
 * We also need to make sure the cache gets cleared whenever
 * the protection is turned on.
 */
function protected_node_boost_is_cacheable($path) {

  // the $path may be an alias, unalias first
  $url = drupal_lookup_path('source', $path);
  if (!$url) {

    // $path is not an alias, use $path as is for our test
    $url = $path;
  }
  $p = explode('/', $url);
  if (count($p) == 2 && $p[0] == 'node' && is_numeric($p[1])) {

    // if protected, do not cache (i.e. not caching == return FALSE)
    return !protected_node_isset_protected($p[1]);
  }
}

/**
 * Add the Protected Node fieldset to the CCK.
 */
function protected_node_content_extra_fields($type_name) {
  $extra = array();
  $extra['protected_node'] = array(
    'label' => t('Protected node'),
    'description' => t('Protected node module form.'),
    'weight' => 20,
  );
  return $extra;
}

/**
 * This method marks the specified node as protected.
 *
 * The method accepts a password. It is legal to not pass a password in
 * which case the previously defined password is used or the global password.
 * If no password is available, then the node gets locked until edited by
 * the author or the administrator (UID=1) and a password is added.
 *
 * If the \p $passwd parameter is set, then the change is marked in the
 * database. In other words, all users who had previously enter a password
 * will be kicked out.
 *
 * @param[in] $param  The node identifier or whatever valid $param passed to node_load.
 * @param[in] $passwd  The node password.
 *
 * @return TRUE if the node is protection on return.
 */
function protected_node_set_protected($param, $passwd = NULL) {

  // get the existing node
  $node = node_load($param);
  if ($node == FALSE) {

    // not even a valid node identifier?!
    return FALSE;
  }
  if (empty($node->protected_node_is_protected)) {

    // node exists in our table?
    $r = db_result(db_query("SELECT nid FROM {protected_nodes} WHERE nid = %d", $node->nid));
    if ($r) {
      if (empty($passwd)) {

        // in this case, an empty password is fine
        $sql = "UPDATE {protected_nodes} SET protected_node_is_protected = 1 WHERE nid = %d";
        $result = db_query($sql, $node->nid) !== FALSE;
      }
      else {

        // we have to also update the password in this case
        $sql = "UPDATE {protected_nodes} SET protected_node_is_protected = 1," . " protected_node_passwd = '%s', protected_node_passwd_changed = %d WHERE nid = %d";
        $result = db_query($sql, sha1($passwd), time(), $node->nid) !== FALSE;
      }
    }
    else {

      // no entry in the database yet, add it now
      if (empty($passwd)) {
        $passwd = '';
      }
      else {
        $passwd = sha1($passwd);
      }
      $sql = "INSERT INTO {protected_nodes} (protected_node_is_protected," . " protected_node_passwd, protected_node_show_title, nid)" . " VALUES (1, '%s', %d, %d)";
      $result = db_query($sql, $passwd, variable_get('protected_node_show_node_titles', FALSE), $node->nid) !== FALSE;
    }
  }
  else {

    // the node is already protected, change the password if necessary
    if (empty($passwd)) {

      // it is protected; we're done (the password is not to be changed)
      return TRUE;
    }
    $sql = "UPDATE {protected_nodes} SET protected_node_passwd = '%s', protected_node_passwd_changed = %d WHERE nid = %d";
    $result = db_query($sql, sha1($passwd), time(), $node->nid) !== FALSE;
  }
  return $result;
}

/**
 * This method marks the specified node as unprotected.
 *
 * @param[in] int $nid  The node identifier.
 *
 * @return TRUE if the node was protected before the call, FALSE otherwise.
 */
function protected_node_unset_protected($nid) {
  $r = db_result(db_query("SELECT protected_node_is_protected FROM {protected_nodes} WHERE nid = %d", $nid)) == 1;
  db_query("UPDATE {protected_nodes} SET protected_node_is_protected = 0 WHERE nid = %d", $nid);
  return $r;
}

/**
 * This method determines the protected flag status for the given node id.
 *
 * Note that doesn't mean the node is protected for the current user
 * (i.e. the current user may have entered the password successfully.)
 *
 * @param[in] int $nid  The node id to check.
 *
 * @return boolean TRUE if the node identified by the nid you provided is protected, FALSE otherwise.
 */
function protected_node_isset_protected($nid) {
  if (!is_numeric($nid)) {
    return FALSE;
  }
  return db_result(db_query("SELECT protected_node_is_protected FROM {protected_nodes} WHERE nid = %d", $nid)) == 1;
}

/**
 * Revoke access to the current used from the specified protected node.
 * The effect is immediate.
 *
 * Note that the date is not checked so it is possible that the node was
 * already locked and this function still returns TRUE (i.e. the lock
 * release was out of date and thus the node was anyway not accessible.)
 *
 * @param[in] $nid  The node to lock.
 *
 * @return TRUE if the node gets unlocked.
 */
function protected_node_lock($nid) {
  if (is_numeric($nid) && isset($_SESSION['_protected_node']['passwords'][$nid])) {
    unset($_SESSION['_protected_node']['passwords'][$nid]);
    return TRUE;
  }
  return FALSE;
}

/**
 * Give access to the current user to the specified protected node.
 * The duration of the lock is as expected starting now.
 *
 * @param[in] $nid  The node to unlock.
 *
 * @return TRUE if the node gets unlocked.
 */
function protected_node_unlock($nid) {
  if (is_numeric($nid)) {

    // make sure the node exists
    $node = node_load($nid);
    if ($node->protected_node_is_protected) {
      $_SESSION['_protected_node']['passwords'][$nid] = time();
      return TRUE;
    }
  }
  return FALSE;
}

/** @brief WebFM support.
 *
 * Check webfm file access. In this case, we just check whether the
 * node is protected for the current user.
 *
 * @param[out] $access  Variable whether the access result is saved.
 * @param[in] $node  Node being checked.
 * @param[in] $fid  File being checked.
 */
function protected_node_webfm_file_access_alter(&$access, $node, $fid) {
  $access = !protected_node_is_locked($node->nid, 'view');
}

// vim: ts=2 sw=2 et syntax=php

Functions

Namesort descending Description
protected_node_access_callback Callback function to determine who can enter a password.
protected_node_and_attachment If gathering an attachment, verify that it is accessible and if not ask for the password.
protected_node_autocomplete_off After_build function to disable autocomplete for the password fields.
protected_node_boost_is_cacheable Prevent boost from caching protected nodes.
protected_node_content_extra_fields Add the Protected Node fieldset to the CCK.
protected_node_db_rewrite_sql Implementation of hook_db_rewrite_sql().
protected_node_file_download Implementation of hook_file_download(). @link http://api.drupal.org/api/function/hook_file_download/6
protected_node_form_alter Implementation of hook_form_alter(). @link http://api.drupal.org/api/function/hook_form_alter/6
protected_node_help Implementation of hook_help(). @link http://api.drupal.org/api/function/hook_help/6
protected_node_init Implementation of hook_init(). @link http://api.drupal.org/api/function/hook_init/6
protected_node_invoke Call module implemented functions with a parameter passed as reference instead of copy.
protected_node_isset_protected This method determines the protected flag status for the given node id.
protected_node_is_locked Check whether a node is protected and a password is required.
protected_node_load Load the node extension fields.
protected_node_lock Revoke access to the current used from the specified protected node. The effect is immediate.
protected_node_menu Implementation of hook_menu(). @link http://api.drupal.org/api/function/hook_menu/6
protected_node_nodeapi Implementation of hook_nodeapi(). @link http://api.drupal.org/api/function/hook_nodeapi/6
protected_node_node_type Implementation of hook_node_type().
protected_node_perm Implementation of hook_perm(). @link http://api.drupal.org/api/function/hook_perm/6
protected_node_protected_node_hide Implementation of hook_protected_node_hide().
protected_node_save Sets the given node to protected with the provided password. The password cannot be empty.
protected_node_set_protected This method marks the specified node as protected.
protected_node_token_list Implementation of hook_token_list().
protected_node_token_values Implementation of hook_token_values().
protected_node_unlock Give access to the current user to the specified protected node. The duration of the lock is as expected starting now.
protected_node_unset_protected This method marks the specified node as unprotected.
protected_node_webfm_file_access_alter @brief WebFM support.

Constants

Namesort descending Description
PROTECTED_NODE_GLOBAL_PASSWORD Use the global password only. Ignore the node specific password and don't ask for one when editing the node.
PROTECTED_NODE_PER_NODE_AND_GLOBAL_PASSWORD The password is not required. The system uses the global password if the node does not define a password.
PROTECTED_NODE_PER_NODE_PASSWORD The password is required on all nodes unless the node type defines a default password.
PROTECTED_NODE_PROTECTION_ALWAYS The nodes of this type will always be protected.
PROTECTED_NODE_PROTECTION_NEVER Never protect these types of nodes.
PROTECTED_NODE_PROTECTION_PROTECTABLE The author can choose whether the node is protected or not. By default, the node is not protected.
PROTECTED_NODE_PROTECTION_PROTECTED The author can choose whether the node is protected or not. By default, the node is protected.