By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,346 Members | 2,331 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,346 IT Pros & Developers. It's quick & easy.

Download files without <a>

nathj
Expert 100+
P: 938
Hi,

I am working on a system, as you may have seen from my other posts, that allows members to download files. When they download a file I want to store the user ID and the file ID in a table along with the last download time and how many times they have downloaded.

I have the SQL for this already - so no problems there.

Unfortunately you can't use AJAX for this as the code in the onclick() won't fire if there is a URL in the href - the default handler for the URL is fired in stead.

So my idea was simple. Have the link go to a php page that updates the table in the database, downloads the file and then returns the user to where they were originally.

This leads me to my question. How do you download the file in PHP? It may a php file, an m3u file or anything for that matter.

I've read around the low level file functions but all this will do, as far as I can tell, is put the file contents into a local variable. How can I get the file to the clients PC and store the fact that they have done so?

Any help, suggestions or pointer greatly appreciated.

Cheers
nathj

PS How do you get the email notification and signature to set for every post - I have to keep editing my posts to set it?
Sep 5 '07 #1
Share this Question
Share on Google+
12 Replies


ak1dnar
Expert 100+
P: 1,584
How do you get the email notification and signature to set for every post - I have to keep editing my posts to set it?
Control Panel >> New Subscribed Threads >> Set the notification check box for threads, and bottom of the panel you can find instant email notifications.
Sep 5 '07 #2

ronnil
Expert 100+
P: 134
Hi nathj

The answer is in fact painfully simple. What you need is headers (I don't remember your other posts so I don't know how much you have messed with this)

this should get you going (hopefully)

this file we call "download.php"
Expand|Select|Wrap|Line Numbers
  1. <?php
  2. /*
  3. We'll pretend that info about the file is stored in the table "file_table", here we need at least to store the mimetype and the filename. The file is in this case actually stored another place on your server, but could might as well be stored inside the database. for simplicity the files are stored in the same directory as this one (download.php), with their own filename. I'd suggest you name them uniquely, e.g. give them the time uploaded as prefix
  4. */
  5.  
  6. //Get the file id using $_GET
  7. $fid = $_GET['fid'];
  8. //we'll just pretend that your userid is stored in $_SESSION['uid']
  9. $uid = $_SESSION['uid'];
  10.  
  11. //insert the file is downloaded
  12. $sql = "INSERT INTO file_downloaded (uid,fid) VALUES (" . $uid . "," . $fid . ")";
  13. $result = mysql_query($sql) or die('Could not update, you can\'t have the file :P');
  14. //only give the user the file if the query was succesful
  15. if($result)
  16. {
  17.     $sql = "SELECT * FROM file_table WHERE fid=" . $fid . " LIMIT BY 0,1";
  18.     $result = mysql_query($sql) or die('Could not find file');
  19.  
  20.     if($result && mysql_num_rows($result) <> 0)
  21.     {
  22.         $row = mysql_fetch_assoc($result);
  23.         //Tells the browser the mime-type of the file.
  24.         header('Content-type:' . $row['mime_type']);
  25.         //Tells the browser the size of the file.  A kind gesture to let people know how long they have to wait until downloads finish
  26.  
  27.     header('Content-length: ' . $row['file_size']);
  28.         //Tells the browser to download no matter what and gives the correct filename in "save as"
  29.  
  30.     header('Content-disposition: attachment; Filename="' . $row['filename'] . '"');
  31.         //reads the file and outputs it directly. you could also 
  32.         //echo fread(fopen($row['filename'],'r'),filesize($row['filename']));
  33.         //or
  34.  
  35.         //echo $row['file_content'];
  36.         //if you stored the file in a db
  37.     readfile($row['filename']);
  38.     }
  39. }
  40.  
  41. ?>
  42.  
This should be working (haven't tested it). You and others may use it freely :)

Hope it helps :)
Sep 5 '07 #3

ronnil
Expert 100+
P: 134
heh.. forgot to say

You link to this file, either with onclick or with <a href=""></a>, either way you put the file id in the query like http://yoursite.com/path/to/download.php?fid=3245

and then you should get a download query
Sep 5 '07 #4

pbmods
Expert 5K+
P: 5,821
Heya, nathj.

Simply add 'return false;' to the onclick handler of the anchor tag:
Expand|Select|Wrap|Line Numbers
  1. <a href="http://gohereifnojavascript/" onclick="window.location.href='nogohereinstead'; return false;">Check it out!</a>
  2.  
Sep 5 '07 #5

gregerly
Expert 100+
P: 192
Hey Nathj,

There is a new buzz word floating around call "hijax", the act of javascript hijacking a normal <a> tag. The idea is that you link to the file normally with the href att. so if javascript isn't available, your user will still be able to get the file. If javascript is available, it will "Hijak" the using the onclick. The key point to this is that the javascript needs to have "return false" as pbmods already mentioned. This will make the href not fire, and you can do what you want with your javascript. Personally, I think hijax is the way to go as it doesn't assume the existence of javascript, and users can still get their content if no javascript is available.

Greg
Sep 5 '07 #6

nathj
Expert 100+
P: 938
Hi Everyone,

That's loads of help thank you to all of you. I think I am going to go the way Ronnil suggests. This could be exactly hat I'm after. If I get stuck I'll post back.

Thanks again to everyone for the help. It's sent me off trawling the net and reading all sorts of stuff, so that's great.

Cheers
nathj
Sep 6 '07 #7

gregerly
Expert 100+
P: 192
Hi Everyone,

That's loads of help thank you to all of you. I think I am going to go the way Ronnil suggests. This could be exactly hat I'm after. If I get stuck I'll post back.

Thanks again to everyone for the help. It's sent me off trawling the net and reading all sorts of stuff, so that's great.

Cheers
nathj
Hey Nathj,

I'm pretty sure that way will work, but an empty href in an <a> tag may cause validation to fail (if that's something you care about).

Greg
Sep 6 '07 #8

nathj
Expert 100+
P: 938
Hi,

I've now had a play around and guess what - I've got a bit stuck again.

Here's the inside scoop:

1. I have a file download.php which displays the files to download. In this there is a line for the file that looks like this:
[html]
<a id="3" class="inlineLink" href="lib/recorddownload.php?item=3" title="What Good Looks Like - Session 1(mp3)">What Good Looks Like - Session 1(mp3)</a>
[/html]

2. I have a file recorddownload.php which records the fact that a member downloaded a file and then runs the download:
[php]
<?php
session_start();
/*
File: lib/recorddownload.php
Author: Nathan Davies
Created: 09/08/2007
Purpose: To record when a member downloads a file
*/

// the calling file
$lcCallingFile = $_SERVER['HTTP_REFERER'] ;

// Update the database
if(!empty($_GET['item']))
{
require_once($_SERVER['DOCUMENT_ROOT'].'/lib/dataobjects.php'); // any page may then create a data connection for that page. This keeps connections active only for as long as they are required
$loDB = new dataObject("database", "user", "password", "connection");

// get the variables and information relating to the supplied file ID
$lnFileID = $_GET['item'] ;
// array of extension to mime types
$laExtList = array (

// archives
'zip' => 'application/zip',

// documents
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',

// executables
'exe' => 'application/octet-stream',

// images
'gif' => 'image/gif',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',

// audio
'mp3' => 'audio/mpeg',
'm3u' => 'audio/x-mpegurl',
'wav' => 'audio/x-wav',

// video
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo'
) ;

// now establish the file information from the database
$lcSelectFile = "
SELECT
a.name, a.URL
FROM download a
WHERE a.ID = $lnFileID" ;

$laFileInfo = $loDB->queryGetData($lcSelectFile) ;

if($laFileInfo)
{
foreach($laFileInfo as $lcFile)
{
$lcFileName = $lcFile['name'] ;
$lcFilePath = $lcFile['URL'] ;
$lcFileExt = substr($lcFilePath, strpos($lcFilePath, '.') + 1) ;
$lcMimeType = $laExtList[$lcFileExt] ;
}

// update the database
$lnLeaderID = $_SESSION['userID'] ;
// first check that the leader does not already have the download associated with them, if they do there is no reason to load it again.
$lcCheckSQL = "SELECT a.ID, a.frequency + 1 as newFrequency from leaderdownloadassociation a WHERE a.leaderID = $lnLeaderID AND a.downloadID = $lnFileID" ;
$laAssociation = $loDB->queryGetData($lcCheckSQL) ;
if($laAssociation)
{
// update the frequency
$lnFrequency = $laAssociation[0]['newFrequency'] ;
$lcUpdate = "UPDATE leaderdownloadassociation set frequency = $lnFrequency, editdate = now() WHERE leaderID = $lnLeaderID AND downloadID = $lnFileID" ;
$loDB->iQuery($lcUpdate) ;
}
else // first download
{
$lcTableName = "leaderdownloadassociation" ;
$lcFieldList = "leaderID, downloadID, frequency, createdate, editdate" ;
$lcValueList = "$lnLeaderID, $lnFileID, 1, now(), now()";
$loDB->queryInsert($lcTableName, $lcFieldList, $lcValueList, false) ;
}

// download the file
header("Content-Type: " . $lcMimeType);
header("Content-Length: " . filesize($lcFilePath));
header('Content-Disposition: attachment; filename="' . $lcFileName .'"');
readfile($lcFilePath);
}
}

// return to the calling page
header("Location:$lcCallingPage");

?>
[/php]

The database updates perfectly - that's great, the only trouble is I get an error when I come to the downloading part of the process:
[html]
<html><head></head><body><pre>&lt;br /&gt;
&lt;b&gt;Warning&lt;/b&gt;: filesize() [&lt;a href='function.filesize'&gt;function.filesize&lt;/a&gt;]: stat failed for http://www.christianleadership.org.uk/m3u/eddiegibbs_session1.m3u in &lt;b&gt;/homepages/29/d210214908/htdocs/lib/recorddownload.php&lt;/b&gt; on line &lt;b&gt;97&lt;/b&gt;&lt;br /&gt;

&lt;br /&gt;
&lt;b&gt;Warning&lt;/b&gt;: Cannot modify header information - headers already sent by (output started at /homepages/29/d210214908/htdocs/lib/recorddownload.php:97) in &lt;b&gt;/homepages/29/d210214908/htdocs/lib/recorddownload.php&lt;/b&gt; on line &lt;b&gt;97&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;

&lt;b&gt;Warning&lt;/b&gt;: Cannot modify header information - headers already sent by (output started at /homepages/29/d210214908/htdocs/lib/recorddownload.php:97) in &lt;b&gt;/homepages/29/d210214908/htdocs/lib/recorddownload.php&lt;/b&gt; on line &lt;b&gt;98&lt;/b&gt;&lt;br /&gt;
http://www.christianleadership.org.uk/download/audio/eddiegibbs_session1.mp3&lt;br /&gt;

&lt;b&gt;Warning&lt;/b&gt;: Cannot modify header information - headers already sent by (output started at /homepages/29/d210214908/htdocs/lib/recorddownload.php:97) in &lt;b&gt;/homepages/29/d210214908/htdocs/lib/recorddownload.php&lt;/b&gt; on line &lt;b&gt;104&lt;/b&gt;&lt;br /&gt;
</pre></body></html>
[/html]

This is all a bit of mystery to me. I guess what this is saying is that headers have already been set so you can't set any more. But I'm not sure where they have been set or if they have been set.If anyone could explain this to me that would be fantastic.

On the plus side all these troubles are helping me to get a better understanding of how PHP works in this regard.

Cheers
nathj
Sep 6 '07 #9

nathj
Expert 100+
P: 938
Hi,

Well A new problem now. Having played around with the code all afternoon I can now get the download correct using headers but I can't get the database to update. Alternatively I can get the database to update but the downloaded file is a php file listing header errors because content has already been sent.

So here's the state of play at present:

The Call
[html]
<a id="3" class="inlineLink" href="lib/recorddownload.php?item=3&amp;id=1&amp;extra=eddie gibbs_session1.m3u&amp;extra2=audio/x-mpegurl&amp;extra3=76" title="What Good Looks Like - Session 1(mp3)">What Good Looks Like - Session 1(mp3)</a>
[/html]

The PHP code:
[php]
<?php
/*
File: lib/recorddownload.php
Author: Nathan Davies
Created: 09/08/2007
Purpose: To record when a member downloads a file
*/
// the calling file
$lcCallingFile = $_SERVER['HTTP_REFERER'] ;
$lcBaseCall = substr($lcCallingFile, 1, strpos($lcCallingfile, '?')-1) ;
// download the file
header("Content-Type: " . $_GET['extra2']);
header("Content-Length: " . $_GET['extra3']);
header('Content-Disposition: attachment; filename="' . $_GET['extra'] .'"');
readfile($lcFilePath);
// update the db
require_once($_SERVER['DOCUMENT_ROOT'].'/lib/dataobjects.php');
$loDB = new dataObject("database", "user", "password", "server");

$lnLeaderID = $_GET['id'] ;
// first check that the leader does not already have the download associated with them, if they do there is no reason to load it again.
$lcCheckSQL = "SELECT a.ID, a.frequency + 1 as newFrequency from leaderdownloadassociation a WHERE a.leaderID = $lnLeaderID AND a.downloadID = $lnFileID" ;
$laAssociation = $loDB->queryGetData($lcCheckSQL) ;
if($laAssociation)
{
// update the frequency
$lnFrequency = $laAssociation[0]['newFrequency'] ;
$lcUpdate = "UPDATE leaderdownloadassociation set frequency = $lnFrequency, editdate = now() WHERE leaderID = $lnLeaderID AND downloadID = $lnFileID" ;
$loDB->iQuery($lcUpdate) ;
}
else // first download
{
$lcTableName = "leaderdownloadassociation" ;
$lcFieldList = "leaderID, downloadID, frequency, createdate, editdate" ;
$lcValueList = "$lnLeaderID, $lnFileID, 1, now(), now()";
$loDB->queryInsert($lcTableName, $lcFieldList, $lcValueList, false) ;
}
?>
[/php]

Ultimately I need to put some error trapping around this but initially I just want it to work. So at present I have the file downloading fine but the database does not get updated and I know the code itself is fine as I have tested just that part.

Any suggestions greatly appreciated.

Cheers
nathj
Sep 6 '07 #10

ak1dnar
Expert 100+
P: 1,584
Once you call for the download headers, script execution stops.
So first Insert the data to the Table(s), then call for the header as the last part of the script execution.
Sep 6 '07 #11

nathj
Expert 100+
P: 938
Once you call for the download headers, script execution stops.
So first Insert the data to the Table(s), then call for the header as the last part of the script execution.
Hi,

That makes sense. However, I'm about to go on holiday for 2 weeks and I haven't started packing yet so I better try this when I get back - I'll let you know how I get on.

Thanks to everyone for all the help.

Cheers
nathj
Sep 6 '07 #12

nathj
Expert 100+
P: 938
Hi,

Having had 2 sunny weeks Italy I have returned to the wonderful grey that only British weather can produce. So without the temptation to lounge around outside reading all day I have returned to work - my boss is most thankful.

I have now switched the order of the code around and all works a treat. I'm really pleased with this and very greatful for all the help I have received on this part of my project.

Many thanks
nathj
Sep 25 '07 #13

Post your reply

Sign in to post your reply or Sign up for a free account.