Newer
Older
<?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 '
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
.' 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 open() {
return new AttachmentChunkedData($this->id);
}
$file = $this->open();
while ($chunk = $file->read())
echo $chunk;
# 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'));
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());
/* Function assumes the files types have been validated */
Peter Rotich
committed
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;
$data = new AttachmentChunkedData($id);
if (!$data->write($file['data']))
return false;
Peter Rotich
committed
}
/* 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();
}
/**
* Attachments stored in the database are cut into 256kB chunks and stored
* in the FILE_CHUNK_TABLE to overcome the max_allowed_packet limitation of
* LOB fields in the MySQL database
*/
define('CHUNK_SIZE', 256*1024); # Beware if you change this...
class AttachmentChunkedData {
function AttachmentChunkedData($file) {
$this->_file = $file;
$this->_pos = 0;
function length() {
list($length) = db_fetch_row(db_query(
'SELECT SUM(LENGTH(filedata)) FROM '.FILE_CHUNK_TABLE
.' WHERE file_id='.db_input($this->_file)));
return $length;
function seek($location) {
$this->_pos=$location;
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
function read($length=CHUNK_SIZE) {
# Read requested length of data from attachment chunks
$buffer='';
while ($length > 0) {
$chunk_id = floor($this->_pos / CHUNK_SIZE);
$start = $this->_pos % CHUNK_SIZE;
$size = min($length, CHUNK_SIZE - $start);
list($block) = @db_fetch_row(db_query(
'SELECT SUBSTR(filedata, '.($start+1).', '.$size
.') FROM '.FILE_CHUNK_TABLE.' WHERE file_id='
.db_input($this->_file).' AND chunk_id='.$chunk_id));
if (!$block) return false;
$buffer .= $block;
$this->_pos += $size;
$length -= $size;
}
return $buffer;
}
function write($what) {
# Figure out the remaining part of the current chunk (use CHUNK_SIZE
# and $this->_pos, increment pointer into $what and continue to end
# of what
$offset=0;
for (;;) {
$start = $this->_pos % CHUNK_SIZE;
$size = CHUNK_SIZE - $start;
$block = substr($what, $offset, $size);
if (!$block) break;
if (!db_query('REPLACE INTO '.FILE_CHUNK_TABLE
.' SET filedata=INSERT(filedata, '.($start+1).','.$size
.', 0x'.bin2hex($block)
.'), file_id='.db_input($this->_file)
.', chunk_id='.floor($this->_pos / CHUNK_SIZE)))
return false;
$offset += $size;
$this->_pos += strlen($block);
}
return true;
}
function deleteOrpans() {
'DELETE FROM '.FILE_CHUNK_TABLE.' WHERE file_id NOT IN '
.'( SELECT id FROM '.FILE_TABLE.') still_loved');
return db_affected_rows();