diff --git a/attachment.php b/attachment.php index 9a386baae761ec683859f59cb45b1be75319c66e..8e45d8e155c83cae6c31697f49f5b8bfd3910a9e 100644 --- a/attachment.php +++ b/attachment.php @@ -16,23 +16,23 @@ **********************************************************************/ require('secure.inc.php'); require_once(INCLUDE_DIR.'class.attachment.php'); -//Basic checks +// Basic checks if (!$thisclient || !$_GET['id'] || !$_GET['h'] || !($attachment=Attachment::lookup($_GET['id'])) - || !($file=$attachment->getFile())) + || !($file=$attachment->getFile()) + || strcasecmp(trim($_GET['h']), $file->getDownloadHash()) + || !($object=$attachment->getObject()) + || !$object instanceof ThreadEntry + || !($ticket=$object->getThread()->getObject()) + || !$ticket instanceof Ticket + ) Http::response(404, __('Unknown or invalid file')); -//Validate session access hash - we want to make sure the link is FRESH! and the user has access to the parent ticket!! -$vhash=md5($attachment->getFileId().session_id().strtolower($file->getKey())); -if (strcasecmp(trim($_GET['h']), $vhash) - || !($thread=$attachment->getThread()) - || !($object=$thread->getObject()) - || !$object instanceof Ticket - || !$object->checkUserAccess($thisclient)) - Http::response(404, __('Unknown or invalid file')); -//Download the file.. -$file->download(); +if (!$ticket->checkUserAccess($thisclient)) + die(__('Access Denied')); +// Download the file.. +$file->download(); ?> diff --git a/bootstrap.php b/bootstrap.php index 354c9bd208acc4ecde10f0d75df299d53c0e4357..d16f10feefc17f3e4c462a7f792f553208cf00fb 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -92,7 +92,6 @@ class Bootstrap { define('THREAD_TABLE', $prefix.'thread'); define('THREAD_ENTRY_TABLE', $prefix.'thread_entry'); - define('THREAD_ENTRY_ATTACHMENT_TABLE', $prefix.'thread_entry_attachment'); define('THREAD_ENTRY_EMAIL_TABLE', $prefix.'thread_entry_email'); define('TICKET_TABLE',$prefix.'ticket'); diff --git a/include/class.attachment.php b/include/class.attachment.php index fdcbe4ee2770463906d1ba470797a74dcf7c9955..a1ecfd97af55ea66d0ea9c684bb07ef96ab0ba03 100644 --- a/include/class.attachment.php +++ b/include/class.attachment.php @@ -21,35 +21,25 @@ class Attachment { var $file_id; var $ht; - var $thread; + var $object; - function Attachment($id, $tid=0) { + function Attachment($id) { - $sql = 'SELECT a.*, e.thread_id FROM '.THREAD_ENTRY_ATTACHMENT_TABLE.' a ' - . 'LEFT JOIN '.THREAD_ENTRY_TABLE.' e ON (e.id = a.thread_entry_id) ' + $sql = 'SELECT a.* FROM '.ATTACHMENT_TABLE.' a ' . 'WHERE a.id='.db_input($id); - if($tid) - $sql.=' AND a.thread_entry_id='.db_input($tid); + if (!($res=db_query($sql)) || !db_num_rows($res)) + return; - if(!($res=db_query($sql)) || !db_num_rows($res)) - return false; - - $this->ht=db_fetch_array($res); - - $this->id=$this->ht['id']; - $this->file_id=$this->ht['file_id']; - - $this->file = $this->thread = null; - - return true; + $this->ht = db_fetch_array($res); + $this->file = $this->object = null; } function getId() { - return $this->id; + return $this->ht['id']; } function getFileId() { - return $this->file_id; + return $this->ht['file_id']; } function getFile() { @@ -59,10 +49,6 @@ class Attachment { return $this->file; } - function getCreateDate() { - return $this->ht['created']; - } - function getHashtable() { return $this->ht; } @@ -71,32 +57,32 @@ class Attachment { return $this->getHashtable(); } - function getThread() { + function getObject() { - if (!isset($this->thread)) - $this->thread = Thread::lookup($this->ht['thread_id']); + if (!isset($this->object)) + $this->object = ObjectModel::lookup( + $this->ht['object_id'], $this->ht['type']); - return $this->thread; + return $this->object; } - /* Static functions */ - static function getIdByFileHash($hash, $tid=0) { - $sql='SELECT a.id FROM '.THREAD_ENTRY_ATTACHMENT_TABLE.' a ' + static function getIdByFileHash($hash, $objectId=0) { + $sql='SELECT a.id FROM '.ATTACHMENT_TABLE.' a ' .' INNER JOIN '.FILE_TABLE.' f ON(f.id=a.file_id) ' .' WHERE f.`key`='.db_input($hash); - if($tid) - $sql.=' AND a.thread_entry_id='.db_input($tid); + if ($objectId) + $sql.=' AND a.object_id='.db_input($objectId); return db_result(db_query($sql)); } - static function lookup($var, $tid=0) { + static function lookup($var, $objectId=0) { - $id = is_numeric($var) ? $var : self::getIdByFileHash($var, $tid); + $id = is_numeric($var) ? $var : self::getIdByFileHash($var, $oid); return ($id && is_numeric($id) - && ($attach = new Attachment($id, $tid)) + && ($attach = new Attachment($id, $oid)) && $attach->getId()==$id ) ? $attach : null; } @@ -174,7 +160,8 @@ class GenericAttachments { function _getList($separate=false, $inlines=false, $lang=false) { if(!isset($this->attachments)) { $this->attachments = array(); - $sql='SELECT f.id, f.size, f.`key`, f.name, a.inline, a.lang ' + $sql='SELECT f.id, f.size, f.`key`, f.name ' + .', a.inline, a.lang, a.id as attach_id ' .' FROM '.FILE_TABLE.' f ' .' INNER JOIN '.ATTACHMENT_TABLE.' a ON(f.id=a.file_id) ' .' WHERE a.`type`='.db_input($this->getType()) @@ -192,7 +179,7 @@ class GenericAttachments { if (($a['inline'] != $separate || $a['inline'] == $inlines) && $lang == $a['lang']) { $a['file_id'] = $a['id']; - $a['hash'] = md5($a['file_id'].session_id().strtolower($a['key'])); + $a['hash'] = md5($a['file_id'].session_id().$a['key']); $attachments[] = $a; } } diff --git a/include/class.file.php b/include/class.file.php index 7fe6a769942d88d4bd96545496f44d19d2b97a94..caafae2bf9cab7018b6aee5d4a8503d2f7d84295 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -35,7 +35,7 @@ class AttachmentFile { .' FROM '.FILE_TABLE.' f ' .' LEFT JOIN '.ATTACHMENT_TABLE.' a ON(a.file_id=f.id) ' - .' LEFT JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' t + .' LEFT JOIN '.ATTACHMENT_TABLE.' t ON(t.file_id = f.id) ' .' WHERE f.id='.db_input($id) .' GROUP BY f.id'; @@ -579,9 +579,8 @@ class AttachmentFile { // XXX: Allow plugins to define filetypes which do not represent // files attached to tickets or other things in the attachment // table and are not logos + //FIXME: Just user straight up left join $sql = 'SELECT id FROM '.FILE_TABLE.' WHERE id NOT IN (' - .'SELECT file_id FROM '.THREAD_ENTRY_ATTACHMENT_TABLE - .' UNION ' .'SELECT file_id FROM '.ATTACHMENT_TABLE .") AND `ft` = 'T' AND TIMESTAMPDIFF(DAY, `created`, CURRENT_TIMESTAMP) > 1"; diff --git a/include/class.thread.php b/include/class.thread.php index 7b48da65d94b89c2bb6e761f8a5ccfb7af10c6e5..0cd0e5b2620c88001c368c037b9032cab8221b99 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -32,13 +32,13 @@ class Thread { return null; $sql='SELECT thread.* ' - .' ,count(DISTINCT attach.id) as attachments ' + .' ,count(DISTINCT a.id) as attachments ' .' ,count(DISTINCT entry.id) as entries ' .' FROM '.THREAD_TABLE.' thread ' .' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry ON (entry.thread_id = thread.id) ' - .' LEFT JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' attach - ON (attach.thread_entry_id=entry.id) ' + .' LEFT JOIN '.ATTACHMENT_TABLE.' a + ON (a.object_id=entry.id AND a.`type` = "H") ' .' WHERE thread.id='.db_input($id) .' GROUP BY thread.id'; @@ -98,8 +98,8 @@ class Thread { ON (entry.user_id=user.id) ' .' LEFT JOIN '.STAFF_TABLE.' staff ON (entry.staff_id=staff.staff_id) ' - .' LEFT JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' attach - ON (attach.thread_entry_id = entry.id) ' + .' LEFT JOIN '.ATTACHMENT_TABLE.' attach + ON (attach.object_id = entry.id AND attach.`type`="H") ' .' WHERE entry.thread_id='.db_input($this->getId()); if($type && is_array($type)) @@ -129,9 +129,9 @@ class Thread { function deleteAttachments() { // Clear reference table - $sql = 'DELETE FROM '.THREAD_ENTRY_ATTACHMENT_TABLE. ' a ' + $sql = 'DELETE FROM '.ATTACHMENT_TABLE. ' a ' . 'INNER JOIN '.THREAD_ENTRY_TABLE.' e - ON(e.id = a.thread_entry_id) ' + ON(e.id = a.object_id AND a.`type`= "H") ' . ' WHERE e.thread_id='.db_input($this->getId()); $deleted=0; @@ -225,8 +225,8 @@ Class ThreadEntry { .' FROM '.THREAD_ENTRY_TABLE.' entry ' .' LEFT JOIN '.THREAD_ENTRY_EMAIL_TABLE.' email ON (email.thread_entry_id=entry.id) ' - .' LEFT JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' attach - ON (attach.thread_entry_id=entry.id) ' + .' LEFT JOIN '.ATTACHMENT_TABLE.' attach + ON (attach.object_id=entry.id AND attach.`type` = "H") ' .' WHERE entry.id='.db_input($id); if ($type) @@ -242,8 +242,7 @@ Class ThreadEntry { $this->ht = db_fetch_array($res); $this->id = $this->ht['id']; - - $this->attachments = array(); + $this->attachments = new GenericAttachments($this->id, 'H'); return true; } @@ -461,7 +460,7 @@ Class ThreadEntry { XXX: We're doing it here because it will eventually become a thread post comment (hint: comments coming!) XXX: logNote must watch for possible loops */ - $this->getTicket()->logNote(__('File Upload Error'), $error, 'SYSTEM', false); + $this->getThread()->getObject()->logNote(__('File Upload Error'), $error, 'SYSTEM', false); } } @@ -491,12 +490,12 @@ Class ThreadEntry { $id=0; if ($attachment['error'] || !($id=$this->saveAttachment($attachment))) { $error = $attachment['error']; - if(!$error) - $error = sprintf(_S('Unable to import attachment - %s'),$attachment['name']); - - $this->getTicket()->logNote(_S('File Import Error'), $error, - _S('SYSTEM'), false); + $error = sprintf(_S('Unable to import attachment - %s'), + $attachment['name']); + //FIXME: $this->logComment(); + $this->getThread()->getObject()->logNote( + _S('File Import Error'), $error, _S('SYSTEM'), false); } return $id; @@ -508,29 +507,9 @@ Class ThreadEntry { */ function saveAttachment(&$file) { - if (is_numeric($file)) - $fileId = $file; - elseif (is_array($file) && isset($file['id'])) - $fileId = $file['id']; - elseif (!($fileId = AttachmentFile::save($file))) - return 0; - $inline = is_array($file) && @$file['inline']; - // TODO: Add a unique index to THREAD_ENTRY_ATTACHMENT_TABLE (file_id, - // thread_entry_id), and remove this block - if ($id = db_result(db_query('SELECT id FROM '.THREAD_ENTRY_ATTACHMENT_TABLE - .' WHERE file_id='.db_input($fileId) - .' AND thread_entry_id=' .db_input($this->getId())))) - - return $id; - - $sql ='INSERT IGNORE INTO '.THREAD_ENTRY_ATTACHMENT_TABLE.' SET created=NOW() ' - .' ,file_id='.db_input($fileId) - .' ,thread_entry_id='.db_input($this->getId()) - .' ,inline='.db_input($inline ? 1 : 0); - - return (db_query($sql) && ($id=db_insert_id()))?$id:0; + return $this->attachments->save($file, $inline); } function saveAttachments($files) { @@ -543,52 +522,39 @@ Class ThreadEntry { } function getAttachments() { - - if ($this->attachments) - return $this->attachments; - - //XXX: inner join the file table instead? - $sql='SELECT a.id, f.id as file_id, f.size, lower(f.`key`) as file_hash, f.name, a.inline ' - .' FROM '.FILE_TABLE.' f ' - .' INNER JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' a - ON(a.file_id=f.id) ' - .' WHERE a.thread_entry_id='.db_input($this->getId()); - - $this->attachments = array(); - if (($res=db_query($sql)) && db_num_rows($res)) { - while ($rec=db_fetch_array($res)) - $this->attachments[] = $rec; - } - - return $this->attachments; + return $this->attachments->getAll(true, false); } function getAttachmentUrls($script='image.php') { $json = array(); foreach ($this->getAttachments() as $att) { - $json[$att['file_hash']] = array( - 'download_url' => sprintf('attachment.php?id=%d&h=%s', $att['id'], - strtolower(md5($att['file_id'].session_id().$att['file_hash']))), + $json[$att['key']] = array( + 'download_url' => sprintf('attachment.php?id=%d&h=%s', + $att['attach_id'], $att['download']), 'filename' => $att['name'], ); } + return $json; } - function getAttachmentsLinks($file='attachment.php', $target='', $separator=' ') { + function getAttachmentsLinks($file='attachment.php', $target='_blank', $separator=' ') { $str=''; - foreach($this->getAttachments() as $attachment ) { - if ($attachment['inline']) - continue; - /* The hash can be changed but must match validation in @file */ - $hash=md5($attachment['file_id'].session_id().$attachment['file_hash']); + foreach ($this->getAttachments() as $att ) { + if ($att['inline']) continue; $size = ''; - if($attachment['size']) - $size=sprintf('<em>(%s)</em>', Format::file_size($attachment['size'])); + if ($att['size']) + $size=sprintf('<em>(%s)</em>', Format::file_size($att['size'])); $str.=sprintf('<a class="Icon file no-pjax" href="%s?id=%d&h=%s" target="%s">%s</a>%s %s', - $file, $attachment['id'], $hash, $target, Format::htmlchars($attachment['name']), $size, $separator); + $file, + $att['attach_id'], + $att['download'], + $target, + Format::htmlchars($att['name']), + $size, + $separator); } return $str; diff --git a/include/class.ticket.php b/include/class.ticket.php index a092fa661e5300e917b3abc657a1365304db8972..9286a2c5c894eeda70da4a889d92ab0c74491ea8 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -197,8 +197,8 @@ class Ticket { ON ( thread.object_id = ticket.ticket_id AND thread.object_type="T" ) ' .' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry ON ( entry.thread_id = thread.id ) ' - .' LEFT JOIN '.THREAD_ENTRY_ATTACHMENT_TABLE.' attach - ON ( attach.thread_entry_id = entry.id ) ' + .' LEFT JOIN '.ATTACHMENT_TABLE.' attach + ON ( attach.object_id = entry.id AND attach.`type` = "H") ' .' WHERE ticket.ticket_id='.db_input($id) .' GROUP BY ticket.ticket_id'; diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 25ceb77835578ee84e655a005131cdc845abf270..3871578ca35d48cbe3fe1ec6929a50a7db1addfd 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -410,12 +410,13 @@ $tcount+= $ticket->getNumNotes(); echo $entry['id']; ?>"><div><?php echo $entry['body']->toHtml(); ?></div></td></tr> <?php + $urls = null; if($entry['attachments'] && ($tentry = $ticket->getThreadEntry($entry['id'])) && ($urls = $tentry->getAttachmentUrls()) && ($links = $tentry->getAttachmentsLinks())) {?> <tr> - <td class="info" colspan="4"><?php echo $tentry->getAttachmentsLinks(); ?></td> + <td class="info" colspan="4"><?php echo $links; ?></td> </tr> <?php } if ($urls) { ?> diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index ca8a3dae54ce2f95a30f3729ff58cffbde89ef0b..252eb31ba78793be662ab22a616f2973246af4b4 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -36f6b32893c2b97c5104ab5302d2dd2e +4b4daf9cf5e199673885f5ef58e743d1 diff --git a/include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql b/include/upgrader/streams/core/b26f29a6-4b4daf9c.cleanup.sql similarity index 84% rename from include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql rename to include/upgrader/streams/core/b26f29a6-4b4daf9c.cleanup.sql index 27dca7f84160f3916b8e4bbb968af9622ae89979..800c8127673c19f3628eb3b4b385a691cac6e96b 100644 --- a/include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql +++ b/include/upgrader/streams/core/b26f29a6-4b4daf9c.cleanup.sql @@ -4,6 +4,8 @@ ALTER TABLE `%TABLE_PREFIX%thread` ALTER TABLE `%TABLE_PREFIX%thread_entry` DROP COLUMN `ticket_id`; +DROP TABLE `%TABLE_PREFIX%ticket_attachment`; + OPTIMIZE TABLE `%TABLE_PREFIX%ticket`; OPTIMIZE TABLE `%TABLE_PREFIX%thread`; OPTIMIZE TABLE `%TABLE_PREFIX%thread_entry`; diff --git a/include/upgrader/streams/core/b26f29a6-186868f5.patch.sql b/include/upgrader/streams/core/b26f29a6-4b4daf9c.patch.sql similarity index 83% rename from include/upgrader/streams/core/b26f29a6-186868f5.patch.sql rename to include/upgrader/streams/core/b26f29a6-4b4daf9c.patch.sql index 7f1898fc6b35b62f35c6af30193337950f81d94f..62280690740e46da12e8f2d052fea4c9a37d3e55 100644 --- a/include/upgrader/streams/core/b26f29a6-186868f5.patch.sql +++ b/include/upgrader/streams/core/b26f29a6-4b4daf9c.patch.sql @@ -58,15 +58,18 @@ UPDATE `%TABLE_PREFIX%ticket` t1 LEFT JOIN `%TABLE_PREFIX%thread` t2 ON ( t2.object_id = t1.ticket_id ) SET t1.thread_id = t2.id; --- convert ticket_attachment to thread_entry_attachment -ALTER TABLE `%TABLE_PREFIX%ticket_attachment` - CHANGE `attach_id` `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, - CHANGE `ref_id` `thread_entry_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT '0'; - DROP `ticket_id`; +-- move records in ticket_attachment to generic attachment table +ALTER TABLE `%TABLE_PREFIX%attachment` + DROP PRIMARY KEY, + ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST, + ADD UNIQUE `file-type` (`object_id`, `file_id`, `type`); -RENAME TABLE `%TABLE_PREFIX%ticket_attachment` TO `%TABLE_PREFIX%thread_entry_attachment`; +INSERT INTO `%TABLE_PREFIX%attachment` + (`object_id`, `type`, `file_id`, `inline`) + SELECT `ref_id`, 'H', `file_id`, `inline` + FROM `%TABLE_PREFIX%ticket_attachment`; --- convert ticket_email_info to thread_entry_mid +-- convert ticket_email_info to thread_entry_email ALTER TABLE `%TABLE_PREFIX%ticket_email_info` CHANGE `thread_id` `thread_entry_id` INT( 11 ) UNSIGNED NOT NULL, CHANGE `email_mid` `mid` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, @@ -74,6 +77,8 @@ ALTER TABLE `%TABLE_PREFIX%ticket_email_info` RENAME TABLE `%TABLE_PREFIX%ticket_email_info` TO `%TABLE_PREFIX%thread_entry_email`; + + -- Set new schema signature UPDATE `%TABLE_PREFIX%config` SET `value` = '2257f6f22ca4b31bea6045b8b7d59d56' diff --git a/scp/attachment.php b/scp/attachment.php index 697f9e38a240f38c96ea8e739af3a50f15823911..ab6d45ab14d31d4122c085f40188c125bb74d897 100644 --- a/scp/attachment.php +++ b/scp/attachment.php @@ -16,21 +16,21 @@ require('staff.inc.php'); require_once(INCLUDE_DIR.'class.attachment.php'); -//Basic checks +// Basic checks if (!$thisstaff || !$_GET['id'] || !$_GET['h'] || !($attachment=Attachment::lookup($_GET['id'])) - || !($file=$attachment->getFile())) + || !($file=$attachment->getFile()) + || strcasecmp(trim($_GET['h']), $file->getDownloadHash()) + || !($object=$attachment->getObject()) + || !$object instanceof ThreadEntry + || !($ticket=$object->getThread()->getObject()) + || !$ticket instanceof Ticket + ) Http::response(404, __('Unknown or invalid file')); -//Validate session access hash - we want to make sure the link is FRESH! and the user has access to the parent ticket!! -$vhash=md5($attachment->getFileId().session_id().strtolower($file->getKey())); -if (strcasecmp(trim($_GET['h']), $vhash) - || !($thread=$attachment->getThread()) - || !($object=$thread->getObject()) - || !$object instanceof Ticket - || !$object->checkStaffAccess($thisstaff)) +if (!$ticket->checkStaffAccess($thisstaff)) die(__('Access Denied')); //Download the file.. diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 7f968dbd6f542276be62ac09f69150c1d5ff5cdc..d91ce79ab86f5fed2f4d628a84c9a2b5ca1ec40a 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -17,12 +17,14 @@ CREATE TABLE `%TABLE_PREFIX%api_key` ( DROP TABLE IF EXISTS `%TABLE_PREFIX%attachment`; CREATE TABLE `%TABLE_PREFIX%attachment` ( + `id` int(10) unsigned NOT NULL auto_increment, `object_id` int(11) unsigned NOT NULL, `type` char(1) NOT NULL, `file_id` int(11) unsigned NOT NULL, `inline` tinyint(1) unsigned NOT NULL DEFAULT '0', `lang` varchar(16), - PRIMARY KEY (`object_id`,`file_id`,`type`) + PRIMARY KEY (`id`), + UNIQUE KEY `file-type` (`object_id`,`file_id`,`type`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq`; @@ -644,19 +646,6 @@ CREATE TABLE `%TABLE_PREFIX%thread_entry` ( KEY `type` (`type`) ) DEFAULT CHARSET=utf8; - -DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_entry_attachment`; -CREATE TABLE `%TABLE_PREFIX%thread_entry_attachment` ( - `id` int(11) unsigned NOT NULL auto_increment, - `file_id` int(10) unsigned NOT NULL default '0', - `thread_entry_id` int(11) unsigned NOT NULL default '0', - `inline` tinyint(1) NOT NULL default '0', - `created` datetime NOT NULL, - PRIMARY KEY (`id`), - KEY `file_id` (`file_id`), - KEY `ref_id` (`thread_entry_id`) -) DEFAULT CHARSET=utf8; - DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_entry_email`; CREATE TABLE `%TABLE_PREFIX%thread_entry_email` ( `id` int(11) unsigned NOT NULL auto_increment,