Help: Fatal error: Uncaught Error: Call to a member function bind_param() on bool!

I am having some issues with a bit code I wrote. Here is the code:

require "code/core.php";

$ip = $_POST['ip'];

function getIPAddress() {  
        if(!empty($_SERVER['HTTP_CLIENT_IP'])) {  
            $ip = $_SERVER['HTTP_CLIENT_IP'];  
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {  
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];  
        } else {  
            $ip = $_SERVER['REMOTE_ADDR'];  
        }  
        return $ip;  
    }  
    $ip = getIPAddress();

$stmt = $db_core->prepare('SELECT COUNT(*) FROM downloads WHERE ip = ?');
$stmt->bind_param('s', $ip);
$stmt->execute();
$stmt->bind_result($count);
$stmt->fetch();

if($count == 0) {
    $stmt = $db_core->prepare('INSERT INTO downloads (ip) VALUES (?)');    
    $stmt->bind_param('s', $ip);
    $stmt->execute();      
} else {        
   $error = "<span class='error_msg'>You can only download once every 24 hours per IP</span>"; 
   echo $error;
   exit;
}

Okay, basically I am getting the user’s IP. Then checking the database to see if it already exists, and throw an error if it does. That part sort of works, except I can’t get the error message to display properly and it thinks the error var is a URL and prints it in the address bar. I have no idea why…

Then if the IP does NOT exist in the database, then insert it into the “downloads” table. But this throws the following error:

Fatal error: Uncaught Error: Call to a member function bind_param() on bool in 
/var/www/vhosts/xxx.net/httpdocs/xx/xx/down.php:34 Stack trace: #0 {main} thrown in 
/var/www/vhosts/xxx.net/httpdocs/xxx/down.php on line 34

What the heck is going on? The insert command works if I leave out the IP exists check. Is it because I have two identical “bind_param” commands? Any help/tips would be most appreciated!!

The error that is preventing the prepare() call from working is probably due to something higher up in the code (there’s not even 34 lines in the posted code), such as a query where all the data wasn’t fetched.

You always need error handling for statements that can fail. For database statements that can fail - connection, query, prepare, and execute, the simplest way of adding error handling, without adding code at each statement, is to use exceptions for errors and in most cases simply let php catch and handle the exception, where php will use its error related settings to control what happens with the actual error information (database statement errors will ‘automatically’ get displayed/logged the same as php errors.)

To enable exceptions for errors for the mysqli extension, add the following line of code before the point where you make the database connection -

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
1 Like

This is the function with the error, I indicated line 34:

if($count == 0) {
    $stmt = $db_core->prepare('INSERT INTO downloads (ip) VALUES (?)');    
   **line 34** --> $stmt->bind_param('s', $ip);
    $stmt->execute();      
} else {        
   $error = "<span class='error_msg'>You can only download once every 24 hours per IP</span>"; 
   echo $error;
   exit;
}

I added the code you showed me. Now I get an error on the line above 34:

$stmt = $db_core->prepare('INSERT INTO downloads (ip) VALUES (?)');

And the new error:

**Fatal error** : Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command now in /var/www/vhosts/xxxx.net/httpdocs/xxxx/down.php:35 Stack trace: #0 /var/www/vhosts/xxx.net/httpdocs/xxx//down.php(35): mysqli->prepare() #1 {main} thrown in **/var/www/vhosts/xxx.net/httpdocs/xx/down.php** on line **35**

? :sunglasses:

Just reviewed your previous threads. This is the same issue as here - Very strange issue with validating info from form and DB!

The line of code that enables exceptions for errors for the mysqli extension, leave that in, so that you will ALWAYS have error handling for the database statements that can fail, you will save a ton of time.

1 Like

Okay, weird. I’m getting the echoed error if the IP is NOT in the DB. This should be the opposite, as the point is if the IP does exist, then produce the error. Not if IP doesn’t exist in DB. Subsequent tries, if the IP already exists, it errors out, except the error is somehow converted to a URL and shown in the browser’s address bar, not echoed as usual.

What it is supposed to do is if IP does NOT exist, add it to DB table, then fetch file. If the IP exists, then produce and echo the error, then exit.

Here is the full code for reference:

require "code/core.php";

$ip = $_POST['ip'];

function getIPAddress() {  
        if(!empty($_SERVER['HTTP_CLIENT_IP'])) {  
            $ip = $_SERVER['HTTP_CLIENT_IP'];  
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {  
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];  
        } else {  
            $ip = $_SERVER['REMOTE_ADDR'];  
        }  
        return $ip;  
 }  
 $ip = getIPAddress();

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

$stmt = $db_core->prepare('SELECT COUNT(*) FROM downloads WHERE ip = ?');
$stmt->bind_param('s', $ip);
$stmt->execute();
$stmt->bind_result($count);
$stmt->fetch();
mysqli_stmt_close($stmt);

if($count == 0) {
    $stmt = $db_core->prepare('INSERT INTO downloads (ip) VALUES (?)');
    $stmt->bind_param('s', $ip);
    $stmt->execute();
} else {        
   $error = "You can only download once every 24 hours per IP!"; 
   echo $error;
   exit;
}

$zip = new ZipArchive();
$filename = "xxx.zip";

if ($zip->open($filename, ZipArchive::CREATE)!==TRUE) {
    exit("cannot open <$filename>\n");
}

$dir = 'xxxx/';

if (is_dir($dir)){

    if ($dh = opendir($dir)){
        while (($file = readdir($dh)) !== false){    
            if (is_file($dir.$file)) {
                if($file != '' && $file != '.' && $file != '..'){
                    $zip->addFile($dir.$file);
                }
            }
                
        }
        closedir($dh);
    }
}

$zip->close();

echo $filename;

Something must be out of order. I’m stuck. Further assistance is most appreciated!

Add var_dump($count); right before the if($count == 0) { line to see what exactly is in $count, both for the initial symptom and the later symptom.

For second symptom, how exactly are you requesting this page and is there anything in core.php besides the database connection code?

Edit: also, what php version are you using, because php8 changed how a loose == string comparison with 0 works?

1 Like

The var is “int(1)” plus the error message. Is that strange?

I have a form inside the file download.php:

<form method="post" action="down.php" id='download'>
<input type='image' src='images/cooltext414192685910710.png'>
</form>

If the validation checks out (their IP has NOT been used to download), then a file transfer takes place. Later I’m going to add a time limit where they can only download like once every 24-48 hours. But I want to get the base code working first. There is also some JS in that file:

<script src="code/jquery-3.2.1.min.js"></script>
      <script >
            $(document).ready(function(){
                $('#download').click(function(){
                    $.ajax({
                        url: 'down.php',
                        type: 'post',
                        success: function(response){
                            window.location = response;
                        }
                    });
                });
            });
        </script>

I’m using PHP 7.4.30

This is the reason for the address bar changing to the error message, this code is trying to “go to” wherever the server-side code is telling it.

To handle the case of an error vs the download redirect, you would output a json encoded value from the server-side code, such as error.the error message here or success.the file name here. The javascript would test the return value. If it’s the error… value, display it. If it’s the succcess… value, perform the redirect.

As to the rest of the symptoms, the type=‘image’ input is a submit button. Since the javascript is not preventing the default action, the ajax code is submitting to the server-side code and the form is submitting a second time to the server side code. In the javascript you need to prevent the default form action.

An alternative for the image would be to just output it as an <img tag, with that id, and the click event should (untested) still trigger the javascript code.

Hmmm…this seems beyond my skills. I think I’ll just revert the code back to a simple download function. :frowning:

The form page would need to be -

<form method="post" action="down.php" id='download'>
<input type='image' src='images/cooltext414192685910710.png'>
</form>

<div id='response'></div>

<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous"></script>
<script >
$(document).ready(function(){
	$('#download').click(function(){
		$.ajax({
			url: 'down.php',
			type: 'post',
			dataType: 'json',
			success: function(response){
				if(response.error){
					$("#response").html(response.error);
				}
				if(response.success){
					window.location = response.success;
				}
			}
		});
		// prevent default form action
		return false;
	});
});
</script>

At the point of echoing the error, do this -

   $error = "You can only download once every 24 hours per IP!"; 
   echo json_encode(['error'=>$error]);
   exit;

At the point of echoing the filename -

echo json_encode(['success'=>$filename]);
1 Like

All I really need now is a simple CRUD to delete ALL entries from a particular column in a table. Can you assist? Thanks!

BTW, I really appreciate your polite and very helpful assistance! Besides the basic CRUD, is there any chance to show a “thank you” page after the user clicks the download button and has no errors (and the transfer begins)?

There are countless delete query examples to be found on the web for you to examine.

The code would -

  1. Need to be secured by a login system so that only a logged in user, with administrative permissions, can see or perform the delete operation.
  2. Use a post method form. A lot of examples you are likely to find are incorrectly using a link to delete data.

Nope. The http(s) request to the download file and the response of outputting the download file is the only thing that can occur for that request.

1 Like

Yeah I figured so, but just thought I’d ask! :smile:

Great, thanks! I have some working examples from other scripts on my site. I’ll check it out.

Again, I really appreciate your help!! :100:

This IP checking to download a file is pointless because people who uses VPN will bypass the download limitation due the IP address being changed every time they change location on the VPN.

1 Like

Yup, I realized that. I think I’ll remove that and maybe add something else like an e-mail that is saved in the DB and then compared, Not much better I suppose though.

Do you have another suggestion?

Sponsor our Newsletter | Privacy Policy | Terms of Service