Contact form with file upload (combing two Cookbooks)

I’m trying to combine two Cookbooks. Having started with Basic Contact Form I’m now trying to add a file upload from Uploading file from the frontend.

The form submits with no errors, however it doesn’t seem to be uploading the file. My controller looks like this:

<?php
return function($kirby, $pages, $page) {

$alert = null;

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

    // check the honeypot
    if(empty(get('website')) === false) {
        go($page->url());
        exit;
    }
    $uploads = $kirby->request()->files()->get('file');

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

    // authenticate as almighty
    $kirby->impersonate('kirby');

    foreach ($uploads as $upload) {

      // check for duplicate
      $files      = page('storage')->files();
      $duplicates = $files->filter(function ($file) use ($upload) {
        // get original safename without prefix
        $pos              = strpos($file->filename(), '_');
        $originalSafename = substr($file->filename(), $pos + 1);

        return $originalSafename === F::safeName($upload['name']) &&
                $file->mime() === $upload['type'] &&
                $file->size() === $upload['size'];
      });

      if ($duplicates->count() > 0) {
        $alerts[$upload['name']] = "The file already exists";
        continue;
      }

      try {
        $name = crc32($upload['name'].microtime()). '_' . $upload['name'];
        $file = page('storage')->createFile([
          'source'   => $upload['tmp_name'],
          'filename' => $name,
          'template' => 'upload',
          'content' => [
              'date' => date('Y-m-d h:m')
          ]
        ]);
        $success = 'Your file upload was successful';
      } catch (Exception $e) {
        $alerts[$upload['name']] = $e->getMessage();
      }
    }

    // Form Data
    $data = [
        'name'  => get('name'),
        'firma' => get('firma'),
        'telefon' => get('telefon'),
        'email' => get('email'),
        'baugruppe' => get('baugruppe'),
        'nachricht'  => get('nachricht'),
        'dienstleistungen'  => get('dienstleistungen'),
        'leiterplatten'  => get('leiterplatten'),
        'bauteile'  => get('bauteile'),
        'mechanische'  => get('mechanische'),
        'baugruppentyp'  => get('baugruppentyp')
    ];

    $rules = [
        'name'  => ['required', 'min' => 3],
        'email' => ['required', 'email'],
        'nachricht'  => ['required', 'min' => 3, 'max' => 3000],
    ];

    $messages = [
        'name'  => 'Please enter a valid name',
        'email' => 'Please enter a valid email address',
        'nachricht'  => 'Please enter a text between 3 and 3000 characters'
    ];

    // some of the data is invalid
    if($invalid = invalid($data, $rules, $messages)) {
        $alert = $invalid;

        // the data is fine, let's send the email
    } else {
        try {
            $kirby->email([
                'template' => 'email',
                'from'     => 'form@kaempfe-elektronik.de',
                'replyTo'  => $data['email'],
                'to'       => 'hello@jamiehunter.design',
                'subject'  => esc($data['name']) . ' sent you a message from your contact form',
                'data'     => [
                    'name' => esc($data['name']),
                    'firma' => esc($data['firma']),
                    'telefon' => esc($data['telefon']),
                    'email' => esc($data['email']),
                    'baugruppe' => esc($data['baugruppe']),
                    'dienstleistungen' => $data['dienstleistungen'],
                    'leiterplatten' => $data['leiterplatten'],
                    'bauteile' => $data['bauteile'],
                    'mechanische' => $data['mechanische'],
                    'baugruppentyp' => $data['baugruppentyp'],
                    'nachricht' => esc($data['nachricht'])
                ]
            ]);

        } catch (Exception $error) {
            $alert['error'] = "The form could not be sent";
        }

        // no exception occured, let's send a success message
        if (empty($alert) === true) {
            $success = 'Ihre Nachricht wurde gesendet, vielen Dank. Wir werden uns bald bei Ihnen melden!';
            $data = [];
        }
    }
}

return [
    'alert'   => $alert,
    'data'    => $data ?? false,
    'success' => $success ?? false
];
};

I have a separate ‘storage’ page set up. Can anyone see where I’m going wrong?

Could you please post the complete thingy, controller and template or ideally provide the project for testing?

Hey -
I think the best would be to break the code to its core down – without any permissions or checking for duplicates.

<?php

return function ($kirby, $page, $site) {
    if ($kirby->request()->is('post') === true && get('submit')) {

      $user = $kirby->user(get('email'));

      $data = [
        'title'                 => get('title'),
        'author'                => $user->id()
      ];

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

      else {

        $kirby->impersonate('kirby');

        try {
          $registration = $site->find('archive')->createChild([
            'slug'     => $data['title'],
            'template' => 'template',
            'draft'    => false,
            'content'  => $data
          ]);

          foreach ($uploads as $upload) {
            try {
              $name = $upload['name'];

              //change this if you want to set the file destination
              $file = $registration->createFile([
                'source'   => $upload['tmp_name'],
                'filename' => $name,
                'template' => 'upload',
                'content' => [
                  'date' => date('Y-m-d h:m')
                ]
              ]);

              $fileArray = A::wrap($file->filename());

              $registration = $registration->update([
                'preview' => Data::encode($fileArray, 'yaml')
              ]);

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

          if ($registration) {
            $kirby->session()->set([
              'referer' => $page->uri(),
              'regName'  => esc($data['family_name'])
            ]);
            go('success');
          }
        }
        catch (Exception $e) {
          $alert = ['Your upload failed: ' . $e->getMessage()];
        }
      }
    }

    return [
        'alert' => $alert ?? null,
        'data'  => $data ?? false,
    ];


};

I think this stores the files in the page itself. But feel free to use this and tweak it to your use.

I spent some time with this and managed to get the form working how I want (an optional single-file upload). Here’s my controller that deals with this:

<?php
return function($kirby, $pages, $page) {

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

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

        // check the honeypot
        if(empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        // Form Data
        $data = [
            'name'  => get('name'),
            'firma' => get('firma'),
            'telefon' => get('telefon'),
            'email' => get('email'),
            'nachricht'  => get('nachricht'),
            'file' => get('file')
        ];

        $rules = [
            'name'  => ['required', 'min' => 3],
            'email' => ['required', 'email'],
            'nachricht'  => ['required', 'min' => 3, 'max' => 3000],
        ];

        $messages = [
            'name'  => 'Please enter a valid name',
            'email' => 'Please enter a valid email address',
            'nachricht'  => 'Please enter a text between 3 and 3000 characters'
        ];

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

        // some of the data is invalid
        if($invalid = invalid($data, $rules, $messages)) {
            $alerts = $invalid;

            // the data is fine, let's send the email
        } else {
            try {
                $kirby->email([
                    'template' => 'email',
                    'from'     => 'form@kaempfe-elektronik.de',
                    'replyTo'  => $data['email'],
                    'to'       => esc($page->kontakFormularEmail()),
                    'subject'  => esc($data['name']) . ' hat Ihnen über Ihr Kontaktformular eine Nachricht geschickt',
                    'data'     => [
                        'name' => esc($data['name']),
                        'firma' => esc($data['firma']),
                        'telefon' => esc($data['telefon']),
                        'email' => esc($data['email']),
                        'nachricht' => esc($data['nachricht']),
                        'filename' => $filename,
                        'file' => $fileURL
                    ]
                ]);
                if ($upload['size'] == 0){
                  $filename = null;
                  $fileURL = null;
                } else {
                  // authenticate as almighty
                  $kirby->impersonate('kirby');

                  try {
                    $filename = esc($data['name']) . '_' . esc($data['firma']) . '_' . $upload['name'];
                    $file = page('storage')->createFile([
                      'source'   => $upload['tmp_name'],
                      'filename' => $filename,
                      'template' => 'upload',
                      'content' => [
                        'date' => date('Y-m-d h:m')
                      ]
                    ]);
                    $fileURL = $file->url();
                  } catch (Exception $e) {
                    $alerts[$upload['name']] = $e->getMessage();
                  }
                }

            } catch (Exception $error) {
                $alerts['error'] = "The form could not be sent";
            }

            // no exception occured, let's send a success message
            if (empty($alerts) === true) {
                $success = 'Ihre Nachricht wurde gesendet, vielen Dank. Wir werden uns bald bei Ihnen melden!';
                $data = [];
            }
        }
    }

    return [
        'alerts'   => $alerts,
        'data'    => $data ?? false,
        'success' => $success ?? false
    ];
};

This works great, however seems to struggle with larger files. My client would like a 20mb limit for files, but anything larger than 3mb-ish gets uploaded by the browser, however the form is not sent and the page simply refreshes returning no errors. I have a blueprint setup that has a 20mb limit set up and a php.ini that sets max-file-upload to 20mb, which is why I think an error isn’t getting returned. It seems the controller simply times-out before the file is uploaded. Does this seem plausible?

Ideally I’d like my code to upload any files first (if applicable, as it’s an optional field) and then get the data from the form inputs.

Where are these two variables defined? The code doesn’t seem to be complete… Also, I don’t understand why you try to send the email before you upload the file if you need this data in your email.

Sorry, that code was incorrect (tried piecing it together instead of posting he whole thing). The ‘try’ looks like this:

try {
              if ($upload['size'] == 0){
                $filename = null;
                $fileURL = null;
              } else {
                // authenticate as almighty
                $kirby->impersonate('kirby');

                try {
                  $filename = esc($data['name']) . '_' . esc($data['firma']) . '_' . $upload['name'];
                  $file = page('storage')->createFile([
                    'source'   => $upload['tmp_name'],
                    'filename' => $filename,
                    'template' => 'upload',
                    'content' => [
                      'date' => date('Y-m-d h:m')
                    ]
                  ]);
                  $fileURL = $file->url();
                } catch (Exception $e) {
                  $alerts[$upload['name']] = $e->getMessage();
                }
              }
              $kirby->email([
                  'template' => 'email',
                  'from'     => 'form@kaempfe-elektronik.de',
                  'replyTo'  => $data['email'],
                  'to'       => esc($page->kontakFormularEmail()),
                  'subject'  => esc($data['name']) . ' hat Ihnen über Ihr Kontaktformular eine Nachricht geschickt',
                  'data'     => [
                      'name' => esc($data['name']),
                      'firma' => esc($data['firma']),
                      'telefon' => esc($data['telefon']),
                      'email' => esc($data['email']),
                      'baugruppe' => esc($data['baugruppe']),
                      'dienstleistungen' => $data['dienstleistungen'],
                      'leiterplatten' => $data['leiterplatten'],
                      'bauteile' => $data['bauteile'],
                      'mechanische' => $data['mechanische'],
                      'baugruppentyp' => $data['baugruppentyp'],
                      'nachricht' => esc($data['nachricht']),
                      'filename' => $filename,
                      'file' => $fileURL
                  ]
              ]);

            } catch (Exception $error) {
                $alerts['error'] = "The form could not be sent";
            }

In my ‘email.html.php’ is have this to show if a file was uploaded and include a link to it:

<?php if (is_null($filename) === false): ?>
<p><b>Datei</b><br><a href="<?= $file ?>"><?= $filename ?></a></b><br></p>
<? endif ?>

I managed to work this out using another cookbook (sending email attachment from form). Here’s my working controller, if anyone needs an optional file upload for a form:

<?php
return function($kirby, $page) {

    // initialize variables
    $alerts      = null;
    $success     = '';

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

        // check the honeypot
        if (empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        // get the data and validate the other form fields
        $data = [
          'name'  => get('name'),
          'firma' => get('firma'),
          'telefon' => get('telefon'),
          'email' => get('email'),
          'nachricht'  => get('nachricht'),
        ];

        $rules = [
          'name'  => ['required', 'min' => 3],
          'email' => ['required', 'email'],
          'nachricht'  => ['required', 'min' => 3, 'max' => 3000],
        ];

        $messages = [
          'name'  => 'Please enter a valid name',
          'email' => 'Please enter a valid email address',
          'nachricht'  => 'Please enter a text between 3 and 3000 characters'
        ];

        // some of the data is invalid
        if ($invalid = invalid($data, $rules, $messages)) {
            $alerts = $invalid;
        }

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

        // loop through uploads and check if they are valid
        // make sure the user uploads at least one file
        if ($upload['error'] === 4) {
          $filename = null;
          $fileURL = null;
        //  make sure there are no other errors
        } elseif ($upload['error'] !== 0) {
            $alerts[] = 'The file could not be uploaded';
        // make sure the file is not larger than 2MB…
      } elseif ($upload['size'] > 20000000)  {
            $alerts[] = $upload['name'] . ' is larger than 20 MB';
        // …and the file is a PDF
        } else {
          $kirby->impersonate('kirby');
          $filename = esc($data['name']) . '_' . esc($data['firma']) . '_' . $upload['name'];
          $file = page('storage')->createFile([
            'source'   => $upload['tmp_name'],
            'filename' => $filename,
            'template' => 'upload',
            'content' => [
              'date' => date('Y-m-d h:m')
            ]
          ]);
          $fileURL = $file->url();
        }


        // the data is fine, let's send the email with attachments
        if (empty($alerts)) {
            try {
                $kirby->email([
                  'template' => 'email',
                  'from'     => 'form@kaempfe-elektronik.de',
                  'replyTo'  => $data['email'],
                  'to'       => esc($page->kontakFormularEmail()),
                  'subject'  => esc($data['name']) . ' sent you a message from your contact form',
                  'data'     => [
                      'name' => esc($data['name']),
                      'firma' => esc($data['firma']),
                      'telefon' => esc($data['telefon']),
                      'email' => esc($data['email']),
                      'nachricht' => esc($data['nachricht']),
                      'filename' => $filename,
                      'file' => $fileURL
                  ],
                ]);
            } catch (Exception $error) {
                // we only display a general error message, for debugging use `$error->getMessage()`
                $alerts[] = "The email could not be sent";
            }

            // no exception occurred, let's send a success message
            if (empty($alerts) === true) {
              $success = 'Ihre Nachricht wurde gesendet, vielen Dank. Wir werden uns bald bei Ihnen melden!';
              $data = [];
            }
        }
    }

    // return data to template
    return [
      'alerts'   => $alerts,
      'data'    => $data ?? false,
      'success' => $success ?? false
    ];
};