Jump to content

Ability to define convention for image and file upload names


adrian
 Share

Recommended Posts

I haven't seen anything mentioned about this, but is it possible to define a format for uploads. For example I think it would be great to be able to specify an optional prefix, then the name of the associated page, then an optional suffix and finally a sequentially increasing number. For example:

prefix-page-name-suffix-1.jpg

prefix-page-name-suffix-2.png

Maybe an option to include the page's parent name or a specific field (something category oriented) in there as well might be useful.

I would use some abbreviated version of the site's name as the prefix as a way to help people who might have downloaded something to know where it came from.

Personally I really don't like downloading files with meaningless and inconsistent names, especially stock camera filenames.

Might be a good idea for a module.

EDIT: Module for this functionality is now available at: http://processwire.com/talk/topic/4865-custom-upload-names/

Edited by adrian
Link to comment
Share on other sites

My opinion is that a good default behavior is to retain the original filename as much as possible. Otherwise you lose relation to the original file on your computer. Usually when a CMS auto-generates filenames, it's because they don't have another means of relating back to the source (Page/id), are using it to define a sort order, or they don't want to go to the effort of making sure the filename is secure (auto-generated names have no user input). Auto-generated names are the quick and dirty way out for a CMS. Never realized they would actually be desirable. :) If you have a preference for it to auto-generate names like that, it could certainly be done though. One way to accomplish it would be to make a module that hooks before Pagefile::install and rename the file, and send the new filename to the function. That function receives a single argument of just the filename to add. That filename can be local or http, so you'd only want to attempt a rename if local. :)

public function init() {
  $this->addHook('Pagefile::install', $this, 'hookPagefileInstall'); 
}

public function hookPagefileInstall(HookEvent $event) {
  $filename = $event->arguments(0); 
  if(strpos($filename, '://')) return; // don't attempt to rename http-based files
  $page = $event->object->page; // page that will own the file  
  $info = pathinfo($filename); // PHP's pathinfo() function
  $n = 0; 

  // find the next available filename
  do {
    $n++;
    $newFilename = "$info[dirname]/{$page->name}-$n.$info[extension]"; // my-page-name-1.jpg
  } while(is_file($filename)); 

  rename($filename, $newFilename);
  $event->setArgument(0, $newFilename); // update the argument to the install function  
}

The above was written in the browser and not tested, so may need adjustments before functional. What it does is rename the file before it gets added. The do loop may not actually be necessary here, but that is just to account for the possibility that the directory (PHP's upload dir) already has a file with the target name in it. ProcessWire already takes care of adding numbers to files that are placed in it's own assets/files/ dirs. 

  • Like 5
Link to comment
Share on other sites

Thanks for the explanation Ryan. I had never thought it about it as a lazy way, but I certainly see your point.

I initially used to write my little custom CMSs to retain original filenames, but changed to auto-generated because most of my work is for sites which provide lots of materials designed to be downloaded by end users. Having clients upload files with names like: Final ReportV6 web version for John Feb 23.PDF started to drive me a little crazy though. Not very useful or meaningful to the poor end user who downloads it. No amount of training or explanation could get them to make meaningful filenames.

Thanks for the example module code - I will definitely make use of that. I really can't believe how easy you have made implementing custom modules. The lack of ability to do this with other system (whether real or perceived) is why I never went the CMS route for so many years :) It always seemed like I would be hacking away at things to make the functionality I needed.

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Is it possible to define PW to keep the uploaded filenames untouched?

We have pdf:s that have been linked to each other and while uploading the files, PW renames the files. Then the links break. The PDF:s are meant to be used straight from the web-page or downloaded as a zip file to clients computer. In both situations the filenames should be intact and links inside PDF:s should remain valid.

What is actually the way PW changes the uploaded files name at the moment?

Link to comment
Share on other sites

Ipa,

Currently PW should not change the filename at all, except that it appends a number if you go to upload the same file again. So long as you delete the original, save the page, then upload it again, you shouldn't have any issues with filenames being changed.

What changes are you seeing?

Link to comment
Share on other sites

Pw is sanitizing the filename, meaning it replaces any problematic characters such as Umlauts (ä,ö,ü) and spaces with '_'.

See Pagefiles::cleanBasename()

// ...
$basename = preg_replace('/[^-_.a-zA-Z0-9]/', '_', $basename); 
$ext = preg_replace('/[^a-z0-9.]/', '_', $ext); 
// ...
  • Like 2
Link to comment
Share on other sites

I think PW is also changing the filenames to all lowercase. Could it be possible to only sanitize, but keep the case? Or sometimes it might be even ok to not sanitize umlauts or spaces away, just dangerous chars like /|\. etc. 

Can I define that the file uploaded is replacing the file with same name instead of renaming it?

Link to comment
Share on other sites

  • 4 months later...

Hey Ryan,

What am I missing with hooking into Pagefile::install ?

I can't for the life of me get it to trigger. Is there anything special I need to do with this?

EDIT: The only way I could manage to hook into Pagefile was by making setFilename hookable. It seems to be working fine like this, but if you get a chance, would you mind looking at this, as I think I must be missing something with install.

Only catch seems to be that it gets triggered every time the page is saved, as well as when the file is initially uploaded - is that expected behavior? 

Edited by adrian
Link to comment
Share on other sites

Hi again Ryan,

I attached a draft of the module I am working on. It works, but at the moment still requires that I make setFilename hookable. And it also suffers from the fact that this is hook is triggered on page saving, as well as file upload. I thought about going with InputfieldFile::fileAdded, but that has the problem of not renaming until the page has saved and of course would cause problems with immediate embedding in an RTE before page save, among others.

If you test it you'll see that in the module config options you can limit the module to specific templates and pages (and their children). You can also define the format you want to use for naming files using something like: mysite-{$page->path}-{$page->id} or anything you want really!

I would also like to add the ability to define different formatting options on a field by field basis. The problem is that I can't figure out how to get the current field when hooking into Pagefile - any hints?

I was thinking that maybe a better approach would be to make this module an alternate Inputfield type that could be selected from the field's detail's tab. This would solve the per field formatting options. Maybe have a site/field-wide default format in the module config settings and a separate override format option in the field input settings. Will think about this some more. On reflection not sure this would be a good idea since it would mean it wouldn't be possible to use other Inputfield types, like Antti's cropimage for example. 

Any ideas?

ProcessCustomUploadNames.module

Link to comment
Share on other sites

Adrian, the Pagefile::install hook should be triggered so long as the file doesn't already exist in the destination. Is it possible that the files are already in /site/assets/files/.../? If so, the file doesn't technically need to be installed, so the hook is never called. 

As for getting the field belong to a Pagefile, this morning I've committed an update (to dev) that makes that possible. You can now retrieve the Field object that is part of the Pagefiles/Pagefile in the same way you can retrieve the $pagefile->page, by accessing $pagefile->field (accessible from the pagefile or from the pagefiles array). 

  • Like 2
Link to comment
Share on other sites

Hey Ryan,

Thanks so much for the ability to access the Field object - that really helps.

Unfortunately I still can't figure out the Pagefile::install hook. I am definitely trying to add a new file. I can't think of anything I am missing :) Can you see anything in my code that could be causing the problem?

Thanks for any ideas.

Link to comment
Share on other sites

Ok, well I am still waiting to hear about the Pagefile::install hook issue :), but in the meantime I have extended this module considerably. It now supports specifying multiple rules based on selected fields, templates, parent pages, and file extensions. This way you can infinitely configure the filename format exactly how you want.

I'll add a post in the modules forum board once the hook issue is sorted, but for now, you can access the module at:

https://github.com/adrianbj/CustomUploadNames

I used Pete's work in the EmailToPage module as a starting point for the multiple rename rules in the module config. This mostly seems to be working really well, but unfortunately I can't seem to get ASM fields to initialize properly when adding another rule until it is saved and the page reloads. Any ideas?

Attached is what the config settings looks like.

post-985-0-27677200-1382731560_thumb.png

Link to comment
Share on other sites

Unfortunately I still can't figure out the Pagefile::install hook. I am definitely trying to add a new file.

It doesn't matter if it's a new file or not. What matters is whether it's already located in the destination directory. If it's already in there, then the install() method won't be called and hooks to it wouldn't be called. That's because the purpose of the install method is to copy a file from one location to the destination. I mention this because it's not uncommon when doing data conversion jobs to put the file right in the destination directory before adding it to ProcessWire's data. But since you want the install() hook to be called, you'd want to ensure that the files you are adding aren't already in /site/assets/files/... Let me know if I'm wrong about my guess about the file already being in the destination? If so, please post the code you are using for the non-working hook and I'll experiment here. 

Your module and screenshot look awesome, btw. 

Link to comment
Share on other sites

Hey Ryan - thanks for getting back to me - I think the module will be really cool once I get this figured out!

I just tested on a brand new PW dev installation and I still can't Pagefile::install to work.

This is my hook (line 74 of my module) which doesn't work:

$this->addHookBefore('Pagefile::install', $this, 'customRenameUploads');

As soon as I make setFilename hookable and use the following hook instead, it works perfectly:

$this->addHookBefore('Pagefile::setFilename', $this, 'customRenameUploads');

Maybe I am missing something obvious :)

This comment - "I mention this because it's not uncommon when doing data conversion jobs to put the file right in the destination directory before adding it to ProcessWire's data." has me thinking. I am not really sure what you mean by data conversion jobs, but I am just using PW upload dialog to get the images into the system and I am calling "addHookBefore" so I would think it should happen before the file is copied into the assets destination folder. 

I have attached a version of the module with the install version of the hook, which is the only difference to the version on github, execpt for a error_log call to see if the function is actually being called at all from the hook.

Thanks for looking into it.

ProcessCustomUploadNames.module

ProcessCustomUploadNames.js.txt

Link to comment
Share on other sites

Adrian, sorry for some reason I got it stuck in my head that we were talking about API level adding of images rather than capturing those added from the admin. The install() method doesn't get called on images added from the admin because InputfieldFile puts them right in the target directory, if I recall correctly. So I think the hook that you would actually want would be one of these methods (both of which are already hookable): 

InputfieldFile::processInputAddFile($filename)

This one adds the given $filename to the $pagefiles, perhaps you could rename $filename it on a before call and change the value of $arguments[0] to be your new filename?

or...

InputfieldFile::fileAdded(Pagefile $pagefile)

This one is called after a new file is added, giving you the completed $pagefile

  • Like 1
Link to comment
Share on other sites

Thanks Ryan,

Not sure why I actually never tried that. I have used InputfieldFile::fileAdded in my SVG rasterizer module, so it should have been on my radar.

Anyway, for this module I have gone with InputfieldFile::processInputAddFile and it seems to be working great.

New version is on Github and here is the official module support post:

http://processwire.com/talk/topic/4865-custom-upload-names/

Thanks again for seeing me through this one :)

  • Like 1
Link to comment
Share on other sites

  • 4 years later...

Hello @adrian (or anyone else with experience in automatically renaming image uploaded in the admin),

You might be able to help me I guess :) I've dug up this old thread because I'm stuck with my custom implementation of renaming uploaded files (actually images) in the admin. I do not use Custom Upload Names because it might not support what I need, but most importantly besides being able to rename the files, I also want to generate a JPG from the uploaded PNG ( I use imagecow for that).

Currently I will be the only one to upload images to the ImageField in question so my code does not have to be bullet proof but there is something that does not work the way I want it to. Here is my code

Spoiler

 


<?php

public function init() {
	$this->addHookAfter('InputfieldFile::fileAdded', $this, 'hookCreateJpgClone', array('priority' => 10));
}

public function hookCreateJpgClone($event) {
	$inputfield = $event->object;
	if ($inputfield->name != 'product_image') return; // this hook is for product_image only

	$image = $event->argumentsByName("pagefile");
	$folder = $image->page->id; // the actual file is in this directory of /site/asstes/files
	$folder_path = $this->config->paths->assets . "files/" . $folder; // .../site/asstes/files/{page id}
	$name_incl_path = $folder_path . "/" . $image->name; // full path to the actual file

	$old_name_ending = substr($image->name, strpos($image->name, "-")); // the part of the old name we want to keep
	$new_name_beginning = preg_replace('/\s+/', '_', $image->page->title); //removes whitespaces too
	$newFilename = $new_name_beginning . $old_name_ending;
	$image->removeVariations();
	$image->rename($newFilename);

	$name_core = $this->file_ext_strip($image->name);
	$name_jpg_incl_path = $folder_path . "/" . $name_core . ".jpg";

	if (file_exists($image->filename)) {
		$imagecow = Image::fromFile($image->filename, Image::LIB_IMAGICK);
		$imagecow->format('jpg')->save($name_jpg_incl_path);
		$image->page->product_image->add($name_jpg_incl_path);
	} else {
		$this->log->save("site-warnings", "hookCreateJpgClone() file did not exist: {$image->filename} ");
	}
	$image->page->save();
	$event->setArgument(0, $newFilename); // this does not make a difference
}

 

 

 

 

It seems to work but the issue is that after the image has been uploaded, the inputfiled in the admin tries to load the file with the original filename. After refreshing the page, all seems to be ok, but I would like to see the result right away. I tried to look up how you do it in CustomUploadNames but your module is a bit of a complex one :) 

There is a screen grab to see what happens:

image-file-rename-during-upload-issue.gif.1ff4c1da9c3c515104b83ee9ec1d6d3e.gif

 

Edited by szabesz
typo
Link to comment
Share on other sites

14 hours ago, adrian said:

Does that help?

Oh yes, it does help a lot! :) Thanks for fixing it for me! I was just too much concentrating on anything else but creating those image variation which can be performed in the after hook only. And you are right, renaming them is a different matter which can be done in the before hook.

Edited by szabesz
typo
  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...