diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index c5b021c12c5437c44deb0a86ff9fe4d1cce9543e..d26cfb484f21da64b87ec2ac93b41eb3121d4b0a 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -128,6 +128,25 @@ class DynamicForm extends VerySimpleModel {
             return parent::delete();
     }
 
+
+    function getExportableFields($exclude=array()) {
+
+        $fields = array();
+        foreach ($this->getFields() as $f) {
+            // Ignore core fields
+            if ($exclude && in_array($f->get('name'), $exclude))
+                continue;
+            // Ignore non-data fields
+            elseif (!$f->hasData() || $f->isPresentationOnly())
+                continue;
+
+            $fields['__field_'.$f->get('id')] = $f;
+        }
+
+        return $fields;
+    }
+
+
     static function create($ht=false) {
         $inst = parent::create($ht);
         $inst->set('created', new SqlFunction('NOW'));
@@ -142,6 +161,49 @@ class DynamicForm extends VerySimpleModel {
         }
         return $inst;
     }
+
+
+
+    static function getCrossTabQuery($object_type, $object_id='object_id', $exclude=array()) {
+        $fields = static::getDynamicDataViewFields($exclude);
+        return "SELECT entry.`object_id` as `$object_id`, ".implode(',', $fields)
+            .' FROM '.FORM_ENTRY_TABLE.' entry
+            JOIN '.FORM_ANSWER_TABLE.' ans ON ans.entry_id = entry.id
+            JOIN '.FORM_FIELD_TABLE." field ON field.id=ans.field_id
+            WHERE entry.object_type='$object_type' GROUP BY entry.object_id";
+    }
+
+    // Materialized View for Ticket custom data (MySQL FlexViews would be
+    // nice)
+    //
+    // @see http://code.google.com/p/flexviews/
+    static function getDynamicDataViewFields($exclude) {
+        $fields = array();
+        foreach (static::getInstance()->getFields() as $f) {
+            if ($exclude && in_array($f->get('name'), $exclude))
+                continue;
+
+            $impl = $f->getImpl();
+            if (!$impl->hasData() || $impl->isPresentationOnly())
+                continue;
+
+            $name = ($f->get('name')) ? $f->get('name')
+                : 'field_'.$f->get('id');
+
+            $fields[] = sprintf(
+                'MAX(IF(field.name=\'%1$s\',ans.value,NULL)) as `%1$s`',
+                $name);
+            if ($impl->hasIdValue()) {
+                $fields[] = sprintf(
+                    'MAX(IF(field.name=\'%1$s\',ans.value_id,NULL)) as `%1$s_id`',
+                    $name);
+            }
+        }
+        return $fields;
+    }
+
+
+
 }
 
 class UserForm extends DynamicForm {
@@ -194,32 +256,6 @@ class TicketForm extends DynamicForm {
         return static::$instance;
     }
 
-    // Materialized View for Ticket custom data (MySQL FlexViews would be
-    // nice)
-    //
-    // @see http://code.google.com/p/flexviews/
-    static function getDynamicDataViewFields() {
-        $fields = array();
-        foreach (self::getInstance()->getFields() as $f) {
-            $impl = $f->getImpl();
-            if (!$impl->hasData() || $impl->isPresentationOnly())
-                continue;
-
-            $name = ($f->get('name')) ? $f->get('name')
-                : 'field_'.$f->get('id');
-
-            $fields[] = sprintf(
-                'MAX(IF(field.name=\'%1$s\',ans.value,NULL)) as `%1$s`',
-                $name);
-            if ($impl->hasIdValue()) {
-                $fields[] = sprintf(
-                    'MAX(IF(field.name=\'%1$s\',ans.value_id,NULL)) as `%1$s_id`',
-                    $name);
-            }
-        }
-        return $fields;
-    }
-
     static function ensureDynamicDataView() {
         $sql = 'SHOW TABLES LIKE \''.TABLE_PREFIX.'ticket__cdata\'';
         if (!db_num_rows(db_query($sql)))
@@ -236,13 +272,8 @@ class TicketForm extends DynamicForm {
         // ans.entry_id = entry.id LEFT JOIN ost_form_field field ON
         // field.id=ans.field_id
         // where entry.object_type='T' group by entry.object_id;
-        $fields = static::getDynamicDataViewFields();
-        $sql = 'CREATE TABLE `'.TABLE_PREFIX.'ticket__cdata` (PRIMARY KEY (ticket_id)) AS
-            SELECT entry.`object_id` AS ticket_id, '.implode(',', $fields)
-         .' FROM '.FORM_ENTRY_TABLE.' entry
-            JOIN '.FORM_ANSWER_TABLE.' ans ON ans.entry_id = entry.id
-            JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id
-            WHERE entry.object_type=\'T\' GROUP BY entry.object_id';
+        $sql = 'CREATE TABLE `'.TABLE_PREFIX.'ticket__cdata` (PRIMARY KEY
+                (ticket_id)) AS ' . static::getCrossTabQuery('T', 'ticket_id');
         db_query($sql);
     }
 
diff --git a/include/class.export.php b/include/class.export.php
index b95a7d16d587541188d40d4cc6f45097b00b757f..e0da0906b984991a411b34ba9fa616096d7b4fbd 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -99,15 +99,120 @@ class Export {
 
         return false;
     }
+
+    static function saveUsers($sql, $filename, $how='csv') {
+
+        $exclude = array('name', 'email');
+        $form = UserForm::getUserForm();
+        $fields = $form->getExportableFields($exclude);
+
+        // Field selection callback
+        $fname = function ($f) {
+            return 'cdata.`'.$f->getSelectName().'` AS __field_'.$f->get('id');
+        };
+
+        $sql = substr_replace($sql,
+                ','.implode(',', array_map($fname, $fields)).' ',
+                strpos($sql, 'FROM '), 0);
+
+        $sql = substr_replace($sql,
+                'LEFT JOIN ('.$form->getCrossTabQuery($form->type, 'user_id', $exclude).') cdata
+                    ON (cdata.user_id = user.id) ',
+                strpos($sql, 'WHERE '), 0);
+
+        $cdata = array_combine(array_keys($fields),
+                array_values(array_map(
+                        function ($f) { return $f->get('label'); }, $fields)));
+
+        ob_start();
+        echo self::dumpQuery($sql,
+                array(
+                    'name'  =>  'Name',
+                    'organization' => 'Organization',
+                    'email' =>  'Email'
+                    ) + $cdata,
+                $how,
+                array('modify' => function(&$record, $keys) use ($fields) {
+                    foreach ($fields as $k=>$f) {
+                        if ($f && ($i = array_search($k, $keys)) !== false) {
+                            $record[$i] = $f->export($f->to_php($record[$i]));
+                        }
+                    }
+                    return $record;
+                    })
+                );
+        $stuff = ob_get_contents();
+        ob_end_clean();
+
+        if ($stuff)
+            Http::download($filename, "text/$how", $stuff);
+
+        return false;
+    }
+
+    static function saveOrganizations($sql, $filename, $how='csv') {
+
+        $exclude = array('name');
+        $form = OrganizationForm::getDefaultForm();
+        $fields = $form->getExportableFields($exclude);
+
+        // Field selection callback
+        $fname = function ($f) {
+            return 'cdata.`'.$f->getSelectName().'` AS __field_'.$f->get('id');
+        };
+
+        $sql = substr_replace($sql,
+                ','.implode(',', array_map($fname, $fields)).' ',
+                strpos($sql, 'FROM '), 0);
+
+        $sql = substr_replace($sql,
+                'LEFT JOIN ('.$form->getCrossTabQuery($form->type, '_org_id', $exclude).') cdata
+                    ON (cdata._org_id = org.id) ',
+                strpos($sql, 'WHERE '), 0);
+
+        $cdata = array_combine(array_keys($fields),
+                array_values(array_map(
+                        function ($f) { return $f->get('label'); }, $fields)));
+
+        ob_start();
+        echo self::dumpQuery($sql,
+                array(
+                    'name'  =>  'Name',
+                    'account_manager' => 'Account Manager',
+                    'users' => 'Users'
+                    ) + $cdata,
+                $how,
+                array('modify' => function(&$record, $keys) use ($fields) {
+                    foreach ($fields as $k=>$f) {
+                        if ($f && ($i = array_search($k, $keys)) !== false) {
+                            $record[$i] = $f->export($f->to_php($record[$i]));
+                        }
+                    }
+                    return $record;
+                    })
+                );
+        $stuff = ob_get_contents();
+        ob_end_clean();
+
+        if ($stuff)
+            Http::download($filename, "text/$how", $stuff);
+
+        return false;
+    }
+
 }
 
 class ResultSetExporter {
+    var $output;
+
     function ResultSetExporter($sql, $headers, $options=array()) {
         $this->headers = array_values($headers);
         if ($s = strpos(strtoupper($sql), ' LIMIT '))
             $sql = substr($sql, 0, $s);
         # TODO: If $filter, add different LIMIT clause to query
         $this->options = $options;
+        $this->output = $options['output'] ?: fopen('php://output', 'w');
+
         $this->_res = db_query($sql);
         if ($row = db_fetch_array($this->_res)) {
             $query_fields = array_keys($row);
@@ -161,14 +266,17 @@ class ResultSetExporter {
 }
 
 class CsvResultsExporter extends ResultSetExporter {
+
     function dump() {
-        echo '"' . implode('","', $this->getHeaders()) . "\"\n";
-        while ($row=$this->next()) {
-            foreach ($row as &$val)
-                # Escape enclosed double-quotes
-                $val = str_replace('"','""',$val);
-            echo '"' . implode('","', $row) . "\"\n";
-        }
+
+        if (!$this->output)
+             $this->output = fopen('php://output', 'w');
+
+        fputcsv($this->output, $this->getHeaders());
+        while ($row=$this->next())
+            fputcsv($this->output, $row);
+
+        fclose($this->output);
     }
 }
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 4eeed67e73c36e51ae944873698a701eb34b4a1e..257f4ab9a1958d9269b78ba0e11f1d76d3b0487e 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -493,6 +493,14 @@ class FormField {
         }
         return $this->_widget;
     }
+
+    function getSelectName() {
+        $name = $this->get('name') ?: 'field_'.$this->get('id');
+        if ($this->hasIdValue())
+            $name .= '_id';
+
+        return $name;
+    }
 }
 
 class TextboxField extends FormField {
@@ -593,6 +601,11 @@ class TextareaField extends FormField {
         else
             return Format::htmlchars($value);
     }
+
+    function export($value) {
+        return (!$value) ? $value : Format::html2text($value);
+    }
+
 }
 
 class PhoneField extends FormField {
diff --git a/include/class.organization.php b/include/class.organization.php
index e1e3f44350bad64c26dcde031da2a1a2844b3eb8..3801bba3a423067dcf32bbf0c64dd92d352776c0 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -46,9 +46,12 @@ class OrganizationModel extends VerySimpleModel {
         if (!isset($this->_manager)) {
             if ($this->manager[0] == 't')
                 $this->_manager = Team::lookup(substr($this->manager, 1));
-            if ($this->manager[0] == 's')
+            elseif ($this->manager[0] == 's')
                 $this->_manager = Staff::lookup(substr($this->manager, 1));
+            else
+                $this->_manager = ''; // None.
         }
+
         return $this->_manager;
     }
 
diff --git a/include/class.team.php b/include/class.team.php
index 8d1c22bb06f90b5c9e999593c850891f3f378b61..2d79a987f70260e4778a349d946f8d80bfb6c443 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -52,7 +52,11 @@ class Team {
     }
 
     function asVar() {
-        return $this->getName();
+        return $this->__toString();
+    }
+
+    function __toString() {
+        return (string) $this->getName();
     }
 
     function getId() {
diff --git a/include/class.user.php b/include/class.user.php
index 083368fc6afd91b3147624d057dedd80400eee9b..89a35ab46f54b35e91a4810ffc654510e20e8e68 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -643,7 +643,7 @@ class PersonsName {
         $format = $cfg ? $cfg->getDefaultNameFormat() : 'original';
         list(,$func) = static::$formats[$format];
         if (!$func) $func = 'getFull';
-        return call_user_func(array($this, $func));
+        return (string) call_user_func(array($this, $func));
     }
 
     static function allFormats() {
diff --git a/include/staff/org-view.inc.php b/include/staff/org-view.inc.php
index 5e334c12bcda70c12a995c945a0c81463292c3d4..e866e4b5cdbd1b648691b82b05dd76050b10d904 100644
--- a/include/staff/org-view.inc.php
+++ b/include/staff/org-view.inc.php
@@ -28,7 +28,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !is_object($org)) die('Invalid path')
                 </tr>
                 <tr>
                     <th>Account Manager:</th>
-                    <td>&nbsp; </td>
+                    <td><?php echo $org->getAccountManager(); ?>&nbsp;</td>
                 </tr>
             </table>
         </td>
diff --git a/include/staff/orgs.inc.php b/include/staff/orgs.inc.php
index 4c11d1d369f283ff5bb949f6f17001bf6fd1d815..3da261f3978107a1c6df6da02e91181a589a44e1 100644
--- a/include/staff/orgs.inc.php
+++ b/include/staff/orgs.inc.php
@@ -3,9 +3,15 @@ if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied');
 
 $qstr='';
 
-$select = 'SELECT org.* ';
-
-$from = 'FROM '.ORGANIZATION_TABLE.' org ';
+$select = 'SELECT org.*
+            ,COALESCE(team.name,
+                    IF(staff.staff_id, CONCAT_WS(" ", staff.firstname, staff.lastname), NULL)
+                    ) as account_manager ';
+$from = 'FROM '.ORGANIZATION_TABLE.' org '
+       .'LEFT JOIN '.STAFF_TABLE.' staff ON (
+           LEFT(org.manager, 1) = "s" AND staff.staff_id = SUBSTR(org.manager, 2)) '
+       .'LEFT JOIN '.TEAM_TABLE.' team ON (
+           LEFT(org.manager, 1) = "t" AND team.team_id = SUBSTR(org.manager, 2)) ';
 
 $where = ' WHERE 1 ';
 
@@ -61,6 +67,8 @@ $from .= ' LEFT JOIN '.USER_TABLE.' user ON (user.org_id = org.id) ';
 
 $query="$select $from $where GROUP BY org.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
 //echo $query;
+$qhash = md5($query);
+$_SESSION['orgs_qs_'.$qhash] = $query;
 ?>
 <h2>Organizations</h2>
 <div style="width:700px; float:left;">
@@ -125,7 +133,10 @@ else
 </table>
 <?php
 if($res && $num): //Show options..
-    echo '<div>&nbsp;Page:'.$pageNav->getPageLinks().'&nbsp;</div>';
+    echo sprintf('<div>&nbsp;Page: %s &nbsp; <a class="no-pjax"
+            href="orgs.php?a=export&qh=%s">Export</a></div>',
+            $pageNav->getPageLinks(),
+            $qhash);
 endif;
 ?>
 </form>
diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php
index e02c9dbc8cd90fb1500265770e282fb1ee93e7ce..a9fd4f3b91067a3aee7017808eddfd0981072342 100644
--- a/include/staff/users.inc.php
+++ b/include/staff/users.inc.php
@@ -3,10 +3,12 @@ if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied');
 
 $qstr='';
 
-$select = 'SELECT user.*, email.address as email, account.id as account_id, account.status ';
+$select = 'SELECT user.*, email.address as email, org.name as organization
+          , account.id as account_id, account.status as account_status ';
 
 $from = 'FROM '.USER_TABLE.' user '
       . 'LEFT JOIN '.USER_EMAIL_TABLE.' email ON (user.id = email.user_id) '
+      . 'LEFT JOIN '.ORGANIZATION_TABLE.' org ON (user.org_id = org.id) '
       . 'LEFT JOIN '.USER_ACCOUNT_TABLE.' account ON (account.user_id = user.id) ';
 
 $where='WHERE 1 ';
@@ -23,6 +25,7 @@ if ($_REQUEST['query']) {
     $where .= ' AND (
                     email.address LIKE \'%'.$search.'%\'
                     OR user.name LIKE \'%'.$search.'%\'
+                    OR org.name LIKE \'%'.$search.'%\'
                     OR value.value LIKE \'%'.$search.'%\'
                 )';
 
@@ -66,6 +69,9 @@ $from .= ' LEFT JOIN '.TICKET_TABLE.' ticket ON (ticket.user_id = user.id) ';
 
 $query="$select $from $where GROUP BY user.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
 //echo $query;
+$qhash = md5($query);
+$_SESSION['users_qs_'.$qhash] = $query;
+
 ?>
 <h2>User Directory</h2>
 <div style="width:700px; float:left;">
@@ -123,7 +129,7 @@ else
 
                 // Account status
                 if ($row['account_id'])
-                    $status = new UserAccountStatus($row['status']);
+                    $status = new UserAccountStatus($row['account_status']);
                 else
                     $status = 'Guest';
 
@@ -152,7 +158,10 @@ else
 </table>
 <?php
 if($res && $num): //Show options..
-    echo '<div>&nbsp;Page:'.$pageNav->getPageLinks().'&nbsp;</div>';
+    echo sprintf('<div>&nbsp;Page: %s &nbsp; <a class="no-pjax"
+            href="users.php?a=export&qh=%s">Export</a></div>',
+            $pageNav->getPageLinks(),
+            $qhash);
 endif;
 ?>
 </form>
diff --git a/scp/orgs.php b/scp/orgs.php
index c48592ba7ec9db34543c8141975febf4d9f0f1f7..83a13fb0ba712365295341d5510ad37d2c51822c 100644
--- a/scp/orgs.php
+++ b/scp/orgs.php
@@ -32,7 +32,19 @@ if ($_POST) {
             $msg = "Successfully imported $status clients";
         else
             $errors['err'] = $status;
+        break;
+    default:
+        $errors['err'] = 'Unknown action';
     }
+} elseif ($_REQUEST['a'] == 'export') {
+    require_once(INCLUDE_DIR.'class.export.php');
+    $ts = strftime('%Y%m%d');
+    if (!($token=$_REQUEST['qh']))
+        $errors['err'] = 'Query token required';
+    elseif (!($query=$_SESSION['orgs_qs_'.$token]))
+        $errors['err'] = 'Query token not found';
+    elseif (!Export::saveOrganizations($query, "organizations-$ts.csv", 'csv'))
+        $errors['err'] = 'Internal error: Unable to export results';
 }
 
 $page = $org? 'org-view.inc.php' : 'orgs.inc.php';
diff --git a/scp/users.php b/scp/users.php
index c8a483bab2968a115b112179010eeb374863a847..c6606a8be9737de524f8a3cb6a72eafe184bd2df 100644
--- a/scp/users.php
+++ b/scp/users.php
@@ -80,6 +80,15 @@ if ($_POST) {
             $errors['err'] = 'Unknown action/command';
             break;
     }
+} elseif($_REQUEST['a'] == 'export') {
+    require_once(INCLUDE_DIR.'class.export.php');
+    $ts = strftime('%Y%m%d');
+    if (!($token=$_REQUEST['qh']))
+        $errors['err'] = 'Query token required';
+    elseif (!($query=$_SESSION['users_qs_'.$token]))
+        $errors['err'] = 'Query token not found';
+    elseif (!Export::saveUsers($query, "users-$ts.csv", 'csv'))
+        $errors['err'] = 'Internal error: Unable to dump query results';
 }
 
 $page = $user? 'user-view.inc.php' : 'users.inc.php';