<?php /** * @file * Protected node module. */ /** * Per node password. * * 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); /** * Per node or global password. * * 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); /** * Global password only. * * 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); /** * Implements hook_help(). */ 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.'); } } /** * Implements hook_permission(). */ function protected_node_permission() { $permissions = array( 'access protected node overview page' => array( 'title' => t('Access protected node overview page'), ), 'access protected node password form' => array( 'title' => t('Access protected node password form'), 'description' => t('Access protected node password form page. Without this permission user will be denied access completely.'), ), 'edit any protected node password' => array( 'title' => t('Edit any protected node password'), 'description' => t('Edit the password of any protected node. Grants access to the password fieldset in the node form.'), ), 'view protected content' => array( 'title' => t('View protected content (bypass password)'), 'description' => t('Allow to view any protected node by bypassing the password protection.'), ), 'edit protected content' => array( 'title' => t('Edit protected content (bypass password)'), 'description' => t('Allow to edit any protected node by bypassing the password protection. Do not allow to edit the password. Note: the user will also be able to view the content without entering the password.'), ), ); foreach (node_type_get_types() as $key => $type) { $permissions['edit ' . $key . ' password'] = array( 'title' => t('Edit %type_name password', array('%type_name' => $key)), 'description' => t('Edit password for %type_name nodes', array('%type_name' => $key)), ); } return $permissions; } /** * Implements hook_menu(). */ 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 node password form')) { 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; } /** * Implements hook_init(). */ function protected_node_init() { // Let Drush bypass password protection. if (function_exists('drush_main')) { return; } // Are we about to display a node? // Can user see all nodes anyway? if (user_access('edit protected content')) { return; } if (variable_get('protected_node_use_global_password', PROTECTED_NODE_PER_NODE_PASSWORD) == PROTECTED_NODE_GLOBAL_PASSWORD && isset($_SESSION['has_entered_global_password'])) { 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 other sub-path. $nid = protected_node_is_locked(arg(1), 'view'); } if ($nid === TRUE || $nid === -1) { drupal_access_denied(); exit(); } } elseif (arg(0) == 'system' && arg(1) == 'files') { $requested_url = drupal_parse_url(request_uri()); $path = urldecode(str_replace('system/files', '', $requested_url['path'])); if (!empty($path)) { $nid = protected_node_and_attachment($path); } } 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', array('query' => $query)); } } /** * Check whether a node is protected and a password is required. * * @param int $nid * The node identifier. * @param string $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. * Return -1 if the user is trying to view the node and has both access to * view nodes of that type and the 'view protected content' permission. */ function protected_node_is_locked($nid, $op = 'access') { global $user; // Get the node. $node = node_load($nid); // Is the node protected? if (!isset($node->protected_node_is_protected) || !$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', 1)) { // Prevent caching (do NOT use variable_set() since this is temporary // for this session.). $GLOBALS['conf']['cache'] = 0; } } 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 node password form')) { 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; } elseif (user_access('edit protected content') && node_access('update', $node)) { // User's got edit permission without password // (password for edit/delete rights). return -1; } // 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 global password? if (isset($_SESSION['_protected_node']['passwords']['global'])) { $when = $_SESSION['_protected_node']['passwords']['global']; if ($when > variable_get('protected_node_session_timelimit', 0) /* Global reset time. */ && $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']['global']); } else { // 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) /* Global reset time. */ && $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; } /** * Helper function. * * If gathering an attachment, verify that it is accessible and if * not ask for the password. * * @param string $path * The path to the attachment file. * * @return mixed * File nid if user has access. FALSE otherwise. */ function protected_node_and_attachment($path) { global $user; if (user_access('edit protected content')) { return FALSE; } // Check whether the node linked to this file attachment is protected. $query = db_select('node', 'n'); $query->join('file_usage', 'fu', 'n.nid = fu.id'); $query->join('file_managed', 'fm', 'fm.fid = fu.fid'); $query->join('protected_nodes', 'pn', 'n.nid = pn.nid'); $query->fields('n', array('nid', 'uid')); $query->fields('pn', array('protected_node_passwd_changed')); $query->condition('fu.type', 'node'); $query->condition('fm.uri', '%' . db_like($path), 'LIKE'); $query->condition('pn.protected_node_is_protected', '1'); $number_of_results = $query->countQuery()->execute()->fetchField(); // If number is 0, node is not protected, or file is in a field collection. if (0 == $number_of_results) { if (module_exists('field_collection')) { // Check if file is attached to protected node via field collection. $query = db_select('file_usage', 'fu'); $query->join('file_managed', 'fm', 'fu.fid = fm.fid'); $query->fields('fu', array('id')); $query->condition('fu.type', 'field_collection_item'); $query->condition('fm.uri', '%' . db_like($path), 'LIKE'); $in_field_collection = $query->countQuery()->execute()->fetchField(); // The file is attached to a field collection item. if ($in_field_collection != '0') { $field_collection_ids = $query->execute()->fetchCol(); $field_collection_items = entity_load('field_collection_item', $field_collection_ids); // Get the nids. $protected_node_nids = array(); foreach ($field_collection_items as $field_collection_item) { $protected_node_nids[] = $field_collection_item->hostEntity()->nid; } // Query the node table again with the nid the field collection belongs // to. $query = db_select('node', 'n'); $query->join('protected_nodes', 'pn', 'n.nid = pn.nid'); $query->fields('n', array('nid', 'uid')); $query->fields('pn', array('protected_node_passwd_changed')); $query->condition('n.nid', $protected_node_nids, 'IN'); $query->condition('pn.protected_node_is_protected', '1'); $number_of_results = $query->countQuery()->execute()->fetchField(); if (0 == $number_of_results) { return FALSE; } } else { return FALSE; } } elseif (module_exists('paragraphs')) { // Check if file is attached to protected node via paragraphs. $query = db_select('file_usage', 'fu'); $query->join('file_managed', 'fm', 'fu.fid = fm.fid'); $query->fields('fu', array('id')); $query->condition('fu.type', 'paragraphs_item'); $query->condition('fm.uri', '%' . db_like($path), 'LIKE'); $in_paragraphs = $query->countQuery()->execute()->fetchField(); // The file is attached to a paragraphs item. if ($in_paragraphs != '0') { $paragraphs_ids = $query->execute()->fetchCol(); /** @var \ParagraphsItemEntity[] $paragraphs_items */ $paragraphs_items = entity_load('paragraphs_item', $paragraphs_ids); // Get the nids. $protected_node_nids = array(); foreach ($paragraphs_items as $paragraphs_item) { $nid = _protected_node_get_paragraph_node_host_entity_id($paragraphs_item); if ($nid) { $protected_node_nids[] = $nid; } } // Query the node table again with the nid the paragraph belongs // to. if (!empty($protected_node_nids)) { $query = db_select('node', 'n'); $query->join('protected_nodes', 'pn', 'n.nid = pn.nid'); $query->fields('n', array('nid', 'uid')); $query->fields('pn', array('protected_node_passwd_changed')); $query->condition('n.nid', $protected_node_nids, 'IN'); $query->condition('pn.protected_node_is_protected', '1'); $number_of_results = $query->countQuery()->execute()->fetchField(); if (0 == $number_of_results) { return FALSE; } } else { return FALSE; } } else { return FALSE; } } else { // If not in node, nor in field_collection or paragraphs, return FALSE return FALSE; /* Row doesn't exist, it's not protected */ } } $result = $query->execute(); foreach ($result as $file_info) { // Row doesn't exist, it's not protected || $user is the author. if ($file_info === FALSE || ($user->uid && $user->uid == $file_info->uid)) { return FALSE; } // The user has the bypass password for view. if (user_access('view protected content', $user)) { return FALSE; } // Got the global password? if (isset($_SESSION['_protected_node']['passwords']['global'])) { $when = $_SESSION['_protected_node']['passwords']['global']; // This page reset time && global reset time. if ($when > $file_info->protected_node_passwd_changed && $when > variable_get('protected_node_session_timelimit', 0)) { return FALSE; } // The session is out of date, we can as well get rid of it now. unset($_SESSION['_protected_node']['passwords']['global']); } else { // Got the password? if (isset($_SESSION['_protected_node']['passwords'][$file_info->nid])) { $when = $_SESSION['_protected_node']['passwords'][$file_info->nid]; // This page reset time && global reset time. if ($when > $file_info->protected_node_passwd_changed && $when > variable_get('protected_node_session_timelimit', 0)) { 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]); } } // No password, access denied. return $file_info->nid; } } /** * Module invoke. * * 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); } } /** * Implements hook_form_FORM_ID_alter(). * * Add the protected node form fieldset if the user editing has permission to * edit this node type password. */ function protected_node_form_node_type_form_alter(&$form, &$form_state, $form_id) { if (user_access('edit any protected node password') || user_access('edit ' . $form['type']['#value'] . ' password')) { form_load_include($form_state, 'settings.inc', 'protected_node'); protected_node_node_type_form_alter($form); } } /** * Implements hook_form_FORM_ID_alter(). * * Add the protected node form fieldset if the user editing has permission to * edit this node type password. */ function protected_node_form_node_form_alter(&$form, &$form_state, $form_id) { if (user_access('edit any protected node password') || user_access('edit ' . $form['type']['#value'] . ' password')) { form_load_include($form_state, 'settings.inc', 'protected_node'); protected_node_node_form_alter($form); } } /** * Implements hook_node_load(). */ function protected_node_node_load($nodes, $types) { return protected_node_load($nodes); } /** * Implements hook_node_validate(). */ function protected_node_node_validate($node, $form, &$form_state) { // The node type is never protected if $node->protected_node_is_protected is // not set. if (!isset($node->protected_node_is_protected)) { return; } global $_protected_node_emails; global $_protected_node_random_passwd; $_protected_node_emails = ''; $_protected_node_random_passwd = ''; if ($node->protected_node_is_protected && (user_access('edit any protected node password') || user_access('edit ' . $node->type . ' password'))) { $missing_password = FALSE; if (empty($node->protected_node_passwd)) { // Global content type password exists ? $global_content_type_password = variable_get('protected_node_node_type_password_' . $node->type, ''); if ($global_content_type_password != '') { // it's ok. } else { $result = db_select('protected_nodes') ->fields('protected_nodes', array('protected_node_passwd')) ->condition('nid', $node->nid) ->execute() ->fetchField(); // Getting " " (40 spaces) when empty. $result = trim($result); 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($form['protected_node']['protected_node_emails'], t('Invalid email address: @m. Please correct this mistake and try again.', array('@m' => $m))); // Unset just in case; should be useless though. unset($emails[$k]); } 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(); // Not missing anymore. $missing_password = FALSE; 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($form['protected_node']['protected_node_emails'], t('The node is not published. Therefore no email will be sent.')); } } if ($missing_password) { global $user; 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: form_error($form['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($form['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.')); } } /** * Implements hook_node_insert(). */ function protected_node_node_insert($node) { _protected_node_node_create_or_update($node); } /** * Implements hook_node_update(). */ function protected_node_node_update($node) { _protected_node_node_create_or_update($node); } /** * Helper function. * * Do protected node actions when creating or updating a node. * * @param object $node * A node object. */ function _protected_node_node_create_or_update($node) { // The node type is never protected if $node->protected_node_is_protected // is not set. if (!isset($node->protected_node_is_protected)) { return; } // Ugly but we want to keep some variables between the validation and // insert/update. global $_protected_node_emails; global $_protected_node_random_passwd; 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); // Send notifications if there is at least one email. if ($node->protected_node_is_protected && !empty($node->protected_node_emails) && $node->status == 1 && isset($node->protected_node_clear_passwd)) { module_load_include('mail.inc', 'protected_node'); protected_node_send_mail($node); } } /** * Implements hook_node_view(). */ function protected_node_node_view($node, $view_mode, $langcode) { global $user; if (!empty($node->protected_node_is_protected)) { // Accessed for search indexing? (usually by cron.php). if ($view_mode == '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('view protected content') && _protected_node_check_view_mode($view_mode)) { if (!$user->uid && variable_get('cache', 1)) { // Prevent caching (do NOT use variable_set() since this is temporary // for this session). $GLOBALS['conf']['cache'] = 0; } $global_reset_time = variable_get('protected_node_session_timelimit', 0); $this_page_reset_time = $node->protected_node_passwd_changed; if ($node->uid !== $user->uid) { // Is there a global password? if (isset($_SESSION['_protected_node']['passwords']['global'])) { // Is password out of date? $when = $_SESSION['_protected_node']['passwords']['global']; if ($when <= $global_reset_time || $when <= $this_page_reset_time) { unset($_SESSION['_protected_node']['passwords']['global']); } } // 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 <= $global_reset_time || $when <= $this_page_reset_time) { unset($_SESSION['_protected_node']['passwords'][$node->nid]); } } if (!isset($_SESSION['_protected_node']['passwords'][$node->nid]) && !isset($_SESSION['_protected_node']['passwords']['global'])) { if (!user_access('access protected node password form')) { // 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); } } } } } /** * Implements hook_node_delete(). */ function protected_node_node_delete($node) { db_delete('protected_nodes') ->condition('nid', $node->nid) ->execute(); } /** * Implements 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, 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->body = ''; // Remove $node->content children to avoid the user see content he/she should // not see. $content_children = element_children($node->content); foreach ($content_children as $content_key) { unset($node->content[$content_key]); } } /** * Implements hook_file_download(). */ function protected_node_file_download($uri) { global $user; $path = file_uri_target($uri); // Private file access for image style derivatives. if (strpos($path, 'styles/') === 0) { // Check that the file exists and is an image. if (image_get_info($uri)) { $original_uri = _protected_node_get_original_uri($path, $uri); // Check the permissions of the original to grant access to this image. $headers = module_invoke_all('file_download', $original_uri); // Confirm there's at least one module granting access and none denying // access. if (!empty($headers) && !in_array(-1, $headers)) { return array(); } } return array(); } // Private file access for the original files. $files = file_load_multiple(array(), array('uri' => $uri)); if (count($files)) { $file = reset($files); if ($file->status) { // Is it a file submitted with a webform? if (strpos($file->uri, '://webform/') !== FALSE) { // Pass through Webform submissions to get the nid given the fid. $query = db_select('file_usage', 'fu'); $query->join('webform_submissions', 'ws', 'ws.sid = fu.id'); $query->join('node', 'n', 'n.nid = ws.nid'); $query->join('protected_nodes', 'pn', 'n.nid = pn.nid'); $query->fields('n', array('nid', 'uid')); $query->fields('pn', array('protected_node_passwd_changed')); $query->condition('fu.module', 'webform'); $query->condition('fu.type', 'submission'); $query->condition('fu.fid', $file->fid); $query->condition('pn.protected_node_is_protected', '1'); } else { $query = db_select('node', 'n'); $query->join('file_usage', 'fu', 'n.nid = fu.id'); $query->join('protected_nodes', 'pn', 'n.nid = pn.nid'); $query->fields('n', array('nid', 'uid')); $query->fields('pn', array('protected_node_passwd_changed')); $query->condition('fu.fid', $file->fid); $query->condition('fu.type', 'node'); $query->condition('pn.protected_node_is_protected', '1'); } $number_of_results = $query->countQuery()->execute()->fetchField(); if (0 == $number_of_results) { return array(); /* Row doesn't exist, it's not protected */ } $result = $query->execute(); foreach ($result as $file_info) { // If the file belongs to the current user let them see it. if ($file_info === FALSE || ($user->uid && $user->uid == $file_info->uid)) { return array(); } // The user has the bypass password for view. if (user_access('view protected content', $user)) { return array(); } // Got the global password? if (isset($_SESSION['_protected_node']['passwords']['global'])) { $when = $_SESSION['_protected_node']['passwords']['global']; // This page reset time && global reset time. if ($when > $file_info->protected_node_passwd_changed && $when > variable_get('protected_node_session_timelimit', 0)) { return array(); } } elseif (isset($_SESSION['_protected_node']['passwords'][$file_info->nid])) { $when = $_SESSION['_protected_node']['passwords'][$file_info->nid]; // This page reset time && global reset time. if ($when > $file_info->protected_node_passwd_changed && $when > variable_get('protected_node_session_timelimit', 0)) { return array(); } } } // No password, access denied. return -1; } // Manage case of Webform files upload. elseif (strpos($file->uri, '://webform/') !== FALSE) { return array(); } // Do not protect temporary files. else { return array(); } } // No a file managed by protected node. return array(); } /** * Helper function used to return the original uri from a path and an uri. * * @see protected_node_file_download() */ function _protected_node_get_original_uri($path, $uri) { $args = explode('/', $path); // Discard the first part of the path (styles). array_shift($args); // Discard the second part of the path (style_name). array_shift($args); // Discard the third part of the path (scheme). array_shift($args); // Then the remaining parts are the path to the image. $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args); return $original_uri; } /** * 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. */ function _protected_node_save(&$node) { // The node type is never protected if $node->protected_node_is_protected // is not set. if (!isset($node->protected_node_is_protected)) { return; } // 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 retrieve nid because protected_node_passwd may exist and be empty. $result = db_select('protected_nodes') ->fields('protected_nodes', array( 'nid', 'protected_node_passwd', 'protected_node_emails', )) ->condition('nid', $node->nid) ->execute() ->fetchAssoc(); 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 ($changed) { 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 = hash('sha256', $node->protected_node_passwd); } } } else { $changed = FALSE; $node->protected_node_passwd = $result['protected_node_passwd']; } // Check if the email addresses is empty. if (empty($node->protected_node_emails)) { if (!empty($result['protected_node_emails'])) { // Keep the addresses. $saved_emails = $result['protected_node_emails']; } else { $saved_emails = ''; } } else { $saved_emails = $node->protected_node_emails; } $args = array( 'protected_node_is_protected' => (int) $node->protected_node_is_protected, 'protected_node_passwd' => $node->protected_node_passwd, 'protected_node_show_title' => (int) $node->protected_node_show_title, 'protected_node_emails' => $saved_emails, 'protected_node_hint' => isset($node->protected_node_hint) ? $node->protected_node_hint : '', ); if ($changed) { $args['protected_node_passwd_changed'] = REQUEST_TIME; } db_update('protected_nodes') ->fields($args) ->condition('nid', $node->nid) ->execute(); } 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 = hash('sha256', $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. db_insert('protected_nodes') ->fields(array( 'protected_node_is_protected' => (int) $node->protected_node_is_protected, 'protected_node_passwd' => $node->protected_node_passwd, 'protected_node_show_title' => (int) $node->protected_node_show_title, 'nid' => $node->nid, 'protected_node_emails' => isset($node->protected_node_emails) ? $node->protected_node_emails : '', 'protected_node_hint' => isset($node->protected_node_hint) ? $node->protected_node_hint : '', )) ->execute(); } } /** * Load the node extension fields. * * @param[in] object $node * The node to complement with the protected node parameters. * * @return array * An array with the node extended fields or FALSE. */ function protected_node_load($nodes) { foreach ($nodes as &$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' => 0, 'protected_node_passwd' => '', 'protected_node_passwd_changed' => 0, 'protected_node_show_title' => 0, 'protected_node_emails' => '', '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; } $result = db_select('protected_nodes') ->fields('protected_nodes', array( 'protected_node_is_protected', 'protected_node_passwd', 'protected_node_passwd_changed', 'protected_node_show_title', 'protected_node_emails', 'protected_node_hint', )) ->condition('nid', $node->nid) ->execute() ->fetchAssoc(); 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; } foreach ($result as $property => &$value) { $node->$property = $value; } } } /** * Implements hook_token_info(). * * 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_info() { $info['tokens']['node'] = array( 'is-protected' => array( 'name' => t('Node protected status'), 'description' => t("Whether the node is protected (yes/no)."), ), 'password' => array( 'name' => t('Node protected password'), 'description' => t("The password in clear (only if available, empty otherwise)."), ), 'protected-title' => array( 'name' => t('Node protected show title'), 'description' => t("Whether the title node of the node is protected (yes/no)."), ), 'password-hint' => array( 'name' => t('Node protected password hint'), 'description' => t("The password hint as entered in this node."), ), ); return $info; } /** * Implements hook_node_type_delete(). * * 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_delete($info) { variable_del('protected_node_fieldset_' . $info->type); variable_del('protected_node_protection_' . $info->type); variable_del('protected_node_node_type_password_' . $info->type); // Should already be deleted by the submit(). variable_del('protected_node_node_type_password_field_' . $info->type); } /** * Implements hook_tokens(). */ function protected_node_tokens($type, $tokens, array $data = array(), array $options = array()) { $replacements = array(); if ($type == 'node' && !empty($data['node'])) { $node = $data['node']; if (!empty($node->protected_node_is_protected)) { foreach ($tokens as $name => $original) { switch ($name) { case 'is-protected': $replacements[$original] = t('yes'); break; case 'password': $replacements[$original] = empty($node->protected_node_clear_passwd) ? '' : $node->protected_node_clear_passwd; break; case 'protected-title': $replacements[$original] = empty($node->protected_node_show_title) ? t('yes') : t('no'); break; case 'password-hint': $replacements[$original] = $node->protected_node_hint; break; } } } } return $replacements; } /** * 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. */ function protected_node_autocomplete_off($form_element, &$form_state) { $form_element['pass1']['#attributes']['autocomplete'] = 'off'; $form_element['pass2']['#attributes']['autocomplete'] = 'off'; return $form_element; } /** * Implements hook_boost_is_cacheable(). * * 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) { if (arg(0) == 'node' && is_numeric(arg(1))) { // If protected, do not cache (i.e. not caching == return FALSE). return !protected_node_isset_protected(arg(1)); } return TRUE; } /** * 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 bool * 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? $select = db_select('protected_nodes') ->fields('protected_nodes', array('nid')) ->condition('nid', $node->nid) ->execute() ->fetchField(); if ($select) { if (empty($passwd)) { // In this case, an empty password is fine. $result = db_update('protected_nodes') ->fields(array( 'protected_node_is_protected' => 1, )) ->condition('nid', $node->nid) ->execute() !== FALSE; } else { // We have to also update the password in this case. $result = db_update('protected_nodes') ->fields(array( 'protected_node_is_protected' => 1, 'protected_node_passwd' => hash('sha256', $passwd), 'protected_node_passwd_changed' => REQUEST_TIME, )) ->condition('nid', $node->nid) ->execute() !== FALSE; } } else { // No entry in the database yet, add it now. if (empty($passwd)) { $passwd = ''; } else { $passwd = hash('sha256', $passwd); } $result = db_insert('protected_nodes') ->fields(array( 'nid' => $node->nid, 'protected_node_is_protected' => 1, 'protected_node_passwd' => $passwd, 'protected_node_show_title' => variable_get('protected_node_show_node_titles', FALSE), )) ->execute() !== 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; } $result = db_update('protected_nodes') ->fields(array( 'protected_node_passwd' => hash('sha256', $passwd), 'protected_node_passwd_changed' => REQUEST_TIME, )) ->condition('nid', $node->nid) ->execute() !== FALSE; } return $result; } /** * This method marks the specified node as unprotected. * * This function ensures that the specified node is not protected anymore. * It does not delete the row from the database which means calling the * protected_node_set_protected() function with the same $nid parameter * will restore the previous state (assuming the node was protected before.) * * When the node was previously protected and this call succeeds, the method * returns TRUE. * * If an invalid $nid is passed FALSE is returned. * * @param[in] int $nid * The node identifier. * * @return bool * TRUE if the node was protected before the call, FALSE otherwise. */ function protected_node_unset_protected($nid) { $result = db_select('protected_nodes') ->fields('protected_nodes', array('protected_node_is_protected')) ->condition('nid', $nid) ->execute() ->fetchField() == 1; db_update('protected_nodes') ->fields(array( 'protected_node_is_protected' => 0, )) ->condition('nid', $nid) ->execute(); return $result; } /** * 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 bool * 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; } $result = db_select('protected_nodes') ->fields('protected_nodes', array('protected_node_is_protected')) ->condition('nid', $nid) ->execute() ->fetchField() == 1; return $result; } /** * 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 bool * TRUE if the node gets unlocked. */ function protected_node_lock($nid) { if (is_numeric($nid) && isset($_SESSION['_protected_node']['passwords']['global'])) { unset($_SESSION['_protected_node']['passwords']['global']); return TRUE; } 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 bool * 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) { if (isset($_SESSION['has_entered_global_password'])) { $_SESSION['_protected_node']['passwords']['global'] = REQUEST_TIME; } else { $_SESSION['_protected_node']['passwords'][$nid] = REQUEST_TIME; } return TRUE; } } return FALSE; } /** * Helper function. * * Evaluate whether the current view mode is one to check the password for. * * @param string $view_mode * Eg: full, teaser, or custom (eg: ds view modes). * * @return bool * TRUE if we are checking passwords, FALSE if not. */ function _protected_node_check_view_mode($view_mode) { $permitted_view_modes = variable_get('protected_node_checked_view_modes', _protected_node_get_default_checked_view_modes()); return in_array($view_mode, $permitted_view_modes); } /** * Helper function. * * Used to get the default checked view modes. */ function _protected_node_get_default_checked_view_modes() { $default_view_modes = &drupal_static(__FUNCTION__); if (!isset($default_view_modes)) { $node_info = entity_get_info('node'); $default_view_modes = array(); foreach ($node_info['view modes'] as $id => $item) { $default_view_modes[] = $id; } } return $default_view_modes; } /** * Helper function. * * Used to get the options for the protected_node_failed_password_ip_limit * variable. */ function _protected_node_get_failed_password_ip_limit_options() { return drupal_map_assoc(array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 75, 100, 125, 150, 200, 250, 500, )); } /** * Helper function. * * Used to get the options for the protected_node_failed_password_ip_window * variable. */ function _protected_node_get_failed_password_ip_window_options() { return array(0 => t('None (flood control disabled)')) + drupal_map_assoc(array( 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400, ), 'format_interval'); } /** * Helper function to get recursively the host entity nid of the paragraph. * * @param \ParagraphsItemEntity $paragraphs_item * A paragraph item. * * @return int|false * Return FALSE if the paragraph or the parent paragraphs are not attached to * a node */ function _protected_node_get_paragraph_node_host_entity_id(ParagraphsItemEntity $paragraphs_item) { // Need to load the host entity otherwise host entity date are null. paragraphs_item_get_host_entity($paragraphs_item); $host_entity_type = $paragraphs_item->hostEntityType(); if ($host_entity_type == 'node') { return $paragraphs_item->hostEntity()->nid; } elseif ($host_entity_type == 'paragraphs_item') { $host_id = array($paragraphs_item->hostEntityId()); $host_paragraphs_items = entity_load('paragraphs_item', $host_id); return _protected_node_get_paragraph_node_host_entity_id(array_shift($host_paragraphs_items)); } return FALSE; }