--- image.module	Thu Jan 15 14:27:12 2009
+++ new image.module	Thu Jan 15 13:27:14 2009
@@ -163,6 +163,7 @@ function image_operations_rebuild($nids)
     if ($node = node_load($nid)) {
       if ($node->type == 'image') {
         $node->rebuild_images = TRUE;
+        drupal_set_message(t("Rebuilding %node-title's resized images.", array('%node-title' => $node->title)));
         image_update($node);
       }
     }
@@ -544,6 +545,15 @@ function image_update(&$node) {
       // Find the original image.
       $original_file = db_fetch_object(db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND image_size = '%s'", $node->nid, IMAGE_ORIGINAL));
 
+      if (!file_exists($original_file->filepath)) {
+        if (image_access('update', $node)) {
+	      	// Only show the message if they have a chance to understand it.
+				  drupal_set_message(t("Unable to locate original file %filepath - something's gone missing. Not rebuilding sizes as that may cause data loss. Not updating image node !node. You may possibly fix this by replacing the image.",
+            array('%filepath' => $original_file->filepath, '!node' => l($node->title, 'node/'. $node->nid .'/edit'))), 'error');
+	      }
+				return;
+			}
+
       // Delete all but the original image.
       $result = db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND f.fid <> %d", $node->nid, $original_file->fid);
       while ($file = db_fetch_object($result)) {
@@ -555,6 +565,23 @@ function image_update(&$node) {
         db_query("DELETE FROM {image} WHERE fid = %d", $file->fid);
       }
 
+      // Rename/move originals to match updated filename patterns.
+      if (variable_get('image_move_originals', FALSE)) {
+        $new_filename = _image_filename($original_file->filepath, IMAGE_ORIGINAL, FALSE, $node);
+        if (($original_file->filepath != $new_filename) && file_exists($original_file->filepath)) {
+          drupal_set_message(t("Moving original image from %orig to %new.", array('%orig' => $original_file->filepath, '%new' => $new_filename)));
+          if (file_move($original_file->filepath, $new_filename)) {
+						// image_insert is not expecting the original to be changable.
+		        db_query("DELETE FROM {files} WHERE fid = %d AND filename = '%s'", $original_file->fid, IMAGE_ORIGINAL);
+    		    db_query("DELETE FROM {image} WHERE nid = %d AND image_size = '%s'", $node->nid, IMAGE_ORIGINAL);
+            _image_insert($node, IMAGE_ORIGINAL, $new_filename);
+          }
+          else {
+            drupal_set_message(t("Failed to move original image from %orig to %new.", array('%orig' => $original_file->filepath, '%new' => $new_filename)), 'error');
+          }
+        }
+      }
+
       $node->images = _image_build_derivatives($node, FALSE);
 
       // Display a message to the user if they're be able to modify the node
@@ -786,7 +813,7 @@ function _image_build_derivatives($node,
   // Resize for the necessary sizes.
   $image_info = image_get_info($original_path);
   foreach ($needed_sizes as $key => $size) {
-    $destination = _image_filename($original_path, $key, $temp);
+    $destination = _image_filename($original_path, $key, $temp, $node);
 
     $status = FALSE;
     switch ($size['operation']) {
@@ -816,30 +843,162 @@ function _image_build_derivatives($node,
 
 /**
  * Creates an image filename.
+ *
+ * Uses a token template string to invent new filename and folder structure for each derivative file.
  */
-function _image_filename($filename, $label = IMAGE_ORIGINAL, $temp = FALSE) {
-  $path = variable_get('image_default_path', 'images') .'/';
+function _image_filename($filename, $label = IMAGE_ORIGINAL, $temp = FALSE, $node = NULL) {
+
+  // Temporary file handling - don't change path.
   if ($temp) {
-    $path .= 'temp/';
+	  $path = variable_get('image_default_path', 'images').'/temp/';
+
+    $filename = basename($filename);
+
+    // Insert the resized name in non-original images.
+    if ($label && ($label != IMAGE_ORIGINAL)) {
+      $pos = strrpos($filename, '.');
+      if ($pos === false) {
+        // The file had no extension - which happens in really old image.module
+        // versions, so figure out the extension.
+        $image_info = image_get_info(file_create_path($path . $filename));
+        $filename = $filename .'.'. $label .'.'. $image_info['extension'];
+      }
+      else {
+        $filename = substr($filename, 0, $pos) .'.'. $label . substr($filename, $pos);
+      }
+    }
+
+    return file_create_path($path . $filename);
   }
 
+  // Rename / move by pattern.
   $filename = basename($filename);
+  $pos = strrpos($filename, '.');
 
-  // Insert the resized name in non-original images
-  if ($label && ($label != IMAGE_ORIGINAL)) {
-    $pos = strrpos($filename, '.');
-    if ($pos === false) {
-      // The file had no extension - which happens in really old image.module
-      // versions, so figure out the extension.
-      $image_info = image_get_info(file_create_path($path . $filename));
-      $filename = $filename .'.'. $label .'.'. $image_info['extension'];
-    }
-    else {
-      $filename = substr($filename, 0, $pos) .'.'. $label . substr($filename, $pos);
+  // Build filename according to token pattern.
+  // When previewing a new (temp) node, we don't know the nid, but it should be available by submit time, so it'll work out.
+  $tokens = array(
+    '%nodepath'  => $node->path ? $node->path : $node->nid ? 'node-'. $node->nid : '',
+    '%filename'  => image_cleanstring(substr($filename, 0, $pos)),
+    '%filename_raw'  => substr($filename, 0, strrpos($filename, '.')),
+    '%label'     => $label,
+    '%extension' => strtolower(substr($filename, $pos+1)),
+    '%extension_raw' => substr($filename, $pos+1),
+    '%nid'       => $node->nid,
+    '%uid'       => $node->uid,
+    '%date'      => date('Y-m-d'),
+    '%year'      => date('Y'),
+    '%month'     => date('m'),
+    '%day'       => date('d'),
+  );
+
+  // Initialize null vocab placeholders to avoid tokens coming through.
+  $available_vocabs = taxonomy_get_vocabularies('image');
+  foreach ($available_vocabs as $voc) { $tokens['%vocab-'. $voc->vid] = ''; }
+
+  // When called from image_update or elsewhere, the full node_load may not have happened, so terms are no yet available.
+  if (empty($node->taxonomy)) {
+    // Fetch them.
+    $node->taxonomy = taxonomy_node_get_terms($node);
+  }
+  // In other cases the 'taxonomy' array may be all sorts of different structures. Needs deep parsing.
+ 	// This term parser may be called repeatedly per image, and in bulks, so it's sorta cached.
+  static $term_tokens;
+  if (! $node->nid || ! $term_tokens[$node->nid]) {
+    $term_tokens[$node->nid] = image_node_term_tokens($node->taxonomy);
+  }
+	$tokens = array_merge($tokens, $term_tokens[$node->nid]);
+
+  // Even original images may be renamed, but they won't include the derivative label.
+  if ($label == IMAGE_ORIGINAL) {
+    $tokens['%label'] = '';
+  }
+
+  $pattern = variable_get('image_derivatives_filename_pattern', _image_default_filename_pattern());
+  $filepath = strtr($pattern, $tokens);
+
+  // Sanitize possible typos and collapse dividers around tokens that are not there. More than one slash,underscore,dash or dot turn into just one whatever.
+  // TODO test edge cases.
+  $filepath = preg_replace('|([/_\-\.])[/_\-\.]+|', '$1', $filepath);
+
+  $fullpath = file_directory_path() .'/'. $filepath;
+
+  // When using advanced filename patterns, we may need to ensure a directory to put this image into exists.
+  $target_dir = dirname($fullpath);
+  if (! is_dir($target_dir)) {
+    image_mkdirs($target_dir);
+  }
+  return $fullpath;
+}
+
+/**
+ * Remove dodgy characters from filenames and parts.
+ * Force to lower and seperate with "-"
+ */
+function image_cleanstring($string) {
+  // Trim any leading or trailing separators
+  $string = preg_replace('/[^a-z0-9_\.]+/', '-', strtolower(trim($string)));
+  $string = preg_replace("/^\-+|\-+$/", "", $string);
+  return $string;
+}
+
+/**
+ * The taxonomy array may be of (at least) four different formats depending on how we are being called and the vocab rules!
+ * Need to parse it all out to deduce the term labels to use as tokens.
+ * 
+ * Calling this repeatedly is probably inefficient. But I don't want to mess with the expected taxonomy layout and mess things up.
+ * 
+ * @param $taxonomy
+ *   'taxonomy' array, as from $node->taxonomy.
+ * @return
+ *   Array of tokens, keyed by vocab identifier.
+ */
+function image_node_term_tokens($taxonomy) {
+  // TODO Weighting? Which to choose when there's multiples selected within a vocab?
+  // Currently just grabbing the first one we find.
+  
+  if (empty($taxonomy)) { return array(); }
+	$tokens = array();
+
+  // Sort terms into vocabulary bags and choose one. Treat [tags] as just another possibility.
+  foreach ($taxonomy as $termkey=>$termthing) {
+  	// Figure out what termthing really is.
+    if ($termkey == 'tags') {
+      // In freetagging submission, the taxonomy may still be plaintext. Parse it if I can.
+      // $node->taxonomy = array('tags' => "words, here");
+      foreach ($termthing as $vid => $tagstring) {
+        $tag_array = explode(',', $tagstring);
+        $top_tag = array_shift($tag_array); 
+        $tokens['%vocab-'. $vid] = image_cleanstring($top_tag);
+      }
     }
+	  else if (is_object($termthing)) {
+    	// $node->taxonomy = array( 17 => Object(tid => 17, vid => 2, name => 'word' ) );
+	  	// it's OK
+      if(! $tokens['%vocab-'. $termthing->vid]) {
+        $tokens['%vocab-'. $termthing->vid] = image_cleanstring($termthing->name);
+      }
+	  }
+	  else if (is_numeric($termthing)) {
+    	// $node->taxonomy = array( 2 => 17 );
+	    $term = taxonomy_get_term($termthing);
+      if(! $tokens['%vocab-'. $term->vid]) {
+	      $tokens['%vocab-'. $term->vid] = image_cleanstring($term->name);
+      }
+	  }
+	  else if (is_array($termthing)) {
+	  	// Actually the tids of several terms nested inside a vocab container;
+    	// $node->taxonomy = array( 2 => array(17, 18) );
+    	foreach ($termthing as $tid) {
+		    $term = taxonomy_get_term($tid);
+        if(! $tokens['%vocab-'. $term->vid]) {
+	        $tokens['%vocab-'. $term->vid] = image_cleanstring($term->name);
+        }
+    	}
+	  }
   }
 
-  return file_create_path($path . $filename);
+	return $tokens;
 }
 
 /**
@@ -847,7 +1006,7 @@ function _image_filename($filename, $lab
  *
  * @param $size
  *   An optional string to return only the image size with the specified key.
-  * @param $aspect_ratio
+ * @param $aspect_ratio
  *   Float value with the ratio of image height / width. If a size has only one
  *   dimension provided this will be used to compute the other.
  * @return
@@ -916,7 +1075,7 @@ function _image_is_required_size($size) 
  */
 function _image_insert(&$node, $size, $image_path) {
   $original_path = $node->images[IMAGE_ORIGINAL];
-  if (file_move($image_path, _image_filename($original_path, $size))) {
+  if (file_move($image_path, _image_filename($original_path, $size, FALSE, $node)) || ($image_path == $original_path)) {
     // Update the node to reflect the actual filename, it may have been changed
     // if a file of the same name already existed.
     $node->images[$size] = $image_path;
@@ -1012,6 +1171,23 @@ function image_create_node_from($filepat
   file_delete($original_path);
 
   return $node;
+}
+
+/**
+ * Recursive mkdir. Utility function.
+ */
+function image_mkdirs($strPath, $mode = 0777) {
+  if(! $strPath){ trigger_error("Null call to image_mkdirs()", E_USER_WARNING); }
+  return is_dir($strPath) or ( image_mkdirs(dirname($strPath), $mode) and mkdir($strPath, $mode) );
+}
+
+/**
+ * Return the default filename pattern, incorporating the old 'images' directory
+ * path if appropriate.
+ */
+function _image_default_filename_pattern() {
+  // image_default_path is no longer used, but referenced here for backwards compatability.
+  return variable_get('image_default_path', 'images') .'/%filename_raw.%label.%extension_raw';
 }
 
 /**
