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