* * Licenced under the GPL v2. */ /* DRUPAL API HOOKS */ /** * Add new permissions allowing role-based access to the ability to set node-level permissions * * Implementation of hook_perm(). Note that these permissions do not give or deny the user the right to view the node (the purpose of this module) but instead determine whether the user has the right to edit the access settings for a given node. Essentially, these permissions will control whether the restricted access options appear on the form when editing a node. */ function restricted_content_perm() { return array('restrict content access', 'restrict own content access'); } /** * Add a configuration page for the module under 'Administer > Content management' * * Implementation of hook_menu(). */ function restricted_content_menu() { $items['admin/content/restricted'] = array( 'title' => 'Restricted content', 'page callback' => 'drupal_get_form', 'page arguments' => array('restricted_content_settings_form'), 'access arguments' => array('restrict content access'), 'file' => 'restricted_content.admin.inc', ); return $items; } /** * Add restricted access options to the node edit form * * Implementation of hook_form_alter(). */ function restricted_content_form_alter(&$form, $form_state, $form_id) { // REVIEW:AB:20100324: This could be used to set defaults per node type on the node *type* edit form if ($form_id == 'node_type_form') { // Add options to the node edit form } elseif ($form['#id'] == 'node-form') { $default = unserialize(db_result(db_query("SELECT rids FROM {restricted_content} WHERE nid = %d", $form['nid']['#value']))); $form['restricted_content'] = array( '#type' => 'fieldset', '#title' => t('Restricted Access'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, '#access' => restricted_content_canEditAccessPermissions($form['uid']['#value']), ); $form['restricted_content']['rids'] = array( '#type' => 'checkboxes', '#title' => t('Restrict access to users with the following user roles'), '#description' => t('If no roles are selected, the node will be viewable by all users.'), '#options' => user_roles(), '#default_value' => is_array($default) ? $default : array(), ); $form['#submit'][] = 'restricted_content_node_form_submit'; } } /** * Modify page render output to display a barrier page instead of the page content, where applicable * * Implementation of hook_nodeapi. */ function restricted_content_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { global $user; // If node is being deleted, clean up any stored access controls for this node if ($op == 'delete') { db_query("DELETE FROM {restricted_content} WHERE nid = %d", $node->nid); // On view, copy the teaser so that teaser is available as a token tag even where body would normally be used // This is required because node.module will delete either the body or teaser from $node depending upon the display context, and only one of the two will then be available by the time 'alter' is triggered below. } elseif ($op == 'view') { $node->teaser_shadow = $node->teaser; // On alter, determine whether the node is accessible to the current user, and display the appropriate barrier } elseif ($op == 'alter' && !restricted_content_hasAccess($node->nid)) { $node->restricted = TRUE; $node->comment = COMMENT_NODE_DISABLED; // Select an appropriate body replacement depending on whether the user is logged in or anonymous $key = (!$user->uid) ? 'message_anon' : 'message'; $message = restricted_content_var($key); // Perform token replacement if token module is available. if (module_exists('token')) { $message = token_replace($message, 'node', $node); } $node->body = $message; } } /** * Register restricted content specific tokens * * Adds tokens for string replacement in the barrier page templates * * Implementation of hook_token_list(). */ function restricted_content_token_list($type = 'all') { if ($type == 'node' || $type == 'all') { $tokens['node']['type-name-lower'] = t('Node type (user-friendly version lowercased)'); $tokens['node']['restricted-content-roles-required-list'] = t('Comma separated (with "or" before last item) list of roles that have access to this content, eg "Member, Editor or Webmaster"'); $tokens['node']['restricted-content-roles-required-list-with-prefix'] = t('Comma separated list of roles that have access to this content (with "or" before last item and an \'a\' or \'an\' as appropriate before the first item), eg "a Member, Editor or Webmaster"'); $tokens['node']['teaser'] = t('Rendered teaser for the node'); $tokens['global']['site-register-url'] = t('The URL of the register user page, redirecting back to the denied node once registration is complete (redirection only if supported by the user module)'); $tokens['global']['site-login-url'] = t('The URL of the sign in page, redirecting back to the denied node once login is complete (redirection only if supported by the user module)'); } return $tokens; } /** * Fill in values of custom restricted-content-specific tokens * * Populates the tokens that can be used within the barrier page templates, * particularly to provide a list of roles that have acess to the specified * node, so that the user knows what user level they need to attain in order * to be granted access. * * Implementation of hook_token_values(). */ function restricted_content_token_values($type, $object = NULL) { if ($type == 'node') { $rolenames = user_roles(); $validroles = restricted_content_getRolesWithAccess($object->nid); foreach ($validroles as $k=>$v) $validroles[$k] = $rolenames[$v]; $cnt = count($validroles); $str = ($cnt > 1) ? join(", ", array_slice($validroles,0,$cnt-1))." or ".$validroles[$cnt-1] : current($validroles); $tokens['restricted-content-roles-required-list'] = $str; $prefix = in_array($str[0], array('a','e','i','o','u')) ? "an" : "a"; $tokens['restricted-content-roles-required-list-with-prefix'] = $prefix." ".$str; $tokens['type-name-lower'] = drupal_strtolower(node_get_types('name', $object)); $tokens['teaser'] = $object->teaser_shadow; $tokens['site-register-url'] = url('user/register', array("query"=>"destination=".urlencode($object->path))); $tokens['site-login-url'] = url('user/login', array("query"=>"destination=".urlencode($object->path))); } return $tokens; } /* DRUPAL THEMING HOOKS */ /** * Remove sensitive properties of a restricted node to which the current user has been denied access * * Implementation of MODULE_preprocess_HOOK() */ function restricted_content_preprocess_node(&$vars) { if (!empty($vars['node']->restricted)) { $vars['submitted'] = FALSE; $vars['picture'] = FALSE; $vars['taxonomy'] = FALSE; $vars['terms'] = FALSE; $vars['links'] = FALSE; } } /* CALLBACKS */ /** * Store updated restricted access settings for a given node * * This function is the callback set up in restricted_content_form_alter */ function restricted_content_node_form_submit($form, $form_state) { $nid = $form_state['values']['nid']; $rids = array_keys(array_filter($form_state['values']['restricted_content']['rids'])); db_query("DELETE FROM {restricted_content} WHERE nid = %d", $nid); if ($rids) { db_query("INSERT INTO {restricted_content} VALUES (%d, '%s')", $nid, serialize($rids)); } } /* MODULE UTILITY FUNCTIONS */ /** * Returns true if specified user has right to set restricted access rules on nodes * * Module utility function */ function restricted_content_canEditAccessPermissions($uid) { global $user; return user_access('restrict content access') || ($uid == $user->uid && user_access('restrict own content access')); } /** * Returns list of roles that are allowed to view the specified node * * Module utility function. Uses restricted content rules defined by this module to determine which roles are allowed to view the specified node. * * @param integer $nid A node ID. * @return array List of role IDs (array of integers) */ function restricted_content_getRolesWithAccess($nid) { global $user; if (!$account) $account = $user; $rids = unserialize(db_result(db_query("SELECT rids FROM {restricted_content} WHERE nid = %d", $nid))); if (empty($rids) or !is_array($rids)) $rids = array(); return $rids; } /** * Returns true if the specified (or current) user has right to view the specified node * * Module utility function. Uses restricted content rules defined by this module to determine whether the user has the right to view the specified node. * * @param integer $nid A node ID. * @param integer $account An optional user account to check, defaults to the current user. * @return bool TRUE if the user has access to the node, or FALSE if the user is * restricted from the node. */ function restricted_content_hasAccess($nid, $account = NULL) { global $user; if (!$account) $account = $user; $rids = restricted_content_getRolesWithAccess($nid); return !$rids || array_intersect($rids, array_keys($account->roles)); } /** * Returns the value of a module specific configuration variable * * Wrapper for variable_get(), to allow defaults to be supplied where the site administrator has not yet configured the module. */ function restricted_content_var($name, $default = NULL) { $defaults = array( 'restricted_content_message' => t('This !token-type-name has been restricted to certain users.', array('!token-type-name' => module_exists('token') ? '[type-name-lower]' : t('content'))), 'restricted_content_message_anon' => t('Please register for a user account to view this !token-type-name.', array('!token-type-name' => module_exists('token') ? '[type-name-lower]' : t('content'), '!token-register' => module_exists('token') ? '[site-register-url]' : url('user/register'))), ); $name = 'restricted_content_' . $name; if (!isset($defaults[$name])) { trigger_error(t('Default variable for %variable not found.', array('%variable' => $name))); } return variable_get($name, isset($default) || !isset($defaults[$name]) ? $default : $defaults[$name]); }