0

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>
pjk_ok
  • 618
  • 7
  • 35
  • 90
  • Is your problem having the HTML form *load* the right data or having the PHP form *save* the right data? – Martin Jan 30 '22 at 22:19
  • *"The problem of course being I can't get a foreach loop to work as I would like"* Why not? What have you tried? Why did it fail? Update your question. – Martin Jan 30 '22 at 22:21
  • @Martin - you posted an answer on here that looked like it would possibly work but seem to have deleted it? Can you undelete it? Thanks – pjk_ok Jan 31 '22 at 17:09
  • My answer was a variation on what daniel has already written. – Martin Jan 31 '22 at 17:45

2 Answers2

3

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

daniel
  • 273
  • 2
  • 7
  • Hi @daniel thanks for taking the time to answer. That doesn't seem to work. I need to associate the specific instance of the input element with the equivalent $_POST variable instance, which i can't seem to work out how to do. – pjk_ok Jan 30 '22 at 15:26
  • I don't understand. With my example `name="image-title[]"` you will get an array like `$_POST[image_id] = title`, but I just see that you changed your post a lot... – daniel Jan 30 '22 at 20:25
  • I simplified the code - I didn't touch anything to do with the problem though. I think there may be some confusion about what I'm trying to achieve. I want the input elements in the loop to show the user inputted text if they don't fail the error check, and the ones that do fail, to then show the error message. All of this is prior to submission to the database. If all instances are successful, the data is then submitted with PDO statements. – pjk_ok Jan 30 '22 at 22:02
  • 4
    This answer gives no explanation about how the problem is fixed. The foreach only relates to the PDO execution, not the outputting of the $_POST variable in each input element? – pjk_ok Jan 31 '22 at 17:14
  • When I do it this way I get `
    Notice: Array to string conversion in /Applications/MAMP/htdocs/project/upload-details.php on line 127
    Array` referring to the input element (shown inside the input element), and the error message `Undefined offset: 4 in /Applications/MAMP/htdocs/project/upload-details.php on line 20` which refers to the line ` $id=$_POST['image-id'][ $index ];` in the above code in relation to the validation checks. The `Undefined offset: 4` error is repeated for the other image titles i.e. `Undefined offset: 5` etc
    – pjk_ok Jan 31 '22 at 18:48
  • That's even more confusing. I'm relatively new to PHP/programming, but I don't understand why you're outputting HTML inside a print_r() function. I just want the input element to output the contents of its relative $_POST value which I initially thought I could do my placing that `` element inside a foreach. On my phone earlier I briefly saw an answer by @Martin which looked logical but he's deleted it. I don't need the PDO code for when the form is submitted because that isn't part of the problem. I do appreciate your help though. – pjk_ok Jan 31 '22 at 22:57
  • 1
    @paulo_cam sorry, i often work with the printf() function to generate html code. I have now changed the code. now it's the way you made it. I also added error checking. if an incorrect title is entered, the wrong title will be displayed so that you do not have to enter it again. now that should answer your question, right? – daniel Feb 01 '22 at 00:08
  • No. It doesn't throw any errors, but it doesn't output the titles in the input elements. – pjk_ok Feb 07 '22 at 01:37
  • My example is 100% working. Then you copy&paste something wrong ;-) Please update your code in your posting. – daniel Feb 08 '22 at 20:06
2

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>";
}
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
  • Hi - thanks for taking the time to answer. This doesn't work. There is a typo in the code - missing a closing bracket on the `if (! isset($error[$db_image_id])` line, that it won't let me amend. Also it's throwing an error saying `Undefined index: image-title` in relation to the line `$value = $_POST['image-title'][$db_image_id];` and also `Trying to access array offset on value of type null` in relation to the same line of code ? – pjk_ok Feb 07 '22 at 01:06
  • There is also a closing bracket missing on the new error output code you've provided in the other code block part of your answer, which again it won't let me edit/amend either. This time on `if (isset($error) && isset($error[$row['image_id']])` – pjk_ok Feb 07 '22 at 01:08
  • I've fixed the typos. RE: the `Trying to access array offset on value of type null` error - that is saying `$_POST['image-title']` is null, which would happen on first page load, if you haven't hit the submit button yet. In that case I guess you would want to show the DB value, right? I've updated my answer to do that. – Don't Panic Feb 07 '22 at 02:30