Creating page from frontend + file upload: error.file.mime.missing

Hi,

I’m trying to combine two cookbook recipes:

Everything works fine so far – the only thing is that it throws "error.file.mime.missing" alert upon submission, even though the page is created and files are uploaded as its children successfully.
“The media type for “img-1-0.45521000-1617410187” cannot be detected”

Is there anything I’m missing? var_dump($upload[$i]) also shows me ["type"]=> string(10) "image/jpeg"

Thanks!

<?php
return function ($kirby, $page) {
  $alerts  = [];
  $success = '';
  if ($kirby->request()->is('post') === true && get('submit')) {

    // check the honeypot
    if (empty(get('website')) === false) {
      go($page->url());
      exit();
    }
    $data = [
      'firstname'     => get('firstname'),
      'lastname'      => get('lastname'),
      'name'          => get('firstname') . get('lastname'),
      'alphabetize'   => get('lastname'),
      'featured'      => false,
      'netid'         => get('email'),
      'title'         => get('title'),
      'text'          => get('text'),
      'timestamp'     => time(),
    ];
    
    $upload[0] = $kirby->request()->files()->get('file_1');
    $upload[1] = $kirby->request()->files()->get('file_2');
    $upload[2] = $kirby->request()->files()->get('file_3');
    $upload[3] = $kirby->request()->files()->get('file_4');
    $upload[4] = $kirby->request()->files()->get('file_5');

    $rules = [
    ];

    $messages = [
    ];

    // some of the data is invalid
    if ($invalid = invalid($data, $rules, $messages)) {
      $alert = $invalid;
    } 
    else {
      // authenticate as almighty
      $kirby->impersonate('kirby');
      
      try {
        // create page first
        $submission = $page->createChild([
          'title'    => $data['title'],
          'slug'     => str::slug($data['name']),
          'template' => 'project',
          'content'  => $data
        ]);
        $submission->changeStatus('listed');
        // handle uploads
        for ($i = 0; $i < count($upload); $i++) {
          $name = str::slug($data['name']).'--'. $i .'--'. microtime() .'--'. $upload[$i]['name'];
          $file = page($submission->id())->createFile([
            'source'   => $upload[$i]['tmp_name'],
            'filename' => $name,
          ]);
        }
        $success = 'Your file upload was successful';
      } 
      catch (Exception $e) {
        $alerts[$upload['name']] = $e->getMessage();
      }
    }
  }
  return compact('data', 'alerts', 'success');
};

Is the error thrown for all files you try to upload? Are all 5 file inputs required/filled?

Could you post the form as well please?

Hi! the form is below. And it seems like the error is only thrown for the last one in the array…?

snippets/upload-form.php

<form action="" method="post" enctype="multipart/form-data">

<div class="form-element half">
  <label for="firstname">First name <span class="asterisk">*</span></label>
  <input type="text" id="firstname" name="firstname" value="<?= $data['firstname'] ?? null ?>" required/>
</div>
<div class="form-element half">
  <label for="lastname">Last name <span class="asterisk">*</span></label>
  <input type="text" id="lastname" name="lastname" value="<?= $data['lastname'] ?? null ?>" required/>
</div>
<div class="form-element half">
  <label for="email">Email <span class="asterisk">*</span></label>
  <input class="sm" type="text" name="email" id="email" value="<?= $data['netid'] ?? null ?>" placeholder="netid" required/>
</div>
<div class="form-element">
  <label for="title">Project title <span class="asterisk">*</span><br></label>
  <input type="text" id="title" name="title" value="<?= $data['title'] ?? null ?>" required/>
</div>
<div class="form-element">
  <label for="text">Project description <span class="asterisk">*</span></label>
  <textarea name="text" id="text" placeholder="250 words..." required><?= $data['text'] ?? null ?></textarea>
</div>
<div class="form-element">
  <label for="file">Project Image 1 <span class="asterisk">*</span> — Hero</label>
  <img class="preview p-1" id="preview_1" src=""/>
  <input name="file_1" type="file" onchange="readURL(this,'#preview_1');" required/>
</div>
<div class="form-element">
  <label for="file">Project Image 2 <span class="asterisk">*</span></label>
  <img class="preview p-1" id="preview_2" src=""/>
  <input name="file_2" type="file" onchange="readURL(this,'#preview_2');" />
</div>
<div class="form-element">
  <label for="file">Project Image 3 <span class="asterisk">*</span></label>
  <img class="preview p-1" id="preview_3" src=""/>
  <input name="file_3" type="file" onchange="readURL(this,'#preview_3');" />
</div>
<div class="form-element">
  <label for="file">Project Image 4 <span class="asterisk">*</span></label>
  <img class="preview p-1" id="preview_4" src=""/>
  <input name="file_4" type="file" onchange="readURL(this,'#preview_4');" />
</div>
<div class="form-element">
  <label for="file">Project Image 5 <span class="asterisk">*</span></label>
  <img class="preview p-1" id="preview_5" src=""/>
  <input name="file_5" type="file" onchange="readURL(this,'#preview_5');" />
</div>
<div class="honey">
   <label for="message">If you are a human, leave this field empty</label>
   <input type="website" name="website" id="website" value="<?= isset($data['website']) ? esc($data['website']) : null ?>"/>
</div>
<div class="form-element p-2">
  <input class="button p-2" type="submit" name="submit" value="Submit" />
</div>
</form>

templates/upload.php

<?php snippet('header') ?>
<?php if ($success): ?>
  <div class="alert success">
    <p><?= $success ?></p>
  </div>
<?php else: ?>
  <?php if (empty($alerts) === false): ?>
    <ul>
      <?php foreach ($alerts as $alert): ?>
        <li><?= $alert ?></li>
      <?php endforeach ?>
    </ul>
  <?php endif ?>

  <article>
    <h1 class="p-2 t-l"><?= $page->title() ?></h1>
    <!-- <?= kirbytext($page->text()) ?> -->
    <?php snippet('upload-form', compact('data')); ?>
  </article>
  
<?php endif ?>

<?php snippet('footer') ?>

There are a few issues:

  • $data is not defined at page load, you would either have to initiate this variable at the top or use a default in the return array (doesn’t work with compact()):

  • try/catch

catch (Exception $e) {
        $alerts[$upload['name']] = $e->getMessage();
      }

This refers to the complete try/catch, so $upload['name'] is undefined. You need to wrap your uploads in a try/catch block as well

       // handle uploads
        for ($i = 0; $i < count($upload); $i++) {
          try {
            $name = Str::slug($data['name']).'--'. $i .'--'. microtime() .'--'. $upload[$i]['name'];
            $file = page($submission->id())->createFile([
              'source'   => $upload[$i]['tmp_name'],
              'filename' => $name,
            ]);

          } catch(Exception $e) {
            $alerts[$upload[$i]['name']] = $e->getMessage();
          }


        }
  • $upload should also be initialized at the top of the file

  • I guess you will still add error handling for missing fields/files?

Thank you! It soled the issue beautifully. One more thing if I may ask:

I am now getting into building validation rules, and wonder what’d be the best way to validate minimum and maximum image dimensions? Right now invalid() only looks at $data. I guess I can put another conditional but wondering if there’d be a better solution…

Thank you so much! I’m putting my controller as it stands below:

<?php
return function ($kirby, $page) {
  $alerts  = [];
  $success = '';
  $data = [];
  $upload = [];
  
  if ($kirby->request()->is('post') === true && get('submit')) {

    // check the honeypot
    if (empty(get('website')) === false) {
      go($page->url());
      exit();
    }
    $data = [
      'firstname'     => get('firstname'),
      'lastname'      => get('lastname'),
      'name'          => get('firstname') .' '. get('lastname'),
      'alphabetize'   => get('lastname'),
      'n-number'      => get('n-number'), // add N00
      'n-number-full' => 'N00'.get('n-number'),
      'portfoliourl'  => get('portfoliourl'),
      'title'         => get('title'),
      'text'          => get('text'),
      'projecturl'    => get('projecturl'),
      'timestamp'     => time(),
      'topics'        => '',
      'disciplines'   => '',
      'questions'     => '',
    ];
    
    $upload[0] = $kirby->request()->files()->get('file_1');
    $upload[1] = $kirby->request()->files()->get('file_2');
    $upload[2] = $kirby->request()->files()->get('file_3');
    $upload[3] = $kirby->request()->files()->get('file_4');
    $upload[4] = $kirby->request()->files()->get('file_5');

    
    // TO DO: images to be Min width 5000, Min height 2500, max 10mb

    $rules = [
      'firstname'  => ['alpha'],
      'lastname'  => ['alpha'],
      'n-number'  => ['num'],
      'title'  => ['lessThan140Chars'],
      'text'  => ['lessThan250Words'],
    ];

    $messages = [
      'firstname'  => 'Please enter valid First Name',
      'lastname'  => 'Please enter valid Last Name',
      'n-number'  => 'Please enter valid N#',
      'title'  => 'Project Title needs to be shorter than 140 characters.',
      'text'  => 'Project Description needs to be shorter than 250 words.',
    ];

    // some of the data is invalid
    if ($invalid = invalid($data, $rules, $messages)) {
      $alerts = $invalid;
    } 
    else {
      // authenticate as almighty
      $kirby->impersonate('kirby');
      
      try {
        // create page first
        $submission = $page->createChild([
          'title'    => $data['title'],
          'slug'     => str::slug($data['name']),
          'template' => 'project',
          'content'  => $data
        ]);
        $submission->changeStatus('listed');
        // handle uploads
        for ($i = 0; $i < count($upload); $i++) {
          try {
            $name = str::slug($data['name']).'_'. $i .'_'. microtime() .'_'. $upload[$i]['name'];
            $file = page($submission->id())->createFile([
              'source'   => $upload[$i]['tmp_name'],
              'filename' => $name,
              'mime'     => $upload[$i]['type'],
            ]);
          }
          catch(Exception $e) {
            $alerts[$upload[$i]['name']] = $e->getMessage();
          }
        }
        $success = 'Thank you, '.$data['name'].'.<br>Your submission was successfully recorded.';
      } 
      catch (Exception $e) {
        $alerts[$data['name']] = $e->getMessage();
      }
    }
  }
  return compact('data', 'alerts', 'success');
};

You could use a file blueprints with an accept rule like in the recipe

Hi there.
I am also trying to accomplish a file upload from frontend and i followed all your introductions in the Uploading files from frontend Tutorial but every time i’m trying to use the createFile() method i get the following mime-Type error:

Argument 1 passed to Kirby\Toolkit\Mime::matches() must be of the type string, null given, called in D:\Projekte\site\kirby\src\Image\Image.php on line 214

I cannot figure out, what im doing wrong. Using PHP Version 7.4.1, MAMP as local server on Win 10.

I already dumped all the information about the uploading file:
name: test.jpg
type: image/jpeg
tmp_name: C:\Users\laptop\AppData\Local\Temp\phpB6D2.tmp
error: 0
size: 4356

Can’t find an issue there, so a advice would be nice.

Hm, what is your exact Kirby version?

Have you followed the recipe exactly as described or have you made any changes?

Is the files you are trying to upload a valid image? Somehow looks as if its mime type is not recognized. Have you tried with different files?