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> </td> + <td><?php echo $org->getAccountManager(); ?> </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> Page:'.$pageNav->getPageLinks().' </div>'; + echo sprintf('<div> Page: %s <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> Page:'.$pageNav->getPageLinks().' </div>'; + echo sprintf('<div> Page: %s <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';