Prevent File rename on upload

A customer of mine wants to upload images to the website but without changing the file name. Is this somehow possible? Currently everything is done in lowercase and spaces become a “-”. Is there any way to stop this?

1 Like

Don’t think so. This happens in the create function of the File class (via the FileActions trait).

Speculation (this hasn’t been tested):
to override stuff happening there you’d have to have a custom Page model that overrides the createFile function with a variant that doesn’t create a Kirby\Cms\File, but your own class that extends File while overriding its create method with a function that doesn’t rename the filename. Like:

site/plugins/unsafenames/index.php

<?php

class UnsafeFile extends Kirby\Cms\File {
	/**
	 * Just copied this from Kirby source... 
	 *
	 * Creates a new file on disk and returns the
	 * File object. The store is used to handle file
	 * writing, so it can be replaced by any other
	 * way of generating files. Does not sanitize 
	 * filename on create
	 *
	 * @param array $props
	 * @param bool $move If set to `true`, the source will be deleted
	 * @return static
	 * @throws \Kirby\Exception\InvalidArgumentException
	 * @throws \Kirby\Exception\LogicException
	 */
	public static function create(array $props, bool $move = false)
	{
		if (isset($props['source'], $props['parent']) === false) {
			throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File');
		}


		// REPLACED:
		// $props['filename'] = F::safeName($props['filename'] ?? basename($props['source'])); 
		// WITH:       
		$props['filename'] = $props['filename'] ?? basename($props['source']);
                
		// continue with business as usual...

		$props['model'] = strtolower($props['template'] ?? 'default');

		// create the basic file and a test upload object
		$file = static::factory($props);
		$upload = $file->asset($props['source']);

		// gather content
		$content = $props['content'] ?? [];

		// make sure that a UUID gets generated and
		// added to content right away
		if (Uuids::enabled() === true) {
			$content['uuid'] ??= Uuid::generate();
		}

		// create a form for the file
		$form = Form::for($file, ['values' => $content]);

		// inject the content
		$file = $file->clone(['content' => $form->strings(true)]);

		// run the hook
		$arguments = compact('file', 'upload');
		return $file->commit('create', $arguments, function ($file, $upload) use ($move) {
			// remove all public versions, lock and clear UUID cache
			$file->unpublish();

			// only move the original source if intended
			$method = $move === true ? 'move' : 'copy';

			// overwrite the original
			if (F::$method($upload->root(), $file->root(), true) !== true) {
				throw new LogicException('The file could not be created');
			}

			// always create pages in the default language
			if ($file->kirby()->multilang() === true) {
				$languageCode = $file->kirby()->defaultLanguage()->code();
			} else {
				$languageCode = null;
			}

			// store the content if necessary
			$file->save($file->content()->toArray(), $languageCode);

			// add the file to the list of siblings
			$file->siblings()->append($file->id(), $file);

			// return a fresh clone
			return $file->clone();
		});
	}
}

class UnsafeFilenamePage extends Kirby\Cms\Page {
	/**
	 * Creates a new file with unsafe filename
	 *
	 * @param array $props
	 * @param bool $move If set to `true`, the source will be deleted
	 * @return \Kirby\Cms\File
	 */
	public function createFile(array $props, bool $move = false)
	{
		$props = array_merge($props, [
			'parent' => $this,
			'url'    => null
		]);

		return UnsafeFile::create($props, $move);
	}
}

then create the page models:

site/models/default.php

<?php

class DefaultPage extends UnsafeFilenamePage {
  // eventually your stuff here
}

As you can see it would be rather hacky… Lots of code just to change that single line.
Another approach could possibly be to create your own files section that doesn’t use $page->createFile()) - but that also means lots of code if you want to keep all other Kirby features.

1 Like

Hi @rasteiner,

thanks for your help! But it sounds really extreme hacky :grin: The customer wanted to upload the files with the appropriate caption to “save himself the work” of entering it manually. But then he should rather take the minimum effort. Otherwise, this is really too dangerous for me.

Well in that case, you could still allow the client to upload the file name as they would like and use a file upload hook to pick up the filename, undo the safe file name bit (Capitlise first letter, remove the kebabs, add full stop on the end).

Kirby has some useful things to help:

I just discovered there is a hook that fires before the name gets converted (i think - it might be refering to changing the files name manually through the panel), so you can probably avoid some of the above and just get the name to update the meta file caption field.

Also a nice idea. I will suggest it to the customer that we could also solve this so

i think the conversion to the “safefilename” format happens because of web SEO best practices

Instead, use all lowercase filenames and replace spaces with hyphens .

If you want to move the original filename to something like a title/alt field you can do that with a file blueprint and the file.created:before hook. Something like the following (but untested).

site/blueprints/files/default.yml

fields:
  alt:
    label: SEO Alt Text
    type: text

site/config/config.php

return [
    'hooks' => [
        'file.create:before' => function (Kirby\Cms\File $file, Kirby\Filesystem\File $upload) {
            $file->update(['alt' => $upload->name()];
        }
    ]
];

Is there any update on this?

The automatic renaming is usually nice, but annoying if it can’t be turned off. I have a client that has a press release section on their page. They have a system of naming the downloads for their customers to understand what each file contains (even after downloaded) and want to make sure that downloads keep that special name. I feel a bit pressured by Kirby. :grinning:

Also, upon some further inspection, I guess there is the same sanitation happening client side in the Panel while entering filenames (when renaming or during upload). You are not able to enter anything but lowercase characters and hyphens in the upload dialog. In the rename dialog you can also enter underscores, which is prohibited during upload. I guess that’s an inconsistency (bug).

So, to make sure: if I want to adjust filename handling of files in the Panel, I would need to overwrite core parts on the server and client side? I will need to create a fork of the project to be able to safely install updates in the future?

The thing is: I will have to do it, since my client asks for it. But this seems like there needs to be some level of adjustment possible, not by changing the core.

Code that needs to be adjusted

Server-side

Prevent the application of F::safeName() when creating a file

Adjust the returned dialog when calling the changeName API endpoint (by changing the allowed character list)

Client-side

Adjust the filename input in the upload dialog (by adding allowed character list)

Allowed character inconsistency (bug)

Looking for these I also found the cause of the inconsistency when changing the filename in the upload dialog compared to changing it afterwards. During upload a-z0-9 is used as allowed character set (source), while changeName uses a-z0-9@._- (source).

F::safeName() allows underscores as well a-z0-9@._- (source), so they stay in place.