? image/image.module.both patches
Index: image/image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/image.module,v
retrieving revision 1.209.2.52
diff -u -p -w -r1.209.2.52 image.module
--- image/image.module	2 Mar 2008 14:18:49 -0000	1.209.2.52
+++ image/image.module	25 Mar 2008 06:18:43 -0000
@@ -89,11 +89,49 @@ function image_admin_settings() {
     '#type' => 'fieldset',
     '#title' => t('File paths')
   );
-  $form['paths']['image_default_path'] = array(
+
+  $example_tokens = array(
+    '%nodepath'     => 'safe/url/example' ,
+    '%filename'     => 'sample-filename',
+    '%filename_raw' => 'Sample filename',
+    '%label'        => 'thumb',
+    '%extension'    => 'jpg',
+    '%nid'          => 666,
+    '%uid'          => $GLOBALS['user']->uid,
+    '%date'         => date('Y-m-d'),
+    '%year'         => date('Y'),
+    '%month'        => date('m'),
+    '%day'          => date('d'),
+  );
+  $available_vocabs = taxonomy_get_vocabularies('image');
+  $vocablist = '';
+  foreach($available_vocabs as $voc) {
+    $example_tokens['%vocab-'. $voc->vid] = 'a-term';
+    $vocablist .= ' %vocab-'. $voc->vid .' = '. $voc->name .' ';
+   }
+
+  $example_path = variable_get('file_directory_path', 'files') .'/'. strtr(variable_get('image_derivatives_filename_pattern', _image_default_filename_pattern()), $example_tokens );
+
+  $form['paths']['image_derivatives_filename_pattern'] = array(
     '#type' => 'textfield',
-    '#title' => t('Default image path'),
-    '#default_value' => variable_get('image_default_path', 'images'),
-    '#description' => t('Subdirectory in the directory "%dir" where pictures will be stored. Do not include trailing slash.', array('%dir' => variable_get('file_directory_path', 'files'))),
+    '#title' => t('Filename naming pattern'),
+    '#default_value' => variable_get('image_derivatives_filename_pattern', _image_default_filename_pattern()),
+    '#description' => t('
+      Pattern to use when saving or creating derivatives (like thumbnails) for an image.
+      Available tokens are: %tokens.<br/><pre>%example_path</pre>
+      <b>%filename</b> is a sanitized version of the upload name, with special characters converted to "-" and lowercased.
+      If you want to keep the messy filename, use <b>%filename_raw</b>.<br />
+      <b>%vocab-n</b> will be replaced with the a term from the named vocabulary: %vocablist. <br />
+      Missing elements will be collapsed cleanly if possible.
+      (<b>images/%vocab-2/%filename.%label.%extension</b> will become <b>images/%filename.%extension</b> if no vocab-2 term or label is present).
+      Files are not renamed or moved after upload, even if these parameters are changed.
+      ',
+      array(
+        '%tokens' => join(', ', array_keys($example_tokens)),
+        '%example_path' => $example_path,
+        '%vocablist' => $vocablist,
+      )
+    ),
   );
 
   $form['image_max_upload_size'] = array(
@@ -166,6 +204,15 @@ function image_admin_settings() {
 }
 
 /**
+ * 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.%label.%extension';
+}
+
+/**
  * Check that the sizes provided have the required amount of information.
  */
 function image_settings_sizes_validate(&$form) {
@@ -336,7 +383,7 @@ function image_prepare(&$node, $field_na
     }
 
     // Save the file to the temp directory.
-    $file = file_save_upload($field_name, _image_filename($file->filename, IMAGE_ORIGINAL, TRUE));
+    $file = file_save_upload($field_name, _image_filename($file->filename, IMAGE_ORIGINAL, TRUE, $node));
     if (!$file) {
       return;
     }
@@ -721,6 +768,7 @@ function image_update(&$node) {
     }
 
     $sizes = image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]);
+
     foreach ($sizes as $key => $size_info) {
       if (!empty($node->images[$key]) && $node->images[$key] != $original_path) {
         _image_insert($node, $key, $node->images[$key]);
@@ -919,7 +967,7 @@ function _image_build_derivatives(&$node
 
   // Resize for the necessary sizes.
   foreach ($needed_sizes as $key => $size) {
-    $destination = _image_filename($original_path, $key, $temp);
+    $destination = _image_filename($original_path, $key, $temp, $node);
     if (!image_scale($original_path, $destination, $size['width'], $size['height'])) {
       drupal_set_message(t('Unable to create scaled %label image', array('%label' => $size['label'])), 'error');
       return FALSE;
@@ -931,30 +979,104 @@ 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') .'/';
-  if ($temp) {
-    $path .= 'temp/';
+function _image_filename($filename, $label = IMAGE_ORIGINAL, $temp = FALSE, $node = NULL) {
+  $filename = basename($filename);
+  $pos = strrpos($filename, '.');
+
+  // 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
+  $tokens = array(
+    '%nodepath'  => $node->path ? $node->path : $node->nid ? 'node-'. $node->nid : '',
+    '%filename'  => image_cleanstring(substr($filename, 0, strpos($filename, '.'))),
+    '%filename_raw'  => substr($filename, 0, strpos($filename, '.')),
+    '%label'     => $label,
+    '%extension' => 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] = ''; }
+
+  if (empty($node->taxonomy)) {
+    // When called from image_update or elsewhere, the full node_load may not have happened, so terms are unavailable
+    // Fetch them.
+   $node->taxonomy = taxonomy_node_get_terms($node->nid);
+  }
+  if (!empty($node->taxonomy)) {
+    // Sort terms into vocabulary bags and choose one
+    // TODO Weighting? Which to choose?
+    if ($tags = $node->taxonomy['tags']) {
+      // In freetagging submission, the taxonomy may still be plaintext. Parse it if I can.
+      foreach ($tags as $vid => $tagstring) {
+        $tag_array = explode(',', $tagstring);
+        $top_tag = array_shift($tag_array);
+        $tokens['%vocab-'. $vid] = image_cleanstring($top_tag);
+      }
+    }
+    else {
+      foreach ($node->taxonomy as $term) {
+        $tokens['%vocab-'. $term->vid] = image_cleanstring($term->name);
+      }
+    }
   }
 
-  $filename = basename($filename);
+  // Even original images may be renamed, but they won't include the derivative label
+  if ($label == IMAGE_ORIGINAL) {
+    $tokens['%label'] = '';
+  }
 
-  // Insert the resized name in non-original images
-  if ($label && ($label != IMAGE_ORIGINAL)) {
-    $pos = strrpos($filename, '.');
+  $pattern = variable_get('image_derivatives_filename_pattern', _image_default_filename_pattern());
+
+  // TODO Remove this block? Looks like it hasn't actually worked for years.'
     if ($pos === false) {
       // The file had no extension - which happens in really old image.module
-      // versions, so figure out the extension.
+    // versions, so figure out the extension, and construct the filename again.
       $image_info = image_get_info(file_create_path($path . $filename));
-      $filename = $filename .'.'. $label .'.'. $image_info['extension'];
+    $tokens['%filename'] = $filename;
+    $tokens['%extension'] = $image_info['extension'];
+    $filepath = strtr($pattern, $tokens);
     }
-    else {
-      $filename = substr($filename, 0, $pos) .'.'. $label . substr($filename, $pos);
+
+  $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);
+
+  if ($temp) {
+    // TODO Is this right? Where should temp files fo now?
+    $filepath = 'temp/'. $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;
   }
 
-  return file_create_path($path . $filename);
+/**
+ * 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;
 }
 
 /**
@@ -1031,7 +1153,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))) {
     // 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;
@@ -1118,3 +1240,11 @@ function image_create_node_from($filepat
   return $node;
 }
 
+/**
+ * Recursive mkdir. Utility func
+ */
+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) );
+}
+
