Displaying BLOB fields from SQL Server database with PHP issue - php

I have a database field in SQL Server 2012 database. They can contain PDF, WORD, Excel or image files. My Blob field type is varbinary(max). My code works fine using ColdFusion, however I have tried to convert it to PHP and it doesn't seem to be working. Below is the code I am using. Can someone tell me what I am doing wrong?
The example below is just for a word document and when the Word application is launched, I get a "File Conversion" dialog box asking me to select the encoding that makes the document readable none of which makes any difference. The preview window shows a bunch of garbage.
<?PHP
$aserverName = "SQ-ENT12-D01\Dev";
$aconnectionInfo = array( "Database"=>"Event_Registration", "UID"=>$uid, "PWD"=>$pwd, "CharacterSet"=>"UTF-8");
$aconn = sqlsrv_connect( $aserverName, $aconnectionInfo);
// Get the record where the binary data is
$GetBlobSQL = "select * from Congress where Congress_ID = 102";
$GetBlob = sqlsrv_query($aconn, $GetBlobSQL, array(), array('Scrollable' => 'buffered'));
$GetBlobRecordCount = sqlsrv_num_rows($GetBlob);
$brec = sqlsrv_fetch_array( $GetBlob, SQLSRV_FETCH_ASSOC, SQLSRV_SCROLL_ABSOLUTE, 0);
$WhatFile = ($brec['Document_Agenda']);
$WhatBlob = ($brec['AD_Agenda']);
$MyExt = substr($WhatFile, -4);
$WhatExt = strtolower($MyExt);
header('Content-type: application/ms-word');
header('Content-Disposition: attachment; filename="'.$WhatFile.'"');
echo $WhatBlob;
?>
I know the file or data in the database is not corrupted because my original ColdFusion code works fine to retrieve and display the data.

Try removing "CharacterSet"=>"UTF-8" from the $aconnectionInfo. You may be confusing the database and causing conversions to take place that are not needed or desired.

Related

PHP retrieve multiple rows with stream content at once with sqlsrv

This is kind of a follow up of this question.
This code retries a row from the database where one column is a resource and creates a file system file with it.
$query = "select top(1) DESCRIPTION, FILETYPE, DOCUMENT from dbo.Documents;";
$stmt = sqlsrv_query($this->sqlsrv_conn, $query);
if (sqlsrv_fetch($stmt)) {
$document = sqlsrv_get_field($stmt, 2, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
// $fileName = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
// $ext = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
file_put_contents(
// $fileName . '.' . $ext,
'filename'.'.doc',
stream_get_contents($document),
);
}
Now I have to do this with all the records in the database, not only one. What is the best way to achieve that?
I looked at sqlsrv_fetch_array which has an argument "$fetchType" but this is to define in which format the rows are grouped together (numbered or assoc array).
How can I define the fetchType for each column under that array of rows? Like I can with sqlsrv_get_field($stmt, 2, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)) when only one row is fetched.
Note: I have received the feedback that my questions tend to be unclear, if you see things that can be improved to make this question better and easier to answer, please let me know.
Edit
Thank you #Zhorov the while loop does work! But I face a new silly issue.
I lied, the code I posted was not exactly the code that worked for me. Apparently I didn't test it properly with fetching the file name and extension as well. What worked was only when I fetched the binary data as a stream with nothing else.
I don't understand it at all. The following code works like expected:
$query = "select top(1) DESCRIPTION, FILETYPE, DOCUMENT from dbo.Documents;";
$stmt = sqlsrv_query($this->sqlsrv_conn, $query);
if (sqlsrv_fetch($stmt)) {
$document = sqlsrv_get_field($stmt, 2, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
// $fileName = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
file_put_contents(
// $fileName . '.doc',
'filename.doc',
stream_get_contents($document),
);
}
But if I also want to fetch the file name or anything else
$query = "select top(1) DESCRIPTION, FILETYPE, DOCUMENT from dbo.Documents;";
$stmt = sqlsrv_query($this->sqlsrv_conn, $query);
if (sqlsrv_fetch($stmt)) {
$document = sqlsrv_get_field($stmt, 2, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
$fileName = sqlsrv_get_field($stmt, 0);
file_put_contents(
'filename.doc',
stream_get_contents($document),
);
}
I get the following exception:
Error: stream_get_contents(): supplied resource is not a valid stream resource File
I debugged it down to this with xdebug. Here are screenshots of the two scenarios. This is the run with a valid stream and no exception (you can see the "type=stream"):
And here, the only thing I did is remove the comment on the line and a breakpoint right before the exception happens (you can see the "type=Unknown"):
Why does fetching something else break the stream? (This behaviour is the same when I using while loop or not)
Edit 2
Behaviour does change when changing the order in which sqlsrv_get_field is called. If I put the binary data after the file name, the $document variable is false. Not even an "Unknown" stream.
Edit: Reason for this is, as #Zhorov pointed out:
From documentation - sqlsrv_get_field retrieves data from the specified field of the current row. Field data must be accessed in order. For example, data from the first field cannot be accessed after data from the second field has been accessed
#Zhorov brought me on the right track for the solution.
It appears that the stream content has to be accessed last. If I get the file name before document, stream is "Unknown" and an exception is thrown. But if I get the stream at last, it works!
See for yourself. The following is the version where the stream is invalid:
And when retrieving the binary data in the column DOCUMENT last, it works:

Windows Indexing Search through PHP Doesn't Return Images

I'm using Windows Indexing search together with PHP to search inside thousands of files.
I got it working by using the PHP COM class.
But the problem is that the search result only return found txt, docx, pdf and document types of files. It doesn't return images or videos no matter what properties I tried searching.
When I search for 001 as the string:
actual result: y:/sample001.txt, y:/book001.pdf
Expect to also see: y:/picture001.jpg
But the result doesn't show jpg or any other types of media files.
Why did it not search for images (or videos)?
Thanks in advance for any help.
<?php # searchfunction.php
function comsearch($string){
$path = "file:y:/";
$conn = new COM("ADODB.Connection") or die("Cannot start ADO");
$recordset = new COM("ADODB.Recordset");
//setting the limit for the query
$recordset->MaxRecords = 1000;
$conn->Open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';");
//creating the query against windows search indexer database.
//we specify the path and the string we are looking for
$recordset->Open("SELECT System.FileName, System.ItemPathDisplay, System.FileExtension FROM SYSTEMINDEX WHERE SCOPE='".$path."' AND CONTAINS('".$string."') AND System.FileExtension='.jpg'", $conn);
if(!$recordset->EOF){
$recordset->MoveFirst();
}
$files = array();
while(!$recordset->EOF) {
$filename = $recordset->Fields->Item("System.FileName")->value;
$files[]= $filename;
$recordset->MoveNext();
}
return $files;
}
?>
The code above returns nothing at all with the condition System.FileExtension='.jpg' even when I have thousands of .jpg in my directory.
But when I try it without this condition, the result successfully returns txt, pdf and other document types of files in the same directory.
What did I do wrong?

Filename being shortened by script

I have some php code that connects to a MSSQL, looks up some values and retrieves a file that is stored in binary format. The table has the filename, header and the binary data. Presently my code is downloading the file as expected however, if there is a space in the filename, the name is cut short, as if the space is acting as a delimeter. e.g. value in db "This is an example file.pdf" outputted filename "This" and the browser recognises the pdf type.
My somewhat functional code is below:
/* Retrieve and display the data.
The return data is retrieved as a binary stream. */
if ( sqlsrv_fetch( $stmt ) )
{
$noticeID = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_INT);
$filename = sqlsrv_get_field( $stmt, 1, SQLSRV_PHPTYPE_STRING("UTF-8"));
$header = sqlsrv_get_field( $stmt, 2, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY));
$download = sqlsrv_get_field( $stmt, 3, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
header('Content-Type: '.$header);
header("Content-Disposition: attachment; filename=$filename");
echo "Your file, $filename is ready for download";
fpassthru($download);
}
else
{
echo "Error in retrieving data.</br>";
print_r( sqlsrv_errors(), true);
}
I suspect that the issue is that the filename is stored as a VARCHAR(255) in the DB and somehow is breaking the "Content-Disposition" header line. Research brought me to this page but it isn't very clear on how to handle VARCHAR and when I try and "hack" it by guessing (see below) and get a filename "index.php" (although it does download the file)
$filename = sqlsrv_get_field( $stmt, 1, SQLSRV_PHPTYPE_VARCHAR);
I've also tried this in place of the faulty header line:
$filename = sqlsrv_get_field( $stmt, 1, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)))
It still cuts off the filename. If there is no space in the filename, the code executes perfectly.
BONUS: does anyone know how I could save the file to the filesystem rather than having a download dialog? This code is proof of concept for a data migration script I'm writing and that would also be very helpful :)
Please try the following: concatenate the header as follows:
header('Content-Disposition: attachment; filename="'."$filename".'"');
And save the file via:
function file_put_contents()

Errors while exporting to .xls using PHP

I've asked a similar question previously but Ive been told my question is just me being lazy so let me rephrase.
Ive been using a PHP class script to enable me to export my SQL data to a .xls file but the resultant excel file doesnt display any values and no error is being displayed on the webpage itself.
The class file Im using is documented in the link below:
http://web.burza.hr/blog/php-class-for-exporting-data-in-multiple-worksheets-excel-xml/
And Ive incorporated it in my site as follows
$dbase->loadextraClass('excel.xml');
$excel = new excel_xml();
$header_style = array(
'bold' => 1,
'size' => '14',
'color' => '#000000',
'bgcolor' => '#ffffff'
);
$excel->add_style('header',$header_style);
if(isset($_POST['fec_save']))
{
if($_POST['reporttype']=='films')
{
$films = $dbase->runquery("SELECT datetime,title,country_of_origin,language,runningtime,(SELECT name FROM fec_client WHERE filmid = fec_film.filmid) AS client, (SELECT rating_decision FROM fec_rating_report WHERE filmid = fec_film.filmid) AS rating FROM fec_film WHERE datetime >= '".strtotime($_POST['fromdate'])."' AND datetime <= '".strtotime($_POST['todate'])."'",'multiple');
$filmcount = $dbase->getcount($films);
//continue with excel buildup
$columns = array('Date','Title','Origin','Language','Minutes','Client','Rating');
$excel->add_row($columns,'header');
for($i=1; $i<=$filmcount; $i++)
{
$film = $dbase->fetcharray($films);
$excel->add_row($film['datetime'],$film['title'],$film['country_of_origin'],$film['language'],$film['runningtime'],$film['client'],$film['rating']);
}
$excel->create_worksheet('From_'.str_replace(' ','',$_POST['fromdate']).'_to'.str_replace(' ','',$_POST['todate']));
$xml = $excel->generate();
$excel->download('FilmsClassified_from_'.str_replace(' ','',$_POST['fromdate']).'_to'.str_replace(' ','',$_POST['todate']));
}
}
I would like some assistance as to what I maybe doing wrong.
Thanks
Too long to post as a comment:
$xml = $excel->generate();
creates all the xml file content as a string and stores it in $xml
while
$excel->create_worksheet('From_'.str_replace(' ','',$_POST['fromdate']).'_to'.str_replace(' ','',$_POST['todate']));
creates the xml and directs it to output, with appropriate headers for downloading.
So you're not using the class correctly, as that's unnecessary duplication of work.

mysql blob image not displaying

I'm having a problem getting my images to display after extracting them from a database.
I have 2 separate tables, one for the meta data and another to hold the actual blob data. that table is a regular BLOB and i only store 60k chunks of data in each row. I recompile the image when i want to render it.
i keep getting this error though:
the image "http://imgStore.localhost/ImageBuilder/index/id/11" cannot be displayed because it contains errors.
here is how the flow works however.
/Images/image/id/11 will have an image inside of it like this
<img src="http://imgStore.localhost/ImageBuilder/index/id/11" />
the Images controller handles insertions and edits as well as listing the images
while ImageBuilder is only concerned with displaying a given image
here is the table structure:
images ------
image_id INT
image_name VARCHAR
image_type VARCHAR
image_size INT
loaded_date DATETIME
image_data --
image_data_id INT
image_id INT
data BLOB
here is how i save the file into the database:
( NOTE: i'm using the latest Zend Framework )
( insertion action ) ------------------
$image = new ImgStore_Model_Images($form->getValues());
$image->setImage_size(((int) substr($form->image_file->getFileSize(), 0, -2) * 1024));
$image->setImage_type($form->image_file->getMimeType());
$image->setLoaded_date(time());
$image->setLoaded_by($user->get('contacts_id'));
$mapper = new ImgStore_Model_ImagesMapper();
$image_id = $mapper->save($image);
// open the uploaded file to read binary data
$fp = fopen($form->image_file->getFileName(), "r");
$dataMapper = new ImgStore_Model_ImageDataMapper();
// loop through the file and push the contents into
// image data entries
while( !feof($fp) ){
// Make the data mysql insert safe
$binary_data = addslashes(fread($fp, 60000));
$data_entry = new ImgStore_Model_ImageData();
$data_entry->setImage_id($image_id);
$data_entry->setImage_data($binary_data);
$dataMapper->save($data_entry);
}
fclose($fp);
and here is how it is extracted:
(action) ------------------
$this->_helper->_layout->disableLayout();
// get the image meta data
$image_id = $this->_request->getParam('id', '0');
$mapper = new ImgStore_Model_ImagesMapper();
$info = $mapper->getInfo($image_id);
// build the image and push it to the view
$mapper = new ImgStore_Model_ImageDataMapper();
$this->view->image = $mapper->buildImage($image_id);
$this->view->name = $info->getImage_name();
$this->view->type = $info->getImage_type();
$this->view->size = $info->getImage_size();
(model) ------------------
public function buildImage($image_id)
{
// get the image data
$sql = "SELECT image_data
FROM image_data
WHERE image_id='$image_id'
ORDER BY image_data_id ASC";
$results = $this->_adapter->fetchAll($sql);
// piece together the image and return it
$image = NULL;
foreach( $results as $row ){
$image .= $row['image_data'];
}
return $image;
} #end buildImage function
(view) ------------------
<?php
header( "Content-Type: " . $this->type );
header('Content-Disposition: inline; filename="'.$this->name.'"');
echo $this->image;
?>
i have tried to use an image that was small enough to take up only one row in the image_data table as well, so i don't believe it has anything to do with the recompilation of the image_data rows.
any help would be appreciated, i truly have no idea what is wrong with this.
edited some formatting for display purposes.
I recently did something like this but used a different approach for the rendering. Zend won't fire up the app if the request URI to an actual file, so I created a render action in my file controller that created a copy of the image on the drive. This makes scaling and management much easier since the files are all in one central db, but also gives the performance benefits of reading from the disk. Here's my open action:
public function openAction() {
$file = // find the file in the db
if(! $file) {
throw new Zend_Exception('File not found', 404);
}
$path = // get the filepath
if(! file_exists($path) || $this->_request->getParam('reload') == true) {
file_put_contents($path, $file->image_data);
}
$this->_redirect('document root relative path');
}
there's no real value in cluttering up the database with image data. (Fetching the data from the database will also be significantly slower than simply loading it off disk)
I suggest that you just store the images on the file system, and store the path to the image in the database alongside the meta data.

Categories