How to send files as attachments via email with Uniform?

I have already created a working form with Uniform. Now my client would like to send files with this form as well.

The form controller is structured as below:

<?php

use Uniform\Form;

return function ($kirby) {
   $form = new Form([
  'bewerbungals' => [
	      'rules' => ['required'],
     'message' => 'Geben Sie die Stelle ein, für die Sie sich bewerben',
  ],
  'name' => [
	      'rules' => ['required'],
     'message' => 'Geben Sie Ihren Namen ein',
  ],
  'strasse' => [
	      'rules' => ['required'],
     'message' => 'Geben Sie Ihre Strasse und Hausnummer ein',
  ],
  'plzort' => [
	      'rules' => ['required'],
     'message' => 'Geben Sie Ihren Wohnort und Ihre Postleitzahl ein',
  ],
  
  'email' => [
     'rules' => ['required', 'email'],
     'message' => 'Geben Sie Ihre Emailadresse ein',
  ],
  
  'filefield' => [
        'rules' => [
            'mime' => ['application/pdf'],
            'filesize' => 5000,
        ],
        'message' => [
            'Bitte senden Sie ausschliesslich PDFs.',
            'Die gesendeten Dateien müssen kleiner als 5MB sein.',
        ],
    ],
  
  'datenschutz' => [
      'rules' => ['required'],
      'message' => 'Markieren Sie die Checkbox für die Datenschutzerklärung',
  ],
    
   ]);
   
  
$attachments = [];

// Which command should be used here?
$attachments[] = ???['filefield'];


   if ($kirby->request()->is('POST')) {
  $form->emailAction([
     'to' => 'misterx@t-online.de',
     'from' => 'kontakt@misterx.de',
     'subject' => 'Neue Bewerbung von {{email}}',
     'attachments' => $attachments,
  ]);
   }

   return compact('form');
};

How can I convert the field ‘filefield’, which contains the files selected in the form, into an array?

Do I need another controller to do that?

The uniform docs says the email action accepts the same options as Kirby Email engine - https://getkirby.com/docs/guide/emails#sending-email-with-attachments

I think you probably need to use the upload action first and then chain the email action on to it so that it picks up the uploaded file.https://kirby-uniform.readthedocs.io/en/latest/actions/upload/

You dont need that array, you just need to pick up the uploaded files in the way described above.

Like this? What do you set in for ‘attachments’ in ‘emailAction’?

  if ($kirby->request()->is('POST')) {
  $form->uploadAction(['fields' => [
            'filefield' => [
                'target' => kirby()->roots()->content(),
                'prefix' => false,
            ],
        ]]);
  $form->emailAction([
     'to' => 'misterx@t-online.de',
     'from' => 'kontakt@misterx.de',
     'subject' => 'Neue Bewerbung von {{email}}',
     'attachments' => ???,
  ]);
   }

Check out this example: https://getkirby.com/docs/cookbook/forms/email-with-attachments

When I use the code below, in which code from the page “Emails with attachments” is integrated, I get the following error message:

Whoops \ Exception \ ErrorException (E_WARNING)

Invalid argument supplied for foreach()

for the line

foreach ($uploads as $upload) {

Here the complete controller code:

<?php

use Uniform\Form;

return function ($kirby) {
   $form = new Form([
      'bewerbungals' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie die Stelle ein, für die Sie sich bewerben',
      ],
      'name' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihren Namen ein',
      ],
      'strasse' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihre Strasse und Hausnummer ein',
      ],
      'plzort' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihren Wohnort und Ihre Postleitzahl ein',
      ],
      'telefon' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihre Telefonnummer ein',
      ],
      'fax' => [],
      
      'email' => [
         'rules' => ['required', 'email'],
         'message' => 'Geben Sie Ihre Emailadresse ein',
      ],

      'filefield' => [
            'rules' => [
                'mime' => ['application/pdf'],
                'filesize' => 5000,
            ],
            'message' => [
                'Bitte senden Sie ausschliesslich PDFs.',
                'Die gesendeten Dateien müssen kleiner als 5MB sein.',
            ],
        ],
      
      'datenschutz' => [
          'rules' => ['required'],
          'message' => 'Markieren Sie die Checkbox für die Datenschutzerklärung',
      ],
        
   ]);
   
  
 // get the uploads
        $uploads = $kirby->request()->files()->get('filefield');
	//'filefield' is the name of the file input (multiple)


 // loop through uploads and check if they are valid
        foreach ($uploads as $upload) {

		// if no file is uploaded (its not required)
            if ($upload['error'] === 4) {

                	// do nothing
          
            } 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;
            }  
        }


   if ($kirby->request()->is('POST')) {
      $form->emailAction([
         'to' => 'mystery@t-online.de',
         'from' => 'kontakt@mystery.de',
         'subject' => 'Neue Bewerbung von {{email}}',
         'attachments' => $attachments,
      ]);
   }

   return compact('form');
};

Does Uniform process the variable ‘$uploads’ differently? What am I doing wrong here?

What does $upload return if you dump it?

How can you do that?

In your controller,

dump($uploads);

I’ve tried that. Where do you see the output?

Should appear in the page when you send the form.

I commented out ‘emailAction’ and received the following content from $uploads after submitting the form:

Array
(
[name] => erlaubnis-arbeitnehmerueberlassung_ba.pdf
[type] => application/pdf
[tmp_name] => /tmp/phpd2s0jp
[error] => 0
[size] => 574013
)

Since this is a multiple file input, I selected three PDF files to upload. But the variable content only shows the last selected file. The other two selected PDFs are missing.

The file input in the form is

<input type="file" name="filefield" multiple="3"/>

Should be

<input type="file" name="filefield[]" multiple />

multiple is a boolean, you can’t add a number to it. And you need the ‘[]’ after the name of the field

And you also need <form action="" method="post" enctype="multipart/form-data">.

I changed the input, the enctype was already included. Now the $uploads variable content seems OK:

Array
(
    [0] => Array
        (
            [name] => agb_10.2019.pdf
            [type] => application/pdf
            [tmp_name] => /tmp/phpWqJm7F
            [error] => 0
            [size] => 146463
        )

    [1] => Array
        (
            [name] => anwenderurkunde_-bap-dgb-tarifvertrage.pdf
            [type] => application/pdf
            [tmp_name] => /tmp/phpazoCtF
            [error] => 0
            [size] => 229107
        )

    [2] => Array
        (
            [name] => erlaubnis-arbeitnehmerueberlassung_ba.pdf
            [type] => application/pdf
            [tmp_name] => /tmp/phpFQDVPE
            [error] => 0
            [size] => 574013
        )

)

But I now get the following error:

Invalid argument supplied for foreach()

for

foreach ($uploads as $upload) {

Complete code for $uploads:

// loop through uploads and check if they are valid
        foreach ($uploads as $upload) {

		// if no file is uploaded (its not required)
            if ($upload['error'] === 4) {

                	// do nothing
          
            } 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 order of your stuff looks wrong as well, the part with the uploads should be after you check for a post request.

It’s working! It was the order. Thank you very much for the support!

Summary for sending email attachments with uniform forms:

In the form:

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

and

<input type="file" name="filefield[]" multiple />

The controller:

<?php

use Uniform\Form;

return function ($kirby) {
   $form = new Form([
      'bewerbungals' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie die Stelle ein, für die Sie sich bewerben',
      ],
      'name' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihren Namen ein',
      ],
      'strasse' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihre Strasse und Hausnummer ein',
      ],
      'plzort' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihren Wohnort und Ihre Postleitzahl ein',
      ],
      'telefon' => [
	      'rules' => ['required'],
         'message' => 'Geben Sie Ihre Telefonnummer ein',
      ],
      'fax' => [],
      
      'email' => [
         'rules' => ['required', 'email'],
         'message' => 'Geben Sie Ihre Emailadresse ein',
      ],
      
      'filefield' => [
            'rules' => [
                'mime' => ['application/pdf'],
                'filesize' => 5000,
            ],
            'message' => [
                'Bitte senden Sie ausschliesslich PDFs.',
                'Die gesendeten Dateien müssen kleiner als 5MB sein.',
            ],
        ],
      
      'datenschutz' => [
          'rules' => ['required'],
          'message' => 'Markieren Sie die Checkbox für die Datenschutzerklärung',
      ],
        
   ]);
   
  
 // get the uploads
        $uploads = $kirby->request()->files()->get('filefield');
	//'filefield' is the name of the file input 

   if ($kirby->request()->is('POST')) {
          // loop through uploads 
	  foreach ($uploads as $upload) {

		// if no file is uploaded (its not required)
            if ($upload['error'] === 4) {

                	// do nothing
          
            } 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;
            }  
        } ;
	   
	   
      $form->emailAction([
         'to' => 'someone@t-online.de',
         'from' => 'kontakt@somefirm.de',
         'subject' => 'Neue Bewerbung von {{email}}',
         'attachments' => $attachments,
      ]);
   };

   return compact('form');
};

Form action is

<?php echo $page->url() ?>

@Gert Do you still have questions? Or was your last post just meant to provide the missing piece of your form?

I wanted to provide the missing form action but I actually have some more questions:

How can I specify Word and Jpg-files under “rules” => “mime” as suitable file types besides PDF. And how do I specify the “message” for this?

Is it somehow possible to upload the files one after the other into the input field? So far it only works for several files at the same time.

In Uniform, is it possible to save the content of the file input so that it does not disappear when the page is reloaded because a required field has not been filled in?

I think it should be an array of mime types:

'mime' => ['application/pdf', 'image/jpeg', 'application/word_mime_type'],

You probably need multiple fields in this case, not sure.

That has nothing to do with uniform, I think. But I think that’s a problem when one of the files is wrong, it will probably delete them all.

I have now added three single file inputs to the form, but the MIME recognition in the controller does not work. You could send a gif file, which should not be possible, since this file type is not listed in MIME. What could be the reason? Does the MIME recognition only work if the field is required?

'filefield1' => [
            'rules' => [
                'mime' => ['application/pdf', 'image/jpeg', 'application/doc', 'application/docx'],
                'filesize' => 3000,
            ],
            'message' => [
                'Bitte senden Sie ausschliesslich PDF, Word und Jpeg-Dateien.',
                'Die gesendeten Dateien müssen kleiner als 3MB sein.',
            ],
        ],
      
      'filefield2' => [
            'rules' => [
                'mime' => ['application/pdf', 'image/jpeg', 'application/doc', 'application/docx'],
                'filesize' => 3000,
            ],
            'message' => [
                'Bitte senden Sie ausschliesslich PDF, Word und Jpeg-Dateien.',
                'Die gesendeten Dateien müssen kleiner als 3MB sein.',
            ],
        ],
      
      'filefield3' => [
            'rules' => [
                'mime' => ['application/pdf', 'image/jpeg', 'application/doc', 'application/docx'],
                'filesize' => 3000,
            ],
            'message' => [
                'Bitte senden Sie ausschliesslich PDF, Word und Jpeg-Dateien.',
                'Die gesendeten Dateien müssen kleiner als 3MB sein.',
            ],
        ],

This is the Post-Code:

   if ($kirby->request()->is('POST')) {
	  foreach ($uploads as $upload) {

		// if no file is uploaded (its not required)
            if ($upload['error'] === 4) {

                	// do nothing
          
            } 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;
            }  
        }; 
	  //info@consens-zeitarbeit.de
	   
      $form->emailAction([
	     'template' => 'email',
         'to' => 'someone@t-online.de',
         'from' => 'bewerbung@murky-murk.de',
         'subject' => 'Neue Bewerbung von {{email}}',
         'attachments' => $attachments,
      ]);   
   };

   return compact('form');
};

The single variables are combined into an array this way (that works):

$upload1 = $kirby->request()->files()->get('filefield1');
$upload2 = $kirby->request()->files()->get('filefield2');
$upload3 = $kirby->request()->files()->get('filefield3');

$uploads = array($upload1, $upload2, $upload3);

The rest stays the same.