Index: checkout.module
===================================================================
--- checkout.module	(revision 1333)
+++ checkout.module	(working copy)
@@ -1,358 +1,360 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Enables users to lock documents for modification.
- */
-
-/**
- * Implementation of hook_perm().
- */
-function checkout_perm() {
-  return array('check out documents', 'keep documents checked out', 'administer checked out documents');
-}
-
-/**
- * Implementation of hook_help().
- */
-function checkout_help($section) {
-  switch ($section) {
-    case 'admin/help#checkout':
-      $output = '<p>'. t("Drupal, by default, does not take into account the possibility of multiple users wanting to update the same document (node) at the same time. There is consequently a danger that two users try to edit a node at the same time (a race condition), with one user overwriting the other's modifications.") .'</p>';
-      $output .= '<p>'. t("The checkout module offers active protection against concurrent edits. When a user begins to modify a document, it is considered being 'checked out' of the system for exclusive editing access by that user. It only becomes available to other users once the editor navigates out of the edit page - by submitting the content, viewing the node, or selecting another menu item. The system also takes into account that some users edit multiple documents at once, so there is a direct correlation between a browser session and the locks that are placed upon a document.") .'</p>';
-      $output .= '<p>'. t('If permission has been given at the <a href="!uri">access control</a> page, it is possible for users to keep a document checked out when they submit their changes; they can therefore be sure that their document is locked between sessions.', array('!uri' => url('admin/user/access'))) .'</p>';
-      $output .= '<p>'. t('A cron job is provided which will automatically check in documents that have been checked out beyond a configurable period of time.') .'</p>';
-      return $output;
-    case 'admin/content/node/checkout':
-      return '<p>'. t("Below is a list of all checked out documents. Click on 'check-in' to release a editing lock.") .'</p>';
-  }
-  if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'checkout') {
-    return '<p>'. t("Below is a list of all documents checked out by you. Click on 'check-in' to release a editing lock.") .'</p>';
-  }
-}
-
-/**
- * Implementation of hook_menu().
- */
-function checkout_menu($may_cache) {
-  global $user;
-  $items = array();
-
-  if ($may_cache) {
-    $admin_access = user_access('administer checked out documents');
-    $items[] = array('path' => 'admin/content/node/checkout',
-      'title' => t('Checked out'),
-      'callback' => 'checkout_admin_overview',
-      'access' => $admin_access,
-      'weight' => 5,
-      'type' => MENU_LOCAL_TASK);
-    $items[] = array('path' => 'admin/content/node/checkout/release',
-      'title' => t('Check-in content'),
-      'callback' => 'checkout_admin_release',
-      'access' => $admin_access,
-      'type' => MENU_CALLBACK);
-  }
-  else {
-    if (arg(0) == 'user' && is_numeric(arg(1)) && $user->uid == arg(1)) {
-      $user_access = user_access('check out documents');
-      $items[] = array('path' => 'user/'. arg(1) .'/checkout',
-        'title' => t('Track check-outs'),
-        'callback' => 'checkout_user_overview',
-        'callback arguments' => array(arg(1)),
-        'access' => $user_access,
-        'weight' => 5,
-        'type' => MENU_LOCAL_TASK);
-      $items[] = array('path' => 'user/'. arg(1) .'/checkout/release',
-        'title' => t('Check-in content'),
-        'callback' => 'checkout_user_release',
-        'callback arguments' => array(arg(1)),
-        'access' => $user_access,
-        'type' => MENU_CALLBACK);
-    }
-  }
-
-  return $items;
-}
-
-/**
- * Implementation of hook_form_alter().
- */
-function checkout_form_alter($form_id, &$form) {
-  if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
-    if (user_access('keep documents checked out')) {
-      $node = $form['#node'];
-      if ($node->nid) {
-        // Place checkbox immediately after the log message
-        $form['checkout'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Keep document checked out'),
-          '#return_value' => 1,
-          '#weight' => 20.1,
-          '#default_value' => FALSE,
-          '#description' => t('Check this box if you wish to keep this document locked for editing after submit.'),
-        );
-      }
-    }
-  }
-  else if ($form_id == 'node_configure') {
-    // Make sure our element appears before the submit buttons
-    $form['buttons']['#weight'] = 10;
-
-    $period = array(0 => t('Disabled')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
-    $form['checkout_clear'] = array(
-      '#type' => 'select',
-      '#title' => t('Automatic check-in'),
-      '#default_value' => variable_get('checkout_clear', 0),
-      '#options' => $period,
-      '#description' => t('The period after which checked out documents will be automatically released.'),
-    );
-  }
-}
-
-/**
- * Implementation of hook_nodeapi().
- */
-function checkout_nodeapi(&$node, $op, $teaser, $page) {
-  global $user;
-
-  switch ($op) {
-    case 'prepare':
-      if ($node->nid > 0) {
-        $data = checkout_checkout($node->nid);
-        if ($data && $data->uid != $user->uid) {
-          checkout_message($data); // won't return
-        }
-      }
-      break;
-    
-    case 'update':
-      if (empty($node->checkout)) {
-        checkout_release($node->nid);
-      }
-      else {
-        db_query("UPDATE {checkout} SET persistent = 1 WHERE nid = %d", $node->nid);
-      }
-      break;
-    
-    case 'delete':
-      checkout_release($node->nid);
-      break;
-  }
-}
-
-/**
- * Implementation of hook_cron().
- *
- * Release nodes that have been locked longer than the configured period.
- */
-function checkout_cron() {
-  $checkout_clear = variable_get('checkout_clear', 0);
-  if ($checkout_clear > 0) {
-    $result = db_query('DELETE FROM {checkout} WHERE timestamp < %d', time() - $checkout_clear);
-    if ($num = db_affected_rows($result)) {
-      $message = t('Released @count document(s) checked out for more than @period.', array('@count' => $num, '@period' => format_interval($checkout_clear)));
-      drupal_set_message($message);
-      watchdog('checkout', $message);
-    }
-  }
-}
-
-/**
- * Implementation of hook_exit().
- *
- * Release the node that has been last edited, but only if we're coming
- * directly from an edit page to protect against unlocking nodes while
- * editing/surfing in concurrent browser windows.
- */
-function checkout_exit() {
-  global $user;
-
-  if ($user->uid) {
-    $edit_types = array('edit', 'classify', 'outline');
-    $request = array_reverse(explode('/', request_uri()));
-    $referer = array_reverse(explode('/', referer_uri()));
-  
-    if (!in_array($request[0], $edit_types) && in_array($referer[0], $edit_types)) {
-      $nid = $referer[1];
-      if (!db_result(db_query("SELECT persistent FROM {checkout} WHERE nid = %d", $nid))) {
-        checkout_release($nid);
-      }
-    }
-  }
-}
-
-/**
- * Lock a node for editing.
- *
- * If the document isn't currently checked out the returned info object has
- * just one property: the user id of the current user. Otherwise it contains
- * the information about when and by whom the document was checked out.
- *
- * @param integer $nid
- *   A node id.
- *
- * @return object A check-out info object.
- */
-function checkout_checkout($nid) {
-  global $user;
-
-  db_lock_table('checkout');
-
-  $result = db_query("SELECT nid, uid, timestamp FROM {checkout} WHERE nid = %d", $nid);
-  if (db_num_rows($result)) {
-    $info = db_fetch_object($result);
-  }
-  else if (user_access('check out documents')) {
-    db_query("INSERT INTO {checkout} (nid, uid, persistent, timestamp) VALUES (%d, %d, %d, %d)", $nid, $user->uid, 0, time());
-
-    $info = new stdClass;
-    $info->uid = $user->uid;
-  }
-  else {
-    $info = NULL;
-  }
-
-  db_unlock_tables();
-
-  return $info;
-}
-
-/**
- * Display an error message and send the user back to the node view.
- *
- * @param object $info
- *   A check-out info object.
- *
- * @return void This function doesn't return.
- *
- * @see checkout_checkout()
- */
-function checkout_message($info) {
-  $username = theme('username', user_load(array('uid' => $info->uid)));
-  $date = format_date($info->timestamp, 'medium');
-  $message = t('This document is locked for editing by !name since @date.', array('!name' => $username, '@date' => $date));
-
-  if (user_access('administer checked out documents')) {
-    $uri = url("admin/content/node/checkout/release/$info->nid", 'destination='. request_uri());
-    $message .= '<br />'. t('Click <a href="!uri">here</a> to check back in now.', array('!uri' => $uri));
-  }
-
-  drupal_set_message($message, 'error');
-
-  drupal_goto("node/$info->nid");
-}
-
-/**
- * Release a node.
- *
- * @param integer $nid
- *   A node id.
- */
-function checkout_release($nid) {
-  db_query('DELETE FROM {checkout} WHERE nid = %d', $nid);
-}
-
-/**
- * Return a list of all checked out documents.
- *
- * @return string $output
- */
-function checkout_admin_overview() {
-  $header = array(
-    array('data' => t('Title'), 'field' => 'n.title', 'sort' => 'asc'),
-    array('data' => t('Username'), 'field' => 'u.name'),
-    array('data' => t('Check-out date'), 'field' => 'c.timestamp'),
-    t('Operations'),
-  );
-  
-  $rows = array();
-  $result = pager_query('SELECT c.nid, c.uid, c.timestamp, n.title, u.name FROM {checkout} c INNER JOIN {node} n ON n.nid = c.nid INNER JOIN {users} u ON u.uid = c.uid'. tablesort_sql($header), 50, 0, NULL);
-  
-  while ($data = db_fetch_object($result)) {
-    $row = array();
-    $row[] = l($data->title, "node/$data->nid");
-    $row[] = theme('username', user_load(array('uid' => $data->uid)));
-    $row[] = format_date($data->timestamp, 'small');
-    $row[] = l(t('check-in'), "admin/content/node/checkout/release/$data->nid");
-    $rows[] = $row;
-  }
-  
-  $output = theme('table', $header, $rows, array('id' => 'checkout'));
-  if (!$rows) {
-    $output .= t('No documents checked out.');
-  }
-  else if ($pager = theme('pager', array(), 50, 0)) {
-    $output .= $pager;
-  }
-
-  return $output;
-}
-
-/**
- * Menu callback; releases a document editing lock.
- *
- * @param integer $nid
- *   A node id.
- *
- * @return void This function doesn't return.
- */
-function checkout_admin_release($nid) {
-  checkout_release($nid);
-  drupal_set_message(t('The editing lock has been released.'));
-  drupal_goto('admin/content/node/checkout');
-}
-
-/**
- * Return a list of a users's checked out documents.
- *
- * @param integer $uid
- *   A user id.
- *
- * @return string $output
- */
-function checkout_user_overview($uid) {
-  $header = array(
-    array('data' => t('Title'), 'field' => 'n.title', 'sort' => 'asc'),
-    array('data' => t('Check-out date'), 'field' => 'c.timestamp'),
-    t('Operations'),
-  );
-  
-  $rows = array();
-  $result = pager_query('SELECT c.nid, c.timestamp, n.title FROM {checkout} c INNER JOIN {node} n ON n.nid = c.nid WHERE c.uid = %d'. tablesort_sql($header), 50, 0, NULL, $uid);
-  
-  while ($data = db_fetch_object($result)) {
-    $row = array();
-    $row[] = l($data->title, "node/$data->nid");
-    $row[] = format_date($data->timestamp, 'small');
-    $row[] = l(t('check-in'), "user/$uid/checkout/release/$data->nid");
-    $rows[] = $row;
-  }
-  
-  $output = theme('table', $header, $rows, array('id' => 'checkout'));
-  if (!$rows) {
-    $output .= t('No documents checked out.');
-  }
-  else if ($pager = theme('pager', array(), 50, 0)) {
-    $output .= $pager;
-  }
-
-  return $output;
-}
-
-/**
- * Menu callback; releases a document editing lock.
- *
- * @param integer $uid
- *   A user id.
- * @param integer $nid
- *   A node id.
- *
- * @return void This function doesn't return.
- */
-function checkout_user_release($uid, $nid) {
-  checkout_release($nid);
-  drupal_set_message(t('The editing lock has been released.'));
-  drupal_goto("user/$uid/checkout");
-}
-
+<?php
+// $Id$
+
+/**
+ * @file
+ * Enables users to lock documents for modification.
+ */
+
+/**
+ * Implementation of hook_perm().
+ */
+function checkout_perm() {
+  return array('check out documents', 'keep documents checked out', 'administer checked out documents');
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function checkout_help($section) {
+  switch ($section) {
+    case 'admin/help#checkout':
+      $output = '<p>'. t("Drupal, by default, does not take into account the possibility of multiple users wanting to update the same document (node) at the same time. There is consequently a danger that two users try to edit a node at the same time (a race condition), with one user overwriting the other's modifications.") .'</p>';
+      $output .= '<p>'. t("The checkout module offers active protection against concurrent edits. When a user begins to modify a document, it is considered being 'checked out' of the system for exclusive editing access by that user. It only becomes available to other users once the editor navigates out of the edit page - by submitting the content, viewing the node, or selecting another menu item. The system also takes into account that some users edit multiple documents at once, so there is a direct correlation between a browser session and the locks that are placed upon a document.") .'</p>';
+      $output .= '<p>'. t('If permission has been given at the <a href="!uri">access control</a> page, it is possible for users to keep a document checked out when they submit their changes; they can therefore be sure that their document is locked between sessions.', array('!uri' => url('admin/user/access'))) .'</p>';
+      $output .= '<p>'. t('A cron job is provided which will automatically check in documents that have been checked out beyond a configurable period of time.') .'</p>';
+      return $output;
+    case 'admin/content/node/checkout':
+      return '<p>'. t("Below is a list of all checked out documents. Click on 'check-in' to release a editing lock.") .'</p>';
+  }
+  if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'checkout') {
+    return '<p>'. t("Below is a list of all documents checked out by you. Click on 'check-in' to release a editing lock.") .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function checkout_menu($may_cache) {
+  global $user;
+  $items = array();
+
+  if ($may_cache) {
+    $admin_access = user_access('administer checked out documents');
+    $items[] = array('path' => 'admin/content/node/checkout',
+      'title' => t('Checked out'),
+      'callback' => 'checkout_admin_overview',
+      'access' => $admin_access,
+      'weight' => 5,
+      'type' => MENU_LOCAL_TASK);
+    $items[] = array('path' => 'admin/content/node/checkout/release',
+      'title' => t('Check-in content'),
+      'callback' => 'checkout_admin_release',
+      'access' => $admin_access,
+      'type' => MENU_CALLBACK);
+  }
+  else {
+    if (arg(0) == 'user' && is_numeric(arg(1)) && $user->uid == arg(1)) {
+      $user_access = user_access('check out documents');
+      $items[] = array('path' => 'user/'. arg(1) .'/checkout',
+        'title' => t('Track check-outs'),
+        'callback' => 'checkout_user_overview',
+        'callback arguments' => array(arg(1)),
+        'access' => $user_access,
+        'weight' => 5,
+        'type' => MENU_LOCAL_TASK);
+      $items[] = array('path' => 'user/'. arg(1) .'/checkout/release',
+        'title' => t('Check-in content'),
+        'callback' => 'checkout_user_release',
+        'callback arguments' => array(arg(1)),
+        'access' => $user_access,
+        'type' => MENU_CALLBACK);
+    }
+  }
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function checkout_form_alter($form_id, &$form) {
+  if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
+    if (user_access('keep documents checked out')) {
+      $node = $form['#node'];
+      if ($node->nid) {
+        // Place checkbox immediately after the log message
+        $form['checkout'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Keep document checked out'),
+          '#return_value' => 1,
+          '#weight' => 20.1,
+          '#default_value' => FALSE,
+          '#description' => t('Check this box if you wish to keep this document locked for editing after submit.'),
+        );
+      }
+    }
+  }
+  else if ($form_id == 'node_configure') {
+    // Make sure our element appears before the submit buttons
+    $form['buttons']['#weight'] = 10;
+
+    $period = array(0 => t('Disabled')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+    $form['checkout_clear'] = array(
+      '#type' => 'select',
+      '#title' => t('Automatic check-in'),
+      '#default_value' => variable_get('checkout_clear', 0),
+      '#options' => $period,
+      '#description' => t('The period after which checked out documents will be automatically released.'),
+    );
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function checkout_nodeapi(&$node, $op, $teaser, $page) {
+  global $user;
+
+  switch ($op) {
+    case 'prepare':
+      if ($node->nid > 0 && ($_POST['op'] != 'Submit')) {     // p.lindstrom - don't checkout on Submit 
+        $data = checkout_checkout($node->nid);
+        //if ($data && $data->uid != $user->uid) {    
+        if ($data->nid) {                                     // p.lindstrom - lock even if same user   
+          checkout_message($data); // won't return
+        }
+      }
+      break;
+    
+    case 'update':
+      if (empty($node->checkout)) {
+        checkout_release($node->nid);
+      }
+      else {
+        db_query("UPDATE {checkout} SET persistent = 1 WHERE nid = %d", $node->nid);
+      }
+      break;
+    
+    case 'delete':
+      checkout_release($node->nid);
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Release nodes that have been locked longer than the configured period.
+ */
+function checkout_cron() {
+  $checkout_clear = variable_get('checkout_clear', 0);
+  if ($checkout_clear > 0) {
+    $result = db_query('DELETE FROM {checkout} WHERE timestamp < %d', time() - $checkout_clear);
+    if ($num = db_affected_rows($result)) {
+      $message = t('Released @count document(s) checked out for more than @period.', array('@count' => $num, '@period' => format_interval($checkout_clear)));
+      drupal_set_message($message);
+      watchdog('checkout', $message);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_exit().
+ *
+ * Release the node that has been last edited, but only if we're coming
+ * directly from an edit page to protect against unlocking nodes while
+ * editing/surfing in concurrent browser windows.
+ */
+function checkout_exit() {
+  global $user;
+
+  if ($user->uid) {
+    $edit_types = array('edit', 'classify', 'outline');
+    $request = array_reverse(explode('/', request_uri()));
+    $referer = array_reverse(explode('/', referer_uri()));
+  
+    if (!in_array($request[0], $edit_types) && in_array(str_replace(strstr($referer[0],"?"),"",$referer[0]), $edit_types)) {    // p.lindstrom - fix for case of destination query edit?destination=node/123
+      $nid = $referer[1];
+      if (!db_result(db_query("SELECT persistent FROM {checkout} WHERE nid = %d", $nid))) {
+        checkout_release($nid);
+      }
+    }
+  }
+}
+
+/**
+ * Lock a node for editing.
+ *
+ * If the document isn't currently checked out the returned info object has
+ * just one property: the user id of the current user. Otherwise it contains
+ * the information about when and by whom the document was checked out.
+ *
+ * @param integer $nid
+ *   A node id.
+ *
+ * @return object A check-out info object.
+ */
+function checkout_checkout($nid) {
+  global $user;
+
+  db_lock_table('checkout');
+
+  $result = db_query("SELECT nid, uid, timestamp FROM {checkout} WHERE nid = %d", $nid);
+  if (db_num_rows($result)) {
+    $info = db_fetch_object($result);
+  }
+  else if (user_access('check out documents')) {
+    db_query("INSERT INTO {checkout} (nid, uid, persistent, timestamp) VALUES (%d, %d, %d, %d)", $nid, $user->uid, 0, time());
+
+    $info = new stdClass;
+    $info->uid = $user->uid;
+    drupal_set_message(t('This document is now locked against simultaneous editing.  It will unlock when you navigate elsewhere.'));     // p.lindstrom 
+  }
+  else {
+    $info = NULL;
+  }
+
+  db_unlock_tables();
+
+  return $info;
+}
+
+/**
+ * Display an error message and send the user back to the node view.
+ *
+ * @param object $info
+ *   A check-out info object.
+ *
+ * @return void This function doesn't return.
+ *
+ * @see checkout_checkout()
+ */
+function checkout_message($info) {
+  $username = theme('username', user_load(array('uid' => $info->uid)));
+  $date = format_date($info->timestamp, 'medium');
+  $message = t('This document is locked for editing by !name since @date.', array('!name' => $username, '@date' => $date));
+
+  if (user_access('administer checked out documents')) {
+    $uri = url("admin/content/node/checkout/release/$info->nid", 'destination='. substr(request_uri(),1));    // p.lindstrom - remove leading / from request_uri
+    $message .= '<br />'. t('Click <a href="!uri">here</a> to check back in now. <strong>WARNING:</strong> Any unsaved changes in another window will be overwritten.', array('!uri' => $uri));
+  }
+
+  drupal_set_message($message, 'error');
+
+  drupal_goto("node/$info->nid");
+}
+
+/**
+ * Release a node.
+ *
+ * @param integer $nid
+ *   A node id.
+ */
+function checkout_release($nid) {
+  db_query('DELETE FROM {checkout} WHERE nid = %d', $nid);
+}
+
+/**
+ * Return a list of all checked out documents.
+ *
+ * @return string $output
+ */
+function checkout_admin_overview() {
+  $header = array(
+    array('data' => t('Title'), 'field' => 'n.title', 'sort' => 'asc'),
+    array('data' => t('Username'), 'field' => 'u.name'),
+    array('data' => t('Check-out date'), 'field' => 'c.timestamp'),
+    t('Operations'),
+  );
+  
+  $rows = array();
+  $result = pager_query('SELECT c.nid, c.uid, c.timestamp, n.title, u.name FROM {checkout} c INNER JOIN {node} n ON n.nid = c.nid INNER JOIN {users} u ON u.uid = c.uid'. tablesort_sql($header), 50, 0, NULL);
+  
+  while ($data = db_fetch_object($result)) {
+    $row = array();
+    $row[] = l($data->title, "node/$data->nid");
+    $row[] = theme('username', user_load(array('uid' => $data->uid)));
+    $row[] = format_date($data->timestamp, 'small');
+    $row[] = l(t('check-in'), "admin/content/node/checkout/release/$data->nid");
+    $rows[] = $row;
+  }
+  
+  $output = theme('table', $header, $rows, array('id' => 'checkout'));
+  if (!$rows) {
+    $output .= t('No documents checked out.');
+  }
+  else if ($pager = theme('pager', array(), 50, 0)) {
+    $output .= $pager;
+  }
+
+  return $output;
+}
+
+/**
+ * Menu callback; releases a document editing lock.
+ *
+ * @param integer $nid
+ *   A node id.
+ *
+ * @return void This function doesn't return.
+ */
+function checkout_admin_release($nid) {
+  checkout_release($nid);
+  drupal_set_message(t('The editing lock has been released.'));
+  drupal_goto('admin/content/node/checkout');
+}
+
+/**
+ * Return a list of a users's checked out documents.
+ *
+ * @param integer $uid
+ *   A user id.
+ *
+ * @return string $output
+ */
+function checkout_user_overview($uid) {
+  $header = array(
+    array('data' => t('Title'), 'field' => 'n.title', 'sort' => 'asc'),
+    array('data' => t('Check-out date'), 'field' => 'c.timestamp'),
+    t('Operations'),
+  );
+  
+  $rows = array();
+  $result = pager_query('SELECT c.nid, c.timestamp, n.title FROM {checkout} c INNER JOIN {node} n ON n.nid = c.nid WHERE c.uid = %d'. tablesort_sql($header), 50, 0, NULL, $uid);
+  
+  while ($data = db_fetch_object($result)) {
+    $row = array();
+    $row[] = l($data->title, "node/$data->nid");
+    $row[] = format_date($data->timestamp, 'small');
+    $row[] = l(t('check-in'), "user/$uid/checkout/release/$data->nid");
+    $rows[] = $row;
+  }
+  
+  $output = theme('table', $header, $rows, array('id' => 'checkout'));
+  if (!$rows) {
+    $output .= t('No documents checked out.');
+  }
+  else if ($pager = theme('pager', array(), 50, 0)) {
+    $output .= $pager;
+  }
+
+  return $output;
+}
+
+/**
+ * Menu callback; releases a document editing lock.
+ *
+ * @param integer $uid
+ *   A user id.
+ * @param integer $nid
+ *   A node id.
+ *
+ * @return void This function doesn't return.
+ */
+function checkout_user_release($uid, $nid) {
+  checkout_release($nid);
+  drupal_set_message(t('The editing lock has been released.'));
+  drupal_goto("user/$uid/checkout");
+}
+
