Index: checkout.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/checkout/checkout.module,v
retrieving revision 1.9.2.1
diff -u -p -r1.9.2.1 checkout.module
--- checkout.module	21 Mar 2008 09:47:23 -0000	1.9.2.1
+++ checkout.module	28 Jun 2008 12:31:07 -0000
@@ -16,72 +16,56 @@ function checkout_perm() {
 /**
  * Implementation of hook_help().
  */
-function checkout_help($section) {
-  switch ($section) {
+function checkout_help($path, $arg) {
+  switch ($path) {
     case 'admin/help#checkout':
-      $output = '<p>'. t("Drupal's default locking strategy is optimistic, that is, whenever two users try to edit the same piece of content, the one that hits the 'save' button first wins the race, while the other one receives a message stating <em>this content has been modified by another user, changes cannot be saved</em>. Depending on the organization size (the number of users that work on site content in parallel) this might not be an acceptable solution.") .'</p>';
-      $output .= '<p>'. t("The Checkout module implements pessimistic locking, which means that a content (node) will be exclusively locked whenever a user hits the 'edit' button, while all other users trying to concurrently edit the same node will be rejected. The lock will be automatically released when the user saves the content or moves away from the edit page.") .'</p>';
-      $output .= '<p>'. t('If permission has been given, it is possible for users to keep a node checked out when they submit their changes; they can therefore be sure that their document is locked between sessions.') .'</p>';
+      $output = '<p>'. t("Drupal's default locking strategy is optimistic, that is, whenever two users try to edit the same piece of content, the one hitting the save button first wins the race, while the other is displayed a message stating <em>this content has been modified by another user, changes cannot be saved</em>. Depending on the number of editors in your organization this might not be an acceptable solution.") .'</p>';
+      $output .= '<p>'. t("The Checkout module implements pessimistic locking, which simply means that a content will be exclusively locked whenever a user starts editing it. The lock will be automatically released when the user saves the content or moves away from the edit page.") .'</p>';
+      $output .= '<p>'. t('If permission has been given, it is possible to keep content checked out even after finishing the edit.') .'</p>';
       $output .= '<p>'. t('The ability to lock content is <a href="!uri">permission</a> based. Documents that have been "forgotten" to check in can be automatically released through cron.', array('!uri' => url('admin/user/access'))) .'</p>';
       return $output;
 
     case 'admin/content/node/checkout':
       return '<p>'. t("Below is a list of all locked documents. Click on <em>check in</em> to release a lock.") .'</p>';
-  }
 
-  if (arg(0) == 'user' && arg(2) == 'checkout') {
-    return '<p>'. t("Below is a list of all documents locked by you. Click on <em>check in</em> to release a lock.") .'</p>';
+    case 'user/%user/checkout':
+      return '<p>'. t("Below is a list of all documents locked by you. Click on <em>check in</em> to release a lock.") .'</p>';
   }
 }
 
 /**
  * Implementation of hook_menu().
  */
-function checkout_menu($may_cache) {
-  global $user;
-  $items = array();
-
-  $admin_access = user_access('administer checked out documents');
-  $user_access = user_access('check out documents');
-
-  if ($may_cache) {
-    $items[] = array('path' => 'admin/content/node/checkout',
-      'title' => t('Locked documents'),
-      'callback' => 'checkout_overview',
-      'access' => $admin_access,
-      'weight' => 5,
-      'type' => MENU_LOCAL_TASK);
-  }
-  else {
-    if ($user->uid && $user_access) {
-      checkout_handle_request($user->uid);
-    }
-
-    if (arg(0) == 'admin' && is_numeric(arg(5))) {
-      $items[] = array('path' => 'admin/content/node/checkout/release',
-        'title' => t('Check in'),
-        'callback' => 'checkout_release_item',
-        'callback arguments' => array(arg(5), NULL),
-        'access' => $admin_access,
-        'type' => MENU_CALLBACK);
-    }
-
-    if (arg(0) == 'user' && is_numeric(arg(1)) && $user->uid == arg(1)) {
-      $items[] = array('path' => 'user/'. arg(1) .'/checkout',
-        'title' => t('Locked documents'),
-        'callback' => 'checkout_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'),
-        'callback' => 'checkout_release_item',
-        'callback arguments' => array(arg(4), arg(1)),
-        'access' => $user_access,
-        'type' => MENU_CALLBACK);
-    }
-  }
+function checkout_menu() {
+  $items['admin/content/node/checkout'] = array(
+    'title' => 'Locked documents',
+    'page callback' => 'checkout_overview',
+    'access callback' => 'user_access',
+    'access arguments' => array('administer checked out documents'),
+    'weight' => 5,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/content/node/checkout/release'] = array(
+    'page callback' => 'checkout_release_item',
+    'page arguments' => array(5, NULL),
+    'access arguments' => array('administer checked out documents'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['user/%user/checkout'] = array(
+    'title' => 'Locked documents',
+    'page callback' => 'checkout_overview',
+    'page arguments' => array(1),
+    'access callback' => 'user_access',
+    'access arguments' => array('check out documents'),
+    'weight' => 5,
+    'type' => MENU_LOCAL_TASK
+  );
+  $items['user/%user/checkout/release'] = array(
+    'page callback' => 'checkout_release_item',
+    'page arguments' => array(4, 1),
+    'access arguments' => array('check out documents'),
+    'type' => MENU_CALLBACK
+  );
 
   return $items;
 }
@@ -89,9 +73,9 @@ function checkout_menu($may_cache) {
 /**
  * Implementation of hook_form_alter().
  */
-function checkout_form_alter($form_id, &$form) {
+function checkout_form_alter(&$form, $form_state, $form_id) {
   if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
-    if (user_access('keep documents checked out')) {
+    if (user_access('check out documents') && user_access('keep documents checked out')) {
       $form['checkout'] = array(
         '#type' => 'checkbox',
         '#title' => t('Keep document locked'),
@@ -124,6 +108,37 @@ function checkout_nodeapi(&$node, $op, $
   global $user;
 
   switch ($op) {
+    // If a node exists, 'load' is always called when viewing or editing. But
+    // when going from an edit form to a form for a non-existent node, e.g. 
+    // node/add/page or when using the clone module, only 'prepare' is called.
+    // To release the node we edited before we need handle_request for
+    // 'prepare', too.
+    case 'prepare': 
+    // For all other cases where existing nodes are shown or edited.
+    case 'load':
+      if (user_access('check out documents')) {
+        checkout_handle_request($user->uid, $op);
+      }
+      break;
+
+    case 'validate':
+      if (isset($node->nid) && user_access('check out documents')) {
+        // Existing node. Check if we still own the lock.
+        if ($lock = checkout_fetch_lock($node->nid)) {
+          if ($lock->uid != $user->uid) {
+            // Lock is no longer ours.
+            form_set_error('changed', t('Your lock has been removed!<br />'.  checkout_lock_owner($lock) .'<br />You can still save the cotent if this user aborts the edit operation without saving changes.'));
+          }
+        }
+        else {
+          // Node is not locked. Try to re-lock if node is unchanged.
+          if (node_last_changed($node->nid) > $node->changed || !checkout_node($current_nid, $uid)) {
+            form_set_error('alsochanged', t('Your lock has been removed due to inactivity or by an administrator. Since the document was changed we cannot regain the lock.'));
+          }
+        }
+      }
+      break;
+
     case 'insert':
     case 'update':
       if (!empty($node->checkout)) {
@@ -165,8 +180,11 @@ function checkout_cron() {
  *
  * @param $uid
  *   The user id to (un)lock nodes for.
+  * @param $op
+ *   The node operation.
  */
-function checkout_handle_request($uid) {
+function checkout_handle_request($uid, $op) {
+  
   // Build referer path
   $referer_uri = parse_url(referer_uri());
   if (variable_get('clean_url', 0)) {
@@ -189,6 +207,13 @@ function checkout_handle_request($uid) {
     checkout_release($previous_nid, $uid);
   }
 
+  // For op 'prepare' we must never lock. If we handle a form for a new,
+  // non-existing node, we cannot lock it. Otherwise, 'load' will be called, too,
+  // and locks it.
+  if ($op == 'prepare') {
+    return;
+  }
+  
   // Check whether to lock the current node.
   if ($current_nid && (!$previous_nid || $previous_nid != $current_nid)) {
     // Try to lock the node.
@@ -220,6 +245,34 @@ function checkout_get_nid($path) {
   return FALSE;
 }
 
+
+/**
+ * Fetch the lock for a node.
+ *
+ * @param $nid
+ *   A node id.
+ * @return
+ *   The lock for the node. FALSE, if the document is not locked.
+ */
+function checkout_fetch_lock($nid) {
+  return db_fetch_object(db_query_range("SELECT * FROM {checkout} WHERE nid = %d", array($nid), 0, 1));
+}
+
+
+/**
+ * Tell who has locked node.
+ *
+ * @param $lock
+ *   The lock for a node.
+ * @return
+ *   String with the message.
+ */
+function checkout_lock_owner($lock) {
+  $username = theme('username', user_load(array('uid' => $lock->uid)));
+  $date = format_date($lock->timestamp, 'medium');
+  return t('This document is locked for editing by !name since @date.', array('!name' => $username, '@date' => $date));
+}
+
 /**
  * Try to lock a document for editing.
  *
@@ -231,32 +284,23 @@ function checkout_get_nid($path) {
  *   FALSE, if a document has already been locked by someone else.
  */
 function checkout_node($nid, $uid) {
-  global $user;
-
-  db_lock_table('checkout');
-  $result = db_query("SELECT * FROM {checkout} WHERE nid = %d", $nid);
-
-  if (db_num_rows($result)) {
+  if ($lock = checkout_fetch_lock($nid)) {
     // Node is already locked.
-    $lock = db_fetch_object($result);
-    db_unlock_tables();
 
     // Deny editing this node even if the node is locked by the same user.
     // The only exception to this rule is when the user had previously acquired
     // a persistent lock.
     if ($lock->uid != $uid || !$lock->persistent) {
-      $username = theme('username', user_load(array('uid' => $lock->uid)));
-      $date = format_date($lock->timestamp, 'medium');
-      $message = t('This document is locked for editing by !name since @date.', array('!name' => $username, '@date' => $date));
+      $message = checkout_lock_owner($lock);
 
-      if ($user->uid == $uid) {
+      if ($lock->uid == $uid) {
         $url = "user/$uid/checkout/release/$nid";
       }
       else if (user_access('administer checked out documents')) {
         $url = "admin/content/node/checkout/release/$nid";
       }
       if (isset($url)) {
-        $message .= '<br />'. t('Click <a href="!release-url">here</a> to check back in now.', array('!release-url' => url($url, 'destination='. $_GET['q'])));
+        $message .= '<br />'. t('Click <a href="!release-url">here</a> to check back in now.', array('!release-url' => url($url, array('query' => 'destination='. $_GET['q']))));
       }
 
       drupal_set_message($message, 'error');
@@ -266,7 +310,6 @@ function checkout_node($nid, $uid) {
   else {
     // Lock node.
     db_query("INSERT INTO {checkout} (nid, uid, timestamp) VALUES (%d, %d, %d)", $nid, $uid, time());
-    db_unlock_tables();
 
     drupal_set_message(t('This document is now locked against simultaneous editing. It will unlock when you navigate elsewhere.'));
   }
@@ -309,13 +352,16 @@ function checkout_release($nid, $uid = N
 /**
  * Build an overview of locked documents.
  *
- * @param $uid
- *   A user id.
+ * @param $account
+ *   A user object.
  */
-function checkout_overview($uid = NULL) {
+function checkout_overview($account = NULL) {
   $header = array(array('data' => t('Title'), 'field' => 'n.title', 'sort' => 'asc'));
-  if (!$uid) {
+  if (!$account) {
     $header[] = array('data' => t('Username'), 'field' => 'u.name');
+  } 
+  else {
+    $uid = $account->uid;
   }
   $header[] = array('data' => t('Locked since'), 'field' => 'c.timestamp');
   $header[] = array('data' => t('Persistent lock'), 'field' => 'c.persistent');
@@ -359,9 +405,9 @@ function checkout_overview($uid = NULL) 
  * @return
  *   This function will execute a redirect and doesn't return.
  */
-function checkout_release_item($nid, $uid = NULL) {
+function checkout_release_item($nid, $account = NULL) {
   checkout_release($nid, $uid, TRUE);
   drupal_set_message(t('The editing lock has been released.'));
-  drupal_goto($uid ? "user/$uid/checkout" : 'admin/content/node/checkout');
+  drupal_goto($account->uid ? "user/$account->uid/checkout" : 'admin/content/node/checkout');
 }
 
