Newer
Older
<?php
/*************************************************************************
class.export.php
Exports stuff (details to follow)
Jared Hancock <jared@osticket.com>
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Export {
// XXX: This may need to be moved to a print-specific class
static $paper_sizes = array(
/* @trans */ 'Letter',
/* @trans */ 'Legal',
static function dumpQuery($sql, $headers, $how='csv', $options=array()) {
$exporters = array(
'csv' => CsvResultsExporter,
'json' => JsonResultsExporter
);
$exp = new $exporters[$how]($sql, $headers, $options);
return $exp->dump();
}
# XXX: Think about facilitated exporting. For instance, we might have a
# TicketExporter, which will know how to formulate or lookup a
# format query (SQL), and cooperate with the output process to add
# extra (recursive) information. In this funciton, the top-level
# SQL is exported, but for something like tickets, we will need to
# export attached messages, reponses, and notes, as well as
# attachments associated with each, ...
static function dumpTickets($sql, $how='csv') {
// Add custom fields to the $sql statement
foreach (TicketForm::getInstance()->getFields() as $f) {
// Ignore core fields
if (in_array($f->get('name'), array('priority')))
continue;
// Ignore non-data fields
elseif (!$f->hasData() || $f->isPresentationOnly())
continue;
$name = $f->get('name') ?: 'field_'.$f->get('id');
$key = 'cdata.'.$name;
$tickets = $sql->models()
->select_related('user', 'user__default_email', 'dept', 'staff',
'team', 'staff', 'cdata', 'topic', 'status', 'cdata__:priority')
->options(QuerySet::OPT_NOCACHE)
'collab_count' => TicketThread::objects()
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->aggregate(array('count' => SqlAggregate::COUNT('collaborators__id'))),
'attachment_count' => TicketThread::objects()
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->filter(array('entries__attachments__inline' => 0))
->aggregate(array('count' => SqlAggregate::COUNT('entries__attachments__id'))),
'thread_count' => TicketThread::objects()
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->exclude(array('entries__flags__hasbit' => ThreadEntry::FLAG_HIDDEN))
->aggregate(array('count' => SqlAggregate::COUNT('entries__id'))),
));
// Fetch staff information
// FIXME: Adjust Staff model so it doesn't do extra queries
foreach (Staff::objects() as $S)
$S->get('junk');
'number' => __('Ticket Number'),
'created' => __('Date Created'),
'cdata.subject' => __('Subject'),
'user.name' => __('From'),
'user.default_email.address' => __('From Email'),
'cdata.:priority.priority_desc' => __('Priority'),
'dept::getLocalName' => __('Department'),
'topic::getName' => __('Help Topic'),
'lastupdate' => __('Last Updated'),
'est_duedate' => __('Due Date'),
'isoverdue' => __('Overdue'),
'isanswered' => __('Answered'),
'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;
})
);
Http::download($filename, "text/$how");
self::dumpTickets($sql, $how);
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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'),
'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, $obj) 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);
static function saveUsers($sql, $filename, $how='csv') {
$exclude = array('name', 'email');
$form = UserForm::getUserForm();
$fields = $form->getExportableFields($exclude, 'cdata.');
$cdata = array_combine(array_keys($fields),
array_values(array_map(
function ($f) { return $f->getLocal('label'); }, $fields)));
$users = $sql->models()
->select_related('org', 'cdata');
'org' => __('Organization'),
'::getEmail' => __('Email'),
array('modify' => function(&$record, $keys, $obj) 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, 'cdata.');
$cdata = array_combine(array_keys($fields),
array_values(array_map(
function ($f) { return $f->getLocal('label'); }, $fields)));
$cdata += array(
'::getNumUsers' => 'Users',
'::getAccountManager' => 'Account Manager',
);
array(
'name' => 'Name',
) + $cdata,
$how,
array('modify' => function(&$record, $keys, $obj) 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;
}
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
static function agents($agents, $filename='', $how='csv') {
// Filename or stream to export agents to
$filename = $filename ?: sprintf('Agents-%s.csv',
strftime('%Y%m%d'));
Http::download($filename, "text/$how");
$depts = Dept::getDepartments();
echo self::dumpQuery($agents, array(
'::getName' => 'Name',
'::getUsername' => 'Username',
'::getStatus' => 'Status',
'permissions' => 'Permissions',
'::getDept' => 'Primary Department',
) + $depts,
$how,
array('modify' => function(&$record, $keys, $obj) use ($depts) {
if (($i = array_search('permissions', $keys)))
$record[$i] = implode(",", array_keys($obj->getPermission()->getInfo()));
$roles = $obj->getRoles();
foreach ($depts as $k => $v) {
if (is_numeric($k) && ($i = array_search($k, $keys)) !== false) {
$record[$i] = $roles[$k] ?: '';
}
}
return $record;
})
);
exit;
}
}
class ResultSetExporter {
function __construct($sql, $headers, $options=array()) {
$this->headers = array_values($headers);
// Remove limit and offset
$sql->limit(null)->offset(null);
# TODO: If $filter, add different LIMIT clause to query
$this->options = $options;
$this->output = $options['output'] ?: fopen('php://output', 'w');
$this->headers = array();
$this->keys = array();
foreach ($headers as $field=>$name) {
$this->headers[] = $name;
$this->keys[] = $field;
if ($this->_res instanceof IteratorAggregate)
$this->_res = $this->_res->getIterator();
}
function getHeaders() {
return $this->headers;
}
function next() {
$object = $this->_res->current();
$this->_res->next();
foreach ($this->keys as $field) {
list($field, $func) = explode('::', $field);
$path = explode('.', $field);
$current = $object;
// Evaluate dotted ORM path
if ($field) {
foreach ($path as $P) {
if (isset($current->{$P}))
$current = $current->{$P};
else {
$current = $P;
break;
}
}
}
// Evalutate :: function call on target current
if ($func && (method_exists($current, $func) || method_exists($current, '__call'))) {
if (isset($this->options['modify']) && is_callable($this->options['modify']))
$record = $this->options['modify']($record, $this->keys, $object);
return $record;
}
function nextArray() {
if (!($row = $this->next()))
return false;
return array_combine($this->keys, $row);
}
function dump() {
# Useful for debug output
while ($row=$this->nextArray()) {
var_dump($row);
}
}
}
class CsvResultsExporter extends ResultSetExporter {
if (!$this->output)
$this->output = fopen('php://output', 'w');
// Detect delimeter from the current locale settings. For locales
// which use comma (,) as the decimal separator, the semicolon (;)
// should be used as the field separator
$delimiter = ',';
if (class_exists('NumberFormatter')) {
$nf = NumberFormatter::create(Internationalization::getCurrentLocale(),
NumberFormatter::DECIMAL);
$s = $nf->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
if ($s == ',')
$delimiter = ';';
}
// Output a UTF-8 BOM (byte order mark)
fputs($this->output, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($this->output, $this->getHeaders(), $delimiter);
while ($row=$this->next())
fputcsv($this->output, $row, $delimiter);
fclose($this->output);
}
}
class JsonResultsExporter extends ResultSetExporter {
function dump() {
require_once(INCLUDE_DIR.'class.json.php');
$exp = new JsonDataEncoder();
$rows = array();
while ($row=$this->nextArray()) {
$rows[] = $row;
}
echo $exp->encode($rows);
}
}
require_once INCLUDE_DIR . 'class.json.php';
require_once INCLUDE_DIR . 'class.migrater.php';
require_once INCLUDE_DIR . 'class.signal.php';
define('OSTICKET_BACKUP_SIGNATURE', 'osTicket-Backup');
define('OSTICKET_BACKUP_VERSION', 'B');
class DatabaseExporter {
var $stream;
var $tables = array(CONFIG_TABLE, SYSLOG_TABLE, FILE_TABLE,
FILE_CHUNK_TABLE, STAFF_TABLE, DEPT_TABLE, TOPIC_TABLE, GROUP_TABLE,
STAFF_DEPT_TABLE, TEAM_TABLE, TEAM_MEMBER_TABLE, FAQ_TABLE,
FAQ_TOPIC_TABLE, FAQ_CATEGORY_TABLE, DRAFT_TABLE,
CANNED_TABLE, TICKET_TABLE, ATTACHMENT_TABLE,
THREAD_TABLE, THREAD_ENTRY_TABLE, THREAD_ENTRY_EMAIL_TABLE,
LOCK_TABLE, THREAD_EVENT_TABLE, TICKET_PRIORITY_TABLE,
EMAIL_TABLE, EMAIL_TEMPLATE_TABLE, EMAIL_TEMPLATE_GRP_TABLE,
FILTER_TABLE, FILTER_RULE_TABLE, SLA_TABLE, API_KEY_TABLE,
TIMEZONE_TABLE, SESSION_TABLE, PAGE_TABLE,
FORM_SEC_TABLE, FORM_FIELD_TABLE, LIST_TABLE, LIST_ITEM_TABLE,
FORM_ENTRY_TABLE, FORM_ANSWER_TABLE, USER_TABLE, USER_EMAIL_TABLE,
PLUGIN_TABLE, THREAD_COLLABORATOR_TABLE, TRANSLATION_TABLE,
USER_ACCOUNT_TABLE, ORGANIZATION_TABLE, NOTE_TABLE
function __construct($stream, $options=array()) {
}
function write_block($what) {
fwrite($this->stream, JsonDataEncoder::encode($what));
fwrite($this->stream, "\n");
$header = array(
array(OSTICKET_BACKUP_SIGNATURE, OSTICKET_BACKUP_VERSION),
array(
'version'=>THIS_VERSION,
'table_prefix'=>TABLE_PREFIX,
'salt'=>SECRET_SALT,
'dbtype'=>DBTYPE,
'streams'=>DatabaseMigrater::getUpgradeStreams(
UPGRADE_DIR . 'streams/'),
),
);
$this->write_block($header);
}
function dump($error_stream) {
// Allow plugins to change the tables exported
Signal::send('export.tables', $this, $this->tables);
$this->dump_header();
foreach ($this->tables as $t) {
if ($error_stream) $error_stream->write("$t\n");
$table = array();
$res = db_query("select column_name from information_schema.columns
where table_schema=DATABASE() and table_name='$t'");
while (list($field) = db_fetch_row($res))
$table[] = $field;
if (!$table) {
if ($error_stream) $error_stream->write(
sprintf(__("%s: Cannot export table with no fields\n"), $t));
die();
}
$this->write_block(
array('table', substr($t, strlen(TABLE_PREFIX)), $table));
db_query("select * from $t");
// Dump row data
while ($row = db_fetch_row($res))
$this->write_block($row);
$this->write_block(array('end-table'));
}
}
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
function transfer($destination, $query, $callback=false, $options=array()) {
$header_out = false;
$res = db_query($query, true, false);
$i = 0;
while ($row = db_fetch_array($res)) {
if (is_callable($callback))
$callback($row);
if (!$header_out) {
$fields = array_keys($row);
$this->write_block(
array('table', $destination, $fields, $options));
$header_out = true;
}
$this->write_block(array_values($row));
}
$this->write_block(array('end-table'));
}
function transfer_array($destination, $array, $keys, $options=array()) {
$this->write_block(
array('table', $destination, $keys, $options));
foreach ($array as $row) {
$this->write_block(array_values($row));
}
$this->write_block(array('end-table'));
}