Rename files on upload only working partly

when i use the following Hook

'hooks' => [
   'file.create:after' => function ($file) {
      $tpl = $file->template();
      $filename = $tpl."-voice-" . uniqid();
      if ($tpl=='voiceimage') {
                try {
                    $file->changeName($filename);
                    $file->update([
                    'filename' => $filename,
                ]);
                    } catch(Exception $e) {
                }             
            }
       }

the files get renamed but the pannel still trying to load the old thumbnails the thumbnails are not generated because the parent image is not existing anymore after the rename. how can i solve that?
do i need to use the file.create:before hook?

Hi, welcome to our forum?

When exactly does that happen? When uploading to a files field or to a files section? I can reproduce it with a field, because Kirby then stores the original filename in the field. Works fine in a section.

What is the purpose of the call to $file->update() here?

Hello Texnixe,

yes, it’s my first post here in the Forum. Thanks for that warm welcome, the fast reply and the code formating :slight_smile: (i’ve searched the button to add code)
The $file->update stuff was only for testing.

let me tell you what i try to build, maybe it helps to understand the problem a bit more.

I’ve builded virtual pages for user profiles from a database. now i like to add an image to the profile.
i’ve added a field of type file and added it to the DB. When i upload a file the filename and path gets writen to the DB, so far so good.

Now i like to rename the image to something unique and do some image manipulation with GD-Lib and only write the original renamed file to the DB with new path and filename.
With the hook above i’m saving the file with the new name to the folder, but there is still the old filename in the pannel and no thumbnail images visible/build and i’m not able to save the path to the database.

I would use a files section instead of a files field for uploading the file, then you don’t run into this issue. A files field automatically selects the uploaded file and when you save, the original filename is stored.

You can format code here by wrapping it into three backticks on a separate line before and after the code block!

three-backticks

Hello Sonja,

i’ve replaced the Field with a section and changed my blueprint from

fields:
  shortname:
    label: shortname
    type: hidden
    maxlength: 600
  image:
    type: files
    template: voiceimage
    label: Select voice image...
    max: 1
    multiple: false
    query: page.images
    uploads: 
      parent: site.find('voices')
      template: voiceimage

to

sections:
  content:
    type: fields
    fields:
      shortname:
        label: shortname
        type: text
        maxlength: 600
  image:
    type: files
    template: voiceimage
    label: Select voice image...
    max: 1
    multiple: false
    query: page.images
    uploads: 
      parent: site.find('voices')
      template: voiceimage

now the hook for changing the Filename seems to work. but i got two new problems now.
First one is that i’m not able to make a new virtual page after i’ve changed the blueprint and i’m not sure what’s the reason for it.
The second one is that i got now the picture data in the Database but it still uses the old filename.
The uploaded filename was image.jpg

- 
  filename: image.jpg
  dragText: '(image: voices/image.jpg)'
  icon:
    type: file-image
    ratio: 3/2
    back: pattern
    color: '#de935f'
    cover: false
    url: >
      https://site.local/media/pages/voices/f984485175-0/image.jpg
    cards:
      url: >
        data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw
      srcset: >
        https://site.local/media/pages/voices/f984485175-0/image-352x.jpg
        352w,
        https://site.local/media/pages/voices/f984485175-0/image-864x.jpg
        864w,
        https://site.local/media/pages/voices/f984485175-0/image-1408x.jpg
        1408w
    list:
      url: >
        data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw
      srcset: >
        https://site.local/media/pages/voices/f984485175-0/image-38x38.jpg
        1x,
        https://site.local/media/pages/voices/f984485175-0/image-76x76.jpg
        2x
  id: voices/image.jpg
  image:
    ratio: 3/2
    back: pattern
    cover: false
    url: >
      https://site.local/media/pages/voices/f984485175-0/image.jpg
    cards:
      url: >
        data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw
      srcset: >
        https://site.local/media/pages/voices/f984485175-0/image-352x.jpg
        352w,
        https://site.local/media/pages/voices/f984485175-0/image-864x.jpg
        864w,
        https://site.local/media/pages/voices/f984485175-0/image-1408x.jpg
        1408w
    list:
      url: >
        data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw
      srcset: >
        https://site.local/media/pages/voices/f984485175-0/image-38x38.jpg
        1x,
        https://site.local/media/pages/voices/f984485175-0/image-76x76.jpg
        2x
  info: ""
  link: /pages/voices/files/image.jpg
  text: image.jpg
  type: image
  url: >
    https://site.local/media/pages/voices/f984485175-0/image.jpg
  uuid: voices/image.jpg

What is this second code snippet?

And could you please post your code for the virtual pages?

The second code Snippet is that actual used one with a Files Section instead of a Files Field like sonja recommended to use.

Here is the Code of the Virtual Pages Child:

<?php

class VoicesPage extends Kirby\Cms\Page
{
    public function children()
    {
        $voices = [];

        foreach (Db::select('voices') as $voice) {
            $voices[] = [
                'slug'     => $voice->slug(),
                'num'      => $voice->status() === 'listed' ? 0 : null,
                'template' => 'voice',
                'model'    => 'voice',
                'content'  => [
                    'title'  => $voice->title() ?? 'New Voice',
                    'shortname'   => $voice->shortname(),
                    'image'   => $voice->image(),
                    'status' => is_null($voice->status()) ? 'draft' : $voice->status()
                ]
            ];
        }

        return Pages::factory($voices, $this);
    }
}

and the code for the Parent

<?php

class VoicePage extends Kirby\Cms\Page
{
    public function changeSlug(string $slug, string $languageCode = null)
    {
        // always sanitize the slug
        $slug = Str::slug($slug);


        $data['slug'] = $slug;

        if ($comment = Db::first('voices', '*', ['slug' => $this->slug()])) {
            if (Db::update('voices', $data, ['slug' => $this->slug()])) {
                return $this;
            };
        } 
        return $this;
    }

    protected function changeStatusToDraft()
    {
        $data['status'] = 'null';

        if ($comment = Db::first('voices', '*', ['slug' => $this->slug()])) {
            return Db::update('voices', $data, ['slug' => $this->slug()]);
        }

        return $this;
    }

    protected function changeStatusToListed(int $position = null)
    {
        // create a sorting number for the page
        $num = $this->createNum($position);

        // don't sort if not necessary
        if ($this->status() === 'listed' && $num === $this->num()) {
            return $this;
        }

        $data['status'] = 'listed';

        if ($comment = Db::first('voices', '*', ['slug' => $this->slug()])) {
            return Db::update('voices', $data, ['slug' => $this->slug()]);
        }

        if ($this->blueprint()->num() === 'default') {
            $this->resortSiblingsAfterListing($num);
        }

        return $this;
    }

    protected function changeStatusToUnlisted()
    {
        if ($this->status() === 'unlisted') {
            return $this;
        }

        $data['status'] = 'unlisted';

        if ($comment = Db::first('voices', '*', ['slug' => $this->slug()])) {
            return Db::update('voices', $data, ['slug' => $this->slug()]);
        }

        $this->resortSiblingsAfterUnlisting();

        return $this;
    }

    public function changeTitle(string $title, string $languageCode = null)
    {
        $data['shortname'] = $title;

        if ($comment = Db::first('voices', '*', ['slug' => $this->slug()])) {
            if (Db::update('voices', $data, ['slug' => $this->slug()])) {
                return $this;
            };
        } 
        return $this;
    }

    public function writeContent(array $data, string $languageCode = null): bool
    {
        $data['shortname'] = $data['title'];
        unset($data['title']);
        
        if ($voice = Db::first('voices', '*', ['slug' => $this->slug()])) {
            return Db::update('voices', $data, ['slug' => $this->slug()]);
        } else {
            $data['slug'] = $this->slug();
            return Db::insert('voices', $data);
        }

    }

    public function delete(bool $force = false): bool
    {
        return Db::delete('voices', ['slug' => $this->slug()]);
    }

    public function isDraft(): bool
    {
        return in_array($this->content()->status(), ['listed', 'unlisted']) === false;
    }

    public function title(): Kirby\Cms\Field
    {
        return $this->shortname()->excerpt(100)->or('New voice');
    }

}

For this to work you definitely need a field.

Maybe it has to be a two step process.

  1. Upload the file via a section.
  2. Once the hook has run, select the renamed file in a files field, so that the value gets stored in the database.

Here you assign an image to a content field

do you got a piece of example code to show me how i could select the renamed file and build the thumbnails that kirby needs to display it in the backend? This drives me crazy.

thanks again

That wouldn’t need any code. You upload via the section, wait for the hook to do the job, then you select the renamed file via a files field. So instead of uploading via the files field which created the issue in the first place.

Hello Pixelijn,

i don’t understand exactly how you like to do this?
Should i add a section and a field? and select it in the field after upload manually?
i’d like to have a programmed solution that the editor only needs to upload the file and nothing more.

btw. i also like to have a multiupload with renaming the files and displaying them in the backend with the new renamed files.

Yes, exactly.

In that case you would have to write the values to the field in your hook ($page->update())

Note that you have to store the result of $file->changeName($filename); in a new variable before you continue working with it, because the object itself is immutable.

$newFile = $file->changeName($filename);
// continue with $newFile, e.g. to get the filename of the new file.

ok, i understand that i need to write the selected image to the field, but how do i get it back later in the section when loading the page again. then it is only saved in the filed and saved to the DB.
and whats with the build thumbnails? they still got the wrong naming and are placed in folders like the virtual page is named, i’d like to avoid that and have all images in one folder. isn’t there a easy solution to just rename the uploaded file and change the path? and keep that information saved to the field?

I don’t understand. The file wouldn’t disappear?

no, the file doesn’t disappear but how it will be displayed after loading when the thumbnails have not been generated with the same filename.

i couldn’t imagine that doing something like renaming a file on upload is such a big deal.
is it maybe more easy to build an own upload field plugin that handles things like renaming and stuff?

You could force Kirby to generate the desired set of thumbnails in your hook