diff --git a/include/ajax.tasks.php b/include/ajax.tasks.php
index cd1f58e50f303fdc376abc6428e316c658f5391b..8181b4eaea1646aba8e229a96a6ac8fd9d2755b2 100644
--- a/include/ajax.tasks.php
+++ b/include/ajax.tasks.php
@@ -22,6 +22,46 @@ require_once(INCLUDE_DIR.'class.task.php');
 
 class TasksAjaxAPI extends AjaxController {
 
+    function add() {
+        global $thisstaff;
+
+        $info=$errors=array();
+        if ($_POST) {
+            Draft::deleteForNamespace('task.add', $thisstaff->getId());
+            // Default form
+            $form = TaskForm::getInstance();
+            $form->setSource($_POST);
+            // Internal form
+            $iform = TaskForm::getInternalForm($_POST);
+            $isvalid = true;
+            if (!$iform->isValid())
+                $isvalid = false;
+            if (!$form->isValid())
+                $isvalid = false;
+
+            if ($isvalid) {
+                $vars = $_POST;
+                $vars['default_formdata'] = $form->getClean();
+                $vars['internal_formdata'] = $iform->getClean();
+                $desc = $form->getField('description');
+                if ($desc
+                        && $desc->isAttachmentsEnabled()
+                        && ($attachments=$desc->getWidget()->getAttachments()))
+                    $vars['cannedattachments'] = $attachments->getClean();
+                $vars['staffId'] = $thisstaff->getId();
+                $vars['poster'] = $thisstaff;
+                $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
+                if (($task=Task::create($vars, $errors)))
+                    Http::response(201, $task->getId());
+            }
+
+            $info['error'] = __('Error adding task - try again!');
+        }
+
+        include STAFFINC_DIR . 'templates/task.tmpl.php';
+    }
+
+
     function preview($tid) {
         global $thisstaff;
 
@@ -67,6 +107,12 @@ class TasksAjaxAPI extends AjaxController {
                 'delete' => array(
                     'verbed' => __('deleted'),
                     ),
+                'reopen' => array(
+                    'verbed' => __('reopen'),
+                    ),
+                'close' => array(
+                    'verbed' => __('closed'),
+                    ),
                 );
 
         if (!isset($actions[$action]))
@@ -88,53 +134,92 @@ class TasksAjaxAPI extends AjaxController {
         switch ($action) {
         case 'assign':
             $inc = 'task-assign.tmpl.php';
-            if ($_POST && !$errors) {
-                if (!isset($_POST['staff_id']) || !is_numeric($_POST['staff_id']))
-                    $errors['staff_id'] = __('Assignee selection required');
-                else {
-                    foreach ($_POST['tids'] as $tid) {
-                        if (($t=Task::lookup($tid))
-                                && $t->getDeptId() != $_POST['dept_id']
-                                // Make sure the agent is allowed to
-                                // access and assign the task.
-                                && $t->checkStaffPerm($thisstaff, Task::PERM_ASSIGN)
-                                // Do the transfer
-                                && $t->assign($_POST, $e)
-                                )
-                            $i++;
-                    }
+            $form = AssignmentForm::instantiate($_POST);
+            if ($_POST && $form->isValid()) {
+                foreach ($_POST['tids'] as $tid) {
+                    if (($t=Task::lookup($tid))
+                            // Make sure the agent is allowed to
+                            // access and assign the task.
+                            && $t->checkStaffPerm($thisstaff, Task::PERM_ASSIGN)
+                            // Do the transfer
+                            && $t->assign($form, $e)
+                            )
+                        $i++;
+                }
 
-                    if (!$i) {
-                        $info['error'] = sprintf(
-                                __('Unable to %1$s %2$s'),
-                                __('assign'),
-                                _N('selected task', 'selected tasks', $count));
-                    }
+                if (!$i) {
+                    $info['error'] = sprintf(
+                            __('Unable to %1$s %2$s'),
+                            __('assign'),
+                            _N('selected task', 'selected tasks', $count));
                 }
             }
             break;
         case 'transfer':
             $inc = 'task-transfer.tmpl.php';
+            $form = TransferForm::instantiate($_POST);
+            if ($_POST && $form->isValid()) {
+                foreach ($_POST['tids'] as $tid) {
+                    if (($t=Task::lookup($tid))
+                            // Make sure the agent is allowed to
+                            // access and transfer the task.
+                            && $t->checkStaffPerm($thisstaff, Task::PERM_TRANSFER)
+                            // Do the transfer
+                            && $t->transfer($form, $e)
+                            )
+                        $i++;
+                }
+
+                if (!$i) {
+                    $info['error'] = sprintf(
+                            __('Unable to %1$s %2$s'),
+                            __('transfer'),
+                            _N('selected task', 'selected tasks', $count));
+                }
+            }
+            break;
+        case 'reopen':
+            $info['status'] = 'open';
+        case 'close':
+            $inc = 'task-status.tmpl.php';
+            $info['status'] = $info['status'] ?: 'closed';
+            $perm = '';
+            switch ($info['status']) {
+            case 'open':
+                // If an agent can create a task then they're allowed to
+                // reopen closed ones.
+                $perm = Task::PERM_CREATE;
+                break;
+            case 'closed':
+                $perm = Task::PERM_CLOSE;
+                break;
+            default:
+                $errors['err'] = __('Unknown action');
+            }
+            // Check generic permissions --  department specific permissions
+            // will be checked below.
+            if ($perm && !$thisstaff->hasPerm($perm))
+                $errors['err'] = sprintf(
+                        __('You do not have permission to %s %s'),
+                        __($action),
+                        __('tasks'));
+
             if ($_POST && !$errors) {
-                if (!isset($_POST['dept_id']) || !is_numeric($_POST['dept_id']))
-                    $errors['dept_id'] = __('Department selection required');
+                if (!$_POST['status']
+                        || !in_array($_POST['status'], array('open', 'closed')))
+                    $errors['status'] = __('Status selection required');
                 else {
                     foreach ($_POST['tids'] as $tid) {
                         if (($t=Task::lookup($tid))
-                                && $t->getDeptId() != $_POST['dept_id']
-                                // Make sure the agent is allowed to
-                                // access and transfer the task.
-                                && $t->checkStaffPerm($thisstaff, Task::PERM_TRANSFER)
-                                // Do the transfer
-                                && $t->transfer($_POST, $e)
+                                && $t->checkStaffPerm($thisstaff, $perm ?: null)
+                                && $t->setStatus($_POST['status'], $_POST['comments'])
                                 )
                             $i++;
                     }
 
                     if (!$i) {
                         $info['error'] = sprintf(
-                                __('Unable to %1$s %2$s'),
-                                __('transfer'),
+                                __('Unable to change status of %1$s'),
                                 _N('selected task', 'selected tasks', $count));
                     }
                 }
@@ -198,7 +283,7 @@ class TasksAjaxAPI extends AjaxController {
         } elseif($_POST && !isset($info['error'])) {
             $info['error'] = $errors['err'] ?: sprintf(
                     __('Unable to %1$s  %2$s'),
-                    $actions[$action]['verbed'],
+                    __('process'),
                     _N('selected task', 'selected tasks', $count));
         }
 
@@ -242,8 +327,9 @@ class TasksAjaxAPI extends AjaxController {
                     $task->getId())
                 );
 
-        if ($_POST) {
-            if ($task->transfer($_POST, $errors)) {
+        $form = $task->getTransferForm($_POST);
+        if ($_POST && $form->isValid()) {
+            if ($task->transfer($form, $errors)) {
                 $_SESSION['::sysmsgs']['msg'] = sprintf(
                         __('%s successfully'),
                         sprintf(
@@ -255,7 +341,7 @@ class TasksAjaxAPI extends AjaxController {
                 Http::response(201, $task->getId());
             }
 
-            $info = array_merge($info, Format::htmlchars($_POST));
+            $form->addErrors($errors);
             $info['error'] = $errors['err'] ?: __('Unable to transfer task');
         }
 
@@ -281,26 +367,29 @@ class TasksAjaxAPI extends AjaxController {
                 ':action' => sprintf('#tasks/%d/assign',
                     $task->getId()),
                 );
-        if ($_POST) {
-            if ($task->assign($_POST,  $errors)) {
+        if ($task->isAssigned()) {
+            $info['notice'] = sprintf(__('%s is currently assigned to %s'),
+                    __('Task'),
+                    $task->getAssigned());
+        }
+
+        $form = $task->getAssignmentForm($_POST);
+        if ($_POST && $form->isValid()) {
+            if ($task->assign($form, $errors)) {
                 $_SESSION['::sysmsgs']['msg'] = sprintf(
                         __('%s successfully'),
                         sprintf(
                             __('%s assigned to %s'),
                             __('Task'),
-                            $task->getStaff()
-                            )
+                            $form->getAssignee())
                         );
-
                 Http::response(201, $task->getId());
             }
 
-            $info = array_merge($info, Format::htmlchars($_POST));
+            $form->addErrors($errors);
             $info['error'] = $errors['err'] ?: __('Unable to assign task');
         }
 
-        $info['staff_id'] = $info['staff_id'] ?: $task->getStaffId();
-
         include STAFFINC_DIR . 'templates/task-assign.tmpl.php';
     }
 
@@ -367,14 +456,14 @@ class TasksAjaxAPI extends AjaxController {
             switch ($_POST['a']) {
             case 'postnote':
                 $vars = $_POST;
-                $attachments = $task_note_form->getField('attachments')->getClean();
+                $attachments = $note_form->getField('attachments')->getClean();
                 $vars['cannedattachments'] = array_merge(
                     $vars['cannedattachments'] ?: array(), $attachments);
                 if(($note=$task->postNote($vars, $errors, $thisstaff))) {
                     $msg=__('Note posted successfully');
                     // Clear attachment list
-                    $task_note_form->setSource(array());
-                    $task_note_form->getField('attachments')->reset();
+                    $note_form->setSource(array());
+                    $note_form->getField('attachments')->reset();
                     Draft::deleteForNamespace('task.note.'.$task->getId(),
                             $thisstaff->getId());
                 } else {
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 28ed7d2b4fbfcd6de7af37038c6321d02dc3e628..d1ea440c0495351edb894f76acccb13599d670be 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -924,7 +924,8 @@ class TicketsAjaxAPI extends AjaxController {
                     sprintf('ticket.%d.task', $ticket->getId()),
                     $thisstaff->getId());
             // Default form
-            $form = TaskForm::getDefaultForm()->getForm($_POST);
+            $form = TaskForm::getInstance();
+            $form->setSource($_POST);
             // Internal form
             $iform = TaskForm::getInternalForm($_POST);
             $isvalid = true;
@@ -963,5 +964,51 @@ class TicketsAjaxAPI extends AjaxController {
 
          include STAFFINC_DIR . 'templates/task.tmpl.php';
     }
+
+    function task($tid, $id) {
+        global $thisstaff;
+
+        if (!($ticket=Ticket::lookup($tid))
+                || !$ticket->checkStaffPerm($thisstaff))
+            Http::response(404, 'Unknown ticket');
+
+        // Lookup task and check access
+        if (!($task=Task::lookup($id))
+                || !$task->checkStaffPerm($thisstaff))
+            Http::response(404, 'Unknown task');
+
+        $info=$errors=array();
+        $note_form = new SimpleForm(array(
+            'attachments' => new FileUploadField(array('id'=>'attach',
+            'name'=>'attach:note',
+            'configuration' => array('extensions'=>'')))
+            ));
+
+        if ($_POST) {
+            switch ($_POST['a']) {
+            case 'postnote':
+                $vars = $_POST;
+                $attachments = $note_form->getField('attachments')->getClean();
+                $vars['cannedattachments'] = array_merge(
+                    $vars['cannedattachments'] ?: array(), $attachments);
+                if(($note=$task->postNote($vars, $errors, $thisstaff))) {
+                    $msg=__('Note posted successfully');
+                    // Clear attachment list
+                    $note_form->setSource(array());
+                    $note_form->getField('attachments')->reset();
+                    Draft::deleteForNamespace('task.note.'.$task->getId(),
+                            $thisstaff->getId());
+                } else {
+                    if(!$errors['err'])
+                        $errors['err'] = __('Unable to post the note - missing or invalid data.');
+                }
+                break;
+            default:
+                $errors['err'] = __('Unknown action');
+            }
+        }
+
+        include STAFFINC_DIR . 'templates/task-view.tmpl.php';
+    }
 }
 ?>
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 06d0c461afb6fbb020ec949b0fb2841f13b26374..c2c90a3a78510b85886801992bcaf6f43af2321d 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -116,7 +116,7 @@ class DynamicForm extends VerySimpleModel {
         if ($source)
             $this->reset();
         $fields = $this->getFields();
-        $form = new Form($fields, $source, array(
+        $form = new SimpleForm($fields, $source, array(
             'title' => $this->getLocal('title'),
             'instructions' => $this->getLocal('instructions'))
         );
@@ -287,7 +287,7 @@ class DynamicForm extends VerySimpleModel {
         if (!$cdata
                 || !$cdata['table']
                 || !($e = $answer->getEntry())
-                || $e->getForm()->get('type') != $cdata['object_type'])
+                || $e->form->get('type') != $cdata['object_type'])
             return;
 
         // $record = array();
@@ -315,10 +315,10 @@ class DynamicForm extends VerySimpleModel {
     static function updateDynamicFormEntryAnswer($answer, $data) {
         if (!$answer
                 || !($e = $answer->getEntry())
-                || !$e->getForm())
+                || !$e->form)
             return;
 
-        switch ($e->getForm()->get('type')) {
+        switch ($e->form->get('type')) {
         case 'T':
             return TicketForm::updateDynamicDataView($answer, $data);
         case 'A':
@@ -328,10 +328,10 @@ class DynamicForm extends VerySimpleModel {
     }
 
     static function updateDynamicFormField($field, $data) {
-        if (!$field || !$field->getForm())
+        if (!$field || !$field->form)
             return;
 
-        switch ($field->getForm()->get('type')) {
+        switch ($field->form->get('type')) {
         case 'T':
             return TicketForm::dropDynamicDataView(TicketForm::$cdata['table']);
         case 'A':
@@ -985,7 +985,7 @@ class DynamicFormEntry extends VerySimpleModel {
     function getForm() {
         if (!isset($this->_form)) {
             // XXX: Should source be $this?
-            $form = new Form($this->getFields(), $this->getSource(),
+            $form = new SimpleForm($this->getFields(), $this->getSource(),
             array(
                 'title' => $this->getTitle(),
                 'instructions' => $this->getInstructions(),
diff --git a/include/class.forms.php b/include/class.forms.php
index 3b532cca6c210e6dfd2a4c0f1b93bfa5b1309266..75acdbd92a89fca54b6c751fad420b7384f0d32f 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -31,13 +31,8 @@ class Form {
     var $_errors = null;
     var $_source = false;
 
-    function __construct($fields=array(), $source=null, $options=array()) {
-        $this->fields = $fields;
-        foreach ($fields as $k=>$f) {
-            $f->setForm($this);
-            if (!$f->get('name') && $k && !is_numeric($k))
-                $f->set('name', $k);
-        }
+    function __construct($source=null, $options=array()) {
+
         if (isset($options['title']))
             $this->title = $options['title'];
         if (isset($options['instructions']))
@@ -67,7 +62,7 @@ class Form {
         $this->fields = $fields;
         foreach ($fields as $k=>$f) {
             $f->setForm($this);
-            if (!$f->get('name') && $k)
+            if (!$f->get('name') && $k && !is_numeric($k))
                 $f->set('name', $k);
         }
     }
@@ -3394,7 +3389,7 @@ class TransferForm extends Form {
                     'default'=>'',
                     'configuration' => array(
                         'html' => true,
-                        'size' => 'large',
+                        'size' => 'small',
                         'placeholder' => __('Optional reason for the transfer'),
                         ),
                     )
diff --git a/include/class.task.php b/include/class.task.php
index f081eb0d13540fa9e847157568b096d0d08d8101..c629379f6e88470408ab6eab9e36467576a38958 100644
--- a/include/class.task.php
+++ b/include/class.task.php
@@ -39,8 +39,8 @@ class TaskModel extends VerySimpleModel {
             ),
             'thread' => array(
                 'constraint' => array(
-                    'id'  => 'ThreadModel.object_id',
-                    "'A'" => 'ThreadModel.object_type',
+                    'id'  => 'TaskThread.object_id',
+                    "'A'" => 'TaskThread.object_type',
                 ),
                 'list' => false,
                 'null' => false,
@@ -189,7 +189,7 @@ class Task extends TaskModel {
     var $_entries;
 
     function getStatus() {
-        return $this->isOpen() ? _('Open') : _('Closed');
+        return $this->isOpen() ? __('Open') : __('Completed');
     }
 
     function getTitle() {
@@ -222,6 +222,34 @@ class Task extends TaskModel {
         return $role->hasPerm($perm);
     }
 
+    function getAssignee() {
+
+        if (!$this->isOpen() || !$this->isAssigned())
+            return false;
+
+        if ($this->staff)
+            return $this->staff;
+
+        if ($this->team)
+            return $this->team;
+
+        return null;
+    }
+
+    function getAssigneeId() {
+
+        if (!($assignee=$this->getAssignee()))
+            return null;
+
+        $id = '';
+        if ($assignee instanceof Staff)
+            $id = 's'.$assignee->getId();
+        elseif ($assignee instanceof Team)
+            $id = 't'.$assignee->getId();
+
+        return $id;
+    }
+
     function getAssignees() {
 
         $assignees=array();
@@ -242,15 +270,7 @@ class Task extends TaskModel {
     }
 
     function getThread() {
-
-        //FIXME: use $this->thread once thread classes get ORMed.
-        if (!$this->_thread)
-            $this->_thread = TaskThread::lookup(array(
-                        'object_id' => $this->getId(),
-                        'object_type' => ObjectModel::OBJECT_TYPE_TASK)
-                    );
-
-        return $this->_thread;
+        return $this->thread;
     }
 
     function getThreadEntry($id) {
@@ -285,6 +305,23 @@ class Task extends TaskModel {
         return $this->form;
     }
 
+    function getAssignmentForm($source=null) {
+
+        if (!$source)
+            $source = array('assignee' => array($this->getAssigneeId()));
+
+        return AssignmentForm::instantiate($source,
+                array('dept' => $this->getDept()));
+    }
+
+    function getTransferForm($source=null) {
+
+        if (!$source)
+            $source = array('dept' => array($this->getDeptId()));
+
+        return TransferForm::instantiate($source);
+    }
+
     function addDynamicData($data) {
 
         $tf = TaskForm::getInstance($this->id, true);
@@ -311,6 +348,40 @@ class Task extends TaskModel {
         return $this->_entries ?: array();
     }
 
+    function setStatus($status, $comments='') {
+        global $thisstaff;
+
+        switch($status) {
+        case 'open':
+            if ($this->isOpen())
+                return false;
+
+            $this->reopen();
+            break;
+        case 'closed':
+            if ($this->isClosed())
+                return false;
+            $this->close();
+            break;
+        default:
+            return false;
+        }
+
+        $this->save(true);
+        if ($comments) {
+            $errors = array();
+            $this->postNote(array(
+                        'note' => $comments,
+                        'title' => sprintf(
+                            __('Status changed to %s'),
+                            $this->getStatus())
+                        ),
+                    $errors,
+                    $thisstaff);
+        }
+
+        return true;
+    }
 
     function to_json() {
 
@@ -326,13 +397,13 @@ class Task extends TaskModel {
 
         foreach ($this->getDynamicData() as $e) {
             // Make sure the form type matches
-            if (!$e->getForm()
-                    || ($ftype && $ftype != $e->getForm()->get('type')))
+            if (!$e->form
+                    || ($ftype && $ftype != $e->form->get('type')))
                 continue;
 
             // Get the named field and return the answer
-            if ($f = $e->getForm()->getField($field))
-                return $f->getAnswer();
+            if ($a = $e->getAnswer($field))
+                return $a;
         }
 
         return null;
@@ -343,44 +414,82 @@ class Task extends TaskModel {
     }
 
     /* util routines */
-    function assign($vars, &$errors) {
-        global $thisstaff;
+    function assign(AssignmentForm $form, &$errors, $alert=true) {
+
+        $assignee = $form->getAssignee();
+        if ($assignee instanceof Staff) {
+            if ($this->getStaffId() == $assignee->getId()) {
+                $errors['assignee'] = sprintf(__('%s already assigned to %s'),
+                        __('Task'),
+                        __('the agent')
+                        );
+            } elseif(!$assignee->isAvailable()) {
+                $errors['assignee'] = __('Agent is unavailable for assignment');
+            } else {
+                $this->staff_id = $assignee->getId();
+            }
+        } elseif ($assignee instanceof Team) {
+            if ($this->getTeamId() == $assignee->getId()) {
+                $errors['assignee'] = sprintf(__('%s already assigned to %s'),
+                        __('Task'),
+                        __('the team')
+                        );
+            } else {
+                $this->team_id = $assignee->getId();
 
-        if (!isset($vars['staff_id']) || !($staff=Staff::lookup($vars['staff_id'])))
-            $errors['staff_id'] = __('Agent selection required');
-        elseif ($staff->getid() == $this->getStaffId())
-            $errors['dept_id'] = __('Task already assigned to agent');
-        else
-            $this->staff_id = $staff->getId();
+            }
+        } else {
+            $errors['assignee'] = __('Unknown assignee');
+        }
 
-        if ($errors || !$this->save())
+        if ($errors || !$this->save(true))
             return false;
 
-        // Transfer completed... post internal note.
+        $this->onAssignment($assignee,
+                $form->getField('comments')->getClean(),
+                $alert);
+
+        return true;
+    }
+
+    function onAssignment($assignee, $note='', $alert=true) {
+        global $thisstaff;
+
+        if (!is_object($assignee))
+            return false;
+
+        $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)');
+        //Assignment completed... post internal note.
         $title = sprintf(__('Task assigned to %s'),
-                $staff->getName());
-        if ($vars['comments']) {
-            $note = $vars['comments'];
-        } else {
+                (string) $assignee);
+
+        if (!$note) {
             $note = $title;
             $title = '';
         }
 
-        $this->postNote(
+        $errors = array();
+        $note = $this->postNote(
                 array('note' => $note, 'title' => $title),
                 $errors,
-                $thisstaff);
+                $assigner,
+                false);
+
+        // Send alerts out
+        if (!$alert)
+            return false;
 
         return true;
     }
 
-    function transfer($vars, &$errors) {
+    function transfer(TransferForm $form, &$errors, $alert=true) {
         global $thisstaff;
 
-        if (!isset($vars['dept_id']) || !($dept=Dept::lookup($vars['dept_id'])))
-            $errors['dept_id'] = __('Department selection required');
+        $dept = $form->getDept();
+        if (!$dept || !($dept instanceof Dept))
+            $errors['dept'] = __('Department selection required');
         elseif ($dept->getid() == $this->getDeptId())
-            $errors['dept_id'] = __('Task already in the department');
+            $errors['dept'] = __('Task already in the department');
         else
             $this->dept_id = $dept->getId();
 
@@ -391,17 +500,21 @@ class Task extends TaskModel {
         $title = sprintf(__('%s transferred to %s department'),
                 __('Task'),
                 $dept->getName());
-        if ($vars['comments']) {
-            $note = $vars['comments'];
-        } else {
+
+        $note = $form->getField('comments')->getClean();
+        if (!$note) {
             $note = $title;
             $title = '';
         }
-
-        $this->postNote(
+        $_errors = array();
+        $note = $this->postNote(
                 array('note' => $note, 'title' => $title),
-                $errors,
-                $thisstaff);
+                $_errors, $thisstaff, false);
+
+        // Send alerts if requested && enabled.
+        if (!$alert)
+            return true;
+
 
         return true;
     }
@@ -421,14 +534,8 @@ class Task extends TaskModel {
         if (!($note=$this->getThread()->addNote($vars, $errors)))
             return null;
 
-        if (isset($vars['task_status'])) {
-            if ($vars['task_status'])
-                $this->reopen();
-            else
-                $this->close();
-
-            $this->save(true);
-        }
+        if (isset($vars['task_status']))
+            $this->setStatus($vars['task_status']);
 
         return $note;
     }
@@ -582,6 +689,13 @@ class Task extends TaskModel {
 
         return $stats;
     }
+
+    static function getAgentActions($agent, $options=array()) {
+        if (!$agent)
+            return;
+
+        require STAFFINC_DIR.'templates/tasks-actions.tmpl.php';
+    }
 }
 
 
diff --git a/include/class.ticket.php b/include/class.ticket.php
index c275127847b51731ed838b4240d7d56d8bd594e1..cbb51b4143eca41bdfdf9570835237d5e51db4be 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -876,10 +876,11 @@ implements RestrictedAccess, Threadable, TemplateVariable {
     }
 
     function getThreadEntries($type=false) {
-        $thread = $this->getThread()->getEntries();
+        $entries = $this->getThread()->getEntries();
         if ($type && is_array($type))
-            $thread->filter(array('type__in' => $type));
-        return $thread;
+            $entries->filter(array('type__in' => $type));
+
+        return $entries;
     }
 
     //Collaborators
diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php
index 7fac7eb1209eb21ad978e27c937c02c24d165e1f..ef18c8bd01c5a988f063d80caea823da0f857ca1 100644
--- a/include/staff/templates/dynamic-form.tmpl.php
+++ b/include/staff/templates/dynamic-form.tmpl.php
@@ -65,7 +65,7 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
                 <span class="error">*</span>
             <?php
             }
-            if (($a = $field->getAnswer()) && $a->isDeleted()) {
+            if ($field->isStorable() && ($a = $field->getAnswer()) && $a->isDeleted()) {
                 ?><a class="action-button float-right danger overlay" title="Delete this data"
                     href="#delete-answer"
                     onclick="javascript:if (confirm('<?php echo __('You sure?'); ?>'))
diff --git a/include/staff/templates/task-assign.tmpl.php b/include/staff/templates/task-assign.tmpl.php
index 48d389b933d8311526e8850864d2cbbd345e3ecb..7019fa49a2b1d12fdb03039921f0f65b7f9d367c 100644
--- a/include/staff/templates/task-assign.tmpl.php
+++ b/include/staff/templates/task-assign.tmpl.php
@@ -1,6 +1,8 @@
 <?php
 global $cfg;
 
+$form = $form ?: AssignmentForm::instantiate($info);
+
 if (!$info[':title'])
     $info[':title'] = sprintf(__('%s Selected Tasks'),
             __('Assign'));
@@ -24,15 +26,17 @@ if ($info['error']) {
 
 $action = $info[':action'] ?: ('#tasks/mass/assign');
 ?>
-<div id="ticket-status" style="display:block; margin:5px;">
-<form class="mass-action" method="post" name="transfer" id="transfer"
+<div style="display:block; margin:5px;">
+<form class="mass-action" method="post"
+    name="assign"
+    id="<?php echo $form->getId(); ?>"
     action="<?php echo $action; ?>">
     <table width="100%">
         <?php
-        if ($info['extra']) {
+        if ($info[':extra']) {
             ?>
         <tbody>
-            <tr><td colspan="2"><strong><?php echo $info['extra'];
+            <tr><td colspan="2"><strong><?php echo $info[':extra'];
             ?></strong></td> </tr>
         </tbody>
         <?php
@@ -40,45 +44,12 @@ $action = $info[':action'] ?: ('#tasks/mass/assign');
        ?>
         <tbody>
             <tr><td colspan=2>
-                <span>
-                <strong><?php echo __('Agent') ?>:&nbsp;</strong>
-                <select name="staff_id">
-                <option value=""><?php
-                    echo __('Select Assignee'); ?></option>
-                <?php
-                foreach (Staff::getAvailableStaffMembers() as $id => $name) {
-                    if ($task && $task->getStaffId() == $id)
-                        $name .= sprintf(' (%s) ', __('current'));
-
-                    echo sprintf('<option value="%d" %s>%s</option>',
-                            $id,
-                            ($info['staff_id'] == $id)
-                             ? 'selected="selected"' : '',
-                            $name
-                            );
-                }
-                ?>
-                </select>
-                <font class="error">*&nbsp;<?php echo
-                $errors['staff_id']; ?></font>
-                </span>
+             <?php
+             $options = array('template' => 'simple', 'form_id' => 'assign');
+             $form->render($options);
+             ?>
             </td> </tr>
         </tbody>
-        <tbody>
-            <tr>
-                <td colspan="2">
-                <?php
-                $placeholder = $info['placeholder'] ?: __('Optional reason for the assignment');
-                ?>
-                <textarea name="comments" id="comments"
-                    cols="50" rows="3" wrap="soft" style="width:100%"
-                    class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext';
-                    ?> no-bar"
-                    placeholder="<?php echo $placeholder; ?>"><?php
-                    echo $info['comments']; ?></textarea>
-                </td>
-            </tr>
-        </tbody>
     </table>
     <hr>
     <p class="full-width">
diff --git a/include/staff/templates/task-status.tmpl.php b/include/staff/templates/task-status.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..b711e6f91c71f2e8b46a6d353ca5ef2e859d5e39
--- /dev/null
+++ b/include/staff/templates/task-status.tmpl.php
@@ -0,0 +1,101 @@
+<?php
+global $cfg;
+
+if (!$info[':title'])
+    $info[':title'] = __('Change Tasks Status');
+
+?>
+<h3><?php echo $info[':title']; ?></h3>
+<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
+<div class="clear"></div>
+<hr/>
+<?php
+if ($info['error']) {
+    echo sprintf('<p id="msg_error">%s</p>', $info['error']);
+} elseif ($info['warn']) {
+    echo sprintf('<p id="msg_warning">%s</p>', $info['warn']);
+} elseif ($info['msg']) {
+    echo sprintf('<p id="msg_notice">%s</p>', $info['msg']);
+} elseif ($info['notice']) {
+   echo sprintf('<p id="msg_info"><i class="icon-info-sign"></i> %s</p>',
+           $info['notice']);
+}
+
+$action = $info[':action'] ?: ('#tasks/mass/'. $action);
+?>
+<div style="display:block; margin:5px;">
+    <form method="post" name="status" id="status"
+        action="<?php echo $action; ?>"
+        class="mass-action">
+        <table width="100%">
+            <?php
+            if ($info[':extra']) {
+                ?>
+            <tbody>
+                <tr><td colspan="2"><strong><?php echo $info[':extra'];
+                ?></strong></td> </tr>
+            </tbody>
+            <?php
+            }
+            ?>
+            <tbody>
+                <tr>
+                    <td colspan=2>
+                        <span>
+                            <strong><?php echo __('Status') ?>:&nbsp;</strong>
+                            <select name="status">
+                            <?php
+                            $statuses = array(
+                                    'open' => __('Open'),
+                                    'closed' => __('Closed'));
+
+                            if (!$info['status'])
+                                echo '<option value=""> '. __('Select One')
+                                .' </option>';
+                            foreach ($statuses as $k => $status) {
+                                echo sprintf('<option value="%s" %s>%s</option>',
+                                        $k,
+                                        ($info['status'] == $k)
+                                         ? 'selected="selected"' : '',
+                                        $status
+                                        );
+                            }
+                            ?>
+                            </select>
+                            <font class="error">*&nbsp;<?php echo
+                            $errors['status']; ?></font>
+                        </span>
+                    </td>
+                </tr>
+            </tbody>
+            <tbody>
+                <tr>
+                    <td colspan="2">
+                        <?php
+                        $placeholder = $info[':placeholder'] ?: __('Optional reason for status change (internal note)');
+                        ?>
+                        <textarea name="comments" id="comments"
+                            cols="50" rows="3" wrap="soft" style="width:100%"
+                            class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext';
+                            ?> no-bar"
+                            placeholder="<?php echo $placeholder; ?>"><?php
+                            echo $info['comments']; ?></textarea>
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+        <hr>
+        <p class="full-width">
+            <span class="buttons pull-left">
+                <input type="reset" value="<?php echo __('Reset'); ?>">
+                <input type="button" name="cancel" class="close"
+                value="<?php echo __('Cancel'); ?>">
+            </span>
+            <span class="buttons pull-right">
+                <input type="submit" value="<?php
+                echo $verb ?: __('Submit'); ?>">
+            </span>
+         </p>
+    </form>
+</div>
+<div class="clear"></div>
diff --git a/include/staff/templates/task-transfer.tmpl.php b/include/staff/templates/task-transfer.tmpl.php
index 462714bb8a431ad8236d42b580464ee3f7129c12..a45311f7f991b4fba4e0522272401edde44c376b 100644
--- a/include/staff/templates/task-transfer.tmpl.php
+++ b/include/staff/templates/task-transfer.tmpl.php
@@ -1,6 +1,8 @@
 <?php
 global $cfg;
 
+$form = $form ?: TransferForm::instantiate($info);
+
 if (!$info[':title'])
     $info[':title'] = sprintf(__('%s Selected Tasks'),
             __('Tranfer'));
@@ -41,43 +43,12 @@ $action = $info[':action'] ?: ('#tasks/mass/transfer');
        ?>
         <tbody>
             <tr><td colspan=2>
-                <span>
-                <strong><?php echo __('Department') ?>:&nbsp;</strong>
-                <select name="dept_id">
-                <?php
-                foreach (Dept::getDepartments() as $id => $name) {
-                    if ($task && $task->getDeptId() == $id)
-                        $name .= sprintf(' (%s) ', __('current'));
-
-                    echo sprintf('<option value="%d" %s>%s</option>',
-                            $id,
-                            ($info['dept_id'] == $id)
-                             ? 'selected="selected"' : '',
-                            $name
-                            );
-                }
-                ?>
-                </select>
-                <font class="error">*&nbsp;<?php echo
-                $errors['dept_id']; ?></font>
-                </span>
+             <?php
+             $options = array('template' => 'simple', 'form_id' => 'transfer');
+             $form->render($options);
+             ?>
             </td> </tr>
         </tbody>
-        <tbody>
-            <tr>
-                <td colspan="2">
-                <?php
-                $placeholder = $info[':placeholder'] ?: __('Optional reason for the transfer');
-                ?>
-                <textarea name="comments" id="comments"
-                    cols="50" rows="3" wrap="soft" style="width:100%"
-                    class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext';
-                    ?> no-bar"
-                    placeholder="<?php echo $placeholder; ?>"><?php
-                    echo $info['comments']; ?></textarea>
-                </td>
-            </tr>
-        </tbody>
     </table>
     <hr>
     <p class="full-width">
diff --git a/include/staff/templates/task.tmpl.php b/include/staff/templates/task.tmpl.php
index bf1355bc0ac77b94c672f322eee7e1389674ccf4..d92da014f9f53e7367f67daecbfe3f41b517c45b 100644
--- a/include/staff/templates/task.tmpl.php
+++ b/include/staff/templates/task.tmpl.php
@@ -3,6 +3,10 @@
 if (!$info['title'])
     $info['title'] = __('New Task');
 
+$namespace = 'task.add';
+if ($ticket)
+    $namespace = sprintf('ticket.%d.task', $ticket->getId());
+
 ?>
 <div id="task-form">
 <h3><?php echo $info['title']; ?></h3>
@@ -24,9 +28,7 @@ if ($info['error']) {
         $form = $form ?: TaskForm::getInstance();
         $form->render(true,
                 __('Create New Task'),
-                array(
-                    'draft-namespace' => sprintf('ticket.%d.task',
-                        $ticket->getId()))
+                array('draft-namespace' => $namespace)
                 );
     ?>
         <tr><th colspan=2><em><?php
diff --git a/include/staff/templates/tasks-actions.tmpl.php b/include/staff/templates/tasks-actions.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..306cd6ea9ab29c390f4dd01c3bca570890b7a2fe
--- /dev/null
+++ b/include/staff/templates/tasks-actions.tmpl.php
@@ -0,0 +1,139 @@
+<?php
+// Tasks' mass actions based on logged in agent
+
+$actions = array();
+
+if ($agent->hasPerm(Task::PERM_CLOSE)) {
+
+    if (isset($options['status'])) {
+        $status = $options['status'];
+    ?>
+        <span
+            class="action-button"
+            data-dropdown="#action-dropdown-tasks-status">
+            <i class="icon-caret-down pull-right"></i>
+            <a class="tasks-status-action"
+                href="#statuses"><i
+                class="icon-flag"></i> <?php
+                echo __('Change Status'); ?></a>
+        </span>
+        <div id="action-dropdown-tasks-status"
+            class="action-dropdown anchor-right">
+            <ul>
+                <?php
+                if (!$status || !strcasecmp($status, 'closed')) { ?>
+                <li>
+                    <a class="no-pjax tasks-action"
+                        href="#tasks/mass/reopen"><i
+                        class="icon-fixed-width icon-undo"></i> <?php
+                        echo __('Reopen Tasks');?> </a>
+                </li>
+                <?php
+                }
+                if (!$status || !strcasecmp($status, 'open')) {
+                ?>
+                <li>
+                    <a class="no-pjax tasks-action"
+                        href="#tasks/mass/close"><i
+                        class="icon-fixed-width icon-ok-circle"></i> <?php
+                        echo __('Close Tasks');?> </a>
+                </li>
+                <?php
+                } ?>
+            </ul>
+        </div>
+<?php
+    } else {
+
+        $actions += array(
+                'reopen' => array(
+                    'icon' => 'icon-undo',
+                    'action' => __('Reopen Tasks')
+                ));
+
+        $actions += array(
+                'close' => array(
+                    'icon' => 'icon-ok-circle',
+                    'action' => __('Close Tasks')
+                ));
+    }
+}
+
+if ($agent->hasPerm(Task::PERM_ASSIGN)) {
+    $actions += array(
+            'assign' => array(
+                'icon' => 'icon-user',
+                'action' => __('Assign Tasks')
+            ));
+}
+
+if ($agent->hasPerm(Task::PERM_TRANSFER)) {
+    $actions += array(
+            'transfer' => array(
+                'icon' => 'icon-share',
+                'action' => __('Transfer Tasks')
+            ));
+}
+
+if ($agent->hasPerm(Task::PERM_DELETE)) {
+    $actions += array(
+            'delete' => array(
+                'icon' => 'icon-trash',
+                'action' => __('Delete Tasks')
+            ));
+}
+if ($actions) {
+    $more = $options['morelabel'] ?: __('More');
+    ?>
+    <span
+        class="action-button"
+        data-dropdown="#action-dropdown-moreoptions">
+        <i class="icon-caret-down pull-right"></i>
+        <a class="tasks-action"
+            href="#moreoptions"><i
+            class="icon-reorder"></i> <?php
+            echo $more; ?></a>
+    </span>
+    <div id="action-dropdown-moreoptions"
+        class="action-dropdown anchor-right">
+        <ul>
+    <?php foreach ($actions as $a => $action) { ?>
+            <li>
+                <a class="no-pjax tasks-action"
+                    <?php
+                    if ($action['dialog'])
+                        echo sprintf("data-dialog='%s'", $action['dialog']);
+                    if ($action['redirect'])
+                        echo sprintf("data-redirect='%s'", $action['redirect']);
+                    ?>
+                    href="<?php
+                    echo sprintf('#tasks/mass/%s', $a); ?>"
+                    ><i class="icon-fixed-width <?php
+                    echo $action['icon'] ?: 'icon-tag'; ?>"></i> <?php
+                    echo $action['action']; ?></a>
+            </li>
+        <?php
+        } ?>
+        </ul>
+    </div>
+ <?php
+ } ?>
+<script type="text/javascript">
+$(function() {
+    $(document).off('.tasks');
+    $(document).on('click.tasks', 'a.tasks-action', function(e) {
+        e.preventDefault();
+        var count = checkbox_checker($('form#tasks'), 1);
+        if (count) {
+            var url = 'ajax.php/'
+            +$(this).attr('href').substr(1)
+            +'?count='+count
+            +'&_uid='+new Date().getTime();
+            $.dialog(url, [201], function (xhr) {
+                $.pjax.reload('#pjax-container');
+             });
+        }
+        return false;
+    });
+});
+</script>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 8f6a86ce857528594c9e6ee2c607d2ecdcb5485c..bc10b928410dc25aeace52771f34910f7e5c5a35 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -390,11 +390,10 @@ $_SESSION[':Q:tickets'] = $orig_tickets;
      <tbody>
         <?php
         // Setup Subject field for display
-        $subject_field = TicketForm::objects()->one()->getField('subject');
+        $subject_field = TicketForm::getInstance()->getField('subject');
         $class = "row1";
         $total=0;
         $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null;
-        $subject_field = TicketForm::objects()->one()->getField('subject');
         foreach ($tickets as $T) {
             $total += 1;
                 $tag=$T['staff_id']?'assigned':'openticket';