pjk_ok
pjk_ok

Reputation: 967

Output $_POST Array Values Inside a While Loop For An Input Element (During Form Validation) — PHP

I have a form that submits images and their titles. It is set up to check for errors via PHP validations on the image title form input elements and output any errors inside each image component (i.e. the instance in the while loop that represents a certain image). This all works OK.

What I would like to do is have it so that when one or more title is filled out correctly, but there are errors on one or more other titles, the $_POST value of the incorrect input is echoed out in the input element so the user doesn't have to re-type it. This has been easy to do on other forms on the site because there is no loop involved, e.g. on a sign up form etc. On a singular instance I would just do <?php echo $image_title; ?> inside the HTML value attribute, which is set referencing $image_title = $_POST['image-title'];]

So my question is, how do I have it so the $image_title $_POST value instances that pass the validations are outputted in their respective <input> elements, when other instances of the $image_title variable fail. If all the checks pass and there are no errors the form is then processed with PDO statements. The form submission button is placed outside of the main loop so all images are processed in one go when all the information is correct. I have a hidden input element that outputs the database image ID for each image, which can be used as a key in a foreach loop of some type. The problem of course being I can't get a foreach loop to work as I would like.

NOTE: To make the code simpler I've removed all the code relating to the outputting the images themselves.

<?php 
if(isset($_POST['upload-submit'])) {

        $image_title = $_POST['image-title'];
        $image_id = $_POST['image-id']; // value attribute from hidden form element

        // checks for errors that are later outputted on each image component
        forEach($image_title as $index => $title) {
            $id=$_POST['image-id'][ $index ];
            if(empty(trim($title))){
            $error[$id] = "Title cannot be empty";
            }
        }

        if (!isset($error)) {

            try {

            // update MySQL database with PDO statements
                
            header("Location: upload-details.php?username={$username}");

            } catch (PDOException $e) {
                echo "Error: " . $e->getMessage();
            }

        }

} else {
    // prevents error being thrown before form submission if input elements are empty
    $image_title = "";
}
?>

<form method="post" enctype="multipart/form-data">

    <!-- IMAGE DETAILS COMPONENT - START -->

    <?php
        $user_id = $_SESSION['logged_in'];
        $stmt = $connection->prepare("SELECT * FROM lj_imageposts WHERE user_id = :user_id");

        $stmt->execute([
            ':user_id' => $user_id
        ]); 

        while ($row = $stmt->fetch()) {

            $db_image_id = htmlspecialchars($row['image_id']);
            $db_image_title = htmlspecialchars($row['image_title']);
    ?>
    
    <div class="upload-details-component">                
        <?php 
            // output error messages from the validation above
            if(isset($error)) {
                foreach($error as $element_id => $msg) {
                    if($element_id == $id) {
                    echo "<p>** ERROR: {$msg}</p>";
                    }
                }
            }
        ?>
            <div class="edit-zone">
                <div class="form-row">

                    <!-- ** THIS IS THE INPUT ELEMENT WHERE I WOULD LIKE THE TITLE OUTPUTTED IF IT DOESN'T FAIL THE VALIDATION ** -->

                    <input value="<?php echo $image_title; ?>" type="text" name="image-title[]">
                </div>
                <div class="form-row">
                    <input type="hidden" name="image-id[]" value="<?php echo $db_image_id; ?>">
                </div>
            </div>
    </div>

    <?php } ?> <!-- // end of while loop -->

    <!-- submit form button is outside of the loop so that it submits all of the images inside the loop in on go -->
    <button type="submit" name="upload-submit">COMPLETE UPLOAD</button>
</form>

Upvotes: 0

Views: 681

Answers (2)

Don&#39;t Panic
Don&#39;t Panic

Reputation: 14520

This is harder than it needs to be, because you can't uniquely identify a particular title input except by the order it appears on the form, with the associated hidden input beside it. You need to do some careful juggling to correlate the IDs from the database with the $index of the $_POST arrays, to make sure your tiles and IDs match. You do have that all working, but this seems fragile and not a good solution IMO. I'd suggest using the database IDs as your form input index values, rather than relying on the order they appear on the form, as I did in my answer to a previous question of yours.

You're already tracking errors with the associated DB ID. So when it comes to displaying the input values, you can just check if there is an error for that DB ID. If there is not, this input passed validation, and you can re-display the value from the current $_POST:

<form method="post" enctype="multipart/form-data">

    <!-- ... your code ... -->

    while ($row = $stmt->fetch()) {
        $db_image_id = htmlspecialchars($row['image_id']);
        $db_image_title = htmlspecialchars($row['image_title']);

        // Start with the value in the DB, this will display first time
        // through.
        $value = $db_image_title;

        // If there's been a POST, and there is no $error for this ID, 
        // we know it passed validation.
        if (sizeof($_POST) && ! isset($error[$db_image_id])) {
            $value = $_POST['image-title'][$db_image_id];
        }
        ?>

        <!-- ... your code ... -->
    
        <div class="edit-zone">
            <div class="form-row">
                <!-- Now we can use whatever value we set above -->
                <input value="<?php echo $value; ?>" type="text" name="image-title[]">
            </div>

Side Note

There's no need to iterate over all your errors to find the one you want. If you have its ID, you can address it directly. Instead of this:

if(isset($error)) {
    foreach($error as $element_id => $msg) {
        // Note your code above does not show what $id is here,
        // AFAICT it is the same as $row['image_id']
        if($element_id == $id) {
        echo "<p>** ERROR: {$msg}</p>";
        }
    }
}

You can simply do:

if (isset($error) && isset($error[$row['image_id']])) {
    echo "<p>** ERROR: " . $error[$row['image_id']] . "</p>";
}

Upvotes: 2

daniel
daniel

Reputation: 303

i think this is what you want:

<input value="<?php echo htmlentities($image_title); ?>" type="text" name="image-title[<?php echo $db_image_id; ?>]">


foreach ($_POST['image-title'] as $key => $value) {
    $stmt->execute([
        ':image_id' => $key,
        ':image_title' => $value
    ]);
}

Edit: I have now created a minimized version and was able to test it successfully. I created the mysql table on my test-maschine to try it out. I hope this will help you now.

With <input name="img[id]" value="title"> an array will created with the following structure: $_POST['img'][id] = title. The id as array key and the title as array value. So the title is uniquely assigned to each id. The array can be walk through with a foreach key=>value loop.

Edit2: I added error checking. If the title is empty or is "testfail" for example, the title will not be written to the database. In addition, the input field keeps the last input (restore $_POST string).

<?php

$connection = new PDO(...);
$error = []; // declare empty array

if(isset($_POST['img'])) {
    try {

        $q = "UPDATE lj_imageposts SET image_title=:image_title
            WHERE image_id = :image_id AND user_id = :user_id";

        $stmt = $connection->prepare($q);

        foreach($_POST['img'] as $key => $value) {

            // here the error check, so that the wrong title 
            // is not written into the database
            if(trim($value) === '' || $value === 'testfail') {
                echo "Error image id $key title $value <br>";
                $error[$key] = TRUE; // set error var with img-id
                continue; // skip to next img, do not execute sql-stmt
            }

            $stmt->execute([
                ':image_id' => $key,
                ':image_title' => $value,
                ':user_id' => $_SESSION['logged_in']
            ]);
            echo "Update image id $key title $value <br>";
        }

    } catch (PDOException $e) {
        echo "Error: " . $e->getMessage();
    }
}
?>

<form method="post" enctype="multipart/form-data">
<?php

    $q = "SELECT * FROM lj_imageposts WHERE user_id = :user_id";
    $stmt = $connection->prepare($q);
    $stmt->execute([':user_id' => $_SESSION['logged_in']]); 

    while ($row = $stmt->fetch()) {

        $id = htmlentities($row['image_id']);

        // check if error with img-id is set above
        if(isset($error[$row['image_id']])) {
            // if error, fill in title from previous post
            $title = htmlentities($_POST['img'][$row['image_id']]);
        } else {
            // if no error, fill in title from database
            $title = htmlentities($row['image_title']);
        }

?>

<input type="text" 
  name="img[<?php echo $id; ?>]"
  value="<?php echo $title; ?>"><br>

<?php
        // alternative version
        //echo '<input type="text" name="img['.$id.']" value="'.$title.'"><br>';

    }

?>
<button type="submit" name="upload-submit">Test</button>
</form>

Test

Upvotes: 3

Related Questions