Contact form validation

Hi there,

I followed the example in the documents for a simple contact form. Upon testing the form is was failing to submit and I was getting a generic mailer error that told me nothing. I search the forum and found an old post where a suggestion was made to edit that controller to see if the form data is being captured.

For clarity I am using Kirby in Docker with Mailhog setup. I have got mailhog configured in my config.php as per the docs.

My controller:

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

    $alert = null;

    if($kirby->request()->is('POST') && get('submit')) {
dump($_POST); // check if you get into this if-statement at all
        // check the honeypot
        if(empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        $data = [
            'name'  => get('name'),
            'email' => get('email'),
            'text'  => get('text')
        ];

        $rules = [
            'name'  => ['required', 'minLength' => 3],
            'email' => ['required', 'email'],
            'text'  => ['required', 'minLength' => 3, 'maxLength' => 3000],
        ];

        $messages = [
            'name'  => 'Please enter a valid name',
            'email' => 'Please enter a valid email address',
            'text'  => '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 {
        dump($data);
            try {
                $kirby->email([
                    //...
                    
                ]);

            } catch (Exception $error) {
                if(option('debug')):
                    $alert['error'] = 'The form could not be sent: ' . $error->getMessage();
                else:
                    $alert['error'] = 'The form could not be sent!';
                endif;
            }

            // no exception occurred, let's send a success message
            if (empty($alert) === true) {
                $success = 'Your message has been sent, thank you. We will get back to you soon!';
                $data = [];
            }
        }
    }

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

and my contact.php

<?php if($success): ?>
            <div class="alert success">
                <p><?= $success ?></p>
            </div>
            <?php else: ?>
            <?php if (isset($alert['error'])): ?>
                <div><?= $alert['error'] ?></div>
            <?php endif ?>
            <form method="post" action="<?= $page->url() ?>">
            <fieldset class="[ flow ]">
                <div class="honeypot [ visually-hidden ]">
                    <label for="website">Website <abbr title="required">*</abbr></label>
                    <input type="url" id="website" name="website" tabindex="-1">
                </div>
                <div class="field">
                    <legend class="visually-hidden">Name</legend>
                    <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">
                    <legend class="visually-hidden">Telephone</legend>
                    <label for="tel">
                        Telephone <abbr title="required">*</abbr>
                    </label>
                    <input type="text" id="tel" name="tel" value="<?= esc($data['tel'] ?? '', 'attr') ?>" required>
                    <?= isset($alert['tel']) ? '<span class="alert error">' . esc($alert['tel']) . '</span>' : '' ?>
                </div>
                <div class="field">
                    <legend class="visually-hidden">Email</legend>
                    <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 class="field">
                    <legend class="visually-hidden">Message</legend>
                    <label for="text">
                        Message <abbr title="required">*</abbr>
                    </label>
                    <textarea id="text" name="text" rows="10" required>
                        <?= esc($data['text'] ?? '') ?>
                    </textarea>
                    <?= isset($alert['text']) ? '<span class="alert error">' . esc($alert['text']) . '</span>' : '' ?>
                </div>
                <input type="submit" name="submit" value="Submit">
            </fieldset>
            </form>
            <?php endif ?>

At the top I can see the form field data being dumped.

Array
(
    [website] => 
    [name] => Mathew
    [tel] => 5464565464564
    [email] => myemail@gmail.com
    [text] =>  sdf fdsfsd sd fsd fsdsdfsdf
    [submit] => Submit
)

Array
(
    [name] => Mathew
    [email] => myemail@gmail.com
    [text] =>  sdf fdsfsd sd fsd fsdsdfsdf
)

The error I then get in the from the form is now:

The form could not be sent: The property “body” is required

What does your email configuration look like?

You left out the parameters in $kirby->email() and your email template, but the error points towards something there.

Hey @texnixe,

For clarity here is my working form template:

<?php if($success): ?>
            <div class="alert success">
                <p><?= $success ?></p>
            </div>
            <?php else: ?>
            <?php if (isset($alert['error'])): ?>
                <div><?= $alert['error'] ?></div>
            <?php endif ?>
            <form method="post" action="<?= $page->url() ?>">
            <fieldset class="[ flow ]">
                <div class="honeypot [ visually-hidden ]">
                    <label for="website">Website <abbr title="required">*</abbr></label>
                    <input type="url" id="website" name="website" tabindex="-1">
                </div>
                <div class="field">
                    <legend class="visually-hidden">Name</legend>
                    <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">
                    <legend class="visually-hidden">Telephone</legend>
                    <label for="tel">
                        Telephone <abbr title="required">*</abbr>
                    </label>
                    <input type="text" id="tel" name="tel" value="<?= esc($data['tel'] ?? '', 'attr') ?>" required>
                    <?= isset($alert['tel']) ? '<span class="alert error">' . esc($alert['tel']) . '</span>' : '' ?>
                </div>
                <div class="field">
                    <legend class="visually-hidden">Email</legend>
                    <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 class="field">
                    <legend class="visually-hidden">Message</legend>
                    <label for="text">
                        Message <abbr title="required">*</abbr>
                    </label>
                    <textarea id="text" name="text" rows="10" required>
                        <?= esc($data['text'] ?? '') ?>
                    </textarea>
                    <?= isset($alert['text']) ? '<span class="alert error">' . esc($alert['text']) . '</span>' : '' ?>
                </div>
                <input type="submit" name="submit" value="Submit">
            </fieldset>
            </form>
            <?php endif ?>

config:

return [
    'debug' => true,
    'panel' => [
        'slug' => 'admin-area'
    ],
    'email' => [
        'transport' => [
          'type' => 'smtp',
          'host' => 'localhost',
          'port' => 1025,
          'security' => false,
        ]
      ],
];

and my contact controller (copied from the docs, I’ve not yet added my telephone field in here. But it makes no different removing it from my template):

<?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;
        }

        $data = [
            'name'  => get('name'),
            'email' => get('email'),
            'text'  => get('text')
        ];

        $rules = [
            'name'  => ['required', 'minLength' => 3],
            'email' => ['required', 'email'],
            'text'  => ['required', 'minLength' => 3, 'maxLength' => 3000],
        ];

        $messages = [
            'name'  => 'Please enter a valid name',
            'email' => 'Please enter a valid email address',
            'text'  => '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'     => 'yourcontactform@yourcompany.com',
                    'replyTo'  => $data['email'],
                    'to'       => 'you@yourcompany.com',
                    'subject'  => esc($data['name']) . ' sent you a message from your contact form',
                    'data'     => [
                        'text'   => esc($data['text']),
                        'sender' => esc($data['name'])
                    ]
                ]);

            } catch (Exception $error) {
                if(option('debug')):
                    $alert['error'] = 'The form could not be sent: <strong>' . $error->getMessage() . '</strong>';
                else:
                    $alert['error'] = 'The form could not be sent!';
                endif;
            }

            // no exception occurred, let's send a success message
            if (empty($alert) === true) {
                $success = 'Your message has been sent, thank you. We will get back to you soon!';
                $data = [];
            }
        }
    }

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

With this in place, I get the error:

The form could not be sent: SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting

As I mentioned I am using Docker and Mailhog. I am starting to wonder if this is something in my setup rather than a problem in Kirby. Although that leaves me even more stumped. I can access the mailhog client via http://localhost:8025/ withouth issues and I can see in Docker that mailhog is running and bound to the relevant ports.

Is there any way to get a more detailed error message from the mailer to detect what is actually causing the problems?

@texnixe I just stumbled upon a fix. I came across an old post someone made where they stated they needed to use 'host' with a value of host.docker.internal. Making this change fixes my issue… Not quite sure I understand whats happening, but thankful nonetheless.

Yes, that’s right.

Alternatively, when you use docker-compose, you can use a named host, see my Docker recipe: Kirby meets Docker | Kirby CMS

Using localhost or 127.0.0.1 works if you run Mailhog in a container but your website runs in a local webserver.