• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1551
  • Last Modified:

ajax php file download and update mysql database field

I am starting to get to grips with ajax and need some assistance to code for form submission in which a mysql field value is updated and a file is downloaded. The principle of the routine is to checkout/download a file for revision and, having done so, ensure that nobody else can checkout the same file.

The first part of the problem I have done by simply using a standard php update function when the form is submitted. However, I am trying to use ajax to manage file download and I am having difficulty getting it configured.

I have attached a screenshot of the form page which is a list of radio buttons and a 'Checkout' (submit) button. When one of the radio buttons is selected and the button clicked, the database entry is updated to set the docCheckedOut field in the document table to true. At the same time a file download dialog should appear, and this isn't happening.

The pertinent parts of the code are shown below. All assistance gratefully received!
<!-- ajax functions all contained in lib.js-->
<!-- Process the download -->
function downloadFile(fileName)
{
	// Takes the file information and passes it to downloadFile.php
	xmlhttp = getxmlhttpObject();
	if (xmlhttp == null)
	{
		alert ("Browser does not support HTTP Request");
	 	return;
	}
	var serverPage = "../includes/downloadFile.php";
	obj = document.getElementById(fileName);
	processAjax(serverPage, obj, "post", "fileName");
}
 
<!-- process the ajax request -->
function processAjax(serverPage, obj, getOrPost, str)
{
	// Get an xmlhttpRequest object
	xmlhttp = getxmlhttpObject();
	if (getOrPost == "get")
	{
		xmlhttp.open("GET", serverPage);
		xmlhttp.onreadystatechange = function()
		{
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
				obj.innerHTML = xmlhttp.responseText;
		}
		xmlhttp.send(null);
	}
	else
	{
		xmlhttp.open("POST", serverPage, true)
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
		xmlhttp.onreadystatechange = function()
		{
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
				obj.innerHTML = xmlhttp.responseText;
		}
		xmlhttp.send(str);
	}
}
 
<!-- get the xmlhttp object -->
function getxmlhttpObject()
{
	// Create a boolean to check for a valid MS IE browser instance
	var xmlhttp = false;
	
	// Check if IE is being used
	try
	{
		// If the js version is > 5
		xmlhttp=new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch (e)
	{
		try
		{
			// If js version <= 5, then use the older activeX object
			xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch(E)
		{
			// Otherwise a non-IE browser is being used
			xmlhttp = false;
		}
	}
	
	// If not using IE, create a js instance of the object
	if (!xmlhttp && typeof XMLHttpRequest != 'undefined')
	{
		xmlhttp = new XMLHttpRequest();
	}
	
	return xmlhttp;
}
 
<!-- php file download routine (courtesy of Zubrag) -->
<?php
// downloadFile.php
// Download a file (with download resumption capability)
 
// Define the downloadable directory
define('BASE_DIR','http://MYDOMAIN/docsGeneral/');
 
/*
This function takes a path to a file to output ($file), 
the filename that the browser will see ($name) and 
the MIME type of the file ($mime_type, optional).
 
If you want to do something on download abort/finish,
register_shutdown_function('function_name');
*/
if(!is_readable($file)) die('File not found or inaccessible!');
	
$size = filesize($file);
$name = rawurldecode($name);
 
/* Figure out the MIME type (if not specified) */
$known_mime_types=array("pdf" => "application/pdf",
			"txt" => "text/plain",
			"html" => "text/html",
			"htm" => "text/html",
			"exe" => "application/octet-stream",
			"zip" => "application/zip",
			"doc" => "application/msword",
			"xls" => "application/vnd.ms-excel",
			"ppt" => "application/vnd.ms-powerpoint",
			"gif" => "image/gif",
			"png" => "image/png",
			"jpeg"=> "image/jpg",
			"jpg" =>  "image/jpg",
			"php" => "text/plain");
 
if ($mime_type=='')
{
$file_extension = strtolower(substr(strrchr($file,"."),1));
if(array_key_exists($file_extension, $known_mime_types)) $mime_type=$known_mime_types[$file_extension];
else $mime_type="application/force-download";
}
 
@ob_end_clean(); //turn off output buffering to decrease cpu usage
 
// required for IE, otherwise Content-Disposition may be ignored
if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off');
 
header('Content-Type: ' . $mime_type);
header('Content-Disposition: attachment; filename="'.$name.'"');
header("Content-Transfer-Encoding: binary");
header('Accept-Ranges: bytes');
 
/* The three lines below basically make the 
download non-cacheable */
header("Cache-control: private");
header('Pragma: private');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
// multipart-download and download resuming support
if(isset($_SERVER['HTTP_RANGE']))
{
list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
list($range) = explode(",",$range,2);
list($range, $range_end) = explode("-", $range);
$range=intval($range);
if(!$range_end) $range_end=$size-1;
else $range_end=intval($range_end);
$new_length = $range_end-$range+1;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range-$range_end/$size");
}
else 
{
$new_length=$size;
header("Content-Length: ".$size);
}
 
/* output the file itself */
$chunksize = 1*(1024*1024); //you may want to change this
$bytes_send = 0;
if ($file = fopen($file, 'r'))
{
if(isset($_SERVER['HTTP_RANGE'])) fseek($file, $range);
while(!feof($file) && (!connection_aborted()) && ($bytes_send<$new_length))
{
$buffer = fread($file, $chunksize);
print($buffer); //echo($buffer); // is also possible
flush();
$bytes_send += strlen($buffer);
}
fclose($file);
}
else die('Error - can not open file.');
 
die();

Open in new window

form.jpg
0
kcalder
Asked:
kcalder
  • 2
  • 2
1 Solution
 
kcalderAuthor Commented:
Oops, forgot to post the code for the form on the calling page....it's below.
<!-- this is an excerpt from the calling page -->
<!-- lib.js is stated as an included file in the head -->
<form action="<?php echo $editFormAction; ?>" method="post" name="checkoutDoc" id="checkoutDoc">
					<?php 
					do { ?>
							<tr>
								<?php
								// Filename for downloading
								$fileName = $row_rstDocList['docFilename'].'_'.$row_rstDocList['docRevision'].'.'.$row_rstDocList['docFileType'];
								
								// Find the name of the document owner
								$docOwner = '';
									
								// Walk the user array 
								for ($j = 0; $j < sizeof($user); $j++) if ($user[$j][0] == $row_rstDocList['docOwnerPK']) $docOwner = $user[$j][1];
					
								// Display changes according to whether the item has been checked out or not
								if ($row_rstDocList['docCheckedOut'] == 0)
								{ 
									// The document has not been checked out and is available for checking out
								?>        
									<td class="revDocListTitle"><input name="checkoutRadio" type="radio" value="<?php echo $row_rstDocList['doc_pk']; ?>" /><?php echo ' '.$row_rstDocList['docTitle']; ?></td>
									<td class="revDocListRevision"><?php echo $row_rstDocList['docRevision']; ?></td>
									<td class="revDocListApplication"><?php echo $row_rstDocList['docApplication']; ?></td>
									<td class="revDocListOwner"><?php echo $docOwner; ?></td>
									<td class="revDocListZone"><?php echo $row_rstDocList['zoneTitle']; ?></td>
									<td class="revDocListPassword"><?php echo $row_rstDocList['agPassword']; ?></td>
									<input type="hidden" name="checkedOut" id="checkedOut" value="1" />
									<input type="hidden" name="checkedOutBy" id="checkedOutBy" value="<?php echo $_SESSION['MM_Username']; ?>" />
									<input type="hidden" name="theFile" id="theFile" value="<?php echo $fileName; ?>" />
									<?php
									// Current reviewer is always initially the person who has checked the document out 
									$currentReviewer = $_SESSION['MM_Username'];
									?>
									<input type="hidden" name="docReviewer" id="docReviewer" value="<?php echo $currentReviewer; ?>" />
								<?php }
								else
								{ 
									// The document has been checked out so find the name of the person who checked it out
									$checkedOutBy = '';
												
									// Walk the array 
									for ($k = 0; $k < sizeof($user); $k++) if ($user[$k][0] == $row_rstDocList['docCheckedOutBy']) $checkedOutBy = $user[$k][1];
								?>
										<td class="revDocListTitleCheckedOut">
											<input name="checkoutDoc" type="radio" value="<?php echo $row_rstDocList['doc_pk']; ?>" disabled="disabled">
												<a class="menuCheckedOut" href="#" title="<?php echo $fileName.' has been checked out for revision by '.$checkedOutBy; ?>"><?php echo $row_rstDocList['docTitle']; ?>
												</a>
										</td>
										<td class="revDocListRevisionCheckedOut"><?php echo $row_rstDocList['docRevision']; ?></td>
										<td class="revDocListApplicationCheckedOut"><?php echo $row_rstDocList['docApplication']; ?></td>
										<td class="revDocListOwnerCheckedOut"><?php echo $docOwner; ?></td>
										<td class="revDocListZoneCheckedOut"><?php echo $row_rstDocList['zoneTitle']; ?></td>
										<td class="revDocListPasswordCheckedOut"><?php echo $row_rstDocList['agPassword']; ?></td>											
								<?php } ?>
							</tr>
					<?php } while ($row_rstDocList = mysql_fetch_assoc($rstDocList)); ?>
							<tr>
								
								<?php // TO DO: Write an ajax onclick function for the submit button that downloads the file ?>								
								
								<td class="revDocListTitle" colspan="6"><input name="submit" id="submit" type="submit" 
																			value="Checkout" onclick="downloadFile(document.getElementById(theFile))" />
								</td>							
							</tr>
					<input type="hidden" name="MM_update" value="checkoutDoc" />
				</form>

Open in new window

0
 
Richard QuadlingSenior Software DeverloperCommented:
I've just had to learn about AJAX and file downloading.

Here is what I found.

When a user selects a file download (non AJAX, normal file browsing), the browser generates a request, the server responds with appropriate headers which tell the browser that the data that follows is for the user and not to be automatically handled by the browser.

When AJAX makes the request, it is AJAX that gets the response. AJAX does NOT invoke a file save dialogue or anything like that.

The solution I had was to create a hidden iframe whose URL was the downloader. The iframe is part of the browser, so when it gets the headers saying this is a file download, the browser would open the file save dialogue.


0
 
Richard QuadlingSenior Software DeverloperCommented:
I had a rather complicated Crystal Report to run server side which outputs a PDF, MS Word document or MS Excel spreadsheet.

I use an AJAX request to start the Crystal Report run.
The response of the request is the URL to be used in the iframe so that it can download the saved file.
The file is deleted once it has been sent (all part of the iframe request handler).

I use Prototype for my JS needs (and I extend it where I need more functionality).

Below is an edited version of the code I use.

The AJAX part simply returns a JS like which calls the tabRG_GenerateReport() function with JSON'd data.


I hope this helps. This solution is working for our sites very well. It has enough protection to stop file downloading directly, as the files don't exist once the download is complete. The files are only created when the AJAX request comes in. All quite tightly controlled.

If not, let me know what else you need.

Richard.
function tabRG_GenerateReport(o_Gen)
	{
	var o_Date = new Date(); // Make sure all requests are unique
	var o_FrameRepGen = new Element
		(
		'iframe',
			{
			className : 'reportDownloadFrame',
			id        : 'download_' + o_Date.getTime(),
			src       : o_Gen.URL
			}
		);
 
	$('tabS_ReportGenerator')
		.appendChild(o_FrameRepGen);
	}

Open in new window

0
 
kcalderAuthor Commented:
I am closing this question out because I have decided to take a different approach using jQuery. I am still having difficulty making that work but it seems appropriate to develop a separate question. Thanks for your assistance.
0

Featured Post

Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

  • 2
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now