Replace/overwrite image (avatar) on upload

I´m trying to update a user avatar via frontend upload. How can I overwrite the existing profile picture (and convert the uploaded on to .jpg if necessary)?

my controller looks like this:

<?php

return function ($kirby, $page) {

  $alerts  = [];
  $success = '';

  if ($kirby->request()->is('post') === true && get('submit')) {

$uploads = $kirby->request()->files()->get('file');

if (count($uploads) > 1) {
  $alerts['exceedMax'] = 'You may only upload 1 file.';
  return compact('alerts', 'success');
}

$kirby->user();

foreach ($uploads as $upload) {

  try {
    $name = crc32($upload['name'].microtime()). '_' . $upload['name'];
    $file = $kirby->user()->createFile([
      'source'   => $upload['tmp_name'],
      'filename' => 'profile.jpg',
      'template' => 'avatar',
      'overwrite' => true
    
    ]);
    $success = 'Your file upload was successful';
  } catch (Exception $e) {
    $alerts[$upload['name']] = $e->getMessage();
  }
}
  }

  return compact('alerts', 'success');
};

Hm, there is no replace method for the user avatar. The API checks if an avatar already exists. If yes, it is deleted before the new avatar is uploaded. That’s how you can also achieve that.

Here is the API route for reference:

/kirby/config/api/routes/users.php

    [
        'pattern' => 'users/(:any)/avatar',
        'method'  => 'POST',
        'action'  => function (string $id) {
            if ($avatar = $this->user($id)->avatar()) {
                $avatar->delete();
            }

            return $this->upload(function ($source, $filename) use ($id) {
                return $this->user($id)->createFile([
                    'filename' => 'profile.' . F::extension($filename),
                    'template' => 'avatar',
                    'source'   => $source
                ]);
            }, $single = true);
        }
    ],

well, i´m pretty sure this is not very elegant: when i implement it like this i get the error “The extensions for “…” is missing”:

<?php

return function ($kirby, $page) {

  $alerts  = [];
  $success = '';

  if ($kirby->request()->is('post') === true && get('submit')) {

    $uploads = $kirby->request()->files()->get('file');

    // we only want 1 file
    if (count($uploads) > 1) {
      $alerts['exceedMax'] = 'You may only upload 1 file.';
      return compact('alerts', 'success');
    }

    // authenticate as user
    $kirby->user();

    foreach ($uploads as $upload) {

      try {
        $name = crc32($upload['name'].microtime()). '_' . $upload['name'];
        $file = $kirby->user()->createFile([
          'source'   => $upload['tmp_name'],
          'pattern' => 'users/(:any)/avatar',
          'method'  => 'POST',
          'action'  => function (string $id) {
            if ($avatar = $this->user($id)->avatar()) {
                $avatar->delete();
            }

            return $this->upload(function ($source, $filename) use ($id) {
                return $this->user($id)->createFile([
                    'filename' => 'profile.' . F::extension($filename),
                    'template' => 'avatar',
                    'source'   => $source
                ]);
            }, $single = true);
        }
        
        ]);
        $success = 'Your file upload was successful';
      } catch (Exception $e) {
        $alerts[$upload['name']] = $e->getMessage();
      }
    }
  }

  return compact('alerts', 'success');
};

I didn’t mean to say you should use the pattern or a route in your code, that was only as a reference how this is done in the Panel.

Ah, got it. adding only this line already did the job:

if ($avatar = $this->user()->avatar()) {
          $avatar->delete();
      }

Hm, I’m working on the same task at the moment - updating the user avatar from the frontend - and can’t get it to work at the moment even after going over your code.

if ($kirby->request()->is('POST') && get('update')) {

  $avatar = $kirby->request()->files()->get('avatar');

  if (!empty($avatar['name'])) {
    try {
      //DELETE EXISTING AVATAR
      if ($previousAvatar = $this->user()->avatar()) {
        $previousAvatar->delete();
      }
      //CREATE NEW AVATAR
      $name = crc32($avatar['name'].microtime()). '_' . $avatar['name'];
      $file = $kirby->user()->createFile([
        'source'   => $avatar['tmp_name'],
        'filename' => 'profile.'.F::extension($avatar['name']),
        'template' => 'avatar'
      ]);
    } catch (Exception $e) {
      $error = true;
      $message = "Could not update avatar!<br/>";
    }
  }
}

The file is being requested correctly (which I’ve checked by echoing the filename), but I’m still getting my error message.

What do you mean?

Sorry for the vague statement. What I meant is that the problem does not appear to be connected to the actual front end form code, as the corresponding file request in the controller seems to be processed correctly and I can retrieve the file name etc. correctly there.
But I seem to be doing something wrong in the actual controller code (posted above). The previous avatar is being deleted alright, but the upload of the new one fails.

EDIT: Fixed it! There was a permission error on the server I had overlooked. The code above works as intended.

Hi, @Weltverloren, could you maybe share a few more details about how the corresponding form in the template looks like? I couldnt get it work yet :confused:

Hey @9zehn81!

It’s quite a basic form:

<form>
  <label for="avatar">Upload avatar</label>
  <input class="clickable" id="avatar" name="avatar" type="file">

  //Display avatar if available
  <?php $user = $kirby->user();
  if($avatar = $user->avatar()): ?>
    <img src="<?= $avatar->url() ?>"/>
  <?php endif ?>

  <input class="button clickable" type="submit" name="update" value="Submit changes">
</form>

Thank you for your quick reply! I tried something like this before, but i could get it work. Even with exactly your controller and the basic form here… No Avatar is uploaded into the userprofile. But there is no error message either.

Try changing the form like this:

<?php if ($user = $kirby->user()) : ?>
<form method="POST" enctype="multipart/form-data">
  <label for="avatar">Upload avatar</label>
  <input class="clickable" id="avatar" name="avatar" type="file">

 
  <?php if($avatar = $user->avatar()): ?>
    <img src="<?= $avatar->url() ?>"/>
  <?php endif ?>

  <input class="button clickable" type="submit" name="update" value="Submit changes">
</form>
<?php endif ?>

Controller:

<?php

return function($kirby)
{
  if ($kirby->request()->is('POST') && get('update')) {

    $avatar = $kirby->request()->files()->get('avatar');
  
    if (!empty($avatar['name'])) {
      try {
        //DELETE EXISTING AVATAR
        if ($previousAvatar = $kirby->user()->avatar()) {
          $previousAvatar->delete();
        }
        //CREATE NEW AVATAR
        $name = crc32($avatar['name'].microtime()). '_' . $avatar['name'];
        $file = $kirby->user()->createFile([
          'source'   => $avatar['tmp_name'],
          'filename' => 'profile.'.F::extension($avatar['name']),
          'template' => 'avatar'
        ]);
      } catch (Exception $e) {
        $error = true;
        $message = "Could not update avatar!<br/>";
      }
    }
  }

  return [
    'error' => $error ?? null,
    'message' => $message ?? null,

  ];
};

You would still have to do something with possible errors in the template

1 Like

aaah, thank you so much Sonja!

I forgot the

enctype="multipart/form-data"

as i did a few times before. Sorry and thanks again :blush:

I’m bringing this old thread back to the top. Currently, on a website I maintain, I plan to allow registered users to update their profile picture from the frontend.

In the solution shown here, I am missing a validation of the uploaded files (e.g. only .JPG). Where would I include such a check here.

Or is this solution not up to date and I should better adapt this cookbook recipe

for profile images?

Any advice, which route to go?

It’s here: Uploading files from frontend | Kirby CMS