diff --git a/attachment.php b/attachment.php
index dc26a66f4db1c2aa4827da3f541f3e674f8fc570..8e45d8e155c83cae6c31697f49f5b8bfd3910a9e 100644
--- a/attachment.php
+++ b/attachment.php
@@ -16,7 +16,7 @@
 **********************************************************************/
 require('secure.inc.php');
 require_once(INCLUDE_DIR.'class.attachment.php');
-//Basic checks
+// Basic checks
 if (!$thisclient
         || !$_GET['id']
         || !$_GET['h']
@@ -30,16 +30,8 @@ if (!$thisclient
         )
     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 90c168beb217c11802005220a79b7d799d308a22..a1ecfd97af55ea66d0ea9c684bb07ef96ab0ba03 100644
--- a/include/class.attachment.php
+++ b/include/class.attachment.php
@@ -21,31 +21,21 @@ 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() {
@@ -67,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;
     }
diff --git a/include/class.file.php b/include/class.file.php
index 74434bcdad9028e894334f4bd02949b834d3d409..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';
@@ -581,8 +581,6 @@ class AttachmentFile {
         //      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 5983988607b2304fdee913bc3fe8d87ee7463b4a..30d8e7799e0ee30ff146a8cc067fb9666f82aade 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;
     }
@@ -501,20 +500,7 @@ Class ThreadEntry {
 
         $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) {
@@ -527,32 +513,15 @@ 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'],
             );
         }
@@ -570,7 +539,13 @@ Class ThreadEntry {
                 $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&nbsp;%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 3031e03d74df062d71366eb63adc678ba5320167..43c1ed87593ebfbd3acd76e592396288b3c6ae80 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/client/tickets.inc.php b/include/client/tickets.inc.php
index d0529ba3eed6a990c24420b454a0c7a738a521a6..e39e43671662b1eff01b62e89c79a9d843f1e6d8 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -68,6 +68,15 @@ $pageNav=new Pagenate($total, $page, PAGE_LIMIT);
 $pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
 $pageNav->paginate($tickets);
 
+//more stuff...
+$qselect.=' ,count(DISTINCT attach.id) as attachments ';
+$qfrom.=' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry
+            ON (entry.thread_id=thread.id AND entry.`type` IN ("M", "R")) ';
+$qfrom.=' LEFT JOIN '.ATTACHMENT_TABLE.' attach
+            ON (attach.object_id=entry.id AND attach.`type` = "H") ';
+$qgroup=' GROUP BY ticket.ticket_id';
+
+$query="$qselect $qfrom $qwhere $qgroup ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
 //echo $query;
 $showing =$total ? $pageNav->showing() : "";
 if(!$results_type)
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 4507c7bea5251831a134cc50b85dbd0207ffe8b2..f4b7840c2dcb6df064d4a4e1e96e2e0f0d4ba0ff 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -150,6 +150,7 @@ TicketForm::ensureDynamicDataView();
 
 // Save the query to the session for exporting
 $_SESSION[':Q:tickets'] = $tickets;
+
 ?>
 
 <!-- SEARCH FORM START -->
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 90b6d124264702bc778822fabde3b0ffcf306508..252eb31ba78793be662ab22a616f2973246af4b4 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-186868f53bf747f3d04a5ad701101690
+4b4daf9cf5e199673885f5ef58e743d1
diff --git a/include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql b/include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql
deleted file mode 100644
index 27dca7f84160f3916b8e4bbb968af9622ae89979..0000000000000000000000000000000000000000
--- a/include/upgrader/streams/core/b26f29a6-186868f5.cleanup.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-ALTER TABLE `%TABLE_PREFIX%thread`
-    DROP COLUMN `tid`;
-
-ALTER TABLE `%TABLE_PREFIX%thread_entry`
-    DROP COLUMN `ticket_id`;
-
-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-186868f5.patch.sql
deleted file mode 100644
index 7f1898fc6b35b62f35c6af30193337950f81d94f..0000000000000000000000000000000000000000
--- a/include/upgrader/streams/core/b26f29a6-186868f5.patch.sql
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @version v1.9.5
- * @signature 2257f6f22ca4b31bea6045b8b7d59d56
- * @title Threads revisited
- *
- * This patch adds ability to thread anything
- *
- */
-
--- Add thread table
-DROP TABLE IF EXISTS `%TABLE_PREFIX%thread`;
-CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%thread` (
-  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `object_id` int(11) unsigned NOT NULL,
-  `object_type` char(1) NOT NULL,
-  `extra` text,
-  `created` datetime NOT NULL,
-  PRIMARY KEY (`id`),
-  KEY `object_id` (`object_id`),
-  KEY `object_type` (`object_type`)
-) DEFAULT CHARSET=utf8;
-
--- create threads
-INSERT INTO `%TABLE_PREFIX%thread`
-    (`object_id`, `object_type`, `created`)
-    SELECT t1.ticket_id, 'T', t1.created
-        FROM `%TABLE_PREFIX%ticket_thread` t1
-        JOIN (
-            SELECT ticket_id, MIN(id) as id
-            FROM `%TABLE_PREFIX%ticket_thread`
-            WHERE `thread_type` = 'M'
-            GROUP BY ticket_id
-    ) t2
-    ON (t1.ticket_id=t2.ticket_id and t1.id=t2.id)
-    ORDER BY t1.created;
-
-ALTER TABLE  `%TABLE_PREFIX%ticket_thread`
-    ADD  `thread_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT  '0' AFTER  `pid` ,
-    ADD INDEX (  `thread_id` );
-
-UPDATE  `%TABLE_PREFIX%ticket_thread` t1
-    LEFT JOIN  `%TABLE_PREFIX%thread` t2 ON ( t2.object_id = t1.ticket_id )
-    SET t1.thread_id = t2.id;
-
--- convert ticket_thread to thread_entry
-ALTER TABLE  `%TABLE_PREFIX%ticket_thread`
-    CHANGE  `thread_type`  `type` CHAR( 1 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
-    ADD INDEX (  `type` );
-
-RENAME TABLE `%TABLE_PREFIX%ticket_thread` TO  `%TABLE_PREFIX%thread_entry` ;
-
--- add thread id to ticket table
-ALTER TABLE  `%TABLE_PREFIX%ticket`
-    ADD  `thread_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT  '0' AFTER  `number` ,
-    ADD INDEX (  `thread_id` );
-
-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`;
-
-RENAME TABLE `%TABLE_PREFIX%ticket_attachment` TO  `%TABLE_PREFIX%thread_entry_attachment`;
-
--- convert ticket_email_info to thread_entry_mid
-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,
-    ADD INDEX (  `thread_entry_id` );
-
-RENAME TABLE `%TABLE_PREFIX%ticket_email_info` TO  `%TABLE_PREFIX%thread_entry_email`;
-
--- Set new schema signature
-UPDATE `%TABLE_PREFIX%config`
-    SET `value` = '2257f6f22ca4b31bea6045b8b7d59d56'
-    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/scp/attachment.php b/scp/attachment.php
index 02194c541d6c84294a6465320b041acf34c2ff77..ab6d45ab14d31d4122c085f40188c125bb74d897 100644
--- a/scp/attachment.php
+++ b/scp/attachment.php
@@ -16,7 +16,7 @@
 require('staff.inc.php');
 require_once(INCLUDE_DIR.'class.attachment.php');
 
-//Basic checks
+// Basic checks
 if (!$thisstaff
         || !$_GET['id']
         || !$_GET['h']
@@ -30,13 +30,7 @@ if (!$thisstaff
         )
     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 7f5fcedbbb1b1c92a29faab10e1374e372face0f..d91ce79ab86f5fed2f4d628a84c9a2b5ca1ec40a 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -646,18 +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,