autologout.module 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php
  2. use Drupal\Core\Url;
  3. use Drupal\Core\Form\FormStateInterface;
  4. use Drupal\Core\Routing\RouteMatchInterface;
  5. /**
  6. * Implements hook_help().
  7. */
  8. function autologout_help($route_name, RouteMatchInterface $route_match) {
  9. switch ($route_name) {
  10. case 'help.page.autologout':
  11. $seconds = \Drupal::service('autologout.manager')->getUserTimeout();
  12. $output = '';
  13. $output .= '<h3>' . t('About') . '</h3>';
  14. $output .= '<p>' . t("This module allows you to force site users to be logged out after a given amount of time due to inactivity after first being presented with a confirmation dialog. Your current logout threshold is %seconds seconds.", ['%seconds' => $seconds]) . '</p>';
  15. return $output;
  16. }
  17. }
  18. /**
  19. * Implements hook_form_FORM_ID_alter().
  20. *
  21. * Adds a field to user/edit to change that users logout.
  22. */
  23. function autologout_form_user_form_alter(&$form, FormStateInterface $form_state) {
  24. $user = \Drupal::currentUser();
  25. $account = $form_state->getFormObject()->getEntity();
  26. $user_id = $account->id();
  27. $access = FALSE;
  28. // If user has access to change, and they are changing their own and only
  29. // their own timeout. Or they are an admin.
  30. if (!\Drupal::currentUser()->isAnonymous() && (($user->hasPermission('change own logout threshold') && $user->id() == $user_id) || $user->hasPermission('administer autologout'))) {
  31. $access = TRUE;
  32. $autologout_data = \Drupal::service('user.data')->get('autologout', $user_id, 'timeout');
  33. }
  34. if ($access) {
  35. $form['user_' . $user_id] = [
  36. '#type' => 'textfield',
  37. '#title' => t('Your current logout threshold'),
  38. '#default_value' => isset($autologout_data) ? $autologout_data : '',
  39. '#size' => 8,
  40. '#description' => t('How many seconds to give a user to respond to the logout dialog before ending their session.'),
  41. '#element_validate' => ['_autologout_user_uid_timeout_validate'],
  42. ];
  43. $form['actions']['submit']['#submit'][] = 'autologout_user_profile_submit';
  44. }
  45. }
  46. /**
  47. * Form validation.
  48. */
  49. function _autologout_user_uid_timeout_validate($element, FormStateInterface $form_state) {
  50. $max_timeout = \Drupal::config('autologout.settings')->get('max_timeout');
  51. $timeout = $element['#value'];
  52. // Set error if timeout isn't strictly a number between 60 and max.
  53. if ($timeout != "" && ($timeout < 10 || ($timeout > 0 && $timeout < 60) || $timeout > $max_timeout || !is_numeric($timeout))) {
  54. $form_state->setError($element, t('The timeout must be an integer greater than 60, and less then %max.', ['%max' => $max_timeout]));
  55. }
  56. }
  57. /**
  58. * Handle submission of timeout threshold in user/edit.
  59. */
  60. function autologout_user_profile_submit(&$form, FormStateInterface $form_state) {
  61. $user_id = $form_state->getFormObject()->getEntity()->id();
  62. $timeout = $form_state->getValue('user_' . $user_id);
  63. $enabled = ($timeout != '') ? TRUE : FALSE;
  64. \Drupal::service('user.data')->set('autologout', $user_id, 'timeout', $timeout);
  65. }
  66. /**
  67. * Implements hook_autologout_prevent().
  68. */
  69. function autologout_autologout_prevent() {
  70. $user = \Drupal::currentUser();
  71. // Don't include autologout JS checks on ajax callbacks.
  72. $paths = [
  73. 'system',
  74. 'autologout_ajax_get_time_left',
  75. 'autologout_ahah_logout',
  76. 'autologout_ahah_set_last',
  77. ];
  78. // getPath is used because Url::fromRoute('<current>')->toString() doesn't
  79. // give correct path for XHR request.
  80. $url = \Drupal::service('path.current')->getPath();
  81. $path_args = explode('/', $url);
  82. if (in_array($path_args[1], $paths)) {
  83. return TRUE;
  84. }
  85. // If user is anonymous or has no timeout set.
  86. if ($user->id() == 0 || (!\Drupal::service('autologout.manager')->getUserTimeout())) {
  87. return TRUE;
  88. }
  89. // If the user has checked remember_me via the remember_me module.
  90. $remember_me = \Drupal::service('user.data')->get('remember_me', $user->id(), 'remember_me');
  91. if (!empty($remember_me)) {
  92. return TRUE;
  93. }
  94. }
  95. /**
  96. * Implements hook_autologout_refresh_only().
  97. */
  98. function autologout_autologout_refresh_only() {
  99. if (!\Drupal::config('autologout.settings')->get('enforce_admin') && \Drupal::service('router.admin_context')->isAdminRoute(\Drupal::routeMatch()->getRouteObject())) {
  100. return TRUE;
  101. }
  102. }
  103. /**
  104. * Implements hook_page_attachments().
  105. *
  106. * Add a form element to every page which is used to detect if the page was
  107. * loaded from browser cache. This happens when the browser's back button is
  108. * pressed for example. The JS will set the value of the hidden input element
  109. * to 1 after initial load. If this is 1 on subsequent loads, the page was
  110. * loaded from cache and an autologout timeout refresh needs to be triggered.
  111. */
  112. function autologout_page_attachments(array &$page) {
  113. $autologout_manager = \Drupal::service('autologout.manager');
  114. // Check if JS should be included on this request.
  115. if ($autologout_manager->preventJs()) {
  116. return;
  117. }
  118. // Check if anything wants to be refresh only. This URL would include the
  119. // javascript but will keep the login alive whilst that page is opened.
  120. $refresh_only = $autologout_manager->refreshOnly();
  121. $settings = \Drupal::config('autologout.settings');
  122. $timeout = $autologout_manager->getUserTimeout();
  123. $timeout_padding = $settings->get('padding');
  124. $redirect_url = $settings->get('redirect_url');
  125. $redirect_query = \Drupal::service('redirect.destination')->getAsArray() + ['autologout_timeout' => 1];
  126. $no_dialog = $settings->get('no_dialog');
  127. $use_alt_logout_method = $settings->get('use_alt_logout_method');
  128. // Get all settings JS will need for dialog.
  129. $msg = t('@msg', ['@msg' => $settings->get('message')]);
  130. $settings = [
  131. 'timeout' => $refresh_only ? ($timeout * 500) : ($timeout * 1000),
  132. 'timeout_padding' => $timeout_padding * 1000,
  133. 'message' => t('@msg', ['@msg' => $msg]),
  134. 'redirect_url' => Url::fromUserInput($redirect_url, ['query' => $redirect_query])->toString(),
  135. 'title' => t('@name Alert', ['@name' => \Drupal::config('system.site')->get('name')]),
  136. 'refresh_only' => $refresh_only,
  137. 'no_dialog' => $no_dialog,
  138. 'use_alt_logout_method' => $use_alt_logout_method,
  139. ];
  140. // If this is an AJAX request, then the logout redirect url should still be
  141. // referring to the page that generated this request.
  142. if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
  143. global $base_url;
  144. $relative_url = str_replace($base_url . '/', '', $_SERVER['HTTP_REFERER']);
  145. $settings['redirect_url'] = Url::fromUserInput($redirect_url, [
  146. 'query' => ['destination' => urlencode($relative_url)],
  147. 'autologout_timeout' => 1,
  148. ]);
  149. }
  150. autologout_attach_js($page, $settings, TRUE);
  151. }
  152. /**
  153. * Implements hook_page_bottom().
  154. */
  155. function autologout_page_bottom() {
  156. if (!\Drupal::service('autologout.manager')->preventJs()) {
  157. $page_bottom['autologout'] = [
  158. '#markup' => '<form id="autologout-cache-check"><input type="hidden" id="autologout-cache-check-bit" value="0" /></form>',
  159. ];
  160. }
  161. }
  162. /**
  163. * Adds the necessary js and libraries.
  164. *
  165. * @param array $element
  166. * The renderable array element to #attach the js to.
  167. * @param array $settings
  168. * The JS Settings.
  169. */
  170. function autologout_attach_js(&$element, $settings) {
  171. $element['#attached']['drupalSettings']['autologout'] = $settings;
  172. $element['#attached']['library'][] = 'autologout/drupal.autologout';
  173. }
  174. /**
  175. * Implements hook_user_login().
  176. *
  177. * Delete stale sessions for the user on login. This stops
  178. * session_limit module thinking the user has reached their
  179. * session limit.
  180. */
  181. function autologout_user_login($account) {
  182. // Cleanup old sessions.
  183. $timeout = \Drupal::service('autologout.manager')->getUserTimeout($account->id());
  184. if (empty($timeout)) {
  185. // Users that don't get logged have their sessions left.
  186. return;
  187. }
  188. $timeout_padding = \Drupal::config('autologout.settings')->get('padding');
  189. $timestamp = REQUEST_TIME - ($timeout + $timeout_padding);
  190. // Find all stale sessions.
  191. $database = \Drupal::database();
  192. $sids = $database->select('sessions', 's')
  193. ->fields('s', ['sid'])
  194. ->condition('uid', $account->id())
  195. ->condition('timestamp', $timestamp, '<')
  196. ->orderBy('timestamp', 'DESC')
  197. ->execute()
  198. ->fetchCol();
  199. if (!empty($sids)) {
  200. // Delete stale sessions at login.
  201. $database->delete('sessions')
  202. ->condition('sid', $sids, 'IN')
  203. ->execute();
  204. }
  205. }