Jump to content

Recommended Posts

I have a download section in my index.php that I'd like to add a download counter to but have no idea how to accomplish this. The existing code works perfectly but I'd like to know how many times the file is downloaded. Would prefer not to use a database.

<!--  DOWNLOAD -->
<div id="download" class="stylized">
    <div "myform">
    <div class="container">
        <div class="row">
            <div class="download">
                <br /><br>
                <h1><center>FoxClone Download Page</center></h1>
                   <?php
                    $files = glob('download/*.iso');
                    $file = $files[count($files) -1];
                    $info = pathinfo($file);
                    $filename =  basename($file);
                    $filename = ltrim($filename,'/');
                    $md5file = md5_file($file);
                   ?>

                     <div class="container">
                        <div class="divL">                       
                            <h3>Get the "<?php echo "{$filename}";?>" file (approx. 600MB)</h3>
                        <center>  <a href="<?php echo "/{$file}";?>"><img src="images/button_get-the-app.png" alt=""></a> </center><br />
                            <h3 style="margin-bottom: 0.5rem;">The MD5sum for "<?php echo "{$filename}";?>" is "<?php echo "{$md5file}";?>  

Thanks in advance,

Larry

Edited by larry29936
added code tags
Link to comment
https://forums.phpfreaks.com/topic/310432-how-to-add-download-counter-to-my-code/
Share on other sites

16 minutes ago, larry29936 said:

Would prefer not to use a database.

Got bad news: you have some data you need to store and a database is the best place for it. Especially when you consider problems like concurrency.

The only thing I need to store is a number. Couldn't that be stored in a text file? When the download button is clicked, read the value in the text file, increment +1, and write it back to the text file? A read of the text file and display in text box.

You want text files for every single file? What happens when two people try to do a download at the same time?

If you have a database available to you and just want to avoid using it, that's not really a good enough reason and you might as well use it.
If you don't have a database available, think about it. It's one of those things where you'll probably discover that having a database turns out to be surprisingly useful.

If you really, really don't want a database, (a) I'd like to know why you're so firm on it, but (b) yes, there are alternatives.

This is my first stint as a "webmaster" and in many ways, ignorant. I'm learning as I go. So, I should go to my web host and set-up a mysql db for two single field tables, one for website hits and one for downloads? 

 

EDIT: If I capture the url for both, I can join the tables and know who visited AND downloaded. (It's been 20 years since I worked with any databases)

Edited by larry29936

Moving this to the PHP forum, even though it'll be SQL stuff for a bit.

 

By website hits are you talking about something other than this download stuff? Then yes, you would have two separate tables for the two separate events.

For the counter, don't use an actual counter in the database. Track individual downloads instead. You can get a counter by doing a count of matching records in the table - don't worry, it's fast. Also means you can do more than just total downloads, like downloads per month.
Because as a rule of thumb, Data Is Good. Get as much as you can. I don't mean tracking users or anything sketchy like that. I mean, if you have information (like a download happening) then you should store everything you can about it. Even if it doesn't seem useful now. Because if you decide later that, say, you'd like to get an overview of where in the world people are downloading your stuff, you can't go back in time and fill in data for event that have already happened.

So for downloads, create a table with:

1. An AUTO_INCREMENTing ID, because these sorts of things are handy to have around
2. The name of the downloaded file
3. The DATETIME of the download
4. The visitor's IP address, which can also help you deal with abuse problems (that hopefully won't happen)

For best performance, create a UNIQUE index on the filename. You can also create a regular INDEX on the filename and the download time (both fields), which will allow you to run fast queries when looking at both fields in the same query.

Tracking website hits will look very similar to that.

If you want a counter for a specific file, and just that file, you do a query to SELECT COUNT(1) from the downloads table WHERE the name of the file is whatever file you want to check. But more often you'll want counters for all files, in which case you can do one query to get all of them at once by SELECTing the name of the file with its COUNT(1) and having the query GROUP BY the file name; pull that information out into a PHP array and you can reference it when creating your HTML markup.

  • Thanks 1
40 minutes ago, larry29936 said:

EDIT: If I capture the url for both, I can join the tables and know who visited AND downloaded. (It's been 20 years since I worked with any databases)

Ah, but what if more than one person downloads the same file? Looking at just the URL won't work.

Are you using sessions yet? It's another thing to throw at you, but it does give you a particular benefit here: a unique identifier for each visitor. While you can store data in the session, you don't actually need to here.

With sessions in place, you can store the visitor's session ID in the database. That will let you key in to what individual people do - whether they visit and download, visit and don't download, browse around, and so on. You may not be particularly interested in implementing this just yet, but like I said: if you don't store the data now while you don't want it, you won't be able to fill it in later when you do want it.

If you aren't familiar with sessions, they're easy: call session_start at the beginning of every page on your site that you want to use sessions, and do it before you output anything. When you store information in your database, you get the session ID with session_id.

Thank you VERY much. 20 years ago, I was writing front-ends for industrial databases. Now you've triggered memories of how to build sql queries, joins, foreign-keys, etc. You also provided the knowledge of how to use the data on the website. Time for a little study before proceeding.

 

Thanks again, 

Larry

BTW-How do I mark this as solved?

 

Edited by larry29936
1 hour ago, larry29936 said:

What data type should I use for ip_address? I guess I should convert to a string and store as a varchar.

Yes. You could store it as a number, because technically they are numbers (kinda), but you don't need the advantages that storing as numbers gives - let alone the hassle of converting those numbers back into their pretty, human-readable forms.

Does this look right?

<?php
session_start()
function getUserIpAddr(){
    if(!empty($_SERVER['HTTP_CLIENT_IP'])){
        //ip from share internet
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
        //ip pass from proxy
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }else{
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}

$session = array[ses]
$ses[] = $session_id;
$ses[] = $ip;

INSERT INTO LOGIN (session_id, when_login, ip_address)
VALUES ($ses[0], now(), ses[1] );

?>
<!DOCTYPE html>

 

 

Essentially. You need to be calling the getUserIpAddr function (and assigning the result to a local $ip variable), $session_id is a function not a variable, and there's no need for a $session array if you're just sticking variables into the array and then pulling the values right back out again.

So, like this?

<?php
session_start()
function getUserIpAddr(){
    if(!empty($_SERVER['HTTP_CLIENT_IP'])){
        //ip from share internet
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
        //ip pass from proxy
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }else{
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}


INSERT INTO LOGIN (session_id, when_login, ip_address)
VALUES (session_id(), now(), $ip );

?>
<!DOCTYPE html>

Actually, here's what I think it should be:


 

<?php
$con = mysql_connect("localhost","xxxxx","yyyyyyyyy");
if (!$con)
  {
    die('Could not connect: ' . mysql_error());
  }
 mysql_select_db("foxclone_data", $con);


function getUserIpAddr(){
    if(!empty($_SERVER['HTTP_CLIENT_IP'])){
        //ip from share internet
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
        //ip pass from proxy
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }else{
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}
session_start()

INSERT INTO LOGIN (when_login, ip_address)
VALUES (now(), $ip );

?>

<!DOCTYPE html>


What do you think? My next question is, do I keep the session open so I can capture any download information or close it and open a new session for downloads?

Edited by larry29936

Here is what I would do:

// start of script every time.
<?php
session_start();
// DO NOT USE THE MYSQL EXTENSION.  IT IS OUTDATED AND NO LONGER VALID.
$con = mysql_connect("localhost","xxxxx","yyyyyyyyy");
if (!$con)
{
	die('Could not connect: ' . mysql_error());
}
$ip = GetUserIpAddr();
mysql_select_db("foxclone_data", $con);
//  build a query statement
$q = "INSERT INTO LOGIN (when_login, ip_address)
		VALUES (now(), '$ip' )";
//	Run your query - personally I use PDO since it is easier than mysqlI
//	Check the result of your query - Did it fail?
//	Create a message informing user of success or failure
//  do your html coding here, placing the message variable where it fits.
exit();
//**************************
function GetUserIpAddr()
{
	if(!empty($_SERVER['HTTP_CLIENT_IP']))
	{
		//ip from share internet
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	}
	elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
	{
		//ip pass from proxy
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
	}
	else
	{
		$ip = $_SERVER['REMOTE_ADDR'];
	}
	//  What if you don't find any ip address?
	return $ip;
}

Note my comments.  One normally (?) begins a session at the top of every script and usually doesn't worry about ending it since PHP will do that for you.

And again - the mysql extension has been deprecated for several years and removed as of php version 7 (or earlier).  Most host provide mysqlI or PDO.  Here on this forum PDO seems to be the preferred one and I agree with that opinion.  It is pretty easy to use and handles prepared queries quite well.  Read the manual

https://www.php.net/manual/en/class.pdo

This is a good thing to write a function for and use in all of your scripts.  Add a dbname to the function arguments too.

 

@ginerjm , I don't understand how $q is being executed in your example. I also don't understand how it knows how the database connection is made without the mysql_connect parameter. I'd appreciate an explanation.

Edited by larry29936

Is this anywhere near being correct?

<?php
session start();

$servername = "localhost";
$username = "xxxxxxx";
$password = "yyyyyyy";
$dbname = "foxclone_data";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    // set the PDO error mode to exception
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sql = "INSERT INTO LOGIN (when_login, ip_address)
        VALUES (now(), '$ip' )";
    // use exec() because no results are returned
    $conn->exec($sql);
    echo "New record created successfully";
    }
catch(PDOException $e)
    {
    echo $sql . "<br>" . $e->getMessage();
    }
$conn = null;

$ip = GetUserIpAddr();

function GetUserIpAddr()
{
    if(!empty($_SERVER['HTTP_CLIENT_IP']))
    {
        //ip from share internet
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }
    elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
        //ip pass from proxy
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    else
    {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    
    return $ip;
}
?>

<!DOCTYPE html>
etc, etc

 

 

I would write a good set of connection code and make it a function by itself.

Then I would call that function to start a connection do my query and processing outside of it.

I would also improve that error message to tell you where and what happened but without the details so you don't let users see it.

And when I do a query I write it so that I can check the results.  Something like:

$qresults = $pdo->query($q);

if (!$qresults)
{
	//  handle error
}

 

This is what my function looks like:

function PDOConnect($l_dbname=NULL, $l_msg=null, $l_options=null)
{
	//  PDO requires it to be enabled in php.ini
	//  add this to the ini file:
	/*
	extension=pdo.so
	extension=pdo_sqlite.so
	extension=sqlite.so
	extension=pdo_mysql.so
	*/
	if ($l_options == null)
	{	// set my default options
	  $l_options = array(PDO::ATTR_EMULATE_PREPARES => false,
			PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
			PDO::MYSQL_ATTR_FOUND_ROWS => true,
			PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
	}
	if ($l_dbname == null)
		$host="mysql:host=localhost;charset=utf8";
	else
		$host="mysql:host=localhost;dbname=$l_dbname;charset=utf8";
	$uid = "xxx";
	$pswd = "xxx";
	try
	{
		$mysql = new PDO($host, $uid, $pswd, $l_options);
	}
	catch (PDOException $e)
	{
		if (strtoupper($l_msg) == "SHOWMSG")
			echo "Fatal Error<br>Failed to connect to mysql via PDO.  PDO Error msg is:<br>".$e->getMessage();
		else
			echo "Fatal Error<br>Possible bad dbname?<br>Failed to connect to mysql via PDO.  Sensitive error msg may be viewed with additional parm to call to PDOConnect(dbname,'showmsg')";
		return false;
	}
	if (!$mysql)
		return false;
	else	// all worked - return handle to pdo connection.
		return $mysql;
}

Basically the only arg I use is the dbname when calling this.  It is designed to allow for future usages though.  The basic call I use is

if (!$pdo = PDOConnect("my_dbname")
{
	//show some kind of message
	//  exit?
}
// proceed with $pdo as the handle to all database interactions

 

Based on what you provided, here's the start of my index.php:

<?php
$ip2 = GetUserIpAddr();
function GetUserIpAddr()
{
	if(!empty($_SERVER['HTTP_CLIENT_IP']))
	{
		//ip from share internet
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	}
	elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
	{
		//ip pass from proxy
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
	}
	else
	{
		$ip = $_SERVER['REMOTE_ADDR'];
	}
	
	return $ip;
}

function PDOConnect($l_dbname="foxclone_data", $l_msg=null, $l_options=null)
{
	
	if ($l_options == null)
	{	// set my default options
	  $l_options = array(PDO::ATTR_EMULATE_PREPARES => false,
			PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
			PDO::MYSQL_ATTR_FOUND_ROWS => true,
			PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
	}
	if ($l_dbname == null)
		$host="mysql:host=localhost;charset=utf8";
	else
		$host="mysql:host=localhost;dbname=$l_dbname;charset=utf8";
	$uid = "xxxxxxx";
	$pswd = "yyyyyyyyy";
	try
	{
		$mysql = new PDO($host, $uid, $pswd, $l_options);
	}
	catch (PDOException $e)
	{
		if (strtoupper($l_msg) == "SHOWMSG")
			echo "Fatal Error<br>Failed to connect to mysql via PDO.  PDO Error msg is:<br>".$e->getMessage();
		else
			echo "Fatal Error<br>Possible bad dbname?<br>Failed to connect to mysql via PDO.  Sensitive error msg may be viewed with additional parm to call to PDOConnect(dbname,'showmsg')";
		return false;
	}
	if (!$mysql)
		return false;
	else	// all worked - return handle to pdo connection.
		return $mysql;
}
$ip2 = string($ip)
session start();
if (!$pdo = PDOConnect("foxclone_data")
{
	echo "Failed to connect to database"
	//  exit?
}
// proceed with $pdo as the handle to all database interactions
else
 {
    $sql = "INSERT INTO LOGIN (ip_address)
		VALUES ('$ip2' )";
    // use exec() because no results are returned
    $pdo->exec($sql);
    echo "New record created successfully";
    }
catch(PDOException $e)
    {
    echo $sql . "<br>" . $e->getMessage();
    }

?>

<!DOCTYPE html>

How many mistakes did I make? I did add the indicated entries in php.ini.

Edited by larry29936

Here is my second demo.  Please note the following changes.

You don't put the dbname in the function header.  You supply the name in the call to the function.

Also - the connection function is something you should store in a folder along with all of your other generalized utility scripts that will be used in many of your future scripts.  Somewhere outside of the root tree so that nobody can get there from a browser connection.  You then always include it using a 'require' statement that points to that folder and script.  You can include the uid and pswd in the script because nobody will be able to see it from the web.

// start of script every time.
session start();
//  setup a path for all of your canned php scripts
$php_scripts = '/home/user/php/'; // a folder above the web accessible tree
//  load the pdo connection module  
require $php_scripts . 'PDO_Connection_Select.php';
//*******************************
//   Begin the script here
$ip2 = GetUserIpAddr();
if (!$pdo = PDOConnect("foxclone_data")
{
	echo "Failed to connect to database"
	//  exit?
}
else
{
	$sql = "INSERT INTO LOGIN (ip_address)
			VALUES ('$ip2' )";
	// use exec() because no results are returned
	$pdo->exec($sql);
	echo "New record created successfully";
}
catch(PDOException $e)
{
	echo "Query failed to run properly: $sql <br><br>" . $e->getMessage();
}
exit();
//*****************************************
//   functions below
//******************************************
function GetUserIpAddr()
{
	if(!empty($_SERVER['HTTP_CLIENT_IP']))
	{
		//ip from share internet
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	}
		elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
	{
		//ip pass from proxy
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
	}
	else
	{
		$ip = $_SERVER['REMOTE_ADDR'];
	}
	return $ip;
}
//******************************************
//   This code s/b removed once you place it into the php folder 
//   and rely on the require line at the top.
function PDOConnect($l_dbname, $l_msg=null, $l_options=null)
{
	if ($l_options == null)
	{	// set my default options
		$l_options = array(PDO::ATTR_EMULATE_PREPARES => false,
		PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
		PDO::MYSQL_ATTR_FOUND_ROWS => true,
		PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
	}
	if ($l_dbname == null)
		$host="mysql:host=localhost;charset=utf8";
	else
		$host="mysql:host=localhost;dbname=$l_dbname;charset=utf8";
	$uid = "xxxxxxx";
	$pswd = "yyyyyyyyy";
	try
	{
		$mysql = new PDO($host, $uid, $pswd, $l_options);
	}
	catch (PDOException $e)
	{
		if (strtoupper($l_msg) == "SHOWMSG")
			echo "Fatal Error<br>Failed to connect to mysql via PDO.  PDO Error msg is:<br>".$e->getMessage();
		else
			echo "Fatal Error<br>Possible bad dbname?<br>Failed to connect to mysql via PDO.  Sensitive error msg may be viewed with additional parm to call to PDOConnect(dbname,'showmsg')";
		return false;
	}
	if (!$mysql)
		return false;
	else	// all worked - return handle to pdo connection.
		return $mysql;
}

 

@ginerjm - A couple of questions for you after you review my PDO_Connection_Select.php and index.php

//PDO_Connection_Select.php

<?php

function PDOConnect($l_dbname, $l_msg=null, $l_options=null)
{
    if ($l_options == null)
    {    // set my default options
        $l_options = array(PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_FOUND_ROWS => true,
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
    }
    if ($l_dbname == null)                        // <---------------- In my case, this is resolving to true. Do I need to set l_dbname above here?
        $host="mysql:host=localhost;charset=utf8";
    else
        $host="mysql:host=localhost;dbname='foxclone_data';charset=utf8";
    $uid = "xxxxx";
    $pswd = "yyyyy";
    try
    {
        $mysql = new PDO($host, $uid, $pswd, $l_options);
    }
    catch (PDOException $e)
    {
        if (strtoupper($l_msg) == "SHOWMSG")
            echo "Fatal Error<br>Failed to connect to mysql via PDO.  PDO Error msg is:<br>".$e->getMessage();
        else
            echo "Fatal Error<br>Possible bad dbname?<br>Failed to connect to mysql via PDO.  Sensitive error msg may be viewed with additional parm to call to PDOConnect(dbname,'showmsg')";
        return false;
    }
    if (!$mysql)
        return false;
    else    // all worked - return handle to pdo connection.
        return $mysql;
}

?>
// index.php

<?php
session_start();
// start of script every time.

//  setup a path for all of your canned php scripts
$php_scripts = '/home/foxclone/php/'; // a folder above the web accessible tree
//  load the pdo connection module  
require $php_scripts . 'PDO_Connection_Select.php';
require $php_scripts . 'GetUserIpAddr.php';
//*******************************
//   Begin the script here
$ip = GetUserIpAddr();
if (!$pdo = PDOConnect("foxclone_data")
{
    echo "Failed to connect to database"    //<--------------------------------   Errors here ("Parse error: syntax error, unexpected 'echo' (T_ECHO) in /home/foxclone/test.foxclone.com/index.php on line 15")
    //  exit?
}
else
{
    $sql = "INSERT INTO LOGIN (ip_address)
            VALUES ('$ip2' )";
    // use exec() because no results are returned
    $pdo->exec($sql);
    echo "New record created successfully";
}
catch(PDOException $e)
{
    echo "Query failed to run properly: $sql <br><br>" . $e->getMessage();
}
exit();

?>

Now for the questions.

Why isn't the db_name getting passed to PDO_Connection_Select.php?

Why the error on the echo of the failure message in index.php?

 

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.