Email form with attachments

Im trying to make a contact form with attachments.
Im following the great tutorial in the cookbook: Email with attachments | Kirby CMS

But I dont want jobs or something, I only want a contact page where I can upload an attachment and send it to a configured mail address.

So here is what I did / changed:

/site/controllers/kontakt.php

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

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

        // initialize variables
        $alerts      = null;
        $attachments = [];

        // 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'),
            'email'     => get('email'),
            'message'   => get('message')
        ];

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

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

        // some of the data is invalid
        if ($invalid = invalid($data, $rules, $messages)) {
            $alerts = $invalid;
        }
        
        // get the uploads
        $uploads = $kirby->request()->files()->get('file');

        // we want no more than 3 files
        if (count($uploads) > 3) {
            $alerts[] = 'You may only upload up to 3 files.';
        }

        // loop through uploads and check if they are valid
        foreach ($uploads as $upload) {
            // make sure the user uploads at least one file
            if ($upload['error'] === 4) {
                $alerts[] = 'You have to attach at least one file';
            //  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'] > 2000000)  {
                $alerts[] = $upload['name'] . ' is larger than 2 MB';
            // …and the file is a PDF
            } elseif ($upload['type'] !== 'application/pdf') {
                $alerts[] = $upload['name'] . ' is not a PDF';
            // all valid, try to rename the temporary file
            } else {
                $name     = $upload['tmp_name'];
                $tmpName  = pathinfo($name);
                // sanitize the original filename
                $filename = $tmpName['dirname']. '/'. F::safeName($upload['name']);
        
                if (rename($upload['tmp_name'], $filename)) {
                    $name = $filename;
                }
                // add the files to the attachments array
                $attachments[] = $name;
            }  
        }
        
        // the data is fine, let's send the email with attachments
        if (empty($alerts)) {
            try {
                $kirby->email([
                    'template' => 'email',
                    'from'     => 'sprachenservice-ortwein@swelp.de',
                    'replyTo'  => $data['email'],
                    'to'       => 'thomas.oppolzer@swelp.de',
                    'subject'     => esc($data['name']) . ' hat ihnen eine Nachricht gesendet.',
                    'data'        => [
                        'message'   => esc($data['message']),
                        'name'      => esc($data['name']),
                    ],
                    'attachments' => $attachments
                ]);
            } 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) {
                // store reference and name in the session for use on the success page
                $kirby->session()->set([
                    'name'      => esc($data['name'])
                ]);
                // redirect to the success page
                go('success');
            }
        }
    }

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

there I deleted reference, because I dont need a reference for that.

after that I modified kontakt.php in site/templates/kontakt.php

<?php snippet('header') ?>
<?= css('assets/css/templates/kontakt.css') ?>
    <main class="main">
        <h1 class="note-header h1"><?= $page->title() ?></h1>
        <?php
      // if the form input does not validate, show a list of alerts
      if($alerts): ?>
      <div class="alert">
        <ul>
          <?php foreach($alerts as $message): ?>
          <li><?= kirbytext($message) ?></li>
          <?php endforeach ?>
        </ul>
      </div>
      <?php endif ?>
      <?php snippet('kontakt-snippet', compact('data')); ?> 
    </div>
    </main>

<?php snippet('footer') ?>

and here the kontakt-snippet.php

<form method="post" action="<?= $page->url() ?>">
            <div class="honeypot">
                <label for="website">Website <abbr title="required">*</abbr></label>
                <input type="url" id="website" name="website" tabindex="-1">
            </div>
            <div class="field">
                <label for="name">
                    Name <abbr title="required">*</abbr>
                </label>
                <input type="text" id="name" name="name" value="<?= esc($data['name'] ?? '', 'attr') ?>" required>
                <?= isset($alert['name']) ? '<span class="alert error">' . esc($alert['name']) . '</span>' : '' ?>
            </div>
            <div class="field">
                <label for="email">
                    Email <abbr title="required">*</abbr>
                </label>
                <input type="email" id="email" name="email" value="<?= esc($data['email'] ?? '', 'attr') ?>" required>
                <?= isset($alert['email']) ? '<span class="alert error">' . esc($alert['email']) . '</span>' : '' ?>
            </div>
            <div id="inputtext" class="field">
                <label for="text">
                    Text <abbr title="required">*</abbr>
                </label>
                <textarea id="text" name="text" required>
                    <?= esc($data['text'] ?? '') ?>
                </textarea>
                <?= isset($alert['text']) ? '<span class="alert error">' . esc($alert['text']) . '</span>' : '' ?>
            </div>
            			
			<div>
				<label> </label>
				<input name="file[]" type="file" multiple required>
			</div>
			
			<div>
				<label> </label>
				<label id="filelabel" for="file">Dateien anhängen
				<span class="help">Max. 3 Dateien (max. Dateigröße 2MB pro Datei)</span>
				</label>
			</div>
			
			<div class="field">
                <label for="submit"> 
                </label>
                <input id="submit" type="submit" name="submit" value="Submit">
            </div>
			
        </form>

Somewhere im missing something which connects the uploaded file in the kontakt-snippet and the controller uploads array.

I tried the original jobkit, there everything is working fine.
for testing I deleted the plugin/application/index.php
and it still works fine.

in my modified email form I dont have a plugin.
then I tried it with plugin but it still errors at the same line.

TypeError

count(): Argument #1 ($value) must be of type Countable|array, null given

when there are not uploads, count() will fail, so try

count($uploads ?? []);

yes, the $uploads array seems to be empty but I cant see why.

POST Data

website
name Hans Test
email hanstest@test.test
text Hallo, wie geht es dir?
file Array ( [0] => test.pdf )
submit Submit

Files empty

Cookies

You have removed an important attribute from your form!

<form class="application-form" method="post" action="<?= $page->url() ?>" enctype="multipart/form-data">

Please read up about it here: HTMLFormElement: enctype property - Web APIs | MDN

I totally overseen this.
I built the mail form without attachments first and adapted it. I forgot to apply this attribute.
its working now.
thank you very much