0

So I'm making my own blog scripts using MYSQL and PHP. I had the whole 'writing the blog to a database' thing working perfectly, until I realised that if you tried to write a blog with speech marks, this would prevent the INSERT statement from working (obviously - the speechmarks were ending the SQL statement).

So I tried to use real_escape_string, and now the INSERT doesn't work even if you exclude quotes. I tried using:

sqlstate

in order to find out the issue, and it returned "42000" - which, after googling for a little bit, refers to a syntax error, which doesn't make much sense as there is no syntax error before the use of real_escape_string.

Also, I'm now getting this error:

 Call to a member function close() on a non-object in /postarticle.php on line 37

Which refers to the close() call in the ELSE statement.

Please may you help? Been going round in circles for a while. Here is my code:

<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';

$connection = new mysqli($host,$user,$pass,$db);

$_SESSION["article"] = $_POST["article"];
$date_of_blog = getdate(); 

$article = ($_SESSION["article"]);

$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;

$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('$newrows','$article','$date_of_blog')";
$sql2 = $connection->real_escape_string($sql2);
$res2 = $connection->query($sql2);

if ($res2->num_rows == $newrows)
{
    $res->close();
    $connection->close();
    header( 'Location: adminpanel.php' );

}
else
{

echo ($connection->sqlstate);
$connection->close();
$res->close();
}

exit();


?>

Also, on a side note, the getdate() call that I've got has never worked. In the database every blog post comes up as: 0000:00:00 00:00:00

EDIT: Issue is now solved. Find the functional code below:

<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';


$connection = new mysqli($host,$user,$pass,$db);

$_SESSION["article"] = $_POST["article"];

$article = ($_SESSION["article"]);

$article = $connection->real_escape_string($article);

$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;

$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES (\"$newrows\",\"$article\",CURDATE())";
$res2 = $connection->query($sql2);

if ($res2 != false)
{
    header( 'Location: adminpanel.php' );
}
else
{   
    echo ($connection->sqlstate);
}
$connection->close();
$res->close();


exit();


?>

I'm very sorry if these questions are basic and annoy the professionals around here; I've tried to follow the guidelines and I've googled for a while etc. I just haven't found any solutions that match my issue(s). Thankyou for your time.

sparkhead95
  • 395
  • 4
  • 14
  • 2
    You must not call `real_escape_string()` on the entire SQL statement `$sql2`. Instead, you have to call it _individually_ on each of the variables used in the query. `$newrows','$article','$date_of_blog'` – Michael Berkowski Jul 16 '15 at 14:47
  • `$newrows = $connection->real_escape_string($newrows);`.... etc, for each variable. – Michael Berkowski Jul 16 '15 at 14:48
  • `real_escape_string` is not magic, it just escapes some stings. If it was possible to run the whole query trough it, it wouldn't be needed in the first place. – Vatev Jul 16 '15 at 14:48
  • The error on `$res->close()` is a result of the query failing because of the misuse of `real_escape_string()` – Michael Berkowski Jul 16 '15 at 14:49
  • @MichaelBerkowski Of course! It's so obvious now. Thankyou, I'll try it out now – sparkhead95 Jul 16 '15 at 14:49
  • @MichaelBerkowski Any idea about the getdate() situation? – sparkhead95 Jul 16 '15 at 14:52
  • `getdate()` returns an array, which is probably not what your db expects. – Marvin Jul 16 '15 at 14:54
  • @sparkhead95 [`getdate()`](http://php.net/manual/en/function.getdate.php) returns an array of date parts. Instead, you are looking to use `date('Y-m-d')` in the PHP side, OR replace it as `VALUES ('$newrows','$article',CURDATE())` in the SQL statement to use MySQL's native `CURDATE()` function (note there are no quotes surrounding) Or use `NOW()` instead of `CURDATE()` for a timestamp rather than date only. – Michael Berkowski Jul 16 '15 at 14:56
  • @MichaelBerkowski Thanks Michael I appreciate this a lot. However I have done the recommended changes and it is still falling into my ELSE clause. I'll post what I have in another comment: – sparkhead95 Jul 16 '15 at 15:03
  • `$article = ($_SESSION["article"]); $article = $connection->real_escape_string($sql2); $sql1 = "SELECT * FROM `Blogs`"; $res1 = $connection->query($sql1); $newrows = $res1->num_rows + 1; $sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES (\"$newrows\",\"$article\",CURDATE())"; $res2 = $connection->query($sql2); if ($res2->num_rows == $newrows) { $res->close(); $connection->close(); header( 'Location: adminpanel.php' ); } else { echo ($connection->sqlstate); $connection->close(); $res->close(); }` – sparkhead95 Jul 16 '15 at 15:05
  • @sparkhead95 I didn't notice that call to `$res2->num_rows`. What is your intent with that? It looks like you might be trying to validate that the `INSERT` was a success, but `$res2->num_rows` will _always_ be `1` because only one row is inserted. Did you mean to try to count the total number of rows in the table and ensure it is 1 greater than the previous query? That is unnecessary, because if the query does not succeed, `$res2` will be `FALSE`. – Michael Berkowski Jul 16 '15 at 15:06
  • @MichaelBerkowski Yes, I was trying to ensure that the query worked by ensuring that there is one more row than before. I have just checked my database and it seems like it is writing to it, and the date stamps are now working, however it seems that my $article" variable is null for some reason... – sparkhead95 Jul 16 '15 at 15:08
  • You used the wrong variable, $sql2 instead of $article: `$article = $connection->real_escape_string($sql2);` <-- should be `$article` there. Is your column `BlogID` an auto_increment? If so (and it appears it should be), you need not include it in the `INSERT` at all. You also need not do the first `SELECT *` query. Just `INSERT` and check `if ($res2)`. If it is `false`, the query failed. Otherwise it succeeded. – Michael Berkowski Jul 16 '15 at 15:11
  • @MichaelBerkowski I've just done a little debugging, and found that my blog post is being correctly stored in the $article **before** using real_escape_string, however **after** using it, the variable is null. EDIT: Yes, just seen I'm using the wrong variable. DOH. it's been a long day. – sparkhead95 Jul 16 '15 at 15:12
  • @MichaelBerkowski Thankyou, it now works. However I'd like to fix my IF statement so that it successfully checks if the SQL has worked. If you post your reply to this question as an official answer, I'll mark it as accepted and upvote, for all your help – sparkhead95 Jul 16 '15 at 15:15
  • @sparkhead95 Okay, I'll summarize all this into a proper answer. (you can't upvote yet though :-) ) – Michael Berkowski Jul 16 '15 at 15:19

2 Answers2

2

There are a number issues with the code as originally posted. Chiefly, the cause of the two issues you initially identified is a misuse of mysqli::real_escape_string(). It needs to be called on each variable individually which appears in the code. So instead of calling it on the whole statement, it must be called multiple times for multiple variables like:

$article = $connection->real_escape_string($connection);

The failure of the query due to incorrect quoting (due to real_escape_string()) is the reason for the error message calling close().

As ascertained in the comments, you are using num_rows + 1 to validate that one new row has been inserted based on the previous number of rows returned. This is problematic for a few reasons. Mainly, it exposes a race condition wherein a row may be inserted from two sessions at once and one or both will fail because the expected value for $newrows doesn't match. Really BlogID should be an auto_increment column in your database. That eliminates the need for any logic around it whatsoever. You don't even need to include it in the INSERT because it will be automatically incremented.

That also completely eliminates the need for the first SELECT statement.

Substituting MySQL's native NOW() function for the date value, you can simplify the statement to:

INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())

To test success or failure of the insert, you just need to verify that its variable is not false.

Putting this together, your code can be reduced as:

if (!isset($_POST['article'])) {
   // exit or handle an empty post somehow...
}
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];

// Escape $article for later use
$article = $connection->real_escape_string($_SESSION["article"]);

// Only an INSERT is needed. $article is already escaped
$sql = "INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())";
// Run the query
$res = $connection->query($sql);

// Test for failure by checking for a false value
if ($res) {
  // The connection & resource closure can be omitted
  // PHP will handle that automatically and implicitly.
  header( 'Location: adminpanel.php' );
  // Explictly exit as good practice after redirection
  exit();
}
else {
  // The INSERT failed. Check the error message
  echo $connection->error;
}

This should bring your current code into working order. However, since you're learning this it is an excellent time to begin learning to use prepared statements via prepare()/bind_param()/execute() in MySQLi. This is a recommended best practice to prevent SQL injection, although using real_escape_string() works as long as you use it correctly and never forget.

See How can I prevent SQL injection in PHP for examples.

But it would look like:

// connection already established, etc...
// Prepare the statement using a ? placeholder for article
$stmt = $connection->prepare("INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES (?, NOW())");
if ($stmt) {
  // bind in the variable and execute
  // Note that real_escape_string() is not needed when using
  // the ? placeholder for article
  $stmt->bind_param('s', $_SESSION['article']);
  $stmt->execute();

  // Redirect
  header( 'Location: adminpanel.php' );
  exit();
}
else {
  echo $connection->error;
}
Community
  • 1
  • 1
Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
  • Thanks again! Really appreciate it. You seem to know your stuff, it would be **really** useful to have an alternate means of getting in touch with you- just whilst I am creating this site; I've come across another issue now with deleting rows. It's probably a little thing too (it always is) – sparkhead95 Jul 17 '15 at 11:12
  • @sparkhead95 I keep [a note on my profile](http://stackoverflow.com/users/541091/michael-berkowski?tab=profile) about questions offsite and have to maintain a policy against them due to some past experiences. But the best thing is always to post questions here on SO for the community, and be sure to make them focused, well explained, and generally on single issues according to the [ask] guidelines. If the community doesn't adequately help right away, you can @ me here for other questions and I can take a look. – Michael Berkowski Jul 17 '15 at 15:58
1

You need to apply the real_escape_string function to the variables not the entire SQL string.

$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('".$connection->real_escape_string($newrows)."','".$connection->real_escape_string($article)."','".$connection->real_escape_string($date_of_blog)."')";

The purpose is to remove anything that might be misinterpreted as query functions by MySQL, but there are parts of the query that you obviously want to be interpreted as such.

Geoff Atkins
  • 1,693
  • 1
  • 17
  • 23