Jump to content

Using $pagefile->uploadName to offer downloads of files with their original filename


Robin S
 Share

Recommended Posts

ProcessWire automatically sanitises the names of files that are uploaded to a Files field. For example, a file named "Café meals under $30.pdf" will become "cafe_meals_under_30.pdf" after it is uploaded.

Since v3.0.212 ProcessWire stores the original unsanitised filename of each uploaded file, and this is accessible via $pagefile->uploadName
https://processwire.com/blog/posts/pw-3.0.226/#file-and-image-improvements
https://processwire.com/api/ref/pagefile/upload-name/

So if I have a field named "files" on my page and I want to provide downloads of the files with their original filename I can output links like this:

$out = '';
foreach($page->files as $file) {
    // uploadName is entity-encoded when output formatting is on
    $original_name_unencoded = html_entity_decode($file->uploadName);
    $out .= "<p><a href='$file->url' download='$original_name_unencoded'>$file->uploadName</a></p>";
}
echo $out;

So far, so good. But I want my site visitors to be able to view PDFs in the browser rather than force a download, yet if they do download them after viewing them I want the files to get the original filename. For this I can use a URL hook to deliver the PDF via PHP rather than directly loading the file.

In /site/ready.php:

$wire->addHook('/view-pdf/{page_id}/{filename}', function($event) {
	$id = (int) $event->page_id;
	$filename = $event->wire()->sanitizer->text($event->filename);
	if(!$id || !$filename) return 'Invalid request';
	// Get the Pagefile via PagefilesManager
	$pm = $event->wire()->pages->get($id)->filesManager;
	$file = $pm->getFile($filename);
	if(!$file) return 'File not found';

	// uploadName is entity-encoded when output formatting is on
	$original_name_unencoded = html_entity_decode($file->uploadName);
	// Set headers and output the PDF content
	header("Content-Type: application/pdf");
	header("Content-Disposition: inline; filename=$original_name_unencoded");
	header("Content-Transfer-Encoding: binary");
	header("Accept-Ranges: bytes");
	@readfile($file->filename);
	return true;
});

In the page template file:

$out = '';
foreach($page->files as $file) {
    if($file->ext === 'pdf') {
        // Deliver PDF files via the URL hook
        $out .= "<p><a href='/view-pdf/$page->id/$file->basename'>$file->uploadName</a></p>";
    } else {
        // Other files receive a download attribute
        // uploadName is entity-encoded when output formatting is on
        $original_name_unencoded = html_entity_decode($file->uploadName);
        $out .= "<p><a href='$file->url' download='$original_name_unencoded'>$file->uploadName</a></p>";
    }
}
echo $out;

 

  • Like 14
  • Thanks 1
Link to comment
Share on other sites

1 hour ago, Jim Bailie said:

I wonder if I can drop the upload name into a template field so the admins have a visual frame of reference on the admin screen.

The upload name is shown in a tooltip when you hover on the file icon in the inputfield:

image.png.67c4a9954eea0ebfa42bca065abf6889.png

If you want something that's always visible you could hook into the inputfield rendering:

// Display upload name in InputfieldFile
$wire->addHookAfter('InputfieldFile::renderItem', function(HookEvent $event) {
	/** @var Pagefile $pagefile */
	$pagefile = $event->arguments(0);
	$event->return = "<p class='upload-name'>$pagefile->uploadName</p>" . $event->return;
});

And then style it with some custom admin CSS:

.InputfieldFileItem .upload-name { color:white; line-height:1.33; padding:6px 10px; margin:0; background-color:#606060; }

There are several ways you can add custom CSS to the PW admin - here is one:

// Add custom CSS to admin
$wire->addHookAfter('AdminTheme::getExtraMarkup', function(HookEvent $event) {
	$config = $event->wire()->config;
	$parts = $event->return;
	$css_url = $config->urls->templates . 'admin-assets/admin-custom.css';
	$modified = filemtime(rtrim($config->paths->root, '/') . $css_url);
	$parts['head'] .= "<link rel='stylesheet' href='$css_url?m=$modified'>";
	$event->return = $parts;
});

Result:

image.png.57034bc4ccd1154a1c92c002eeebfb50.png

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

Hmm. Wow. This is great! I've got some work to do this weekend.

I'll be testing this asap, but I will ask you in advance: Do you think the replace/rename module will preserve the upload name after replacement?

Edit

BTW, PW 3.0.226 - I'm not seeing the uploadName in the tooltip. The upload name was "conservation_station_tester2$$$.pdf" (see below)

Good News -- I AM seeing upload names when looping via the API for files that have been manually uploaded

Bad News  -- Rename/Replace module DOES REMOVE the original upload name and replaces with the sanitized version.

Bad News  -- Uploading a file via the API places the SANITIZED value into uploadName

I'd be grateful for any more insights or ideas you may have on how to proceed. Thanks again!!

screen-shot-20230909.jpg

Link to comment
Share on other sites

12 hours ago, Jim Bailie said:

Bad News  -- Uploading a file via the API places the SANITIZED value into uploadName

How did you upload the file via api? Can you give a quick example to reproduce the issue?

If you do that and post it to this issue and mention Ryan I guess chances are high that he will fix this quickly. At least I suggested the uploadName feature on Feb 16 and he implemented it on Feb 17 😄 

Maybe he just didn't think of API uploads.

  • Like 2
Link to comment
Share on other sites

8 hours ago, bernhard said:

How did you upload the file via api? Can you give a quick example to reproduce the issue?

If you do that and post it to this issue and mention Ryan I guess chances are high that he will fix this quickly. At least I suggested the uploadName feature on Feb 16 and he implemented it on Feb 17 😄 

Maybe he just didn't think of API uploads.

@bernhard Here's the test code. Pretty simple; it uploads the file fine with the sanitized file name. When I loop through after, it outputs the file name and upload name which are the same.

$path = "/var/www/dvmrebuild/storage/";
$fileName = "My_Test_File3$$$.pdf";

$p = $pages->get(1214);

$p->of(false);
$p->venue_files->add($path . $fileName);
$p->save('venue_files');

foreach ($p->venue_files AS $vfile) {
    echo $vfile->name . ' => ' . html_entity_decode($vfile->uploadName) . '<br>';
}

# Outputs:
# my_test_file3.pdf => my_test_file3.pdf

 

Link to comment
Share on other sites

@bernhard Thanks for doing that. I'll dig up my github info and give a thumbs up.

In the meantime, I'll try to cobble something together with some file template fields and some of @Robin S's examples above and in another post.

I also wonder if the fact that the API does not preserve the uploadName cascades down to the fact that the uploadName is not set in Robin's file rename/replace module.

Link to comment
Share on other sites

I've just tested this:

$home->of(false);
$home->rockfrontend_favicon->add(__DIR__ . "/Test$$$.png");
$home->save();
bd($home->rockfrontend_favicon->uploadName());

Which resulted in this:

DdBMXaG.png

So that confirms that uploadName() does not work for API added files.

  • Like 1
Link to comment
Share on other sites

@Robin S It works! I was studying your module to see if I could start digging into it, but right now it might be one pay grade above where I'm currently at; though I feel like I'm catching up. Thank you again.

And just FYI, note the placement of the rename/replace link:

I'm on Windows, Firefox, Monitor at standard 1920 x Whatever

screen-shot-20230904.jpg

Link to comment
Share on other sites

Update: Somehow by looking at Pagefile.php I was able to figure out how to set the uploadName via the API. It seems to work...

$path = "/var/www/dvmrebuild/storage/";
$fileName = "My_Test_File4$$$.pdf";
$p = $pages->get(1214);

$p->of(false);
$p->venue_files->add($path . $fileName);
$p->save('venue_files');

$p->of(false);
$vfile = $p->venue_files->last();
$vfile->file_upload_date = date('Y-m-d');
$vfile->file_title = 'My Test File 4'; 
$vfile->file_original_name = $fileName;
$vfile->filedata('uploadName', htmlentities($fileName));
$p->save('venue_files');

 

Link to comment
Share on other sites

@Jim Bailie, I updated the styling in a new release of the module.

1 minute ago, Jim Bailie said:
$vfile->filedata('uploadName', htmlentities($fileName));

You don't want to encode entities when you set the value or you'll get a double-encoded string when you get uploadName when output formatting is on.

  • 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...