diff --git a/include/ajax.tasks.php b/include/ajax.tasks.php
index e0330dc98daf11ec850e10a6ac859c4916c67271..31ce73c4835f1f8384c5fc9f6cc6f4f59459519e 100644
--- a/include/ajax.tasks.php
+++ b/include/ajax.tasks.php
@@ -22,6 +22,41 @@ require_once(INCLUDE_DIR.'class.task.php');
 
 class TasksAjaxAPI extends AjaxController {
 
+    function lookup() {
+        global $thisstaff;
+
+        $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
+        $tasks = array();
+
+        $visibility = Q::any(array(
+            'staff_id' => $thisstaff->getId(),
+            'team_id__in' => $thisstaff->teams->values_flat('team_id'),
+        ));
+
+        if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts())) {
+            $visibility->add(array('dept_id__in' => $depts));
+        }
+
+
+        $hits = TaskModel::objects()
+            ->filter(Q::any(array(
+                'number__startswith' => $_REQUEST['q'],
+            )))
+            ->filter($visibility)
+            ->values('number')
+            ->annotate(array('tasks' => SqlAggregate::COUNT('id')))
+            ->order_by('-created')
+            ->limit($limit);
+
+        foreach ($hits as $T) {
+            $tasks[] = array('id'=>$T['number'], 'value'=>$T['number'],
+                'info'=>"{$T['number']}",
+                'matches'=>$_REQUEST['q']);
+        }
+
+        return $this->json_encode($tasks);
+    }
+
     function add() {
         global $thisstaff;
 
@@ -86,9 +121,16 @@ class TasksAjaxAPI extends AjaxController {
         $forms = DynamicFormEntry::forObject($task->getId(),
                 ObjectModel::OBJECT_TYPE_TASK);
 
-        if ($_POST) {
+        if ($_POST && $forms) {
+            // TODO: Validate internal form
+
+            // Validate dynamic meta-data
+            if ($task->update($forms, $_POST, $errors)) {
+                Http::response(201, 'Task updated successfully');
+            } elseif(!$errors['err']) {
+                $errors['err']=__('Unable to update the task. Correct the errors below and try again!');
+            }
             $info = Format::htmlchars($_POST);
-            $info['error'] = $errors['err'] ?: __('Coming soon!');
         }
 
         include STAFFINC_DIR . 'templates/task-edit.tmpl.php';
@@ -198,7 +240,7 @@ class TasksAjaxAPI extends AjaxController {
             }
             // Check generic permissions --  department specific permissions
             // will be checked below.
-            if ($perm && !$thisstaff->hasPerm($perm))
+            if ($perm && !$thisstaff->hasPerm($perm, false))
                 $errors['err'] = sprintf(
                         __('You do not have permission to %s %s'),
                         __($action),
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index dde62c7f2a511474610c5913d14446c6df87049d..6b1c6cd6e3ebe471ba32d3a9d803596e6a3407a1 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -793,32 +793,54 @@ class TicketsAjaxAPI extends AjaxController {
                 || !$task->checkStaffPerm($thisstaff))
             Http::response(404, 'Unknown task');
 
-        $info=$errors=array();
-        $note_form = new SimpleForm(array(
+        $info = $errors = array();
+        $note_attachments_form = new SimpleForm(array(
             'attachments' => new FileUploadField(array('id'=>'attach',
-            'name'=>'attach:note',
-            'configuration' => array('extensions'=>'')))
-            ));
+                'name'=>'attach:note',
+                'configuration' => array('extensions'=>'')))
+        ));
+
+        $reply_attachments_form = new SimpleForm(array(
+            'attachments' => new FileUploadField(array('id'=>'attach',
+                'name'=>'attach:reply',
+                'configuration' => array('extensions'=>'')))
+        ));
 
         if ($_POST) {
+            $vars = $_POST;
             switch ($_POST['a']) {
             case 'postnote':
-                $vars = $_POST;
-                $attachments = $note_form->getField('attachments')->getClean();
+                $attachments = $note_attachments_form->getField('attachments')->getClean();
                 $vars['cannedattachments'] = array_merge(
                     $vars['cannedattachments'] ?: array(), $attachments);
-                if(($note=$task->postNote($vars, $errors, $thisstaff))) {
+                if (($note=$task->postNote($vars, $errors, $thisstaff))) {
                     $msg=__('Note posted successfully');
                     // Clear attachment list
-                    $note_form->setSource(array());
-                    $note_form->getField('attachments')->reset();
+                    $note_attachments_form->setSource(array());
+                    $note_attachments_form->getField('attachments')->reset();
                     Draft::deleteForNamespace('task.note.'.$task->getId(),
                             $thisstaff->getId());
                 } else {
-                    if(!$errors['err'])
+                    if (!$errors['err'])
                         $errors['err'] = __('Unable to post the note - missing or invalid data.');
                 }
                 break;
+            case 'postreply':
+                $attachments = $reply_attachments_form->getField('attachments')->getClean();
+                $vars['cannedattachments'] = array_merge(
+                    $vars['cannedattachments'] ?: array(), $attachments);
+                if (($response=$task->postReply($vars, $errors))) {
+                    $msg=__('Update posted successfully');
+                    // Clear attachment list
+                    $reply_attachments_form->setSource(array());
+                    $reply_attachments_form->getField('attachments')->reset();
+                    Draft::deleteForNamespace('task.reply.'.$task->getId(),
+                            $thisstaff->getId());
+                } else {
+                    if (!$errors['err'])
+                        $errors['err'] = __('Unable to post the reply - missing or invalid data.');
+                }
+                break;
             default:
                 $errors['err'] = __('Unknown action');
             }
diff --git a/include/class.config.php b/include/class.config.php
index f9aec5d0f040a3bcf1e338a35dcdaece50ea586c..c9125aaa3a4be615079695d4d3668c43e35c911b 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -1181,7 +1181,6 @@ class OsticketConfig extends Config {
 
     function updateTasksSettings($vars, &$errors) {
         $f=array();
-        $f['default_task_sla_id']=array('type'=>'int',   'required'=>1, 'error'=>__('Selection required'));
         $f['default_task_priority_id']=array('type'=>'int',   'required'=>1, 'error'=>__('Selection required'));
 
         if (!preg_match('`(?!<\\\)#`', $vars['task_number_format']))
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index c6dbac5f979e4c40393ad435b45ef297bf9a2362..479e94736fd3c59e263e0ab017ad968837e94589 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -616,8 +616,8 @@ class DynamicFormField extends VerySimpleModel {
     const FLAG_MASK_VIEW        = 0x20000;
     const FLAG_MASK_NAME        = 0x40000;
 
-    const MASK_MASK_INTERNAL    = 0x400B0;  # !change, !delete, !disable, !edit-name
-    const MASK_MASK_ALL         = 0x700F0;
+    const MASK_MASK_INTERNAL    = 0x400B2;  # !change, !delete, !disable, !edit-name
+    const MASK_MASK_ALL         = 0x700F2;
 
     const FLAG_CLIENT_VIEW      = 0x00100;
     const FLAG_CLIENT_EDIT      = 0x00200;
@@ -1021,9 +1021,9 @@ class DynamicFormEntry extends VerySimpleModel {
         return $this->form;
     }
 
-    function getForm() {
+    function getForm($source=false, $options=array()) {
         if (!isset($this->_form)) {
-            // XXX: Should source be $this?
+
             $fields = $this->getFields();
             if (isset($this->extra)) {
                 $x = JsonDataParser::decode($this->extra) ?: array();
@@ -1031,13 +1031,16 @@ class DynamicFormEntry extends VerySimpleModel {
                     unset($fields[$id]);
                 }
             }
-            $form = new SimpleForm($fields, $this->getSource(),
-            array(
+
+            $source = $source ?: $this->getSource();
+            $options += array(
                 'title' => $this->getTitle(),
-                'instructions' => $this->getInstructions(),
-            ));
-            $this->_form = $form;
+                'instructions' => $this->getInstructions()
+                );
+            $this->_form = new CustomForm($fields, $source, $options);
         }
+
+
         return $this->_form;
     }
 
@@ -1056,8 +1059,6 @@ class DynamicFormEntry extends VerySimpleModel {
             // even when stored elsewhere -- important during validation
             foreach ($this->getDynamicFields() as $f) {
                 $f = $f->getImpl($f);
-                if ($f instanceof ThreadEntryField)
-                    continue;
                 $this->_fields[$f->get('id')] = $f;
                 $f->isnew = true;
             }
@@ -1103,16 +1104,17 @@ class DynamicFormEntry extends VerySimpleModel {
      * Parameters:
      * $filter - (callback) function to receive each field and return
      *      boolean true if the field's errors are significant
+     * $options - options to pass to form and fields.
+     *
      */
-    function isValid($filter=false) {
+    function isValid($filter=false, $options=array()) {
+
         if (!is_array($this->_errors)) {
-            $this->_errors = array();
-            $this->getClean();
-            foreach ($this->getFields() as $field) {
-                if ($field->errors() && (!$filter || $filter($field)))
-                    $this->_errors[$field->get('id')] = $field->errors();
-            }
+            $form = $this->getForm(false, $options);
+            $form->isValid($filter);
+            $this->_errors = $form->errors();
         }
+
         return !$this->_errors;
     }
 
diff --git a/include/class.export.php b/include/class.export.php
index d12dd3bf4762ca9456c56ae31a270bf249264549..b6b9449a5fca4a7bcacb87121c873cc906f99cce 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -99,7 +99,7 @@ class Export {
             );
     }
 
-    /* static */ function saveTickets($sql, $filename, $how='csv') {
+    static  function saveTickets($sql, $filename, $how='csv') {
         ob_start();
         self::dumpTickets($sql, $how);
         $stuff = ob_get_contents();
@@ -110,7 +110,64 @@ class Export {
         return false;
     }
 
+
+    static function dumpTasks($sql, $how='csv') {
+        // Add custom fields to the $sql statement
+        $cdata = $fields = array();
+        foreach (TaskForm::getInstance()->getFields() as $f) {
+            // Ignore non-data fields
+            if (!$f->hasData() || $f->isPresentationOnly())
+                continue;
+
+            $name = $f->get('name') ?: 'field_'.$f->get('id');
+            $key = 'cdata.'.$name;
+            $fields[$key] = $f;
+            $cdata[$key] = $f->getLocal('label');
+        }
+        // Reset the $sql query
+        $tasks = $sql->models()
+            ->select_related('dept', 'staff', 'team', 'cdata')
+            ->annotate(array(
+            'collab_count' => SqlAggregate::COUNT('thread__collaborators'),
+            'attachment_count' => SqlAggregate::COUNT('thread__entries__attachments'),
+            'thread_count' => SqlAggregate::COUNT('thread__entries'),
+        ));
+
+        return self::dumpQuery($tasks,
+            array(
+                'number' =>         __('Task Number'),
+                'created' =>        __('Date Created'),
+                'cdata.title' =>    __('Title'),
+                'dept::getLocalName' => __('Department'),
+                '::getStatus' =>    __('Current Status'),
+                'duedate' =>        __('Due Date'),
+                'staff::getName' => __('Agent Assigned'),
+                'team::getName' =>  __('Team Assigned'),
+                'thread_count' =>   __('Thread Count'),
+                'attachment_count' => __('Attachment Count'),
+            ) + $cdata,
+            $how,
+            array('modify' => function(&$record, $keys) use ($fields) {
+                foreach ($fields as $k=>$f) {
+                    if (($i = array_search($k, $keys)) !== false) {
+                        $record[$i] = $f->export($f->to_php($record[$i]));
+                    }
+                }
+                return $record;
+            })
+            );
+    }
+
+
     static function saveTasks($sql, $filename, $how='csv') {
+
+        ob_start();
+        self::dumpTasks($sql, $how);
+        $stuff = ob_get_contents();
+        ob_end_clean();
+        if ($stuff)
+            Http::download($filename, "text/$how", $stuff);
+
         return false;
     }
 
diff --git a/include/class.forms.php b/include/class.forms.php
index dbd05a2b94e152849f15ee88d43f1e0943b98e97..ebc3c5588e6d58e24c55e60ad099fc965c4d0a54 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -22,6 +22,7 @@ class Form {
     static $renderer = 'GridFluidLayout';
     static $id = 0;
 
+    var $options = array();
     var $fields = array();
     var $title = '';
     var $instructions = '';
@@ -33,6 +34,7 @@ class Form {
 
     function __construct($source=null, $options=array()) {
 
+        $this->options = $options;
         if (isset($options['title']))
             $this->title = $options['title'];
         if (isset($options['instructions']))
@@ -174,13 +176,13 @@ class Form {
         echo $this->getMedia();
     }
 
-    function getLayout() {
+    function getLayout($title=false, $options=array()) {
         $rc = @$options['renderer'] ?: static::$renderer;
         return new $rc($title, $options);
     }
 
-    function asTable($options=array()) {
-        return $this->getLayout()->asTable($this);
+    function asTable($title=false, $options=array()) {
+        return $this->getLayout($title, $options)->asTable($this);
         // XXX: Media can't go in a table
         echo $this->getMedia();
     }
@@ -321,6 +323,26 @@ class SimpleForm extends Form {
     }
 }
 
+class CustomForm extends SimpleForm {
+
+    function getFields() {
+        global $thisstaff, $thisclient;
+
+        $options = $this->options;
+        $user = $options['user'] ?: $thisstaff ?: $thisclient;
+        $isedit = ($options['mode'] == 'edit');
+        $fields = array();
+        foreach (parent::getFields() as $field) {
+            if ($isedit && !$field->isEditable($user))
+                continue;
+
+            $fields[] = $field;
+        }
+
+        return $fields;
+    }
+}
+
 abstract class AbstractForm extends Form {
     function __construct($source=null, $options=array()) {
         parent::__construct($source, $options);
@@ -347,6 +369,14 @@ interface FormRenderer {
 abstract class FormLayout {
     static $default_cell_layout = 'Cell';
 
+    var $title;
+    var $options;
+
+    function __construct($title=false, $options=array()) {
+        $this->title = $title;
+        $this->options = $options;
+    }
+
     function getLayout($field) {
         $layout = $field->get('layout') ?: static::$default_cell_layout;
         if (is_string($layout))
@@ -362,13 +392,17 @@ implements FormRenderer {
       ob_start();
 ?>
       <table class="<?php echo 'grid form' ?>">
-          <caption><?php echo Format::htmlchars($form->getTitle()); ?>
+          <caption><?php echo Format::htmlchars($this->title ?: $form->getTitle()); ?>
                   <div><small><?php echo Format::viewableImages($form->getInstructions()); ?></small></div>
           </caption>
           <tbody><tr><?php for ($i=0; $i<12; $i++) echo '<td style="width:8.3333%"/>'; ?></tr></tbody>
 <?php
       $row_size = 12;
       $cols = $row = 0;
+
+      //Layout and rendering options
+      $options = $this->options;
+
       foreach ($form->getFields() as $f) {
           $layout = $this->getLayout($f);
           $size = $layout->getWidth() ?: 12;
@@ -386,7 +420,7 @@ implements FormRenderer {
           $attrs = array('colspan' => $size, 'rowspan' => $layout->getHeight(),
               'style' => '"'.$layout->getOption('style').'"');
           if ($offs) { ?>
-              <td colspan="<?php echo $offset; ?>"></td> <?php
+              <td colspan="<?php echo $offs; ?>"></td> <?php
           }
           ?>
           <td class="cell" <?php echo Format::array_implode('=', ' ', array_filter($attrs)); ?>
@@ -398,6 +432,10 @@ implements FormRenderer {
               <label class="<?php if ($f->isRequired()) echo 'required'; ?>"
                   for="<?php echo $f->getWidget()->id; ?>">
                   <?php echo Format::htmlchars($label); ?>:
+                <?php if ($f->isRequired()) { ?>
+                <span class="error">*</span>
+                <?php
+                }?>
               </label>
 <?php         }
               if ($f->get('hint')) { ?>
@@ -405,7 +443,7 @@ implements FormRenderer {
                       <?php echo Format::htmlchars($f->get('hint')); ?>
                   </div>
 <?php         }
-              $f->render();
+              $f->render($options);
               if ($f->errors())
                   foreach ($f->errors() as $e)
                       echo sprintf('<div class="error">%s</div>', Format::htmlchars($e));
@@ -656,14 +694,21 @@ class FormField {
     }
 
     /**
-     * FIXME: Temp
+     * Check if the user has edit rights
      *
      */
 
-    function isEditable() {
-        return (($this->get('flags') & DynamicFormField::FLAG_MASK_EDIT) == 0);
+    function isEditable($user=null) {
+
+        if ($user instanceof Staff)
+            $flag = DynamicFormField::FLAG_AGENT_EDIT;
+        else
+            $flag = DynamicFormField::FLAG_CLIENT_EDIT;
+
+        return (($this->get('flags') & $flag) != 0);
     }
 
+
     /**
      * isStorable
      *
@@ -1969,6 +2014,14 @@ class ThreadEntryField extends FormField {
         $config = $this->getConfiguration();
         return $config['attachments'];
     }
+
+    function getWidget($widgetClass=false) {
+        if ($hint = $this->get('hint'))
+            $this->set('placeholder', $hint);
+        $this->set('hint', null);
+        $widget = parent::getWidget($widgetClass);
+        return $widget;
+    }
 }
 
 class PriorityField extends ChoiceField {
@@ -3347,7 +3400,7 @@ class DatetimePickerWidget extends Widget {
         }
         ?>
         <input type="text" name="<?php echo $this->name; ?>"
-            id="<?php echo $this->id; ?>"
+            id="<?php echo $this->id; ?>" style="display:inline-block;width:auto"
             value="<?php echo Format::htmlchars($this->value); ?>" size="12"
             autocomplete="off" class="dp" />
         <script type="text/javascript">
@@ -3374,6 +3427,8 @@ class DatetimePickerWidget extends Widget {
             // TODO: Add time picker -- requires time picker or selection with
             //       Misc::timeDropdown
             echo '&nbsp;' . Misc::timeDropdown($hr, $min, $this->name . ':time');
+
+        echo '</div>';
     }
 
     /**
@@ -3429,11 +3484,8 @@ class ThreadEntryWidget extends Widget {
 
         list($draft, $attrs) = Draft::getDraftAndDataAttrs($namespace, $object_id, $this->value);
         ?>
-        <span class="required"><?php
-            echo Format::htmlchars($this->field->getLocal('label'));
-        ?>: <span class="error">*</span></span><br/>
         <textarea style="width:100%;" name="<?php echo $this->field->get('name'); ?>"
-            placeholder="<?php echo Format::htmlchars($this->field->get('hint')); ?>"
+            placeholder="<?php echo Format::htmlchars($this->field->get('placeholder')); ?>"
             class="<?php if ($cfg->isRichTextEnabled()) echo 'richtext';
                 ?> draft draft-delete" <?php echo $attrs; ?>
             cols="21" rows="8" style="width:80%;"><?php echo
diff --git a/include/class.misc.php b/include/class.misc.php
index 4a7301782600c3cfe69b8b493d3ee02794bda8a8..69063c44229556fa6e1467224bfb4e01dc9d0604 100644
--- a/include/class.misc.php
+++ b/include/class.misc.php
@@ -160,7 +160,7 @@ class Misc {
             $min=0;
 
         ob_start();
-        echo sprintf('<select name="%s" id="%s">',$name,$name);
+        echo sprintf('<select name="%s" id="%s" style="display:inline-block;width:auto">',$name,$name);
         echo '<option value="" selected>'.__('Time').'</option>';
         for($i=23; $i>=0; $i--) {
             for($minute=45; $minute>=0; $minute-=15) {
diff --git a/include/class.nav.php b/include/class.nav.php
index e2dde777539cf2aa5157a68aa6fe4f559f01ae89..aafb4dc2562c92c4cc8cf09e2308cabf6343040a 100644
--- a/include/class.nav.php
+++ b/include/class.nav.php
@@ -177,7 +177,7 @@ class StaffNav {
                     if($staff) {
                         if ($staff->hasPerm(FAQ::PERM_MANAGE))
                             $subnav[]=array('desc'=>__('Categories'),'href'=>'categories.php','iconclass'=>'faq-categories');
-                        if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(Canned::PERM_MANAGE, false))
+                        if ($cfg->isCannedResponseEnabled() && $staff->hasPerm(Canned::PERM_MANAGE, false))
                             $subnav[]=array('desc'=>__('Canned Responses'),'href'=>'canned.php','iconclass'=>'canned');
                     }
                    break;
diff --git a/include/class.staff.php b/include/class.staff.php
index 153bdc0a0d23a6b1d19b9379da7e33d36d9ef88c..6725a9c85ba105c4f619ac72f86af9e39ad6ee48 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -1047,7 +1047,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
         return !$errors;
     }
 
-    function updatePerms($vars, &$errors) {
+    function updatePerms($vars, &$errors=array()) {
         if (!$vars) {
             $this->permissions = '';
             return;
diff --git a/include/class.task.php b/include/class.task.php
index 5f35fd5420d92575dcfa2b6afcc8a8718e79632e..5f9206e8069ced89007d7cace68f54d35ec2d4fb 100644
--- a/include/class.task.php
+++ b/include/class.task.php
@@ -63,6 +63,7 @@ class TaskModel extends VerySimpleModel {
     const PERM_EDIT     = 'task.edit';
     const PERM_ASSIGN   = 'task.assign';
     const PERM_TRANSFER = 'task.transfer';
+    const PERM_REPLY    = 'task.reply';
     const PERM_CLOSE    = 'task.close';
     const PERM_DELETE   = 'task.delete';
 
@@ -87,6 +88,11 @@ class TaskModel extends VerySimpleModel {
                 /* @trans */ 'Transfer',
                 'desc'  =>
                 /* @trans */ 'Ability to transfer tasks between departments'),
+            self::PERM_REPLY => array(
+                'title' =>
+                /* @trans */ 'Post Reply',
+                'desc'  =>
+                /* @trans */ 'Ability to post task update'),
             self::PERM_CLOSE     => array(
                 'title' =>
                 /* @trans */ 'Close',
@@ -168,11 +174,11 @@ class TaskModel extends VerySimpleModel {
         return !$this->isOpen();
     }
 
-    function close() {
+    protected function close() {
         return $this->clearFlag(self::ISOPEN);
     }
 
-    function reopen() {
+    protected function reopen() {
         return $this->setFlag(self::ISOPEN);
     }
 
@@ -193,12 +199,36 @@ class TaskModel extends VerySimpleModel {
 RolePermission::register(/* @trans */ 'Tasks', TaskModel::getPermissions());
 
 
-class Task extends TaskModel implements Threadable {
+class Task extends TaskModel implements RestrictedAccess, Threadable {
     var $form;
     var $entry;
 
     var $_thread;
     var $_entries;
+    var $_answers;
+
+    var $lastrespondent;
+
+    function __onload() {
+        $this->loadDynamicData();
+    }
+
+    function loadDynamicData() {
+        if (!isset($this->_answers)) {
+            $this->_answers = array();
+            foreach (DynamicFormEntryAnswer::objects()
+                ->filter(array(
+                    'entry__object_id' => $this->getId(),
+                    'entry__object_type' => ObjectModel::OBJECT_TYPE_TASK
+                )) as $answer
+            ) {
+                $tag = mb_strtolower($answer->field->name)
+                    ?: 'field.' . $answer->field->id;
+                    $this->_answers[$tag] = $answer;
+            }
+        }
+        return $this->_answers;
+    }
 
     function getStatus() {
         return $this->isOpen() ? __('Open') : __('Completed');
@@ -281,6 +311,27 @@ class Task extends TaskModel implements Threadable {
         return $assignees ? implode($glue, $assignees):'';
     }
 
+    function getLastRespondent() {
+
+        if (!isset($this->lastrespondent)) {
+            $this->lastrespondent = Staff::objects()
+                ->filter(array(
+                'staff_id' => static::objects()
+                    ->filter(array(
+                        'thread__entries__type' => 'R',
+                        'thread__entries__staff_id__gt' => 0
+                    ))
+                    ->values_flat('thread__entries__staff_id')
+                    ->order_by('-thread__entries__id')
+                    ->limit(1)
+                ))
+                ->first()
+                ?: false;
+        }
+
+        return $this->lastrespondent;
+    }
+
     function getParticipants() {
         $participants = array();
         foreach ($this->getThread()->collaborators as $c)
@@ -386,23 +437,39 @@ class Task extends TaskModel implements Threadable {
     function setStatus($status, $comments='') {
         global $thisstaff;
 
+         $ecb = null;
         switch($status) {
         case 'open':
             if ($this->isOpen())
                 return false;
 
             $this->reopen();
+            $this->closed = null;
+
+            $ecb = function ($t) {
+                $t->logEvent('reopened', false, null, 'closed');
+            };
             break;
         case 'closed':
             if ($this->isClosed())
                 return false;
+
             $this->close();
+            $this->closed = SqlFunction::NOW();
+            $ecb = function($t) {
+                $t->logEvent('closed');
+            };
             break;
         default:
             return false;
         }
 
-        $this->save(true);
+        if (!$this->save(true))
+            return false;
+
+        // Log events via callback
+        if ($ecb) $ecb($this);
+
         if ($comments) {
             $errors = array();
             $this->postNote(array(
@@ -449,8 +516,15 @@ class Task extends TaskModel implements Threadable {
     }
 
     /* util routines */
+
+    function logEvent($state, $data=null, $user=null, $annul=null) {
+        $this->getThread()->getEvents()->log($this, $state, $data, $user, $annul);
+    }
+
     function assign(AssignmentForm $form, &$errors, $alert=true) {
+        global $thisstaff;
 
+        $evd = array();
         $assignee = $form->getAssignee();
         if ($assignee instanceof Staff) {
             if ($this->getStaffId() == $assignee->getId()) {
@@ -462,6 +536,10 @@ class Task extends TaskModel implements Threadable {
                 $errors['assignee'] = __('Agent is unavailable for assignment');
             } else {
                 $this->staff_id = $assignee->getId();
+                if ($thisstaff && $thisstaff->getId() == $assignee->getId())
+                    $evd['claim'] = true;
+                else
+                    $evd['staff'] = array($assignee->getId(), $assignee->getName());
             }
         } elseif ($assignee instanceof Team) {
             if ($this->getTeamId() == $assignee->getId()) {
@@ -471,7 +549,7 @@ class Task extends TaskModel implements Threadable {
                         );
             } else {
                 $this->team_id = $assignee->getId();
-
+                $evd = array('team' => $assignee->getId());
             }
         } else {
             $errors['assignee'] = __('Unknown assignee');
@@ -480,6 +558,8 @@ class Task extends TaskModel implements Threadable {
         if ($errors || !$this->save(true))
             return false;
 
+        $this->logEvent('assigned', $evd);
+
         $this->onAssignment($assignee,
                 $form->getField('comments')->getClean(),
                 $alert);
@@ -487,39 +567,90 @@ class Task extends TaskModel implements Threadable {
         return true;
     }
 
-    function onAssignment($assignee, $note='', $alert=true) {
-        global $thisstaff;
+    function onAssignment($assignee, $comments='', $alert=true) {
+        global $thisstaff, $cfg;
 
         if (!is_object($assignee))
             return false;
 
         $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)');
+
         //Assignment completed... post internal note.
-        $title = sprintf(__('Task assigned to %s'),
-                (string) $assignee);
+        $note = null;
+        if ($comments) {
+
+            $title = sprintf(__('Task assigned to %s'),
+                    (string) $assignee);
 
-        if (!$note) {
-            $note = $title;
-            $title = '';
+            $errors = array();
+            $note = $this->postNote(
+                    array('note' => $comments, 'title' => $title),
+                    $errors,
+                    $assigner,
+                    false);
         }
 
-        $errors = array();
-        $note = $this->postNote(
-                array('note' => $note, 'title' => $title),
-                $errors,
-                $assigner,
-                false);
-
-        // Send alerts out
-        if (!$alert)
+        // Send alerts out if enabled.
+        if (!$alert || !$cfg->alertONTaskAssignment())
             return false;
 
+        if (!($dept=$this->getDept())
+            || !($tpl = $dept->getTemplate())
+            || !($email = $dept->getAlertEmail())
+        ) {
+            return true;
+        }
+
+        // Recipients
+        $recipients = array();
+        if ($assignee instanceof Staff) {
+            if ($cfg->alertStaffONTaskAssignment())
+                $recipients[] = $assignee;
+        } elseif (($assignee instanceof Team) && $assignee->alertsEnabled()) {
+            if ($cfg->alertTeamMembersONTaskAssignment() && ($members=$assignee->getMembers()))
+                $recipients = array_merge($recipients, $members);
+            elseif ($cfg->alertTeamLeadONTaskAssignment() && ($lead=$assignee->getTeamLead()))
+                $recipients[] = $lead;
+        }
+
+        if ($recipients
+            && ($msg=$tpl->getTaskAssignmentAlertMsgTemplate())) {
+
+            $msg = $this->replaceVars($msg->asArray(),
+                array('comments' => $comments,
+                      'assignee' => $assignee,
+                      'assigner' => $assigner
+                )
+            );
+            // Send the alerts.
+            $sentlist = array();
+            $options = $note instanceof ThreadEntry
+                ? array(
+                    'inreplyto' => $note->getEmailMessageId(),
+                    'references' => $note->getEmailReferences(),
+                    'thread' => $note)
+                : array();
+
+            foreach ($recipients as $k => $staff) {
+                if (!is_object($staff)
+                    || !$staff->isAvailable()
+                    || in_array($staff->getEmail(), $sentlist)) {
+                    continue;
+                }
+
+                $alert = $this->replaceVars($msg, array('recipient' => $staff));
+                $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
+                $sentlist[] = $staff->getEmail();
+            }
+        }
+
         return true;
     }
 
     function transfer(TransferForm $form, &$errors, $alert=true) {
-        global $thisstaff;
+        global $thisstaff, $cfg;
 
+        $cdept = $this->getDept();
         $dept = $form->getDept();
         if (!$dept || !($dept instanceof Dept))
             $errors['dept'] = __('Department selection required');
@@ -531,25 +662,77 @@ class Task extends TaskModel implements Threadable {
         if ($errors || !$this->save())
             return false;
 
-        // Transfer completed... post internal note.
-        $title = sprintf(__('%s transferred to %s department'),
-                __('Task'),
-                $dept->getName());
+        // Log transfer event
+        $this->logEvent('transferred');
 
+        // Post internal note if any
         $note = $form->getField('comments')->getClean();
-        if (!$note) {
-            $note = $title;
-            $title = '';
+        if ($note) {
+            $title = sprintf(__('%1$s transferred from %2$s to %3$s'),
+                    __('Task'),
+                   $cdept->getName(),
+                    $dept->getName());
+
+            $_errors = array();
+            $note = $this->postNote(
+                    array('note' => $note, 'title' => $title),
+                    $_errors, $thisstaff, false);
         }
-        $_errors = array();
-        $note = $this->postNote(
-                array('note' => $note, 'title' => $title),
-                $_errors, $thisstaff, false);
 
         // Send alerts if requested && enabled.
-        if (!$alert)
+        if (!$alert || !$cfg->alertONTaskTransfer())
             return true;
 
+        if (($email = $dept->getAlertEmail())
+             && ($tpl = $dept->getTemplate())
+             && ($msg=$tpl->getTaskTransferAlertMsgTemplate())) {
+
+            $msg = $this->replaceVars($msg->asArray(),
+                array('comments' => $note, 'staff' => $thisstaff));
+            // Recipients
+            $recipients = array();
+            // Assigned staff or team... if any
+            if ($this->isAssigned() && $cfg->alertAssignedONTaskTransfer()) {
+                if($this->getStaffId())
+                    $recipients[] = $this->getStaff();
+                elseif ($this->getTeamId()
+                    && ($team=$this->getTeam())
+                    && ($members=$team->getMembers())
+                ) {
+                    $recipients = array_merge($recipients, $members);
+                }
+            } elseif ($cfg->alertDeptMembersONTaskTransfer() && !$this->isAssigned()) {
+                // Only alerts dept members if the task is NOT assigned.
+                if ($members = $dept->getMembersForAlerts())
+                    $recipients = array_merge($recipients, $members);
+            }
+
+            // Always alert dept manager??
+            if ($cfg->alertDeptManagerONTaskTransfer()
+                && ($manager=$dept->getManager())) {
+                $recipients[] = $manager;
+            }
+
+            $sentlist = $options = array();
+            if ($note instanceof ThreadEntry) {
+                $options += array(
+                    'inreplyto'=>$note->getEmailMessageId(),
+                    'references'=>$note->getEmailReferences(),
+                    'thread'=>$note);
+            }
+
+            foreach ($recipients as $k=>$staff) {
+                if (!is_object($staff)
+                    || !$staff->isAvailable()
+                    || in_array($staff->getEmail(), $sentlist)
+                ) {
+                    continue;
+                }
+                $alert = $this->replaceVars($msg, array('recipient' => $staff));
+                $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
+                $sentlist[] = $staff->getEmail();
+            }
+        }
 
         return true;
     }
@@ -569,12 +752,78 @@ class Task extends TaskModel implements Threadable {
         if (!($note=$this->getThread()->addNote($vars, $errors)))
             return null;
 
+        $assignee = $this->getStaff();
+
         if (isset($vars['task_status']))
             $this->setStatus($vars['task_status']);
 
+        $this->onActivity(array(
+            'activity' => $note->getActivity(),
+            'threadentry' => $note,
+            'assignee' => $assignee
+        ), $alert);
+
         return $note;
     }
 
+    /* public */
+    function postReply($vars, &$errors, $alert = true) {
+        global $thisstaff, $cfg;
+
+
+        if (!$vars['poster'] && $thisstaff)
+            $vars['poster'] = $thisstaff;
+
+        if (!$vars['staffId'] && $thisstaff)
+            $vars['staffId'] = $thisstaff->getId();
+
+        if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
+            $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
+
+        if (!($response = $this->getThread()->addResponse($vars, $errors)))
+            return null;
+
+        $assignee = $this->getStaff();
+        // Set status - if checked.
+        if ($vars['reply_status_id']
+            && $vars['reply_status_id'] != $this->getStatusId()
+        ) {
+            $this->setStatus($vars['reply_status_id']);
+        }
+
+        /*
+        // TODO: add auto claim setting for tasks.
+        // Claim on response bypasses the department assignment restrictions
+        if ($thisstaff
+            && $this->isOpen()
+            && !$this->getStaffId()
+            && $cfg->autoClaimTasks)
+        ) {
+            $this->staff_id = $thisstaff->getId();
+        }
+        */
+
+        $this->lastrespondent = $response->staff;
+        $this->save();
+
+        // Send activity alert to agents
+        $activity = $vars['activity'] ?: $response->getActivity();
+        $this->onActivity( array(
+                    'activity' => $activity,
+                    'threadentry' => $response,
+                    'assignee' => $assignee,
+                    ));
+        // Send alert to collaborators
+        if ($alert && $vars['emailcollab']) {
+            $signature = '';
+            $this->notifyCollaborators($response,
+                array('signature' => $signature)
+            );
+        }
+
+        return $response;
+    }
+
     function pdfExport($options=array()) {
         global $thisstaff;
 
@@ -596,12 +845,283 @@ class Task extends TaskModel implements Threadable {
         exit;
     }
 
+    /* util routines */
+    function replaceVars($input, $vars = array()) {
+        global $ost;
+
+        return $ost->replaceTemplateVariables($input,
+                array_merge($vars, array('task' => $this)));
+    }
+
+    function asVar() {
+       return $this->getNumber();
+    }
+
+    function getVar($tag) {
+        global $cfg;
+
+        if ($tag && is_callable(array($this, 'get'.ucfirst($tag))))
+            return call_user_func(array($this, 'get'.ucfirst($tag)));
+
+        switch(mb_strtolower($tag)) {
+        case 'phone':
+        case 'phone_number':
+            return $this->getPhoneNumber();
+            break;
+        case 'staff_link':
+            return sprintf('%s/scp/tasks.php?id=%d', $cfg->getBaseUrl(), $this->getId());
+            break;
+        case 'create_date':
+            return new FormattedDate($this->getCreateDate());
+            break;
+         case 'due_date':
+            if ($due = $this->getEstDueDate())
+                return new FormattedDate($due);
+            break;
+        case 'close_date':
+            if ($this->isClosed())
+                return new FormattedDate($this->getCloseDate());
+            break;
+        case 'last_update':
+            return new FormattedDate($this->last_update);
+        default:
+            if (isset($this->_answers[$tag]))
+                // The answer object is retrieved here which will
+                // automatically invoke the toString() method when the
+                // answer is coerced into text
+                return $this->_answers[$tag];
+        }
+        return false;
+    }
+
+    static function getVarScope() {
+        $base = array(
+            'assigned' => __('Assigned agent and/or team'),
+            'close_date' => array(
+                'class' => 'FormattedDate', 'desc' => __('Date Closed'),
+            ),
+            'create_date' => array(
+                'class' => 'FormattedDate', 'desc' => __('Date created'),
+            ),
+            'dept' => array(
+                'class' => 'Dept', 'desc' => __('Department'),
+            ),
+            'due_date' => array(
+                'class' => 'FormattedDate', 'desc' => __('Due Date'),
+            ),
+            'number' => __('Task number'),
+            'recipients' => array(
+                'class' => 'UserList', 'desc' => __('List of all recipient names'),
+            ),
+            'status' => __('Status'),
+            'staff' => array(
+                'class' => 'Staff', 'desc' => __('Assigned/closing agent'),
+            ),
+            'subject' => 'Subject',
+            'team' => array(
+                'class' => 'Team', 'desc' => __('Assigned/closing team'),
+            ),
+            'thread' => array(
+                'class' => 'TaskThread', 'desc' => __('Task Thread'),
+            ),
+            'last_update' => array(
+                'class' => 'FormattedDate', 'desc' => __('Time of last update'),
+            ),
+        );
+
+        $extra = VariableReplacer::compileFormScope(TaskForm::getInstance());
+        return $base + $extra;
+    }
+
+    function onActivity($vars, $alert=true) {
+        global $cfg, $thisstaff;
+
+        if (!$alert // Check if alert is enabled
+            || !$cfg->alertONTaskActivity()
+            || !($dept=$this->getDept())
+            || !($email=$cfg->getAlertEmail())
+            || !($tpl = $dept->getTemplate())
+            || !($msg=$tpl->getTaskActivityAlertMsgTemplate())
+        ) {
+            return;
+        }
+
+        // Alert recipients
+        $recipients = array();
+        //Last respondent.
+        if ($cfg->alertLastRespondentONTaskActivity())
+            $recipients[] = $this->getLastRespondent();
+
+        // Assigned staff / team
+        if ($cfg->alertAssignedONTaskActivity()) {
+            if (isset($vars['assignee'])
+                    && $vars['assignee'] instanceof Staff)
+                 $recipients[] = $vars['assignee'];
+            elseif ($this->isOpen() && ($assignee = $this->getStaff()))
+                $recipients[] = $assignee;
+
+            if ($team = $this->getTeam())
+                $recipients = array_merge($recipients, $team->getMembers());
+        }
+
+        // Dept manager
+        if ($cfg->alertDeptManagerONTaskActivity() && $dept && $dept->getManagerId())
+            $recipients[] = $dept->getManager();
+
+        $options = array();
+        $staffId = $thisstaff ? $thisstaff->getId() : 0;
+        if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) {
+            $options = array(
+                'inreplyto' => $vars['threadentry']->getEmailMessageId(),
+                'references' => $vars['threadentry']->getEmailReferences(),
+                'thread' => $vars['threadentry']);
+
+            // Activity details
+            if (!$vars['message'])
+                $vars['message'] = $vars['threadentry'];
+
+            // Staff doing the activity
+            $staffId = $vars['threadentry']->getStaffId() ?: $staffId;
+        }
+
+        $msg = $this->replaceVars($msg->asArray(),
+                array(
+                    'note' => $vars['threadentry'], // For compatibility
+                    'activity' => $vars['activity'],
+                    'message' => $vars['message']));
+
+        $isClosed = $this->isClosed();
+        $sentlist=array();
+        foreach ($recipients as $k=>$staff) {
+            if (!is_object($staff)
+                // Don't bother vacationing staff.
+                || !$staff->isAvailable()
+                // No need to alert the poster!
+                || $staffId == $staff->getId()
+                // No duplicates.
+                || isset($sentlist[$staff->getEmail()])
+                // Make sure staff has access to task
+                || ($isClosed && !$this->checkStaffPerm($staff))
+            ) {
+                continue;
+            }
+            $alert = $this->replaceVars($msg, array('recipient' => $staff));
+            $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
+            $sentlist[$staff->getEmail()] = 1;
+        }
+
+    }
+
+    /*
+     * Notify collaborators on response or new message
+     *
+     */
+    function  notifyCollaborators($entry, $vars = array()) {
+        global $cfg;
+
+        if (!$entry instanceof ThreadEntry
+            || !($recipients=$this->getThread()->getParticipants())
+            || !($dept=$this->getDept())
+            || !($tpl=$dept->getTemplate())
+            || !($msg=$tpl->getTaskActivityNoticeMsgTemplate())
+            || !($email=$dept->getEmail())
+        ) {
+            return;
+        }
+
+        // Who posted the entry?
+        $skip = array();
+        if ($entry instanceof Message) {
+            $poster = $entry->getUser();
+            // Skip the person who sent in the message
+            $skip[$entry->getUserId()] = 1;
+            // Skip all the other recipients of the message
+            foreach ($entry->getAllEmailRecipients() as $R) {
+                foreach ($recipients as $R2) {
+                    if (0 === strcasecmp($R2->getEmail(), $R->mailbox.'@'.$R->host)) {
+                        $skip[$R2->getUserId()] = true;
+                        break;
+                    }
+                }
+            }
+        } else {
+            $poster = $entry->getStaff();
+        }
+
+        $vars = array_merge($vars, array(
+            'message' => (string) $entry,
+            'poster' => $poster ?: _S('A collaborator'),
+            )
+        );
+
+        $msg = $this->replaceVars($msg->asArray(), $vars);
+
+        $attachments = $cfg->emailAttachments()?$entry->getAttachments():array();
+        $options = array('inreplyto' => $entry->getEmailMessageId(),
+                         'thread' => $entry);
+
+        foreach ($recipients as $recipient) {
+            // Skip folks who have already been included on this part of
+            // the conversation
+            if (isset($skip[$recipient->getUserId()]))
+                continue;
+            $notice = $this->replaceVars($msg, array('recipient' => $recipient));
+            $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
+                $options);
+        }
+    }
+
+    function update($forms, $vars, &$errors) {
+        global $thisstaff;
+
+
+        if (!$forms || !$this->checkStaffPerm($thisstaff, Task::PERM_EDIT))
+            return false;
+
+
+        foreach ($forms as $form) {
+            $form->setSource($vars);
+            if (!$form->isValid(function($f) {
+                return $f->isVisibleToStaff() && $f->isEditableToStaff();
+            }, array('mode'=>'edit'))) {
+                $errors = array_merge($errors, $form->errors());
+            }
+        }
+
+        if ($errors)
+            return false;
+
+        // Update dynamic meta-data
+        $changes = array();
+        foreach ($forms as $f) {
+            $changes += $f->getChanges();
+            $f->save();
+        }
+
+
+        if ($vars['note']) {
+            $_errors = array();
+            $this->postNote(array(
+                        'note' => $vars['note'],
+                        'title' => __('Task Update'),
+                        ),
+                    $_errors,
+                    $thisstaff);
+        }
+
+        if ($changes)
+            $this->logEvent('edited', array('fields' => $changes));
+
+        Signal::send('model.updated', $this);
+        return $this->save();
+    }
+
+    /* static routines */
     static function lookupIdByNumber($number) {
-        $sql = 'SELECT id FROM '.TASK_TABLE
-              .' WHERE `number`='.db_input($number);
-        list($id) = db_fetch_row(db_query($sql));
 
-        return $id;
+        if (($task = self::lookup(array('number' => $number))))
+            return $task->getId();
+
     }
 
     static function isNumberUnique($number) {
@@ -710,7 +1230,7 @@ class Task extends TaskModel implements Threadable {
                         .sprintf('task.flags & %d != 0 ', TaskModel::ISOPEN)
                         .')';
 
-        if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tickets.
+        if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tasks.
             $where[] = 'task.dept_id IN('.implode(',', db_input($depts)).') ';
 
         $where = implode(' OR ', $where);
@@ -807,31 +1327,36 @@ class TaskForm extends DynamicForm {
         return static::$instance;
     }
 
-    static function getInternalForm($source=null) {
+    static function getInternalForm($source=null, $options=array()) {
         if (!isset(static::$internalForm))
-            static::$internalForm = new SimpleForm(self::getInternalFields(), $source);
+            static::$internalForm = new TaskInternalForm($source, $options);
 
         return static::$internalForm;
     }
+}
 
-    static function getInternalFields() {
-        return array(
+class TaskInternalForm
+extends AbstractForm {
+    static $layout = 'GridFormLayout';
+
+    function buildFields() {
+
+        $fields = array(
                 'dept_id' => new DepartmentField(array(
                     'id'=>1,
                     'label' => __('Department'),
-                    'flags' => hexdec(0X450F3),
                     'required' => true,
+                    'layout' => new GridFluidCell(6),
                     )),
                 'staff_id' => new AssigneeField(array(
                     'id'=>2,
                     'label' => __('Assignee'),
-                    'flags' => hexdec(0X450F3),
                     'required' => false,
+                    'layout' => new GridFluidCell(6),
                     )),
                 'duedate'  =>  new DatetimeField(array(
                     'id' => 3,
                     'label' => __('Due Date'),
-                    'flags' => hexdec(0X450B3),
                     'required' => false,
                     'configuration' => array(
                         'min' => Misc::gmtime(),
@@ -842,6 +1367,14 @@ class TaskForm extends DynamicForm {
                     )),
 
             );
+
+        $mode = @$this->options['mode'];
+        if ($mode && $mode == 'edit') {
+            unset($fields['dept_id']);
+            unset($fields['staff_id']);
+        }
+
+        return $fields;
     }
 }
 
diff --git a/include/class.template.php b/include/class.template.php
index f3a459f6de0855b27c0aca7647107d2733e8481e..bc998c8516240abb0f0b8dc24ee21433e96f675a 100644
--- a/include/class.template.php
+++ b/include/class.template.php
@@ -136,32 +136,49 @@ class EmailTemplateGroup {
             'group'=>'c.task',
             'name'=>/* @trans */ 'New Task Alert',
             'desc'=>/* @trans */ 'Alert sent to agents, if enabled, on new task.',
+            'context' => array(
+                'task', 'recipient', 'message',
+            ),
         ),
         'task.activity.notice' => array(
             'group'=>'c.task',
             'name'=>/* @trans */ 'New Activity Notice',
-            'desc'=>/* @trans */ 'Template used to notify collaborators on task activity.'
+            'desc'=>/* @trans */ 'Template used to notify collaborators on task activity.',
+            'context' => array(
+                'task', 'signature', 'message', 'poster', 'recipient',
+            ),
         ),
         'task.activity.alert'=>array(
             'group'=>'c.task',
             'name'=>/* @trans */ 'New Activity Alert',
             'desc'=>/* @trans */ 'Alert sent to selected agents, if enabled, on new activity.',
-
+            'context' => array(
+                'task', 'recipient', 'note', 'comments', 'activity',
+            ),
         ),
         'task.assignment.alert' => array(
             'group'=>'c.task',
             'name'=>/* @trans */ 'Task Assignment Alert',
             'desc'=>/* @trans */ 'Alert sent to agents on task assignment.',
+            'context' => array(
+                'task', 'recipient', 'comments', 'assignee', 'assigner',
+            ),
         ),
         'task.transfer.alert'=>array(
             'group'=>'c.task',
             'name'=>/* @trans */ 'Task Transfer Alert',
             'desc'=>/* @trans */ 'Alert sent to agents on task transfer.',
+            'context' => array(
+                'task', 'recipient', 'note', 'comments', 'activity',
+            ),
         ),
         'task.overdue.alert'=>array(
             'group'=>'c.task',
             'name'=>/* @trans */ 'Overdue Task Alert',
             'desc'=>/* @trans */ 'Alert sent to agents on stale or overdue task.',
+            'context' => array(
+                'task', 'recipient', 'comments',
+            ),
         ),
     );
 
@@ -337,6 +354,10 @@ class EmailTemplateGroup {
         return $this->getMsgTemplate('task.alert');
     }
 
+    function  getTaskActivityAlertMsgTemplate() {
+        return $this->getMsgTemplate('task.activity.alert');
+    }
+
     function  getTaskActivityNoticeMsgTemplate() {
         return $this->getMsgTemplate('task.activity.notice');
     }
diff --git a/include/class.thread.php b/include/class.thread.php
index 9df67a1e0142bd15b2d15c6e96afb95227cc2edd..c0fbb8fec54c199573d176ddac58532e57ff3ea4 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -54,6 +54,7 @@ class Thread extends VerySimpleModel {
 
     var $_object;
     var $_collaborators; // Cache for collabs
+    var $_participants;
 
     function getId() {
         return $this->id;
@@ -211,13 +212,33 @@ class Thread extends VerySimpleModel {
 
         return true;
     }
+
+
+    //UserList of participants (collaborators)
+    function getParticipants() {
+
+        if (!isset($this->_participants)) {
+            $list = new UserList();
+            if ($collabs = $this->getActiveCollaborators()) {
+                foreach ($collabs as $c)
+                    $list->add($c);
+            }
+
+            $this->_participants = $list;
+        }
+
+        return $this->_participants;
+    }
+
+
     // Render thread
     function render($type=false, $options=array()) {
 
         $mode = $options['mode'] ?: self::MODE_STAFF;
 
         // Register thread actions prior to rendering the thread.
-        include_once INCLUDE_DIR . 'class.thread_actions.php';
+        if (!class_exists('tea_showemailheaders'))
+            include_once INCLUDE_DIR . 'class.thread_actions.php';
 
         $entries = $this->getEntries();
         if ($type && is_array($type))
@@ -1037,6 +1058,10 @@ implements TemplateVariable {
         return $this->email_info->save();
     }
 
+    function getActivity() {
+        return new ThreadActivity('', '');
+    }
+
     /* variables */
 
     function __toString() {
@@ -2234,6 +2259,12 @@ class ResponseThreadEntry extends ThreadEntry {
 
     const ENTRY_TYPE = 'R';
 
+    function getActivity() {
+        return new ThreadActivity(
+                _S('New Response'),
+                _S('New response posted'));
+    }
+
     function getSubject() {
         return $this->getTitle();
     }
@@ -2283,6 +2314,12 @@ class NoteThreadEntry extends ThreadEntry {
         return $this->getBody();
     }
 
+    function getActivity() {
+        return new ThreadActivity(
+                _S('New Internal Note'),
+                _S('New internal note posted'));
+    }
+
     static function create($vars, &$errors) {
         return self::add($vars, $errors);
     }
@@ -2566,4 +2603,46 @@ interface Threadable {
     function getThread();
     function postThreadEntry($type, $vars, $options=array());
 }
+
+/**
+ * ThreadActivity
+ *
+ * Object to thread activity
+ *
+ */
+class ThreadActivity implements TemplateVariable {
+    var $title;
+    var $desc;
+
+    function __construct($title, $desc) {
+        $this->title = $title;
+        $this->desc = $desc;
+    }
+
+    function getTitle() {
+        return $this->title;
+    }
+
+    function getDescription() {
+        return $this->desc;
+    }
+    function asVar() {
+        return (string) $this->getTitle();
+    }
+
+    function getVar($tag) {
+        if ($tag && is_callable(array($this, 'get'.ucfirst($tag))))
+            return call_user_func(array($this, 'get'.ucfirst($tag)));
+
+        return false;
+    }
+
+    static function getVarScope() {
+        return array(
+          'title' => __('Activity Title'),
+          'description' => __('Activity Description'),
+        );
+    }
+}
+
 ?>
diff --git a/include/class.thread_actions.php b/include/class.thread_actions.php
index df4cf37e02e1d48cb087a47b293053d20ab17236..428793399807d245f86cf0592041606784cff557 100644
--- a/include/class.thread_actions.php
+++ b/include/class.thread_actions.php
@@ -277,10 +277,11 @@ class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
     function resend($response) {
         global $cfg, $thisstaff;
 
-        $vars = $_POST;
-        $ticket = $response->getThread()->getObject();
+        if (!($object = $response->getThread()->getObject()))
+            return false;
 
-        $dept = $ticket->getDept();
+        $vars = $_POST;
+        $dept = $object->getDept();
         $poster = $response->getStaff();
 
         if ($thisstaff && $vars['signature'] == 'mine')
@@ -299,23 +300,25 @@ class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
             'poster' => $response->getStaff());
         $options = array('thread' => $response);
 
-        if (($email=$dept->getEmail())
-            && ($tpl = $dept->getTemplate())
-            && ($msg=$tpl->getReplyMsgTemplate())
-        ) {
-            $msg = $ticket->replaceVars($msg->asArray(),
-                $variables + array('recipient' => $ticket->getOwner()));
+        // Resend response to collabs
+        if (($object instanceof Ticket)
+                && ($email=$dept->getEmail())
+                && ($tpl = $dept->getTemplate())
+                && ($msg=$tpl->getReplyMsgTemplate())) {
+
+            $msg = $object->replaceVars($msg->asArray(),
+                $variables + array('recipient' => $object->getOwner()));
 
             $attachments = $cfg->emailAttachments()
                 ? $response->getAttachments() : array();
-            $email->send($ticket->getOwner(), $msg['subj'], $msg['body'],
+            $email->send($object->getOwner(), $msg['subj'], $msg['body'],
                 $attachments, $options);
         }
         // TODO: Add an option to the dialog
-        $ticket->notifyCollaborators($response, array('signature' => $signature));
+        $object->notifyCollaborators($response, array('signature' => $signature));
 
         // Log an event that the item was resent
-        $ticket->logEvent('resent', array('entry' => $response->id));
+        $object->logEvent('resent', array('entry' => $response->id));
 
         // Flag the entry as resent
         $response->flags |= ThreadEntry::FLAG_RESENT;
diff --git a/include/class.variable.php b/include/class.variable.php
index 8c10fc61c76d245e9c7c0f2ad4ee1a6ca5ed184e..76acb1d0f2d2d789e312ee4554db8c40b0e4b630 100644
--- a/include/class.variable.php
+++ b/include/class.variable.php
@@ -262,7 +262,7 @@ class VariableReplacer {
             return false;
 
         $contextTypes = array(
-            'activity' => __('Type of recent activity'),
+            'activity' => array('class' => 'ThreadActivity', 'desc' => __('Type of recent activity')),
             'assignee' => array('class' => 'Staff', 'desc' => __('Assigned agent/team')),
             'assigner' => array('class' => 'Staff', 'desc' => __('Agent performing the assignment')),
             'comments' => __('Assign/transfer comments'),
@@ -276,6 +276,7 @@ class VariableReplacer {
             'signature' => 'Selected staff or department signature',
             'staff' => array('class' => 'Staff', 'desc' => 'Agent originating the activity'),
             'ticket' => array('class' => 'Ticket', 'desc' => 'The ticket'),
+            'task' => array('class' => 'Task', 'desc' => 'The task'),
             'user' => array('class' => 'User', 'desc' => __('Message recipient')),
         );
         $context = array();
diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml
index cea0b559ceba8378f1dfc571eebe780d750d5c2a..9a807a05e86b4d87461bed3a6947bc8d7dfecef2 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -191,7 +191,7 @@
   fields:
     - type: text # notrans
       name: title # notrans
-      flags: 0x470B1
+      flags: 0x470A1
       sort: 1
       label: Title
       configuration:
@@ -199,7 +199,7 @@
         length: 50
     - type: thread # notrans
       name: description # notrans
-      flags: 0x450B3
+      flags: 0x450F3
       sort: 2
       label: Description
       hint: Details on the reason(s) for creating the task.
diff --git a/include/i18n/en_US/help/tips/tasks.queue.yaml b/include/i18n/en_US/help/tips/tasks.queue.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..393447f0b16260e6e1f2166d0453c94091794587
--- /dev/null
+++ b/include/i18n/en_US/help/tips/tasks.queue.yaml
@@ -0,0 +1,28 @@
+#
+# This is popup help messages for the Agents Panel -> Tasks
+#
+# Fields:
+# title - Shown in bold at the top of the popover window
+# content - The body of the help popover
+# links - List of links shows below the content
+#   title - Link title
+#   href - href of link (links starting with / are translated to the
+#       helpdesk installation path)
+#
+# The key names such as 'helpdesk_name' should not be translated as they
+# must match the HTML #ids put into the page template.
+#
+---
+advanced:
+    title: Advanced
+    content: >
+        Narrow down your search parameters. Once you have selected your advanced
+        search criteria and run the search, you can <span class="doc-desc-title">Export
+        </span> the data at the bottom of the page.
+
+export:
+    title: Export
+    content: >
+        Export your data currently in view in a CSV file.
+        CSV files may be opened with any spreadsheet software
+        (i.e., Microsoft Excel, Apple Pages, OpenOffice, etc.).
diff --git a/include/i18n/en_US/templates/email/task.activity.alert.yaml b/include/i18n/en_US/templates/email/task.activity.alert.yaml
index 82e7c4ce04474cf4214844d8492560074451dc1d..5c94582edca104cdacf12fb1b2333933d7b09206 100644
--- a/include/i18n/en_US/templates/email/task.activity.alert.yaml
+++ b/include/i18n/en_US/templates/email/task.activity.alert.yaml
@@ -13,34 +13,12 @@ notes: |
     or visits the web portal and posts a new message there.
 
 subject: |
-    New Activity Alert
+    Task Activity [#%{task.number}] - %{activity.title}
 body: |
     <h3><strong>Hi %{recipient.name},</strong></h3>
-    New message appended to task <a
-    href="%{ticket.staff_link}">#%{task.number}</a>
+    Task <a href="%{task.staff_link}">#%{task.number}</a> updated: %{activity.description}
     <br>
     <br>
-    <table>
-    <tbody>
-    <tr>
-        <td>
-             <strong>Title</strong>:
-        </td>
-        <td>
-             %{task.title}
-        </td>
-    </tr>
-    <tr>
-        <td>
-             <strong>Department</strong>:
-        </td>
-        <td>
-             %{task.dept.name}
-        </td>
-    </tr>
-    </tbody>
-    </table>
-    <br>
     %{message}
     <br>
     <br>
diff --git a/include/i18n/en_US/templates/email/task.activity.notice.yaml b/include/i18n/en_US/templates/email/task.activity.notice.yaml
index 45d1e3e51aecdf64851b0f54fc230f71824be969..750e4a09cd5c892ad1ec5e756bd1fbf368651e7b 100644
--- a/include/i18n/en_US/templates/email/task.activity.notice.yaml
+++ b/include/i18n/en_US/templates/email/task.activity.notice.yaml
@@ -5,7 +5,7 @@
 #
 ---
 notes: |
-    Notice sent to collaborators on task activity e.g note or message.
+    Notice sent to collaborators on task activity e.g reply or message.
 
 subject: |
     Re: %{task.title} [#%{task.number}]
@@ -20,9 +20,6 @@ body: |
     <br>
     <hr>
     <div style="color: rgb(127, 127, 127); font-size: small; text-align: center;">
-    <em>You're getting this email because you are a collaborator
-    on task <a href="%{recipient.task_link}" style="color: rgb(84, 141, 212);"
-    >#%{task.number}</a>.  To participate, simply reply to this email
-    or <a href="%{recipient.task_link}" style="color: rgb(84, 141, 212);"
-    >click here</a> for a complete archive of the task thread.</em>
+    <em>You're getting this email because you are a collaborator on task
+    #%{task.number}. To participate, simply reply to this email.</em>
     </div>
diff --git a/include/i18n/en_US/templates/email/task.alert.yaml b/include/i18n/en_US/templates/email/task.alert.yaml
index 08e311fe09f322b8c5606fb1d4e28d8d614aa678..ee6760803c6bb1d052603f1feb9ce3675399ffdf 100644
--- a/include/i18n/en_US/templates/email/task.alert.yaml
+++ b/include/i18n/en_US/templates/email/task.alert.yaml
@@ -12,7 +12,7 @@ subject: |
     New Task Alert
 body: |
     <h2>Hi %{recipient.name},</h2>
-    New task #%{task.number} created
+    New task <a href="%{task.staff_link}">#%{task.number}</a> created
     <br>
     <br>
     <table>
diff --git a/include/i18n/en_US/templates/email/task.assignment.alert.yaml b/include/i18n/en_US/templates/email/task.assignment.alert.yaml
index 4c509b08bb812f629966688d727e09a494256ac9..2a8684c51d65ceac087757a2778285f1d215ab94 100644
--- a/include/i18n/en_US/templates/email/task.assignment.alert.yaml
+++ b/include/i18n/en_US/templates/email/task.assignment.alert.yaml
@@ -1,7 +1,7 @@
 #
 # Email template: task.assignment.alert.yaml
 #
-# Sent to agents when a ticket is assigned to them or the team to which
+# Sent to agents when a task is assigned to them or the team to which
 # they belong.
 # Use %{assigner} to distinguish who made the assignment.
 #
@@ -18,19 +18,6 @@ body: |
     assigned to you by %{assigner.name.short}
     <br>
     <br>
-    <table>
-    <tbody>
-    <tr>
-        <td>
-             <strong>Title</strong>:
-        </td>
-        <td>
-             %{task.title}
-        </td>
-    </tr>
-    </tbody>
-    </table>
-    <br>
     %{comments}
     <br>
     <br>
diff --git a/include/i18n/en_US/templates/email/task.overdue.alert.yaml b/include/i18n/en_US/templates/email/task.overdue.alert.yaml
index 0b48a7337e04efd6dbdf6c0f8c7cfeb15d7b463f..05a8573d3a52b88f9b8d153daba9c4ef313e779f 100644
--- a/include/i18n/en_US/templates/email/task.overdue.alert.yaml
+++ b/include/i18n/en_US/templates/email/task.overdue.alert.yaml
@@ -2,14 +2,12 @@
 # Email template: task.overdue.alert.yaml
 #
 # Sent to agents when a tasks transitions to overdue in the system.
-# Overdue tasks occur based on set due-date as well as the SLA
-# defined for the task.
+# Overdue tasks occur based on set due-date.
 #
 ---
 notes: |
     Sent to agents when a task transitions to overdue in the system.
-    Overdue tasks occur based on the set due-date as well as the SLA
-    defined for the task.
+    Overdue tasks occur based on the set due-date.
 
 subject: |
     Stale Task Alert
diff --git a/include/staff/tasks.inc.php b/include/staff/tasks.inc.php
index ab826da6aeb550dd0fdcb42c2a4c62a0d86f410b..5a3a80df938383678b5536c8918bcdd806223e35 100644
--- a/include/staff/tasks.inc.php
+++ b/include/staff/tasks.inc.php
@@ -1,5 +1,5 @@
 <?php
-$tasks = TaskModel::objects();
+$tasks = Task::objects();
 $date_header = $date_col = false;
 
 // Figure out REFRESH url — which might not be accurate after posting a
@@ -14,26 +14,41 @@ unset($args['a']);
 
 $refresh_url = $path . '?' . http_build_query($args);
 
+$sort_options = array(
+    'updated' =>            __('Most Recently Updated'),
+    'created' =>            __('Most Recently Created'),
+    'due' =>                __('Due Soon'),
+    'number' =>             __('Task Number'),
+    'closed' =>             __('Most Recently Closed'),
+    'hot' =>                __('Longest Thread'),
+    'relevance' =>          __('Relevance'),
+);
+
 $queue_name = strtolower($_GET['status'] ?: $_GET['a']); //Status is overloaded
 switch ($queue_name) {
 case 'closed':
     $status='closed';
     $results_type=__('Closed Tasks');
     $showassigned=true; //closed by.
+    $queue_sort_options = array('closed', 'updated', 'created', 'number', 'hot');
+
     break;
 case 'overdue':
     $status='open';
     $results_type=__('Overdue Tasks');
     $tasks->filter(array('isoverdue'=>1));
+    $queue_sort_options = array('updated', 'created', 'number', 'hot');
     break;
 case 'assigned':
     $status='open';
     $staffId=$thisstaff->getId();
     $results_type=__('My Tasks');
     $tasks->filter(array('staff_id'=>$thisstaff->getId()));
+    $queue_sort_options = array('updated', 'created', 'hot', 'number');
     break;
 default:
 case 'search':
+    $queue_sort_options = array('closed', 'updated', 'created', 'number', 'hot');
     // Consider basic search
     if ($_REQUEST['query']) {
         $results_type=__('Search Results');
@@ -55,17 +70,22 @@ case 'search':
 case 'open':
     $status='open';
     $results_type=__('Open Tasks');
+    $queue_sort_options = array('created', 'updated', 'due', 'number', 'hot');
     break;
 }
 
 // Apply filters
 $filters = array();
-$SQ = new Q(array('flags__hasbit' => TaskModel::ISOPEN));
-if ($status && !strcasecmp($status, 'closed'))
-    $SQ->negate();
+if ($status) {
+    $SQ = new Q(array('flags__hasbit' => TaskModel::ISOPEN));
+    if (!strcasecmp($status, 'closed'))
+        $SQ->negate();
+
+    $filters[] = $SQ;
+}
 
-$filters[] = $SQ;
-$tasks->filter($filters);
+if ($filters)
+    $tasks->filter($filters);
 
 // Impose visibility constraints
 // ------------------------------------------------------------
@@ -106,29 +126,39 @@ $tasks->values('id', 'number', 'created', 'staff_id', 'team_id',
         'dept__name', 'cdata__title', 'flags');
 // Apply requested quick filter
 
-// Apply requested sorting
-$queue_sort_key = sprintf(':Q:%s:sort', $queue_name);
+$queue_sort_key = sprintf(':Q%s:%s:sort', ObjectModel::OBJECT_TYPE_TASK, $queue_name);
+
+if (isset($_GET['sort'])) {
+        $_SESSION[$queue_sort_key] = $_GET['sort'];
+}
+elseif (!isset($_SESSION[$queue_sort_key])) {
+        $_SESSION[$queue_sort_key] = $queue_sort_options[0];
+}
 
-if (isset($_GET['sort']))
-    $_SESSION[$queue_sort_key] = $_GET['sort'];
 switch ($_SESSION[$queue_sort_key]) {
 case 'number':
     $tasks->extra(array(
         'order_by'=>array(SqlExpression::times(new SqlField('number'), 1))
     ));
     break;
-case 'priority,due':
-    $tasks->order_by('cdata__:priority__priority_urgency');
-    // Fall through to add in due date filter
 case 'due':
     $date_header = __('Due Date');
-    $date_col = 'est_duedate';
-    $tasks->values('est_duedate');
-    $tasks->filter(array('est_duedate__isnull'=>false));
-    $tasks->order_by('est_duedate');
+    $date_col = 'duedate';
+    $tasks->values('duedate');
+    $tasks->filter(array('duedate__isnull'=>false));
+    $tasks->order_by('duedate');
     break;
 case 'updated':
-    $tasks->order_by('cdata__:priority__priority_urgency', '-lastupdate');
+    $date_header = __('Last Updated');
+    $date_col = 'updated';
+    $tasks->values('updated');
+    $tasks->order_by('-updated');
+    break;
+case 'hot':
+    $tasks->order_by('-thread_count');
+    $tasks->annotate(array(
+        'thread_count' => SqlAggregate::COUNT('thread__entries'),
+    ));
     break;
 case 'created':
 default:
@@ -150,7 +180,7 @@ $_SESSION[':Q:tasks'] = $tasks;
 // Mass actions
 $actions = array();
 
-if ($thisstaff->hasPerm(Task::PERM_ASSIGN)) {
+if ($thisstaff->hasPerm(Task::PERM_ASSIGN, false)) {
     $actions += array(
             'assign' => array(
                 'icon' => 'icon-user',
@@ -158,7 +188,7 @@ if ($thisstaff->hasPerm(Task::PERM_ASSIGN)) {
             ));
 }
 
-if ($thisstaff->hasPerm(Task::PERM_TRANSFER)) {
+if ($thisstaff->hasPerm(Task::PERM_TRANSFER, false)) {
     $actions += array(
             'transfer' => array(
                 'icon' => 'icon-share',
@@ -166,7 +196,7 @@ if ($thisstaff->hasPerm(Task::PERM_TRANSFER)) {
             ));
 }
 
-if ($thisstaff->hasPerm(Task::PERM_DELETE)) {
+if ($thisstaff->hasPerm(Task::PERM_DELETE, false)) {
     $actions += array(
             'delete' => array(
                 'icon' => 'icon-trash',
@@ -178,20 +208,46 @@ if ($thisstaff->hasPerm(Task::PERM_DELETE)) {
 ?>
 <!-- SEARCH FORM START -->
 <div id='basic_search'>
-    <form action="tasks.php" method="get">
+  <div class="pull-right" style="height:25px">
+    <span class="valign-helper"></span>
+    <span class="action-button muted" data-dropdown="#sort-dropdown">
+      <i class="icon-caret-down pull-right"></i>
+      <span><i class="icon-sort-by-attributes-alt"></i> <?php echo __('Sort');?></span>
+    </span>
+    <div id="sort-dropdown" class="action-dropdown anchor-right"
+    onclick="javascript: $.pjax({
+        url:'?' + addSearchParam('sort', $(event.target).data('mode')),
+        timeout: 2000,
+        container: '#pjax-container'});">
+      <ul class="bleed-left">
+        <?php foreach ($queue_sort_options as $mode) {
+        $desc = $sort_options[$mode];
+        $selected = $mode == $_SESSION[$queue_sort_key]; ?>
+      <li <?php if ($selected) echo 'class="active"'; ?>>
+        <a href="#" data-mode="<?php echo $mode; ?>"><i class="icon-fixed-width <?php
+          if ($selected) echo 'icon-hand-right';
+          ?>"></i> <?php echo Format::htmlchars($desc); ?></a>
+      </li>
+<?php } ?>
+      </ul>
+    </div>
+  </div>
+    <form action="tasks.php" method="get" onsubmit="javascript:
+  $.pjax({
+    url:$(this).attr('action') + '?' + $(this).serialize(),
+    container:'#pjax-container',
+    timeout: 2000
+  });
+return false;">
     <input type="hidden" name="a" value="search">
-    <table>
-        <tr>
-            <td><input type="text" id="basic-ticket-search" name="query"
-            size=30 value="<?php echo Format::htmlchars($_REQUEST['query'],
-            true); ?>"
-                autocomplete="off" autocorrect="off" autocapitalize="off"></td>
-            <td><input type="submit" class="button" value="<?php echo __('Search'); ?>"></td>
-            <td>&nbsp;&nbsp;<a href="#" onclick="javascript:
-                $.dialog('ajax.php/tasks/search', 201);"
-                >[<?php echo __('advanced'); ?>]</a>&nbsp;<i class="help-tip icon-question-sign" href="#advanced"></i></td>
-        </tr>
-    </table>
+    <input type="hidden" name="search-type" value=""/>
+    <div class="attached input">
+      <input type="text" class="basic-search" data-url="ajax.php/tasks/lookup" name="query"
+        autofocus size="30" value="<?php echo Format::htmlchars($_REQUEST['query'], true); ?>"
+        autocomplete="off" autocorrect="off" autocapitalize="off">
+      <button type="submit" class="attached button"><i class="icon-search"></i>
+      </button>
+    </div>
     </form>
 </div>
 <!-- SEARCH FORM END -->
@@ -204,20 +260,6 @@ if ($thisstaff->hasPerm(Task::PERM_DELETE)) {
                 $results_type.$showing; ?></a></h2>
         </div>
         <div class="pull-right flush-right">
-            <span style="display:inline-block">
-                <span style="vertical-align: baseline">Sort:</span>
-            <select name="sort" onchange="javascript:addSearchParam('sort', $(this).val());">
-<?php foreach (array(
-    'created' =>    __('Most Recently Created'),
-    'updated' =>    __('Most Recently Updated'),
-    'due' =>        __('Due Soon'),
-    'priority,due' => __('Priority + Due Soon'),
-    'number' =>     __('Task Number'),
-) as $mode => $desc) { ?>
-            <option value="<?php echo $mode; ?>" <?php if ($mode == $_SESSION[$queue_sort_key]) echo 'selected="selected"'; ?>><?php echo $desc; ?></option>
-<?php } ?>
-            </select>
-            </span>
            <?php
             Task::getAgentActions($thisstaff, array('status' => $status));
             ?>
@@ -321,7 +363,7 @@ if ($thisstaff->hasPerm(Task::PERM_DELETE)) {
             <?php
             } //end of foreach
         if (!$total)
-            $ferror=__('There are no tickets matching your criteria.');
+            $ferror=__('There are no tasks matching your criteria.');
         ?>
     </tbody>
     <tfoot>
@@ -396,7 +438,7 @@ $(function() {
         var url = 'ajax.php/'
         +$(this).attr('href').substr(1)
         +'?_uid='+new Date().getTime();
-        var $options = $(this).data('dialog');
+        var $options = $(this).data('dialogConfig');
         var $redirect = $(this).data('redirect');
         $.dialog(url, [201], function (xhr) {
             if ($redirect)
diff --git a/include/staff/templates/task-edit.tmpl.php b/include/staff/templates/task-edit.tmpl.php
index b90d545f823a2e812da24300225643d69c70ed55..f86e8a70a4c751d92b23b5bf592849bb66b7c08a 100644
--- a/include/staff/templates/task-edit.tmpl.php
+++ b/include/staff/templates/task-edit.tmpl.php
@@ -8,6 +8,8 @@ if (!$info['title'])
 
 $action = $info['action'] ?: ('#tasks/'.$task->getId().'/edit');
 
+$namespace = sprintf('task.%d.edit', $task->getId());
+
 ?>
 <div id="task-form">
 <h3 class="drag-handle"><?php echo $info['title']; ?></h3>
@@ -24,61 +26,29 @@ if ($info['error']) {
 } ?>
 <div id="edit-task-form" style="display:block;">
 <form method="post" class="task" action="<?php echo $action; ?>">
-
-    <table class="form_table dynamic-forms" width="100%" border="0" cellspacing="0" cellpadding="2">
-            <?php if ($forms)
-                foreach ($forms as $form) {
-                    $form->render(true, false, array('mode'=>'edit','width'=>160,'entry'=>$form));
-                    print $form->getForm()->getMedia();
-            } ?>
-    </table>
-    <table class="form_table dynamic-forms" width="100%" border="0" cellspacing="0" cellpadding="2">
-        <tr><th colspan=2><em><?php
-             echo __('Task Visibility & Assignment'); ?></em></th></tr>
+    <div>
     <?php
-        $iform = $iform ?: TaskForm::getInternalForm();
-        foreach ($iform->getFields()  as $name=>$field) {
-            if (!$field->isEditable()) continue;
-         ?>
-        <tr>
-            <td class="multi-line <?php if ($field->get('required')) echo 'required';
-            ?>" style="min-width:120px;" width="160">
-            <?php echo Format::htmlchars($field->get('label')); ?>:</td>
-            <td>
-            <fieldset id="field<?php echo $field->getWidget()->id;
-                ?>" <?php if (!$field->isVisible()) echo 'style="display:none;"'; ?>>
-                <?php echo $field->render(); ?>
-                <?php if ($field->get('required')) { ?>
-                <span class="error">*</span>
-                <?php
-                }
-                foreach ($field->errors() as $E) {
-                    ?><div class="error"><?php echo $E; ?></div><?php
-                } ?>
-            </fieldset>
-          </td>
-        </tr>
-        <?php
-        }
-       ?>
-    </table>
-    <table class="form_table" width="100%" border="0" cellspacing="0" cellpadding="2">
-        <tbody>
-            <tr>
-                <th colspan="2">
-                    <em><strong><?php echo __('Internal Note');?></strong>: <?php
-                     echo __('Reason for editing the task (optional');?> <font class="error">&nbsp;<?php echo $errors['note'];?></font></em>
-                </th>
-            </tr>
-            <tr>
-                <td colspan="2">
-                    <textarea class="richtext no-bar" name="note" cols="21"
-                        rows="6" style="width:80%;"><?php echo $info['note'];
-                        ?></textarea>
-                </td>
-            </tr>
-        </tbody>
-    </table>
+    if ($forms) {
+        foreach ($forms as $form)
+            echo $form->getForm(false, array('mode' => 'edit'))->asTable(
+                    __('Task Information'),
+                    array(
+                        'draft-namespace' => $namespace,
+                        )
+                    );
+    }
+    ?>
+    </div>
+    <div><strong><?php echo __('Internal Note');?></strong>:
+     <font class="error">&nbsp;<?php echo $errors['note'];?></font></div>
+    <div>
+        <textarea class="richtext no-bar" name="note" cols="21" rows="6"
+            style="width:80%;"
+            placeholder="<?php echo __('Reason for editing the task (optional)'); ?>"
+            >
+            <?php echo $info['note'];
+            ?></textarea>
+    </div>
     <hr>
     <p class="full-width">
         <span class="buttons pull-left">
diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php
index a72674066dda6442b29d01709e30c3d9b562823e..e3d577c870c17fe258e9757abbffde34d2dc9bb0 100644
--- a/include/staff/templates/task-view.tmpl.php
+++ b/include/staff/templates/task-view.tmpl.php
@@ -4,6 +4,8 @@ if (!defined('OSTSCPINC')
     || !($role = $thisstaff->getRole($task->getDeptId())))
     die('Invalid path');
 
+global $cfg;
+
 $actions = array();
 
 $actions += array(
@@ -54,7 +56,10 @@ if ($role->hasPerm(Task::PERM_DELETE)) {
 
 $info=($_POST && $errors)?Format::input($_POST):array();
 
-$id    = $task->getId();
+$id = $task->getId();
+$dept = $task->getDept();
+$thread = $task->getThread();
+
 if ($task->isOverdue())
     $warn.='&nbsp;&nbsp;<span class="Icon overdueTicket">'.__('Marked overdue!').'</span>';
 
@@ -67,7 +72,7 @@ if ($task->isOverdue())
             <?php
             if ($ticket) { ?>
                 <strong>
-                <a id="ticket-tasks" href="#"> All Tasks (<?php echo $ticket->getNumTasks(); ?>)</a>
+                <a id="all-ticket-tasks" href="#"> All Tasks (<?php echo $ticket->getNumTasks(); ?>)</a>
                 &nbsp;/&nbsp;
                 <?php echo $task->getTitle(); ?>
                 &nbsp;&mdash;&nbsp;
@@ -76,7 +81,9 @@ if ($task->isOverdue())
                     <?php
                     echo ' class="preview" ';
                     echo sprintf('data-preview="#tasks/%d/preview" ', $task->getId());
-                    echo sprintf('href="#tasks/%d" ', $task->getId());
+                    echo sprintf('href="#tickets/%s/tasks/%d/view" ',
+                            $ticket->getId(), $task->getId()
+                            );
                     ?>
                 ><?php
                 echo sprintf('#%s', $task->getNumber()); ?></a>
@@ -98,6 +105,12 @@ if ($task->isOverdue())
         <div class="flush-right">
             <?php
             if ($ticket) { ?>
+            <a  id="task-view"
+                target="_blank"
+                class="action-button"
+                href="tasks.php?id=<?php
+                 echo $task->getId(); ?>"><i class="icon-share"></i> <?php
+                            echo __('View Task'); ?></a>
             <span
                 class="action-button"
                 data-dropdown="#action-dropdown-task-options">
@@ -116,7 +129,7 @@ if ($task->isOverdue())
                             echo $action['class'] ?: 'task-action'; ?>"
                             <?php
                             if ($action['dialog'])
-                                echo sprintf("data-dialog='%s'", $action['dialog']);
+                                echo sprintf("data-dialog-config='%s'", $action['dialog']);
                             if ($action['redirect'])
                                 echo sprintf("data-redirect='%s'", $action['redirect']);
                             ?>
@@ -136,7 +149,7 @@ if ($task->isOverdue())
                     echo $action['class'] ?: 'task-action'; ?>"
                     <?php
                     if ($action['dialog'])
-                        echo sprintf("data-dialog='%s'", $action['dialog']);
+                        echo sprintf("data-dialog-config='%s'", $action['dialog']);
                     ?>
                     href="<?php echo $action['href']; ?>"><i
                     class="<?php
@@ -308,10 +321,104 @@ if ($ticket)
 else
     $action = 'tasks.php?id='.$task->getId();
 ?>
-<div id="response_options" class="sticky bar stop">
-    <ul class="tabs"></ul>
-    <form id="<?php echo $ticket? 'ticket_task_note': 'task_note'; ?>"
+<div id="task_response_options" class="<?php echo $ticket ? 'ticket_task_actions' : ''; ?> sticky bar stop">
+    <ul class="tabs">
+        <?php
+        if ($role->hasPerm(TaskModel::PERM_REPLY)) { ?>
+        <li class="active"><a href="#task_reply"><?php echo __('Post Update');?></a></li>
+        <li><a href="#task_note"><?php echo __('Post Internal Note');?></a></li>
+        <?php
+        }?>
+    </ul>
+    <?php
+    if ($role->hasPerm(TaskModel::PERM_REPLY)) { ?>
+    <form id="task_reply" class="tab_content spellcheck"
+        action="<?php echo $action; ?>"
+        name="task_reply" method="post" enctype="multipart/form-data">
+        <?php csrf_token(); ?>
+        <input type="hidden" name="id" value="<?php echo $task->getId(); ?>">
+        <input type="hidden" name="a" value="postreply">
+        <input type="hidden" name="lockCode" value="<?php echo ($mylock) ? $mylock->getCode() : ''; ?>">
+        <span class="error"></span>
+        <table style="width:100%" border="0" cellspacing="0" cellpadding="3">
+            <tbody id="collab_sec" style="display:table-row-group">
+             <tr>
+                <td>
+                    <input type='checkbox' value='1' name="emailcollab" id="emailcollab"
+                        <?php echo ((!$info['emailcollab'] && !$errors) || isset($info['emailcollab']))?'checked="checked"':''; ?>
+                        style="display:<?php echo $thread->getNumCollaborators() ? 'inline-block': 'none'; ?>;"
+                        >
+                    <?php
+                    $recipients = __('Add Participants');
+                    if ($thread->getNumCollaborators())
+                        $recipients = sprintf(__('Recipients (%d of %d)'),
+                                $thread->getNumActiveCollaborators(),
+                                $thread->getNumCollaborators());
+
+                    echo sprintf('<span><a class="collaborators preview"
+                            href="#thread/%d/collaborators"><span id="t%d-recipients">%s</span></a></span>',
+                            $thread->getId(),
+                            $thread->getId(),
+                            $recipients);
+                   ?>
+                </td>
+             </tr>
+            </tbody>
+            <tbody id="update_sec">
+            <tr>
+                <td>
+                    <div class="error"><?php echo $errors['response']; ?></div>
+                    <input type="hidden" name="draft_id" value=""/>
+                    <textarea name="response" id="task-response" cols="50"
+                        data-signature-field="signature" data-dept-id="<?php echo $dept->getId(); ?>"
+                        data-signature="<?php
+                            echo Format::htmlchars(Format::viewableImages($signature)); ?>"
+                        placeholder="<?php echo __( 'Start writing your update here.'); ?>"
+                        rows="9" wrap="soft"
+                        class="<?php if ($cfg->isRichTextEnabled()) echo 'richtext';
+                            ?> draft draft-delete" <?php
+    list($draft, $attrs) = Draft::getDraftAndDataAttrs('task.response', $task->getId(), $info['task.response']);
+    echo $attrs; ?>><?php echo $draft ?: $info['task.response'];
+                    ?></textarea>
+                <div id="task_response_form_attachments" class="attachments">
+                <?php
+                    if ($reply_attachments_form)
+                        print $reply_attachments_form->getField('attachments')->render();
+                ?>
+                </div>
+               </td>
+            </tr>
+            <tr>
+                <td>
+                    <div><?php echo __('Status');?>
+                        <span class="faded"> - </span>
+                        <select  name="task_status">
+                            <option value="1" <?php
+                                echo $task->isOpen() ?
+                                'selected="selected"': ''; ?>> <?php
+                                echo _('Open'); ?></option>
+                            <option value="0" <?php
+                                echo $task->isClosed() ?
+                                'selected="selected"': ''; ?>> <?php
+                                echo _('Closed'); ?></option>
+                        </select>
+                        &nbsp;<span class='error'><?php echo
+                        $errors['task_status']; ?></span>
+                    </div>
+                </td>
+            </tr>
+        </table>
+       <p  style="padding-left:165px;">
+           <input class="btn_sm" type="submit" value="<?php echo __('Post Update');?>">
+           <input class="btn_sm" type="reset" value="<?php echo __('Reset');?>">
+       </p>
+    </form>
+    <?php
+    } ?>
+    <form id="task_note"
         action="<?php echo $action; ?>"
+        class="tab_content spellcheck <?php
+            echo $role->hasPerm(TaskModel::PERM_REPLY) ? 'hidden' : ''; ?>"
         name="task_note"
         method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
@@ -320,18 +427,9 @@ else
         <table width="100%" border="0" cellspacing="0" cellpadding="3">
             <tr>
                 <td>
-                    <div>
-                        <div class="faded" style="padding-left:0.15em"><?php
-                        echo __('Note title - summary of the note (optional)'); ?></div>
-                        <input type="text" name="title" id="title" size="60" value="<?php echo $info['title']; ?>" >
-                        <br/>
-                        <span class="error">&nbsp;<?php echo $errors['title']; ?></span>
-                    </div>
-                    <div>
-                        <label><strong><?php echo __('Internal Note'); ?></strong><span class='error'>&nbsp;* <?php echo $errors['note']; ?></span></label>
-                    </div>
-                    <textarea name="note" id="internal_note" cols="80"
-                        placeholder="<?php echo __('Note details'); ?>"
+                    <div><span class='error'><?php echo $errors['note']; ?></span></div>
+                    <textarea name="note" id="task-note" cols="80"
+                        placeholder="<?php echo __('Internal Note details'); ?>"
                         rows="9" wrap="soft" data-draft-namespace="task.note"
                         data-draft-object-id="<?php echo $task->getId(); ?>"
                         class="richtext ifhtml draft draft-delete"><?php
@@ -339,15 +437,15 @@ else
                         ?></textarea>
                     <div class="attachments">
                     <?php
-                        if ($note_form)
-                            print $note_form->getField('attachments')->render();
+                        if ($note_attachments_form)
+                            print $note_attachments_form->getField('attachments')->render();
                     ?>
                     </div>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <div><?php echo __('Task Status');?>
+                    <div><?php echo __('Status');?>
                         <span class="faded"> - </span>
                         <select  name="task_status">
                             <option value="1" <?php
@@ -371,11 +469,14 @@ else
        </p>
     </form>
  </div>
+<?php
+echo $reply_attachments_form->getMedia();
+?>
 
 <script type="text/javascript">
 $(function() {
     $(document).off('.tasks-content');
-    $(document).on('click.tasks-content', 'a#ticket-tasks', function(e) {
+    $(document).on('click.tasks-content', '#all-ticket-tasks', function(e) {
         e.preventDefault();
         $('div#task_content').hide().empty();
         $('div#tasks_content').show();
@@ -388,7 +489,7 @@ $(function() {
         var url = 'ajax.php/'
         +$(this).attr('href').substr(1)
         +'?_uid='+new Date().getTime();
-        var $options = $(this).data('dialog');
+        var $options = $(this).data('dialogConfig');
         var $redirect = $(this).data('redirect');
         $.dialog(url, [201], function (xhr) {
             if ($redirect)
@@ -401,7 +502,7 @@ $(function() {
     });
 
     $(document).off('.tf');
-    $(document).on('submit.tf', 'form#ticket_task_note', function(e) {
+    $(document).on('submit.tf', '.ticket_task_actions form', function(e) {
         e.preventDefault();
         var $form = $(this);
         var $container = $('div#task_content');
diff --git a/include/staff/templates/task.tmpl.php b/include/staff/templates/task.tmpl.php
index 1271318ba3a5e48b7715ec5577987882e5fa1056..7bcafc77d68a267e0a20f62fdf8480f5edec29b0 100644
--- a/include/staff/templates/task.tmpl.php
+++ b/include/staff/templates/task.tmpl.php
@@ -23,41 +23,15 @@ if ($info['error']) {
 } ?>
 <div id="new-task-form" style="display:block;">
 <form method="post" class="org" action="<?php echo $info['action'] ?: '#tasks/add'; ?>">
-    <table width="100%" class="fixed">
     <?php
         $form = $form ?: TaskForm::getInstance();
-        $form->render(true,
-                __('Create New Task'),
+        echo $form->getForm()->asTable(' ',
                 array('draft-namespace' => $namespace)
                 );
-    ?>
-        <tr><th colspan=2><em><?php
-             echo __('Task Visibility & Assignment'); ?></em></th></tr>
-    <?php
+
         $iform = $iform ?: TaskForm::getInternalForm();
-        foreach ($iform->getFields()  as $name=>$field) { ?>
-        <tr>
-            <td class="multi-line <?php if ($field->get('required')) echo 'required';
-            ?>" style="min-width:120px;" >
-            <?php echo Format::htmlchars($field->get('label')); ?>:</td>
-            <td>
-            <fieldset id="field<?php echo $field->getWidget()->id;
-                ?>" <?php if (!$field->isVisible()) echo 'style="display:none;"'; ?>>
-                <?php echo $field->render(); ?>
-                <?php if ($field->get('required')) { ?>
-                <span class="error">*</span>
-                <?php
-                }
-                foreach ($field->errors() as $E) {
-                    ?><div class="error"><?php echo $E; ?></div><?php
-                } ?>
-            </fieldset>
-          </td>
-        </tr>
-        <?php
-        }
-       ?>
-    </table>
+        echo $iform->asTable(__("Task Visibility & Assignment"));
+?>
     <hr>
     <p class="full-width">
         <span class="buttons pull-left">
diff --git a/include/staff/templates/tasks-actions.tmpl.php b/include/staff/templates/tasks-actions.tmpl.php
index 306cd6ea9ab29c390f4dd01c3bca570890b7a2fe..e5407d2ed902e702d023fda00ba497842d35f8a6 100644
--- a/include/staff/templates/tasks-actions.tmpl.php
+++ b/include/staff/templates/tasks-actions.tmpl.php
@@ -3,7 +3,7 @@
 
 $actions = array();
 
-if ($agent->hasPerm(Task::PERM_CLOSE)) {
+if ($agent->hasPerm(Task::PERM_CLOSE, false)) {
 
     if (isset($options['status'])) {
         $status = $options['status'];
@@ -59,7 +59,7 @@ if ($agent->hasPerm(Task::PERM_CLOSE)) {
     }
 }
 
-if ($agent->hasPerm(Task::PERM_ASSIGN)) {
+if ($agent->hasPerm(Task::PERM_ASSIGN, false)) {
     $actions += array(
             'assign' => array(
                 'icon' => 'icon-user',
@@ -67,7 +67,7 @@ if ($agent->hasPerm(Task::PERM_ASSIGN)) {
             ));
 }
 
-if ($agent->hasPerm(Task::PERM_TRANSFER)) {
+if ($agent->hasPerm(Task::PERM_TRANSFER, false)) {
     $actions += array(
             'transfer' => array(
                 'icon' => 'icon-share',
@@ -75,7 +75,7 @@ if ($agent->hasPerm(Task::PERM_TRANSFER)) {
             ));
 }
 
-if ($agent->hasPerm(Task::PERM_DELETE)) {
+if ($agent->hasPerm(Task::PERM_DELETE, false)) {
     $actions += array(
             'delete' => array(
                 'icon' => 'icon-trash',
@@ -102,7 +102,7 @@ if ($actions) {
                 <a class="no-pjax tasks-action"
                     <?php
                     if ($action['dialog'])
-                        echo sprintf("data-dialog='%s'", $action['dialog']);
+                        echo sprintf("data-dialog-config='%s'", $action['dialog']);
                     if ($action['redirect'])
                         echo sprintf("data-redirect='%s'", $action['redirect']);
                     ?>
@@ -120,8 +120,8 @@ if ($actions) {
  } ?>
 <script type="text/javascript">
 $(function() {
-    $(document).off('.tasks');
-    $(document).on('click.tasks', 'a.tasks-action', function(e) {
+    $(document).off('.tasks-actions');
+    $(document).on('click.tasks-actions', 'a.tasks-action', function(e) {
         e.preventDefault();
         var count = checkbox_checker($('form#tasks'), 1);
         if (count) {
diff --git a/include/staff/templates/thread-entry-edit.tmpl.php b/include/staff/templates/thread-entry-edit.tmpl.php
index 1440780061aa0831d3009522a54f501c63425ca4..3f2d0fe56235af4ab6e539da566dc6ba578ce1ee 100644
--- a/include/staff/templates/thread-entry-edit.tmpl.php
+++ b/include/staff/templates/thread-entry-edit.tmpl.php
@@ -12,8 +12,9 @@
 <?php if ($poster && $this->entry->type == 'R') {
     $signature_type = $poster->getDefaultSignatureType();
     $signature = '';
-    if (($T = $this->entry->getThread()->getObject()) instanceof Ticket)
+    if (($T = $this->entry->getThread()->getObject()))
         $dept = $T->getDept();
+
     switch ($poster->getDefaultSignatureType()) {
     case 'dept':
         if ($dept && $dept->canAppendSignature())
@@ -24,7 +25,7 @@
         $signature_type = 'theirs';
         break;
     } ?>
-    data-dept-id="<?php echo $dept->getId(); ?>"
+    data-dept-id="<?php echo $dept ? $dept->getId() : 0; ?>"
     data-poster-id="<?php echo $this->entry->staff_id; ?>"
     data-signature-field="signature"
     data-signature="<?php echo Format::htmlchars(Format::viewableImages($signature)); ?>"
diff --git a/include/staff/ticket-tasks.inc.php b/include/staff/ticket-tasks.inc.php
index 84300a10d52a1ebb1d416bfa97670a7cf8c2f215..824ebb1542b65ab9c49c47d9b445bbff3b2dd0db 100644
--- a/include/staff/ticket-tasks.inc.php
+++ b/include/staff/ticket-tasks.inc.php
@@ -31,9 +31,9 @@ $showing = $pageNav->showing().' '._N('task', 'tasks', $count);
     <?php
     if ($role && $role->hasPerm(Task::PERM_CREATE)) { ?>
         <a
-        class="action-button ticket-task-action"
+        class="green button action-button ticket-task-action"
         data-url="tickets.php?id=<?php echo $ticket->getId(); ?>#tasks"
-        data-dialog='{"size":"large"}'
+        data-dialog-config='{"size":"large"}'
         href="#tickets/<?php
             echo $ticket->getId(); ?>/add-task">
             <i class="icon-plus-sign"></i> <?php
@@ -133,9 +133,10 @@ if ($count) { ?>
 <script type="text/javascript">
 $(function() {
 
-    $(document).off('click.tasks');
-    $(document).on('click.tasks', 'tbody.tasks a, a#reload-task', function(e) {
+    $(document).off('click.taskv');
+    $(document).on('click.taskv', 'tbody.tasks a, a#reload-task', function(e) {
         e.preventDefault();
+        e.stopImmediatePropagation();
         var url = 'ajax.php/'+$(this).attr('href').substr(1);
         var $container = $('div#task_content');
         var $stop = $('ul#ticket_tabs').offset().top;
@@ -146,6 +147,7 @@ $(function() {
             $('.tip_box').remove();
             $('div#tasks_content').hide();
             });
+
         return false;
      });
     // Ticket Tasks
@@ -156,7 +158,7 @@ $(function() {
         +$(this).attr('href').substr(1)
         +'?_uid='+new Date().getTime();
         var $redirect = $(this).data('href');
-        var $options = $(this).data('dialog');
+        var $options = $(this).data('dialogConfig');
         $.dialog(url, [201], function (xhr) {
             var tid = parseInt(xhr.responseText);
             if (tid) {
@@ -172,7 +174,5 @@ $(function() {
         }, $options);
         return false;
     });
-
-
 });
 </script>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 695ac4d745a312fbe8a23042c8ef4e254d549f20..d037930f342b6b0f0d4cd495056ba48073d01bdc 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -417,7 +417,7 @@ $tcount = $ticket->getThreadEntries($types)->count();
 ?>
 <ul  class="tabs clean threads" id="ticket_tabs" >
     <li class="active"><a href="#ticket_thread"><?php echo sprintf(__('Ticket Thread (%d)'), $tcount); ?></a></li>
-    <li><a id="ticket_tasks" href="#tasks"
+    <li><a id="ticket-tasks-tab" href="#tasks"
             data-url="<?php
         echo sprintf('#tickets/%d/tasks', $ticket->getId()); ?>"><?php
         echo __('Tasks');
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 8d6c2bc4a8135ba38bb56ed85bbbe0b844971481..4a6519410d51d8e127c96f6fdca6acf1e4fe05e1 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -192,7 +192,7 @@ $pageNav->setURL('tickets.php', $args);
 $tickets = $pageNav->paginate($tickets);
 
 // Apply requested sorting
-$queue_sort_key = sprintf(':Q:%s:sort', $queue_name);
+$queue_sort_key = sprintf(':Q%s:%s:sort', ObjectModel::OBJECT_TYPE_TICKET, $queue_name);
 
 if (isset($_GET['sort'])) {
     $_SESSION[$queue_sort_key] = $_GET['sort'];
@@ -316,7 +316,7 @@ $_SESSION[':Q:tickets'] = $orig_tickets;
       <span><i class="icon-sort-by-attributes-alt"></i> <?php echo __('Sort');?></span>
     </span>
     <div id="sort-dropdown" class="action-dropdown anchor-right"
-    onclick="javascript: console.log(event); $.pjax({
+    onclick="javascript: $.pjax({
         url:'?' + addSearchParam('sort', $(event.target).data('mode')),
         timeout: 2000,
         container: '#pjax-container'});">
@@ -325,7 +325,7 @@ $_SESSION[':Q:tickets'] = $orig_tickets;
 $desc = $sort_options[$mode];
 $selected = $mode == $_SESSION[$queue_sort_key]; ?>
       <li <?php if ($selected) echo 'class="active"'; ?>>
-        <a data-mode="<?php echo $mode; ?>"><i class="icon-fixed-width <?php
+        <a href="#" data-mode="<?php echo $mode; ?>"><i class="icon-fixed-width <?php
           if ($selected) echo 'icon-hand-right';
           ?>"></i> <?php echo Format::htmlchars($desc); ?></a>
       </li>
@@ -343,7 +343,7 @@ return false;">
     <input type="hidden" name="a" value="search">
     <input type="hidden" name="search-type" value=""/>
     <div class="attached input">
-      <input type="text" id="basic-ticket-search" name="query"
+      <input type="text" class="basic-search" data-url="ajax.php/tickets/lookup" name="query"
         autofocus size="30" value="<?php echo Format::htmlchars($_REQUEST['query'], true); ?>"
         autocomplete="off" autocorrect="off" autocapitalize="off">
       <button type="submit" class="attached button"><i class="icon-search"></i>
diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql
index ab6a3f7576c1cd4b8a633c503e4051334b04057e..35c654582659f8bb30eaddcc9fd1eef35eb68422 100644
--- a/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql
+++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql
@@ -43,6 +43,13 @@ UPDATE `%TABLE_PREFIX%thread` A1
   SET A1.`lastresponse` = A2.`lastresponse`,
       A1.`lastmessage` = A2.`lastmessage`;
 
+-- Mark `message` field as externally stored
+-- DynamicFormField::FLAG_EXT_STORED = 0x00002;
+UPDATE `%TABLE_PREFIX%form_field` A1
+  JOIN `%TABLE_PREFIX%form` A2 ON (A2.`id` = A1.`form_id`)
+  SET A1.`flags` = A1.`flags` | 0x00002
+  WHERE A2.`type` = 'T' AND A1.`name` = 'message';
+
 -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
     SET `value` = '98ad7d550c26ac44340350912296e673'
diff --git a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
index 4eb98233d7ff69fad3fbafdcdb573b0304715a87..84df9fc92396ad1169464db4aa637c135d98cf20 100644
--- a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
+++ b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
@@ -250,13 +250,6 @@ UPDATE `%TABLE_PREFIX%ticket_attachment` A1
 
 DROP TABLE `%TABLE_PREFIX%_unknown_inlines`;
 
--- Mark `message` field as externally stored
--- DynamicFormField::FLAG_EXT_STORED = 0x00002;
-UPDATE `%TABLE_PREFIX%form_field` A1
-  JOIN `%TABLE_PREFIX%form` A2 ON (A2.`id` = A1.`form_id`)
-  SET A1.`flags` = A1.`flags` | 0x00002
-  WHERE A2.`type` = 'T' AND A1.`name` = 'message';
-
 -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
     SET `value` = '1ee831c854fe9f35115a3e672916bb91'
diff --git a/scp/ajax.php b/scp/ajax.php
index be0e7c33db7ca4dfabef4b5f1d5087f164551e00..bdaf0a45483e7548617fd0c3715493f738ba130e 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -181,6 +181,7 @@ $dispatcher = patterns('',
         url_get('^(?P<tid>\d+)/view$', 'task'),
         url_post('^(?P<tid>\d+)$', 'task'),
         url('^add$', 'add'),
+        url('^lookup', 'lookup'),
         url_get('^mass/(?P<action>[\w.]+)', 'massProcess'),
         url_post('^mass/(?P<action>[\w.]+)', 'massProcess')
     )),
diff --git a/scp/js/scp.js b/scp/js/scp.js
index fee30477431bd42982dc70250182d34af6c8f5bb..a97e1ef94ce5ae7e3688b72b97bdc5fd537f79f7 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -252,11 +252,13 @@ var scp_prep = function() {
 
     /* Typeahead tickets lookup */
     var last_req;
-    $('#basic-ticket-search').typeahead({
+    $('input.basic-search').typeahead({
         source: function (typeahead, query) {
             if (last_req) last_req.abort();
+            var $el = this.$element;
+            var url = $el.data('url')+'?q='+query;
             last_req = $.ajax({
-                url: "ajax.php/tickets/lookup?q="+query,
+                url: url,
                 dataType: 'json',
                 success: function (data) {
                     typeahead.process(data);
@@ -264,9 +266,10 @@ var scp_prep = function() {
             });
         },
         onselect: function (obj) {
-            var form = $('#basic-ticket-search').closest('form');
+            var $el = this.$element;
+            var form = $el.closest('form');
             form.find('input[name=search-type]').val('typeahead');
-            $('#basic-ticket-search').val(obj.value);
+            $el.val(obj.value);
             form.submit();
         },
         property: "matches"
@@ -756,7 +759,7 @@ $.orgLookup = function (url, cb) {
 $.uid = 1;
 
 // Tabs
-$(document).on('click.tab', 'ul.tabs li a', function(e) {
+$(document).on('click.tab', 'ul.tabs > li > a', function(e) {
     e.preventDefault();
     var $this = $(this),
         $ul = $(this).closest('ul'),
@@ -777,6 +780,7 @@ $(document).on('click.tab', 'ul.tabs li a', function(e) {
                 // TODO: Add / hide loading spinner
             })
          );
+        $this.removeData('url');
     }
     else {
         $tab.addClass('tab_content');
diff --git a/scp/staff.inc.php b/scp/staff.inc.php
index edbc813006964649e43d1ed6e40cc03282b9fda0..bb06f3eb8e92dcf8094b1f1500137c282eddceec 100644
--- a/scp/staff.inc.php
+++ b/scp/staff.inc.php
@@ -76,7 +76,7 @@ if (!$thisstaff || !$thisstaff->getId() || !$thisstaff->isValid()) {
 //2) if not super admin..check system status and group status
 if(!$thisstaff->isAdmin()) {
     //Check for disabled staff or group!
-    if(!$thisstaff->isactive() || !$thisstaff->isGroupActive()) {
+    if (!$thisstaff->isactive()) {
         staffLoginPage(__('Access Denied. Contact Admin'));
         exit;
     }
diff --git a/scp/tasks.php b/scp/tasks.php
index c22fa5bdb39671a5e47e835d8a8447711b022f6a..ced34f872ad4bd73cd9e75af307393f5c31b84ef 100644
--- a/scp/tasks.php
+++ b/scp/tasks.php
@@ -13,6 +13,7 @@
 
 require('staff.inc.php');
 require_once(INCLUDE_DIR.'class.task.php');
+require_once(INCLUDE_DIR.'class.export.php');
 
 $page = '';
 $task = null; //clean start.
@@ -26,12 +27,18 @@ if ($_REQUEST['id']) {
 }
 
 // Configure form for file uploads
-$note_form = new SimpleForm(array(
+$note_attachments_form = new SimpleForm(array(
     'attachments' => new FileUploadField(array('id'=>'attach',
         'name'=>'attach:note',
         'configuration' => array('extensions'=>'')))
 ));
 
+$reply_attachments_form = new SimpleForm(array(
+    'attachments' => new FileUploadField(array('id'=>'attach',
+        'name'=>'attach:reply',
+        'configuration' => array('extensions'=>'')))
+));
+
 //At this stage we know the access status. we can process the post.
 if($_POST && !$errors):
 
@@ -42,7 +49,7 @@ if($_POST && !$errors):
         switch(strtolower($_POST['a'])):
         case 'postnote': /* Post Internal Note */
             $vars = $_POST;
-            $attachments = $note_form->getField('attachments')->getClean();
+            $attachments = $note_attachments_form->getField('attachments')->getClean();
             $vars['cannedattachments'] = array_merge(
                 $vars['cannedattachments'] ?: array(), $attachments);
 
@@ -51,24 +58,51 @@ if($_POST && !$errors):
 
                 $msg=__('Internal note posted successfully');
                 // Clear attachment list
-                $note_form->setSource(array());
-                $note_form->getField('attachments')->reset();
+                $note_attachments_form->setSource(array());
+                $note_attachments_form->getField('attachments')->reset();
 
                 if($wasOpen && $task->isClosed())
                     $task = null; //Going back to main listing.
                 else
-                    // Ticket is still open -- clear draft for the note
+                    // Task is still open -- clear draft for the note
                     Draft::deleteForNamespace('task.note.'.$task->getId(),
                         $thisstaff->getId());
 
             } else {
-
                 if(!$errors['err'])
                     $errors['err'] = __('Unable to post internal note - missing or invalid data.');
 
                 $errors['postnote'] = __('Unable to post the note. Correct the error(s) below and try again!');
             }
             break;
+        case 'postreply': /* Post an update */
+            $vars = $_POST;
+            $attachments = $reply_attachments_form->getField('attachments')->getClean();
+            $vars['cannedattachments'] = array_merge(
+                $vars['cannedattachments'] ?: array(), $attachments);
+
+            $wasOpen = ($task->isOpen());
+            if (($response=$task->postReply($vars, $errors))) {
+
+                $msg=__('Reply posted successfully');
+                // Clear attachment list
+                $reply_attachments_form->setSource(array());
+                $reply_attachments_form->getField('attachments')->reset();
+
+                if ($wasOpen && $task->isClosed())
+                    $task = null; //Going back to main listing.
+                else
+                    // Task is still open -- clear draft for the note
+                    Draft::deleteForNamespace('task.reply.'.$task->getId(),
+                        $thisstaff->getId());
+
+            } else {
+                if (!$errors['err'])
+                    $errors['err'] = __('Unable to post reply - missing or invalid data.');
+
+                $errors['postreply'] = __('Unable to post the reply. Correct the error(s) below and try again!');
+            }
+            break;
         default:
             $errors['err']=__('Unknown action');
         endswitch;
@@ -139,14 +173,14 @@ if (isset($_SESSION['advsearch:tasks'])) {
                         (!$_REQUEST['status'] || $_REQUEST['status']=='search'));
 }
 
-if ($thisstaff->hasPerm(TaskModel::PERM_CREATE)) {
+if ($thisstaff->hasPerm(TaskModel::PERM_CREATE, false)) {
     $nav->addSubMenu(array('desc'=>__('New Task'),
                            'title'=> __('Open a New Task'),
                            'href'=>'#tasks/add',
                            'iconclass'=>'newTicket task-action',
                            'id' => 'new-task',
                            'attr' => array(
-                               'data-dialog' => '{"size":"large"}'
+                               'data-dialog-config' => '{"size":"large"}'
                                )
                            ),
                         ($_REQUEST['a']=='open'));
@@ -173,7 +207,7 @@ if($task) {
 } else {
 	$inc = 'tasks.inc.php';
     if ($_REQUEST['a']=='open' &&
-            $thisstaff->hasPerm(Task::PERM_CREATE))
+            $thisstaff->hasPerm(Task::PERM_CREATE, false))
         $inc = 'task-open.inc.php';
     elseif($_REQUEST['a'] == 'export') {
         $ts = strftime('%Y%m%d');
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 3986191a94c517f3cbdb39be38b00cc4081bf1cf..6ab645e7d6cdbe4da784f2b0396395cac2eb0d10 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -199,7 +199,7 @@ class Installer extends SetupWizard {
             Organization::PERM_DELETE,
             FAQ::PERM_MANAGE,
             Email::PERM_BANLIST,
-        ), $errors);
+        ));
         $staff->setPassword($vars['passwd']);
         if (!$staff->save()) {
             $this->errors['err'] = __('Unable to create admin user (#6)');