diff -ru webfm-6.x-2.9-alpha2/js/webfm.js webfm/js/webfm.js
--- webfm-6.x-2.9-alpha2/js/webfm.js	2008-04-08 08:28:14.000000000 +0200
+++ webfm/js/webfm.js	2008-08-16 11:32:00.000000000 +0200
@@ -92,6 +92,8 @@
 Webfm.menu_msg["ren"] = "Rename File";
 Webfm.menu_msg["meta"] = "File meta data";
 Webfm.menu_msg["att"] = "Attach to Node";
+Webfm.menu_msg["attdir"] = "Attach all Files in Folder to Node";
+Webfm.menu_msg["unzip"] = "Extract to here";
 Webfm.menu_msg["det"] = "Detach from Node";
 Webfm.menu_msg["dwnld"] = "Download as file";
 Webfm.menu_msg["view"] = "View file";
@@ -297,6 +299,7 @@
       //Add attach-list menu and attach option to listing menu
       try {
         Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["att"], Webfm.menuAttach, Webfm.menuFidVal));
+        Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["attdir"], Webfm.menuAttachDir, Webfm.menuFidVal));
         Webfm.menuHT.put('det', new Webfm.menuElement(Webfm.menu_msg["meta"], Webfm.menuGetMeta, ''));
         Webfm.menuHT.put('det', new Webfm.menuElement(Webfm.menu_msg["det"], Webfm.menuDetach, ''));
       } catch(err) {
@@ -346,12 +349,16 @@
   //global to allow external functions to push new menu elements into the menu array
   Webfm.menuHT = new Webfm.hht();
   try {
-    Webfm.menuHT.put('root', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, Webfm.menuAdmin));
+    //Webfm.menuHT.put('root', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, Webfm.menuAdmin));
+    Webfm.menuHT.put('root', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, ''));
     Webfm.menuHT.put('root', new Webfm.menuElement(Webfm.menu_msg["search"], Webfm.menuSearch, ''));
 
-    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, Webfm.menuAdmin));
-    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rmdir"], Webfm.menuRemove, Webfm.menuAdmin));
-    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rendir"], Webfm.menuRename, Webfm.menuAdmin));
+    //Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, Webfm.menuAdmin));
+    //Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rmdir"], Webfm.menuRemove, Webfm.menuAdmin));
+    //Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rendir"], Webfm.menuRename, Webfm.menuAdmin));
+    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["mkdir"], Webfm.menuMkdir, ''));
+    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rmdir"], Webfm.menuRemove, ''));
+    Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["rendir"], Webfm.menuRename, ''));
     Webfm.menuHT.put('dir', new Webfm.menuElement(Webfm.menu_msg["search"], Webfm.menuSearch, ''));
 
     Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["rm"], Webfm.menuRemove, Webfm.menuFileUid));
@@ -362,7 +369,8 @@
     Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["enum"], Webfm.menuInsert, Webfm.menuAdminNoFidVal));
     Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["denum"], Webfm.menuDbRem, Webfm.menuAdminFidVal));
     Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["perm"], Webfm.menuGetPerm, Webfm.menuFilePerm));
-    Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["clip"], Webfm.menuPutLinkInClipboard, Webfm.menuFidVal))
+    Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["clip"], Webfm.menuPutLinkInClipboard, Webfm.menuFidVal));
+    Webfm.menuHT.put('file', new Webfm.menuElement(Webfm.menu_msg["unzip"], Webfm.menuUnzip, Webfm.menuZipVal));
   } catch(err) {
     alert("Menu Create err\n" + err);
   }
@@ -1793,6 +1819,25 @@
   }
 }
 
+// Attach all files in directory to node
+Webfm.menuAttachDir = function(obj) {
+  files = Webfm.dirListObj.content.files;
+  for(item in files) {
+    obj.clickObj.title = files[item]['id'];
+    Webfm.menuAttach(obj);
+  }
+}
+
+// Unzip ZIP-Archive
+Webfm.menuUnzip = function(obj) {
+  var url = Webfm.ajaxUrl();
+  Webfm.progressObj.show(Webfm.js_msg["work"],  "blue");
+  var path = obj.element.title;
+  path = path.substring(0, path.lastIndexOf("/"));
+  var postObj = { action:encodeURIComponent("unzip"), param0:encodeURIComponent(obj.clickObj.title) };  
+  Webfm.HTTPPost(url, Webfm.dbrem_callback, path, postObj);
+}
+
 Webfm.menuDetach = function(obj) {
   Webfm.alrtObj.msg();
   var path = obj.element.title;
@@ -1957,6 +2002,13 @@
   return false;
 }
 
+Webfm.menuZipVal = function(obj) {
+  if(obj.fmime == 'application/zip')
+    return true;
+  //return false;
+  return true;
+}
+
 Webfm.confirm = function(text) {
   var agree = confirm(text);
   return agree ? true : false;
@@ -2461,9 +2513,21 @@
 Webfm.attach.prototype.fetch = function() {
   var url = Webfm.ajaxUrl();
   // action attribute of node-edit form contains the node number
-  var node_url = Webfm.$('node-form').action;
+  var node_url;
+  if(Webfm.$('node-form')) {
+    node_url = Webfm.$('node-form').action;
+  }
+  if(Webfm.$('comment-form')) {
+    node_url = Webfm.$('comment-form').action;
+  }
   Webfm.progressObj.show(Webfm.js_msg["work"],  "blue");
   var postObj = { action:encodeURIComponent("attach"), param0:encodeURIComponent(node_url) };
+  // If we are in a preview/validate, the fids are still stored in the form.
+  // Pass them to webfm_ajax to reload them.
+  var fids = Webfm.$(Webfm.attachFormInput).value;
+  if (fids.length != 0) {
+    postObj.param1 = encodeURIComponent(fids);
+  }
   Webfm.HTTPPost(url, this.callback, this, postObj);
 }
 
@@ -2476,9 +2540,20 @@
       if(result.data.length) {
         Webfm.admin = result.admin;
         var elInput = Webfm.$(Webfm.attachFormInput);
+        var attach_arr = [];
+        attach_arr = Webfm.$(Webfm.attachFormInput).value.split(',');
+        Webfm.current = null;
         for(var i = 0; i < result.data.length; i++) {
           var filerow = new Webfm.filerow(obj.body, result.data[i], 'attach', '', true, Webfm.menuHT.get('det'), obj.eventListeners);
-          elInput.setAttribute('value', (elInput.getAttribute('value')?elInput.getAttribute('value')+',':'') + result.data[i].id);
+          // Don't add if it already exists.
+          // Note that values are kept in the form for preview/failed validate.
+          for (var j = 0; j < attach_arr.length; j++) {
+            if (result.data[i].id == attach_arr[j])
+              break;
+          }
+          if (j == attach_arr.length) {
+            elInput.setAttribute('value', (elInput.getAttribute('value')?elInput.getAttribute('value')+',':'') + result.data[i].id);
+          }
         }
       }
     } else
@@ -3505,6 +3580,7 @@
   var toSend = '';
   if (typeof object == 'object') {
     xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+    xmlHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
     for (var i in object) {
       toSend += (toSend ? '&' : '') + i + '=' + encodeURIComponent(object[i]);
     }
diff -ru webfm-6.x-2.9-alpha2/webfm.install webfm/webfm.install
--- webfm-6.x-2.9-alpha2/webfm.install	2008-04-08 08:28:14.000000000 +0200
+++ webfm/webfm.install	2008-07-27 13:06:00.000000000 +0200
@@ -76,12 +76,27 @@
       'nid' => array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'default' => 0),
       'fid' => array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'default' => 0),
       'weight' => array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'default' => 0),
+      'cid' => array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'default' => 0),
     ),
-    'primary key' => array('nid', 'fid'),
+    'primary key' => array('nid', 'fid', 'cid' ),
   );
   return $schema;
 }
 
+/**
+ * Add column for comment id in webfm_attach table.
+ */
+function webfm_update_1() {
+  $ret = array();
+  // Add the new colum to store a comment id.
+  db_add_field($ret, 'webfm_attach', 'cid', array('type' => 'int', 'not null' => TRUE, 'default' => '0'));
+
+  // Make it a primary key.
+  db_drop_primary_key($ret, 'webfm_attach');
+  db_add_primary_key($ret, 'webfm_attach', array('nid', 'cid', 'fid'));
+  return $ret;
+}
+
 
 //  switch ($GLOBALS['db_type']) {
 //    case 'mysql':
diff -ru webfm-6.x-2.9-alpha2/webfm.module webfm/webfm.module
--- webfm-6.x-2.9-alpha2/webfm.module	2008-04-23 17:50:06.000000000 +0200
+++ webfm/webfm.module	2008-08-16 11:27:00.000000000 +0200
@@ -156,6 +156,7 @@
 function webfm_admin_settings_validate($form, $form_state) {
   $valid_webfm_root = FALSE;
   $webfm_root_dir_name = $form_state['values']['webfm_root_dir'];
+      
   if(!empty($webfm_root_dir_name)) {
     if(!preg_match('/^[0-9a-zA-Z]/', $webfm_root_dir_name)) {
       form_set_error('webfm_root_dir'. $rid, t('The leading character of the webfm root directory name must be alphanumeric.'));
@@ -167,6 +168,32 @@
     }
   }
 
+///////////////////////////////////////////////////////////////////////////////    
+
+  $valid_user_root = FALSE;
+  // Allow tokenized paths.
+  if (function_exists('token_replace')) {
+    // Validate with current user...
+    global $user;
+    $webfm_user_dir_name = token_replace($form_state['values']['webfm_user_dir'], 'user', $user);
+  }
+  else {
+    $webfm_user_dir_name = $form_state['values']['webfm_user_dir'];
+  }
+
+  if(!empty($webfm_user_dir_name)) {
+    if(!preg_match('/^[0-9a-zA-Z]/', $webfm_user_dir_name)) {
+      form_set_error('webfm_user_dir'. $rid, t('The leading character of the webfm user directory name must be alphanumeric.'));
+    } else if(preg_match('[\.]', $webfm_user_dir_name)) {
+      form_set_error('webfm_user_dir'. $rid, t('The webfm user directory name is not valid.'));
+    } else {
+      $webfm_user_dir = $webfm_root_dir ."/".$webfm_user_dir_name;
+      $valid_webfm_user = file_check_directory($webfm_user_dir, FILE_CREATE_DIRECTORY, 'webfm_user_dir');
+    }
+  }
+
+///////////////////////////////////////////////////////////////////////////////    
+
   if(($form_state['values']['webfm_max_resolution'] != '0')) {
     if(!preg_match('/^[0-9]+[xX][0-9]+$/', $form_state['values']['webfm_max_resolution'])) {
       form_set_error('webfm_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
@@ -180,7 +207,7 @@
     $uploadsize = $form_state['values']['webfm_uploadsize_'. $rid];
     $usersize = $form_state['values']['webfm_usersize_'. $rid];
     $role_root_dir_name = $form_state['values']['root_dir_'.$rid];
-
+    
     if(!empty($role_root_dir_name)) {
       if($valid_webfm_root) {
         if(!preg_match('/^[0-9a-zA-Z]/', $role_root_dir_name)) {
@@ -232,6 +259,25 @@
                                <br />exist for this setting to validate (ie: path/to).')
 
           );
+
+  $form['webfm_user_dir'] =
+    array('#type' => 'textfield',
+          '#title' => t('WebFM user directory'),
+          '#default_value' => variable_get('webfm_user_dir', ''),
+          '#maxlength' => '100',
+          '#size' => '70',
+          '#description' => t('Root directory for users.
+                               <br />This path is relative to "WebFM root directory" path.
+                               <br />If this directory path is compound (ie: path/to/user) then the path must already
+                               <br />exist for this setting to validate (ie: path/to).')
+
+          );
+          
+  if (function_exists('token_replace')) {
+    $form['webfm_user_dir']['#description'] .= ' '. t('You can use the following tokens:');
+    $form['webfm_user_dir']['#suffix'] = theme('token_help', 'user');
+  }
+
   $form['webfm_icon_dir'] =
     array('#type' => 'textfield',
           '#title' => t('Icon directory'),
@@ -339,7 +385,9 @@
             '#default_value' => variable_get("webfm_extensions_".$rid, "jpg jpeg gif png txt html htm doc xls pdf ppt pps"),
             '#maxlength' => 255,
             '#description' => t('Extensions that users in this role can upload. Separate extensions with a space
-                                 <br />and do not include the leading dot.')
+                                 <br />and do not include the leading dot.
+                                 <br />Enter * to accept all extensions.'),
+
             );
 
     $form["settings_role_".$rid]["webfm_uploadsize_".$rid] =
@@ -542,9 +590,96 @@
 }
 
 /**
+ * Implementation of hook_comment().
+ *
+ * Similar to webfm_nodeapi, but for comments instead of nodes.
+ * Note: Enabling webfm in the edit form is done in form_alter. Keeping the
+ * attachments while preview or a failing form_validate is done by form_alter
+ * and webfm_ajax.
+ * webfm_comment is only needed to save the attachments to the table and
+ * for viewing and previewing comments.
+ * We don't need to initialize js in 'validate' here because a failing form
+ * validation re-initializes the whole node, including js.
+ */
+function webfm_comment(&$comment, $op) {
+  if (is_object($comment)) {
+    $cid = $comment->cid;
+    $nid = $comment->nid;
+  }
+  else {
+    $cid = $comment['cid'];
+    $nid = $comment['nid'];
+  }
+
+  # We need the parent node for checking its permission to view attachments etc.
+  $node = node_load($nid);
+
+  switch ($op) {
+    case 'view':
+      if (variable_get("wfm_attach_$node->type", 1) == 1 &&
+         user_access('view webfm attachments') &&
+         variable_get('webfm_attach_body', '')) {
+       // If we preview a comment, $comment->preview is defined because the preview button
+       // for comments is added with $form['preview']. Thus we know that $_POST['attachlist']
+       // is ours. If we are previewing another comment and just "view" this one,
+       // the preview flag is not set and $_POST['attachlist'] belongs to someone else.
+       // This happens when previewing or editing a comment and the node and/or
+       // other comments might be shown, too.
+       // If we preview without 'acces webfm' permissions, we fetch from database,
+       // cf. nodeapi below.
+       if ($comment->preview && user_access('access webfm')) {
+         if ($_POST['attachlist']) {
+           $show_files = webfm_get_temp_attachments($_POST['attachlist']);
+         }
+       }
+       else {
+         // Normal view. Try to load attachments. There is no 'load' op for hook_comment.
+         if (!isset($comment->webfm_files)) {
+           $comment->webfm_files = webfm_get_attachments($cid, 'cid');
+         }
+         if (is_array($comment->webfm_files) && count($comment->webfm_files)) {
+           $show_files = $comment->webfm_files;
+         }
+       }
+       if ($show_files) {
+         $comment->comment .= theme('webfm_attachments', $show_files);
+         drupal_add_css(drupal_get_path('module', 'webfm').'/css/webfm.css');
+       }
+      }
+      break;
+
+    case 'insert':
+      if ($_POST['attachlist']) {
+        $files = explode(',', $_POST['attachlist']);
+        $i = 0;
+        foreach ($files as $fid) {
+          if ($fid)
+            // weight argument determined by position in csv
+            webfm_dbinsert_attach(0, $fid, $i++, $cid);
+        }
+      }
+      break;
+
+    case 'update':
+      // If the user cannot access webfm, $_POST['attachlist'] is always empty
+      // and therefore will delete existing attachments from the node.
+      if (user_access('access webfm')) {
+        $files = explode(',', $_POST['attachlist']);
+        webfm_dbupdate_attach(0, $files, $cid);
+      }
+      break;
+
+    case 'delete':
+      webfm_dbdelete_attachments($cid, 'cid');
+      break;
+  }
+}
+
+/**
  * Implementation of hook_nodeapi().
  */
 function webfm_nodeapi(&$node, $op, $teaser) {
+  global $user;
   switch ($op) {
     case 'load':
       if ((variable_get("wfm_attach_$node->type", 1) == 1) &&
@@ -555,19 +690,61 @@
       break;
 
     case 'view':
-      // Add the attachments list to node body if configured to appear in body
-      if (is_array($node->webfm_files) && variable_get('webfm_attach_body', '')) {
-        if (count($node->webfm_files) && !$teaser) {
+      // Add the attachments list to node body if configured to appear in body.
+      if (variable_get('webfm_attach_body', '')) {
+        // We could be viewing or previewing this node.
+        // Loading a node defines $node->webfm_files, possibly as empty list,
+        // but loading an edit form unsets $node->webfm_files again. Thus, if
+        // $node->webfm is not set, we are previewing this node.
+        // Two cases then:
+        // 1) The user has no webfm access rights, so he can't change anything
+        // and $_POST['attachlist'] is empty. Simulate the 'load' operation
+        // to get the files from the database to show them in the preview.
+        // 2) If user has webfm access, all attachments (saved/unsaved)
+        // are in $_POST['attachlist']. But $_POST['attachlist'] is also set if
+        // we preview e.g. a comment where this node is shown, too, but then
+        // the attachlist is not ours. So we check for webfm_files *before*
+        // checking for $_POST to get around this.
+        if (!user_access('access webfm') && (variable_get("wfm_attach_$node->type", 1) == 1)
+            && user_access('view webfm attachments')) {
+          $node->webfm_files =  webfm_get_attachments($node->nid);
+        }
+        if (is_array($node->webfm_files)) {
+          if (count($node->webfm_files) && !$teaser) {
+            $show_files = $node->webfm_files;
+          }
+        }
+	// We must check for view permissions in a preview, but wfm_attach_$node->type
+	// is true if we already have attachments here.
+        elseif ($_POST['attachlist'] && user_access('view webfm attachments')) {
+          $show_files = webfm_get_temp_attachments($_POST['attachlist']);
+        }
+        if ($show_files) {
           $node->content['webfm_attachments'] = array(
-            '#value' => theme('webfm_attachments', $node->webfm_files),
+            '#value' => theme('webfm_attachments', $show_files),
             '#weight' => 10,
-          );
+            );
           drupal_add_css(drupal_get_path('module', 'webfm').'/css/webfm.css');
         }
       }
       break;
 
+    case 'validate':
+      // When form_validate fails for preview or save, we must reinitialize
+      // javascript, otherwise webfm doesn't work anymore.
+      $modulepath = drupal_get_path('module', 'webfm');
+      drupal_add_js($modulepath .'/js/webfm.js');
+      drupal_add_css($modulepath .'/css/webfm.css');
+      if (is_null($inline_js)) {
+        $clean_url = variable_get('clean_url', 0);
+        $clean = (($clean_url == 0) || ($clean_url == '0')) ? FALSE : TRUE;
+        $inline_js = webfm_inline_js($base_url, $clean, $user->uid);
+      }
+      break;
+
     case 'insert':
+      // We saved the attachment list for preview. Remove before saving.
+      unset($node->attachlist);
       if($_POST['attachlist']) {
         $files = explode(',', $_POST['attachlist']);
         $i = 0;
@@ -580,8 +757,26 @@
       break;
 
     case 'update':
-      $files = explode(',', $_POST['attachlist']);
-      webfm_dbupdate_attach($node->nid, $files);
+      // We saved the attachment list for preview. Remove before saving.
+      unset($node->attachlist);
+      // If the user cannot access webfm, $_POST['attachlist'] is always empty
+      // and therefore will delete existing attachments from the node.
+      if(user_access('access webfm')) {
+        /*
+        $orderfiles = webfm_get_attachments($node->nid);
+        usort($orderfiles, function cmp($a, $b){ return strcmp($a->n, $b->n);});
+        $files = array();
+        foreach($orderfiles as $file) {
+         $files[] = $file->id;
+        }
+        */
+        $files = explode(',', $_POST['attachlist']);
+        webfm_dbupdate_attach($node->nid, $files);
+      }
+      break;
+
+    case 'delete':
+      webfm_dbdelete_attachments($node->nid);
       break;
   }
 }
@@ -607,12 +802,17 @@
       );
   }
 
-  if (isset($form['type'])) {
-    $node = $form['#node'];
-    if ($access &&
-        $form['type']['#value'] .'_node_form' == $form_id &&
-        variable_get('webfm_attach_'.$node->type, 0)) {
-
+  if (isset($form['type']) || $form_id == 'comment_form') {
+    // For a comment form, the webfm permissions are inherited from the comments node.
+    if ($form_id == 'comment_form') {
+      $node = node_load($form['nid']['#value']);
+      $formcheck = TRUE;
+    }
+    else {
+      $node = $form['#node'];
+      $formcheck = ($form['type']['#value'] .'_node_form' == $form_id);
+    }
+    if ($access && $formcheck && variable_get('webfm_attach_'. $node->type, 0)) {
       $modulepath = drupal_get_path('module', 'webfm');
       drupal_add_js($modulepath.'/js/webfm.js');
       drupal_add_css($modulepath.'/css/webfm.css');
@@ -703,11 +903,24 @@
 function webfm_attach_attached_form($node) {
   $form['#theme'] = 'webfm_attach_attached_form';
 
-  // This form input (id = edit-attachlist) will hold the comma-separated ordered list of attached fids
+  // This form input (id = edit-attachlist) will hold the comma-separated
+  // ordered list of attached fids. We need to store the attachments (which
+  // might not yet been saved to the database) in two cases:
+  // 1) A form_validate fails when trying to save/preview. The form is not
+  //    rebuild by form_alter, but form values are refilled. #value => '' would
+  //    delete the attachlist collected so far, so use #default_value => '' instead.
   $form['new']['attachlist']  = array(
     '#type' => 'hidden',
-    '#value' => '');
+    '#default_value' => '',
+    );
 
+  // 2) If form_validate didn't fail, the form is rebuild with form_alter
+  //    and the value is reset. But we have the list in the POST parameters.
+  //    Use default_value here, too, so that it can still be changed if we get
+  //    a form_validate error *after* a successful preview.
+  if ($_POST['attachlist']) {
+    $form['new']['attachlist']['#default_value'] = $_POST['attachlist'];
+  }
   return $form;
 }
 
@@ -974,14 +1187,14 @@
                   $trees[$key] = webfm_tree($root_dir, $current);
                 }
               } else {
-                $err .= t('root directory not set for @role role ', array('@role' => $webfm_access_roles[$key]));
+                $err .= t('root directory not set for @role ', array('@role' => $webfm_access_roles[$key]));
               }
             }
           } else {
             $err = t('no root directory set in WebFM settings for this role');
           }
         }
-
+        
         if(count($trees)) {
           webfm_json(array('status' => TRUE, 'tree' => $trees, 'current' => $webfm_root_path, 'admin' => $webfm_perm == WEBFM_ADMIN, 'err' => $err));
         } else {
@@ -1005,6 +1218,14 @@
              (array_key_exists($root_role, $webfm_roots))) {
             $current = "/".$root;
           }
+          else if(($root = variable_get('webfm_user_dir', '')) &&
+             (array_key_exists($root_role, $webfm_roots))) {
+            // User dir can be tokenized
+            if (function_exists('token_replace')) {
+              $root = token_replace($root, 'user', $user);
+            }
+            $current = "/".$root;
+          }
         }
         if(!isset($current)) {
           webfm_json(array('status' => FALSE, 'data' => 'unknown tree'));
@@ -1019,6 +1240,7 @@
           $tree = webfm_tree($root_dir, $current);
           webfm_json(array('status' => isset($tree) ? TRUE : FALSE, 'tree' => $tree, 'current' => $current, 'admin' => $webfm_perm == WEBFM_ADMIN));
         }
+        
         exit();
         break;
 
@@ -1031,7 +1253,7 @@
             webfm_json(array('status' => FALSE, 'data' => 'illegal read dir'));
             exit();
           }
-
+          
           // Test access rights
           $perm_flag = FALSE;
           if($webfm_perm == WEBFM_ADMIN) {
@@ -1042,6 +1264,7 @@
           } else {
             // If WEBFM_USER, test that read path is inside a legit root dir
             $webfm_roots = webfm_get_root_dirs();
+                        
             foreach($webfm_roots as $key => $sub_root) {
               // The read path must be contained within a legitimate role root dir for this user
               if($sub_root && webfm_check_path($param0, $sub_root)) {
@@ -1084,7 +1307,7 @@
 
             if(is_dir($source)) {
               //Only admins can delete directories (and contained files)
-              if($webfm_perm == WEBFM_ADMIN) {
+              if($webfm_perm == WEBFM_ADMIN || webfm_path_access($source)) {
                 $err_arr[] = array();
                 $ret = webfm_delete_dir_recur($source, TRUE, $err_arr);
                 webfm_json(array('status' => $ret, 'data' => $err_arr));
@@ -1133,7 +1356,7 @@
       //Create new directory
       case "mkdir":
         //Only admins can create directories
-        if($webfm_perm == WEBFM_ADMIN) {
+        //if($webfm_perm == WEBFM_ADMIN) {
           if(isset($_POST["param0"])) {
             $source = $root_dir.trim(rawurldecode($_POST["param0"]));
             $dest = t("New_Folder");
@@ -1142,14 +1365,14 @@
             // exixts in current folder
             $ret = webfm_mkdir($source, $dest, TRUE, $err_arr);
             webfm_json(array('status' => $ret, 'data' => $err_arr));
-  //          if($ret)
+  //          if($ret){
   //            unset($_SESSION['tree_'.$webfm_root_path]);
           } else {
             webfm_json(array('status' => FALSE, 'data' => 'insufficient params'));
           }
-        } else {
-          webfm_json(array('status' => FALSE, 'data' => 'permission denied'));
-        }
+        //} else {
+        //  webfm_json(array('status' => FALSE, 'data' => 'permission denied'));
+        //}
         exit();
         break;
 
@@ -1158,7 +1381,7 @@
         if(isset($_POST["param0"]) && isset($_POST["param1"])) {
           $source = $root_dir.trim(rawurldecode($_POST["param0"]));
           $dest = $root_dir.trim(rawurldecode($_POST["param1"]));
-          if(is_dir($source) && ($webfm_perm != WEBFM_ADMIN)) {
+          if(is_dir($source) && !webfm_path_access($source)) {
             //Only admins can manipulate directories
             webfm_json(array('status' => FALSE, 'data' => 'permission denied'));
             exit();
@@ -1191,7 +1414,8 @@
           if(!ereg('\.\.', $dest)) {
             $err_arr[] = array();
             //rename permissions inside webfm_rename
-            $ret = webfm_rename($source, $dest, ($webfm_perm == WEBFM_USER) ? $user->uid : 1, $err_arr);
+            //$ret = webfm_rename($source, $dest, ($webfm_perm == WEBFM_USER) ? $user->uid : 1, $err_arr);
+            $ret = webfm_rename($source, $dest, $user->uid, $err_arr);
             webfm_json(array('status' => $ret, 'data' => $err_arr));
           } else {
             webfm_json(array('status' => FALSE, 'data' => 'illegal name'));
@@ -1350,14 +1574,43 @@
       case "attach":
         global $node;
         if(isset($_POST["param0"])) {
+          // If $_POST["param1"] is set, we are in a preview or validation failure
+          // and have fids stored in the edit-attachments form, those that we fetched
+          // from the database before and those which are not yet attached (during
+          // a preview). Those fids are transfered through param1.
+          if (isset($_POST["param1"])) {
+            $fids = trim(strtolower(rawurldecode($_POST["param1"])));
+            webfm_json(array('status' => TRUE, 'data' => webfm_get_temp_attachments($fids), 'admin' => 'attach'));
+            exit();
+            break;
+          }
+          // Here we are in the edit form for the first time.
+          // For comments, the URL is "comment/edit/$cid" instead of node/$nid/edit.
+          // Unify those. We must distinguish between new and existing nodes/comments,
+          // because for new nodes webfm passed a string instead of a nid which was
+          // interpreted as nid=0. But now with comments, we *have* entries with nid=0
+          // in the table, so avoid fetching those.
+          // For new nodes and comments we can't get attachments from the database.
           $node_str = trim(strtolower(rawurldecode($_POST["param0"])));
-          // the 'node' var passed via AJAX is the action attribute of id=node-form
-          if (($node_num = strstr($node_str, 'node/')) !== FALSE) {
-            $node_arr = explode("/", $node_num);
-            //'admin' is true (allow drag&drop) since only owners of a node can edit it
-            webfm_json(array('status' => TRUE, 'data' => webfm_get_attachments($node_arr[1]), 'admin' => 'attach'));
-          } else {
-            webfm_json(array('status' => FALSE, 'data' => 'illegal path'));
+          // We edit an existing node: Fetch from database.
+          if (strpos($node_str, '/edit')) {
+            $node_str = str_replace('/edit', '', $node_str);
+            $selector = 'nid';
+            // The 'node' var passed via AJAX is the action attribute of id=node-form.
+            if (($node_num = strstr($node_str, 'node/')) == FALSE) {
+              $node_num = strstr($node_str, 'comment/');
+              $selector = 'cid';
+            }
+            if ($node_num) {
+              $node_arr = explode("/", $node_num);
+              // 'admin' is true (allow drag&drop) since only owners of a node can edit it.
+              webfm_json(array('status' => TRUE, 'data' => webfm_get_attachments($node_arr[1], $selector), 'admin' => 'attach'));
+            }
+           else {
+              webfm_json(array('status' => FALSE, 'data' => 'illegal path'));
+              exit();
+              break;
+            }
           }
         } else {
           webfm_json(array('status' => FALSE, 'data' => 'insufficient params'));
@@ -1478,6 +1731,66 @@
         exit();
         break;
 
+///////////////////////////////////////////////////////////////////////////////
+
+      case "unzip":
+        if(isset($_POST["param0"])) {
+          $fid = rawurldecode($_POST["param0"]);
+          if(($file = webfm_get_file_record($fid)) !== FALSE) {
+            if($webfm_perm == WEBFM_ADMIN ||
+               $user->uid == $file->uid   ||
+               webfm_file_mod_access($file)) {
+              
+				  $zip = new ZipArchive;
+				  $dir = dirname($file->fpath);
+				  
+				  // Extract files...
+				  if ($zip->open($file->fpath) === TRUE) {
+					  $zip->extractTo($dir);
+					  $zip->close();
+					  
+					  // now add files to database...
+					  $zip = zip_open($file->fpath);
+					  if ($zip) {
+						while ($zip_entry = zip_read($zip)) {
+						  $filename = basename(zip_entry_name($zip_entry));
+						  if(!webfm_insert_file($dir . '/' . $filename, $error)) {
+							  webfm_json(array('status' => FALSE, 'data' => $error));
+							}
+						}
+						zip_close($zip);
+					  }
+					  
+					  /*
+					  // and delete archive
+						$error = "";
+						$ret = TRUE;
+						if(@unlink($source)) {
+						  if($file && !webfm_dbdelete_file($file->fid)) {
+						    webfm_json(array('status' => $ret, 'data' => 'webfm_dbdelete_file() fail for '.$source));
+						  }
+						} else if(file_exists($source)) {
+						  webfm_json(array('status' => $ret, 'data' => $source.' could not be deleted'));
+						}
+						*/
+              		webfm_json(array('status' => TRUE, 'data' => 'unzip success'));
+				  } else {
+              		webfm_json(array('status' => FALSE, 'data' => 'unzip failed'));
+				  }
+            } else {
+              webfm_json(array('status' => FALSE, 'data' => 'permission denied'));
+            }
+          } else {
+            webfm_json(array('status' => FALSE, 'data' => 'file record not found'));
+          }
+        } else {
+          webfm_json(array('status' => FALSE, 'data' => 'insufficient params'));
+        }
+        exit();
+        break;
+
+///////////////////////////////////////////////////////////////////////////////
+
       default:
         webfm_json(array('status' => FALSE, 'data' => 'illegal operation'));
         exit();
@@ -1836,8 +2149,24 @@
         $webfm_roots[1] = "/".$path;
       }
     }
+    
+    // User directory
+    $path = variable_get('webfm_user_dir', '');
+    // Allow tokenized paths.
+    if (function_exists('token_replace')) {
+      $path = token_replace($path, 'user', $user);
+    }
+    if(!empty($path)) {
+      // Prevent redundant trees for user directory common with a role root dir
+      if(!in_array($path, $webfm_roots)) {
+        $webfm_roots[$user->name] = "/".$path;
+        // Create directory if neccesarry
+        $userdir = file_directory_path() . '/' . variable_get('webfm_root_dir', '') . '/' . $path;
+        file_check_directory($userdir, FILE_CREATE_DIRECTORY);
+      }
+    }
   }
-
+  
   return $webfm_roots;
 }
 
@@ -2073,9 +2402,16 @@
   return isset($tree)?$tree:'';
 }
 
-function webfm_get_attachments($nid) {
+function webfm_get_attachments($nid, $selector = 'nid') {
+  // If anything but a existing nid/cid is passed, it is equivalent to 0. This
+  // happened in webfm_ajax in the past. Although we fixed it there, make sure
+  // that we never run into this, because now there are entries with nid/cid=0.
+  if ($nid == 0) {
+    return array();
+  }
+
   $files = array();
-  $file_result = db_query('SELECT * FROM {webfm_file} f INNER JOIN {webfm_attach} a ON f.fid = a.fid WHERE a.nid = %d ORDER BY a.weight', $nid);
+  $file_result = db_query("SELECT * FROM {webfm_file} f INNER JOIN {webfm_attach} a ON f.fid = a.fid WHERE a.%s = %d ORDER BY a.weight", $selector, $nid);
   while($file_record = db_fetch_object($file_result)) {
     $_file = new webfm_fdesc($file_record);
     if($_file->result == TRUE) {
@@ -2086,6 +2422,26 @@
 }
 
 /**
+ * Return file descriptors for an attachment that have not yet been saved.
+ * This happens on previewing posts.
+ *
+ * @param array $fids - a comma separated list of fids.
+ * @return array of file descriptors.
+ */
+function webfm_get_temp_attachments($fids) {
+  $files = array();
+  $fids_arr = split(',', $fids);
+  $file_result = db_query('SELECT * FROM {webfm_file} WHERE fid in ('. db_placeholders($fids_arr) .')', $fids_arr);
+  while ($file_record = db_fetch_object($file_result)) {
+    $_file = new webfm_fdesc($file_record);
+    if ($_file->result == TRUE) {
+      $files[] = $_file;
+    }
+  }
+  return $files;
+}
+
+/**
  * File description class
  */
 class webfm_fdesc {
@@ -2269,16 +2625,24 @@
   }
 
   // Files that have been attached are alway considered public to whoever can
-  // access that node (nodeaccess security)
+  // access that node/comment (nodeaccess/commentaccess security).
   if($match == FALSE && $webfm_perm != WEBFM_ADMIN) {
     if($f->perm & WEBFM_FILE_ACCESS_PUBLIC_VIEW) {
       $match = TRUE;
     } else if($webfm_perm == WEBFM_USER || $webfm_perm == WEBFM_ATTACH_VIEW){
-      //Check if the file is attached to a node
-      $query = 'SELECT nid FROM {webfm_attach} WHERE fid = %d';
+      //Check if the file is attached to a node or comment.
+      $query = 'SELECT nid,cid FROM {webfm_attach} WHERE fid = %d';
       $result = db_query($query, $f->fid);
       if($result !== FALSE) {
         while ($dbfid = db_fetch_array($result)) {
+          if ($dbfid['cid'] != 0 ) {
+            // For a comment, a user must be able to view the parent node and have "access_comments".
+            if (!user_access('access comments')) {
+              continue;
+            }
+            $comment = _comment_load($dbfid['cid']);
+            $dbfid['nid'] = $comment->nid;
+          }
           $node = node_load($dbfid['nid']);
           if (node_access('view', $node)) {
             $match = TRUE;
@@ -2482,7 +2846,12 @@
 
   if(!$role_ext_regex[$rid]) {
     $extensions = variable_get("webfm_extensions_".$rid, '');
-    $role_ext_regex[$rid] = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+    if ($extensions == '*') {
+      $role_ext_regex[$rid] = '//';
+    }
+    else {
+      $role_ext_regex[$rid] = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+    }
   }
 
   return $role_ext_regex[$rid];
@@ -2735,16 +3104,26 @@
  *
  * @param int $nid
  * @param array $fids - could be a comma seperated string - we need to work that out - works both ways now
+ * @param int $cid - Optional, comment id.
  * @return TRUE if records were updated - NULL if no changes were required.
  */
-function webfm_dbupdate_attach($nid, $fids){
+function webfm_dbupdate_attach($nid, $fids, $cid = 0){
+  if ($cid == 0 ) {
+    $id = $nid;
+    $selector = 'nid';
+  }
+  else {
+    $id = $cid;
+    $selector = 'cid';
+  }
+
   $i = 0;
-  if(!webfm_check_attach_order($nid, $fids)){ //the new fids are different from the old ones
-    $query = "DELETE FROM {webfm_attach} WHERE nid = %d";
-    if($result = db_query($query, $nid)){
+  if (!webfm_check_attach_order($nid, $fids, $cid)) { //the new fids are different from the old ones
+    $query = "DELETE FROM {webfm_attach} WHERE %s = %d";
+    if ($result = db_query($query, $selector, $id)) {
       foreach($fids as $fid){
         if($fid)
-          webfm_dbinsert_attach($nid, $fid, $i++);
+          webfm_dbinsert_attach($nid, $fid, $i++, $cid);
         $flag = TRUE;
       }
       if($flag === TRUE) return TRUE;
@@ -2757,12 +3136,21 @@
  *
  * @param int $nid - node id
  * @param array $fids - array of file ids
+ * @param int $cid - Optional, comment id.
  * @return bool - TRUE if the attach order is the same - FALSE if it has changed
  */
-function webfm_check_attach_order($nid, $fids){
+function webfm_check_attach_order($nid, $fids, $cid = 0) {
+  if ($cid == 0 ) {
+    $id = $nid;
+    $selector = 'nid';
+  }
+  else {
+    $id = $cid;
+    $selector = 'cid';
+  }
   //check array against db result
-  $query = "SELECT fid FROM {webfm_attach} WHERE nid = %d ORDER BY weight";
-  $result = db_query($query, $nid);
+  $query = "SELECT fid FROM {webfm_attach} WHERE %s = %d ORDER BY weight";
+  $result = db_query($query, $selector, $id);
 
   $match = TRUE;
   $i = 0;
@@ -2785,23 +3173,35 @@
  * @param int $nid - node id
  * @param int $fid - file id from the webfm_file table
  * @param int $weight - weight value
- *
- * @return bool - TRUE if success - else FALSE
- */
-function webfm_dbinsert_attach($nid, $fid, $weight){
-  $query = 'SELECT * FROM {webfm_attach} WHERE nid = %d AND fid = %d';
-  $result = db_query($query, $nid, $fid);
+ * @param int $cid - Optional, comment id. If given, $nid is set to 0 because
+ *   an attachment is stored either for a node OR a comment.
+  * @return bool - TRUE if success - else FALSE
+ */
+function webfm_dbinsert_attach($nid, $fid, $weight, $cid = 0) {
+  if ($cid == 0 ) {
+    $selector = 'nid';
+    $type = 'node';
+    $id = $nid;
+  }
+  else {
+    $selector = 'cid';
+    $type = 'comment';
+    $id = $cid;
+    $nid = 0;
+  }
+  $query = "SELECT * FROM {webfm_attach} WHERE %s = %d AND fid = %d";
+  $result = db_query($query, $selector, $id, $fid);
   $exist = FALSE;
   while ($exist = db_fetch_object($result)){
-   drupal_set_message(t('File is already attached to this node.'));
+    drupal_set_message(t('File is already attached to this %t.', array('%t' => $type)));
    return FALSE;
   }
   if ($exist === FALSE) {
     //actually do the attachment if its not already attached....
-    $query = 'INSERT INTO {webfm_attach} (nid, fid, weight) VALUES (%d, %d, %d)';
-    $result = db_query($query, $nid, $fid, $weight);
+    $query = 'INSERT INTO {webfm_attach} (nid, fid, weight, cid) VALUES (%d, %d, %d, %d)';
+    $result = db_query($query, $nid, $fid, $weight, $cid);
     if($result === FALSE) {
-      drupal_set_message(t('Query Failed:  Could not attach files to node ') . $nid);
+      drupal_set_message(t('Query Failed:  Could not attach files to %t %n', array('%t' => $type, '%n' => $id)));
       return FALSE;
     } else {
       return TRUE;
@@ -2810,6 +3210,26 @@
 }
 
 /**
+ * webfm_dbdelete_attachments - deletes nodes/comments file associations.
+ *
+ * @param int $id  - Node or comment id.
+ * @param selector - Tell if we handle a node or a comment.
+ * @return bool    - TRUE if success - else FALSE
+ *
+ */
+function webfm_dbdelete_attachments($id, $selector = 'nid') {
+  $type = ($selector == 'nid') ? 'node' : 'comment';
+  $query = "DELETE FROM {webfm_attach} WHERE %s = %d";
+  $result = db_query($query, $selector, $id);
+  if($result === FALSE) {
+    drupal_set_message(t('Failed to remove file attachments for deleted %type %id',
+                         array('%type' => $type, '%id' => $id)));
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
  * webfm_dbdelete_attach - deletes node file associations given a nid and fid
  *
  * @param int $nid - node id
diff -ru webfm-6.x-2.9-alpha2/webfm_file.inc webfm/webfm_file.inc
--- webfm-6.x-2.9-alpha2/webfm_file.inc	2008-04-08 08:28:14.000000000 +0200
+++ webfm/webfm_file.inc	2008-08-15 17:25:00.000000000 +0200
@@ -89,8 +89,8 @@
   }
 
   //Directory
-  //if target is a directory, new name is a unique path and we are an admin...
-  else if(is_dir($source) && !is_dir($dest) && ($uid == 1)) {
+  //if target is a directory, new name is a unique path and we have access to that dir...
+  else if(is_dir($source) && !is_dir($dest) && webfm_path_access($source)) {
     //if the target isn't read-only
     if(rename($source, $dest_temp)) {
       //directory rename is OK, back out, rename db files and rename dir again
@@ -204,7 +204,7 @@
  *
  */
 function webfm_move($source, $dest, $uid, &$err_arr) {
-  if(is_dir($source) && $uid == 1) {
+  if(is_dir($source) && webfm_path_access($source)) {
     return webfm_move_dir_recur($source, $dest, $uid, FALSE, $err_arr);
   } elseif(is_file($source)) {
     $dest .= '/' . strrev(substr(strrev($source), 0, strpos(strrev($source), '/')));
