-3

So, I'm making a trivia game--and I have a page where users can submit their own trivia questions which will later be put into games (separate part of the game that isn't too relevant here). Anyway, so prevent spam, and irrelevancy, or false submitting/trolls, I'm making a moderator approval page; this page displays individual "pending" questions that users submit. (When they submit a question, it goes into a "pending" database table called 'usertriviadata'.

Then, it displays each of the pending questions on the moderator approval page, with a submit button where a mod/admin can approve it.

Step by step this is how it works:

  1. The page displays (per trivia category) each individual pending question on the approval page, each one has a submit button.
  2. A moderator can view it, and if they want to approve it, they click the "approve" button.
  3. If the "approve" button is clicked, the system deletes the question from the "pending" 'usertriviadata' table, and inserts it into the 'approved' database table where I will use the data in that table for later.

The problem I'm having, and can't quite figure out how to fix--when I click the approve button, it approves ALL/ANY pending questions in that particular category. Let's say there is 3 pending questions in the "geography" category. I click approve on any of those 3 questions, and it approves all of them. Basically, the deletion and insertion (swapping data between the two database tables) works, but I want to individualize it. I've tried a few different things, but I can't quite get it right.

Any suggestions? The code is below this image (image shows the approval page to get a general idea of what it looks like when there are multiple pending questions):

[enter image description here][1]


    <div class="categories">
    <h3>Geography</h3>
    <?php
    $sql = "SELECT questionID, category, uploaderUsername, question, correctAnswer, answerTwo, answerThree, answerFour FROM usertriviadata WHERE category='geography'";
    $result = $conn->query($sql);
    
    
    if ($result->num_rows > 0) {
        // output data of each row
        while($row = $result->fetch_assoc()) {
    
            $questionID = $row['questionID'];
            $category = $row['category'];
            $uploaderUsername = $row['uploaderUsername'];
            $question = $row['question'];
            $correctAnswer = $row['correctAnswer'];
            $answerTwo = $row['answerTwo'];
            $answerThree = $row['answerThree'];
            $answerFour = $row['answerFour'];
            echo "<div class='individuals'><p>Question ID: $questionID</p> <p>Category: $category</p> <p>Uploader Username: $uploaderUsername</p> <p>Question: $question</p> <p>Correct Answer: $correctAnswer</p> <p>Answer 2: $answerTwo</p> <p>Answer 3: $answerThree</p> <p>Answer 4: $answerFour</p> <form action='' method='GET'><input type='submit' name='submit' value='Approve'/></form> </div> ";
            
            if (isset($_GET["submit"])) {
    
                // Move question to approved table
                $sql = "INSERT INTO approved (category, uploaderUsername, question, correctAnswer, answerTwo, answerThree, answerFour)
                VALUES ('$category', '$uploaderUsername', '$question', '$correctAnswer', '$answerTwo', '$answerThree', '$answerFour')";
                // Error Handles
                if ($conn->query($sql) === TRUE) {
                    echo "<p class='green'>Question approved.</p>";
                } else {
                    echo "Error: " . $sql . "<br>" . $conn->error;
                }
    
                // Delete question from pending/usertriviadata table
                $sql = "DELETE FROM usertriviadata WHERE questionID='$questionID'";
                if ($conn->query($sql) === TRUE) {
                    echo "<p class='green'>Question removed from pending/usertriviadata database table. Please wait 5 seconds before approving another post.</p>";
                    echo "<meta http-equiv='refresh' content='5; URL=../triviaApproval/moderatorApproval' />";
                } else {
                    echo "Error: " . $sql . "<br>" . $conn->error;
                }
                
            }
    
    
        }
      } else {
        echo "No posts need approved here.";
      }
      
    
    ?>
    </div>```
    
    If you want the CSS as well, it is here:
    
    ```html {
        background-color: #AC6A6C;
        font-family: "Trirong", serif;
        color: #DEF706;
        text-align: center;
    }
    .option-a {
        text-align: center;
        display: inline;
        background-color: gray;
        color: #DEF706;
        font-size: 125%;
        width: 100px;
        padding: 0.5%;
        text-decoration: none;
    }
    .option-a:hover {
        cursor: pointer;
        background-color: #4E4E4D;
    }
    .option-a:focus {
        padding: 0.3%;
        background-color: blue;
    }
    .individuals {
        padding: 1%;
        display: inline-block;
        border: 1px solid;
    }
    .green {
        color: green;
    }

```[enter image description here][2]


 
  • 1
    _"Any suggestions?"_ Instead of creating two separate tables for active/pending questions, put all the questions in one table and use a boolean field to indicate active/pending status. When querying for active questions, add `ACTIVE = 1` or whatever to your where clause. Then your process to "make a question live" simply becomes updating one field of one row. – Alex Howansky Apr 23 '22 at 22:54
  • Wouldn't that still just set all the particular items from that category as approved, when clicking "approve" on just ONE of them? At least with the way I'm displaying them on the page. – quietWind01 Apr 23 '22 at 22:57
  • If you want to approve a single question then you use `WHERE questionId = :id AND ACTIVE = 0` and if you want to approve all questions in a category then you use `WHERE category = :category AND ACTIVE = 0`. – Alex Howansky Apr 23 '22 at 23:00
  • I don't see any reference to any id or category posted to the form. You'll need to build the form so that it passes either the questionId or the category to the submission page, and then reference that value in your query. – Alex Howansky Apr 23 '22 at 23:05
  • If under the code comment: // Delete question from pending/usertriviadata table I do `WHERE questionId = :id AND ACTIVE = 0` would that work for the exact question I click "approve" on? – quietWind01 Apr 23 '22 at 23:05
  • That questionId is not coming from the form, it's coming from the previous query. Your SELECT query selects _all_ questions in the "geography" category, then you loop over that result set, and copy each row to the active table. There's no WHERE clause on the initial query that limits it to the desired row(s). I.e., if the user is selecting something on the form to indicate what question they want to approve, you're ignoring it. – Alex Howansky Apr 23 '22 at 23:09
  • @AlexHowansky When a user submits a question to be approved, it passes the data into the database--each question is given an AUTO_INCREMENT (id) value when the data sends to the pending table. For some reason my image didn't upload here, but if you would have seen it, you'd see that the code above does pull the questionID of the row for the data it's displaying on the approval page. – quietWind01 Apr 23 '22 at 23:09
  • No. It's not pulling the questionId from the form submit, it's pulling the questionId from the select query. – Alex Howansky Apr 23 '22 at 23:10
  • Mind if I move this to chat? Nevermind I can't. – quietWind01 Apr 23 '22 at 23:10
  • @AlexHowansky Yes... in the code above, it pulls $questionID (questionID) from the table and displays it on the approval page. I'm a bit confused at what you mean by that. – quietWind01 Apr 23 '22 at 23:13
  • I don't know if this helps, but this is an Imgur with 3 images: one for the structure of each data table, and one that shows the moderatorApproval page with two example questions that are pending. https://imgur.com/a/eC8b0i3 – quietWind01 Apr 23 '22 at 23:18
  • Your forms have no inputs, they consist of only a submit button, so there's no way to indicate which question was selected. I.e., all your approve buttons are identical. Then when you submit, you run a query that has no WHERE clause, so it's just going to select all the questions and copy them. The form submit needs to pass the questionId (perhaps via a hidden element) and the select query need to use that in a where clause. – Alex Howansky Apr 23 '22 at 23:22
  • **Warning:** You are wide open to [SQL Injections](https://php.net/manual/en/security.database.sql-injection.php) and should use parameterized **prepared statements** instead of manually building your queries. They are provided by [PDO](https://php.net/manual/pdo.prepared-statements.php) or by [MySQLi](https://php.net/manual/mysqli.quickstart.prepared-statements.php). Never trust any kind of input! Even when your queries are executed only by trusted users, [you are still in risk of corrupting your data](http://bobby-tables.com/). [Escaping is not enough!](https://stackoverflow.com/q/32391315) – Dharman Apr 23 '22 at 23:25
  • Okay, that makes a bit more sense--I didn't realize you were talking about the form I was using for the submit button, since I'm really only using that to trigger the code in the if statement. I could have gotten around that altogether as well by just using an anchor tag and setting the href="" to something like, ?postApproved, but it would mess with a couple other things I have going on in the page and I don't like doing that method. – quietWind01 Apr 23 '22 at 23:30
  • @Dharman I realize that. I am going to work on that later, way before the game is open to the public. I just want to get the approval system working/down first. – quietWind01 Apr 23 '22 at 23:31
  • No matter what I'm doing with it, I'm still either getting an error, or it just approves ALL of the questions from the "geography" category. I tried passing the $questionID in with the form as a hidden input, and doing what you say in the SELECT statement at the top for adding the $questionID into the WHERE clause, but it just throws a large error. – quietWind01 Apr 23 '22 at 23:41

2 Answers2

0

You have this, a separate form for each question pending approval:

<form action='' method='GET'>
  <input type='submit' name='submit' value='Approve'/>
</form>
<form action='' method='GET'>
  <input type='submit' name='submit' value='Approve'/>
</form>
<form action='' method='GET'>
  <input type='submit' name='submit' value='Approve'/>
</form>

But note they're all the same. There's no ID to indicate which form represents which question. Pressing any one of these buttons is going to do the same thing. In the script that handles the submit, you run this query:

    $sql = "SELECT ... FROM usertriviadata WHERE category='geography'";

Note there's no WHERE clause to limit the select to a single question. So, you're using the same query to render the question list as you are to approve. It seems that you're trying to use if (isset($_GET["submit"])) { inside the question loop as a condition to determine which of the questions was selected. However, this can't work. There is only one $_GET["submit"] -- there is not one per question. So clicking any one of the form buttons is going to copy all of the questions.

You're confusing yourself by having one script do both the form rendering and the form processing. I'd separate those two functions for simplicity -- have one file render the list and a second file process the form submit. So, your form render would do something like this to include the question id in each form:

<form action='approve.php' method='POST'>
  <input type='hidden' name='questionId' value='<?= $questionID =?'>
  <input type='submit' name='submit' value='Approve'/>
</form>

And then, in approve.php, use $_POST['questionId'] in your WHERE clause to process the activation.

Other recommendations:

  • Use POST for form submits, not GET. In general, if the request results in a change to the database, it should be a POST.
  • If you build a form that has one checkbox per question (instead of one whole form per question) then you can allow multiple questions to be approved at once.
  • Fix your SQL injection now. Shrugging it off to "I'll just fix it later" is a really bad habit, especially when it's no harder to just do it correctly from the start.
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • ```$sql = "SELECT ... FROM usertriviadata WHERE category='geography'";``` This isn't my query to handle the submit. This is the query to select all of the pending questions from the "pending" table, with the category of "geography". This already does what I want it to, all it does is just display all of those pending questions. It doesn't do anything with them until the submit button is pressed and THEN both the INSERT and DELETE queries are ran. – quietWind01 Apr 24 '22 at 00:00
  • So let me get this straight so I actually understand it and not just DOING it. I use the SELECT query with the WHILE statement to display all of the pending questions in the category "geography" on the page. Then I add a hidden input in the form that echos ```$questionID```. Then for both of the INSERT and DELETE queries, I add ```WHERE questionID='$QuestionID'``` ? Correct? And take the entire ```if (isset($_GET["submit"]))``` statement and separate it from the ```if ($result->num_rows > 0)``` statement? – quietWind01 Apr 24 '22 at 00:04
  • _"This isn't my query to handle the submit."_ Yes it is. You're using the same file as your action target, so it gets run once when you render the form, and then the same thing gets run again when you submit. Both runs select all questions in the category. If you're having trouble understanding this, take my advice and separate your code into two files -- one to render the form, and one to process the submit. This will make the logic of each much more straightforward. – Alex Howansky Apr 24 '22 at 00:21
  • Yes? 1. The SELECT query runs selects all questions from the "geography" category that are pending approval, and displays each question from that category individually on the page, each with the submit button/form, yes. 2. In the IF statement with isset(), it checks if that submit button was pressed. If so, it takes the data from the SELECT query, and inserts it into the approved table using the INSERT query. 3. At the same time, it deletes the same data from the pending/usertriviadata table. What if I just completely get rid of the form with the submit button and do it a different way? – quietWind01 Apr 24 '22 at 00:53
  • I'm sorry to be such a pain in your behind, but I think there's a big misunderstanding here and I'm trying to figure out EXACTLY what you're telling me to do. I'm more of a verbal/visual learner so it's hard for me to grasp what you're telling me I should do without SEEING it done or being broken down into specific steps. – quietWind01 Apr 24 '22 at 01:01
0

EDIT: No it doesn't. It's a step I feel in the right direction but it's still messed up. Taking a break on this issue. I'm so frustrated.

Okay, so... this kind of falls under what you were saying, and I'm not sure if this is exactly what you were getting me to do, BUT, I did this and it works flawlessly.


    <div class="categories">
    <h3>Geography</h3>
    <?php
    $sql = "SELECT questionID, category, uploaderUsername, question, correctAnswer, answerTwo, answerThree, answerFour FROM usertriviadata WHERE category='geography'";
    $result = $conn->query($sql);
    
    if ($result->num_rows > 0) {
        // output data of each row
        while($row = $result->fetch_assoc()) {
    
            $questionID = $row['questionID'];
            $category = $row['category'];
            $uploaderUsername = $row['uploaderUsername'];
            $question = $row['question'];
            $correctAnswer = $row['correctAnswer'];
            $answerTwo = $row['answerTwo'];
            $answerThree = $row['answerThree'];
            $answerFour = $row['answerFour'];
            echo "<div class='individuals'><p>Question ID: $questionID</p> <p>Category: $category</p> <p>Uploader Username: $uploaderUsername</p> <p>Question: $question</p> <p>Correct Answer: $correctAnswer</p> <p>Answer 2: $answerTwo</p> <p>Answer 3: $answerThree</p> <p>Answer 4: $answerFour</p> <form action='' method='POST'><input type='hidden' name='questionId' value='<?= $questionID =?'><input type='submit' name='submit' value='Approve'/></form> </div> ";
        }
      } else {
        echo "No posts need approved here.";
      }
    ?>
    </div>

At the bottom of the page:


    <?php
    
    if (isset($_POST["submit"])) {
    
        // Move question to approved table
        $sql = "INSERT INTO approved (category, uploaderUsername, question, correctAnswer, answerTwo, answerThree, answerFour)
        VALUES ('$category', '$uploaderUsername', '$question', '$correctAnswer', '$answerTwo', '$answerThree', '$answerFour')";
        // Error Handles
        if ($conn->query($sql) === TRUE) {
            echo "<p class='green'>Question approved.</p>";
        } else {
            echo "Error: " . $sql . "<br>" . $conn->error;
        }
    
        // Delete question from pending/usertriviadata table
        $sql = "DELETE FROM usertriviadata WHERE questionID='$questionID'";
        if ($conn->query($sql) === TRUE) {
            echo "<p class='green'>Question removed from pending/usertriviadata database table. Please wait 5 seconds before approving another post.</p>";
            echo "<meta http-equiv='refresh' content='5; URL=../triviaApproval/moderatorApproval' />";
        } else {
            echo "Error: " . $sql . "<br>" . $conn->error;
        }
        
    }
    
    
    
    $conn->close();
    ?>

Now I'll work on SQL injection.

  • I lied. I double tested it. It's only working for the geography category... so now, if I approve a post from the "science" category for example, it deletes one (seemingly at random?) from the geography category. Going to go back and look and see if I made a typo somewhere by chance. EDIT: it's deleting them in reverse sequential order of their $questionID, just so happens that the geography category was listed last and therefore had the highest numeric $questionID – quietWind01 Apr 24 '22 at 01:32