-
Jared Hancock authored
MySQL has a limit on the maximum amount that can be transferred in one statement. It's the max_allowed_packet setting. The value of this setting will be the approximate upper limit of attachments that can be handled by the database given the current access model for osTicket. The issue came up for attachment uploads and was corrected, so that uploads are chunk inserted into the database. Downloads, however, were forgotten. Strangely, it took quite a bit of debugging to track down the problem. This patch corrects attachment downloads by fetching 256kB chunks of the attachment at a time and sending them directly to the client. This will also overcome PHP's memory limit which would be the second-level blocker of attachment sizes. Lastly, the AttachmentFile::getData() method is simulated using output buffering. This will provide the same access as the previous getData() method; however, it is still subject ot PHP's memory limits.
Jared Hancock authoredMySQL has a limit on the maximum amount that can be transferred in one statement. It's the max_allowed_packet setting. The value of this setting will be the approximate upper limit of attachments that can be handled by the database given the current access model for osTicket. The issue came up for attachment uploads and was corrected, so that uploads are chunk inserted into the database. Downloads, however, were forgotten. Strangely, it took quite a bit of debugging to track down the problem. This patch corrects attachment downloads by fetching 256kB chunks of the attachment at a time and sending them directly to the client. This will also overcome PHP's memory limit which would be the second-level blocker of attachment sizes. Lastly, the AttachmentFile::getData() method is simulated using output buffering. This will provide the same access as the previous getData() method; however, it is still subject ot PHP's memory limits.
class.file.php 7.70 KiB
<?php
/*********************************************************************
class.file.php
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class AttachmentFile {
var $id;
var $ht;
function AttachmentFile($id) {
$this->id =0;
return ($this->load($id));
}
function load($id=0) {
if(!$id && !($id=$this->getId()))
return false;
$sql='SELECT id, type, size, name, hash, f.created, '
.' count(DISTINCT c.canned_id) as canned, count(DISTINCT t.ticket_id) as tickets '
.' FROM '.FILE_TABLE.' f '
.' LEFT JOIN '.CANNED_ATTACHMENT_TABLE.' c ON(c.file_id=f.id) '
.' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' t ON(t.file_id=f.id) '
.' WHERE f.id='.db_input($id)
.' GROUP BY f.id';
if(!($res=db_query($sql)) || !db_num_rows($res))
return false;
$this->ht=db_fetch_array($res);
$this->id =$this->ht['id'];
return true;
}
function reload() {
return $this->load();
}
function getHashtable() {
return $this->ht;
}
function getInfo() {
return $this->getHashtable();
}
function getNumTickets() {
return $this->ht['tickets'];
}
function isCanned() {
return ($this->ht['canned']);
}
function isInUse() {
return ($this->getNumTickets() || $this->isCanned());
}
function getId() {
return $this->id;
}
function getType() {
return $this->ht['type'];
}
function getMime() {
return $this->getType();
}
function getSize() {
return $this->ht['size'];
}
function getName() {
return $this->ht['name'];
}
function getHash() {
return $this->ht['hash'];
}
function sendData() {
# XXX: For maximum efficiency,
# do "show variables like 'max_allowed_packet'", and use the
# lesser of half of PHP's memory limit and that value as the
# chunk_size
$chunk_size = 256 * 1024;
for ($start=1; $start<$this->getSize(); $start+=$chunk_size) {
list($data) = db_fetch_row(db_query(
'SELECT SUBSTRING(filedata,'.$start.','.$chunk_size
.') FROM '.FILE_TABLE.' WHERE id='.db_input($this->getId())));
echo $data;
}
}
function getData() {
# XXX: This is horrible, and is subject to php's memory
# restrictions, etc. Don't use this function!
ob_start();
$this->sendData();
$data = &ob_get_contents();
ob_end_clean();
return $data;
}
function delete() {
$sql='DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1';
return (db_query($sql) && db_affected_rows());
}
function display() {
header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream'));
header('Content-Length: '.$this->getSize());
$this->sendData();
exit();
}
function download() {
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: public');
header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream'));
$filename=basename($this->getName());
$user_agent = strtolower ($_SERVER['HTTP_USER_AGENT']);
if ((is_integer(strpos($user_agent,'msie'))) && (is_integer(strpos($user_agent,'win')))) {
header('Content-Disposition: filename='.$filename.';');
}else{
header('Content-Disposition: attachment; filename='.$filename.';' );
}
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$this->getSize());
$this->sendData();
exit();
}
/* Function assumes the files types have been validated */
function upload($file) {
if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name']))
return false;
$info=array('type'=>$file['type'],
'size'=>$file['size'],
'name'=>$file['name'],
'hash'=>MD5(MD5_FILE($file['tmp_name']).time()),
'data'=>file_get_contents($file['tmp_name'])
);
return AttachmentFile::save($info);
}
function save($file) {
if(!$file['hash'])
$file['hash']=MD5(MD5($file['data']).time());
if(!$file['size'])
$file['size']=strlen($file['data']);
$sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() '
.',type='.db_input($file['type'])
.',size='.db_input($file['size'])
.',name='.db_input($file['name'])
.',hash='.db_input($file['hash']);
if (!(db_query($sql) && ($id=db_insert_id())))
return false;
foreach (str_split($file['data'], 1024*100) as $chunk) {
$sql='UPDATE '.FILE_TABLE
.' SET filedata = CONCAT(filedata,'.db_input($chunk).')'
.' WHERE id='.db_input($id);
if(!db_query($sql)) {
db_query('DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($id).' LIMIT 1');
return false;
}
}
return $id;
}
/* Static functions */
function getIdByHash($hash) {
$sql='SELECT id FROM '.FILE_TABLE.' WHERE hash='.db_input($hash);
if(($res=db_query($sql)) && db_num_rows($res))
list($id)=db_fetch_row($res);
return $id;
}
function lookup($id) {
$id = is_numeric($id)?$id:AttachmentFile::getIdByHash($id);
return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null;
}
/**
* Removes files and associated meta-data for files which no ticket,
* canned-response, or faq point to any more.
*/
/* static */ function deleteOrphans() {
$res=db_query(
'DELETE FROM '.FILE_TABLE.' WHERE id NOT IN ('
# DISTINCT implies sort and may not be necessary
.'SELECT DISTINCT(file_id) FROM ('
.'SELECT file_id FROM '.TICKET_ATTACHMENT_TABLE
.' UNION ALL '
.'SELECT file_id FROM '.CANNED_ATTACHMENT_TABLE
.' UNION ALL '
.'SELECT file_id FROM '.FAQ_ATTACHMENT_TABLE
.') still_loved'
.')');
return db_affected_rows();
}
}
class AttachmentList {
function AttachmentList($table, $key) {
$this->table = $table;
$this->key = $key;
}
function all() {
if (!isset($this->list)) {
$this->list = array();
$res=db_query('SELECT file_id FROM '.$this->table
.' WHERE '.$this->key);
while(list($id) = db_fetch_row($res)) {
$this->list[] = new AttachmentFile($id);
}
}
return $this->list;
}
function getCount() {
return count($this->all());
}
function add($fileId) {
db_query(
'INSERT INTO '.$this->table
.' SET '.$this->key
.' file_id='.db_input($fileId));
}
function remove($fileId) {
db_query(
'DELETE FROM '.$this->table
.' WHERE '.$this->key
.' AND file_id='.db_input($fileId));
}
}
?>