-1

I have a contact form with a privacy consent checkbox which the user has to check. If they don't, a required error message should appear, as it successfully does for the other fields.

The problem is that I'm getting an Undefined index: privacy notice for the checkbox if it's unchecked. If I submit the form with it checked, then it works.

I understand that if the checkbox is unchecked, PHP basically doesn't know it exists, therefore it doesn't send the data to $_POST.

All the forum posts I read mention using isset() or empty() to fix this, but I'm already using it and used it in different ways to try things out.

It's probably something obvious, but I just can't figure it out for the past several hours.

Here's a part of the validation class:

class ValidateContactForm
{
    private $data;
    private $file;
    private static $fields = [
        'name',
        'email',
        'subject',
        'message',
        'privacy'
    ];
    private static $fileInput = 'file';
    private $errors = [];

    public function __construct($postData, $fileData)
    {
        $this->data = $postData;
        $this->file = $fileData;
    }

    public function validateForm()
    {
        // foreach (self::$fields as $field) {
        //     if (!array_key_exists($field, $this->data)) {
        //         trigger_error('An input key does not have a match in the array', E_USER_NOTICE);
        //         return;
        //     }
        // }

        if (!array_key_exists(self::$fileInput, $this->file)) {
            trigger_error('The file input key does not match', E_USER_NOTICE);
        }

        $this->validateName();
        $this->validateEmail();
        $this->validateSubject();
        $this->validateMessage();
        $this->validatePrivacy();
        $this->validateFile();

        return $this->errors;
    }

    private function validatePrivacy()
    {
        $val = $this->data['privacy'];

        if (empty($val)) {
            $this->addError('privacy', 'The privacy checkbox is required.');
        }
    }

The validatePrivacy() method checks if $_POST['privacy'] is empty.

Here's the part of the contact form class where I check if the fields are set:

class ContactForm
{
    public function sendContactForm()
    {
        if ($_POST['name'] != ''
            && $_POST['email'] != ''
            && $_POST['message'] != ''
            && $_POST['subject'] != ''
            && !empty($_POST['privacy'])
        ) {
            $name = $this->sanitize($_POST['name']);
            $email = $this->sanitize($_POST['email']);
            $message = $this->sanitize($_POST['message']);
            $subject = $this->sanitize($_POST['subject']);
            $privacy = $_POST['privacy'];

I also tried isset($_POST['privacy']) and $_POST['privacy'] != ''.

Here's a part of the file with the instantiation and HTML:

<?php

require_once 'includes/head.php';
require_once 'vendor/autoload.php';

use App\ContactForm;
use App\Validation\ValidateContactForm;

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

    $validation = new ValidateContactForm($_POST, $_FILES);
    $errors = $validation->validateForm();

    if (empty($errors)) {
        $contactForm = new ContactForm;
        $sendContactForm = $contactForm->sendContactForm();

        if (!headers_sent()) {            
            header('Location:' . htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES) . '?' . 'sent');
            exit;
        }
    }
}

?>

<div class="form-group form-check">
    <input type="checkbox" name="privacy" class="form-check-input <?php echo isset($errors['privacy']) ? 'is-invalid' : ''; ?>" id="inputCheckbox" value="yes">
    <label class="form-check-label" for="inputCheckbox">I understand that this information will be collected in order to be contacted and process a potential order. *</label>
    <p class="invalid-feedback"><?php echo $errors['privacy'] ?? ''; ?></p>                                              
</div>

If I use vard_dump on it, it returns null, but I assume it's expected behavior since it doesn't exist unless the checkbox is checked.

I know I can use the HTML required attribute (and I will use that, too) or JavaScript, but I'd like to rely more on server validation. I also want to learn this stuff and not give in to workarounds.

Radu B
  • 103
  • 2
  • 10

3 Answers3

1

In validatePrivacy() you're calling $this->data['privacy'] even before you've checked if it is set, which is what is giving you the Undefined index: privacy error.

You can try to change the validatePrivacy() function to:

private function validatePrivacy()
    {
        if (empty($this->data['privacy']) {
            $this->addError('privacy', 'The privacy checkbox is required.');
        }
    }

dkutin
  • 31
  • 5
1

You have some options.

  1. Assume that if it is missing that the person didn't check the box.
  2. Ensure that you always get something, false or true, or some known value you can control.

For solution #1: check $_POST using array_key_exists. If it returns false, then the value of "privacy" is false.

For solution #2: This is a common problem with checkboxes. The solution is to add a hidden input with the same name as the checkbox and set it to false or 0. In HTML, make sure that the hidden input is before the actual checkbox. When the person enters the form but they do not check the box, the value of the hidden input will be sent. If they do check the box, the checkbox value will override the hidden input value. This way you will always get something.

...
<input type="hidden" name="privacy" value="0" />
<input type="checkbox" name="privacy" />
...
ryantxr
  • 4,119
  • 1
  • 11
  • 25
  • I implemented the 1st solution, but I commented it out because it didn't find 'privacy' in the array and it was triggering the error I set in trigger_error(). But your second solution worked, even with the array_key_exists enabled back. Thanks! I've seen it in a couple of other posts, but there were people saying it won't work for some browsers. Is it true? – Radu B Dec 13 '19 at 20:55
  • 1
    As far as I know it works all the time. Some frameworks actually implement this. – ryantxr Dec 13 '19 at 20:57
0

It's not clear what value you're looking to be held in $privacy, should it be a bool at the end? If so, try this:

<?php

// Will set to a bool (true if checked, false otherwise)
$privacy = (!empty($_POST['privacy']));

// Will set to a string ('Yes' if checked, 'No' otherwise)
$privacy = (!empty($_POST['privacy']) ? 'Yes' : 'No');
Aran
  • 2,429
  • 1
  • 19
  • 19