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')
->annotate(array(
'collab_count' => SqlAggregate::COUNT('thread__collaborators'),
'attachment_count' => SqlAggregate::COUNT('thread__entries__attachments'),
'thread_count' => SqlAggregate::COUNT('thread__entries'),
));
'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;
})
);
}
/* static */ function saveTickets($sql, $filename, $how='csv') {
ob_start();
self::dumpTickets($sql, $how);
$stuff = ob_get_contents();
ob_end_clean();
if ($stuff)
Http::download($filename, "text/$how", $stuff);
return false;
}
static function saveTasks($sql, $filename, $how='csv') {
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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)));
$cdata += array('account_manager' => 'Account Manager', 'users' => 'Users');
ob_start();
echo self::dumpQuery($sql,
array(
'name' => 'Name',
) + $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 {
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;
$this->_res = $sql->getIterator();
$this->_res->rewind();
}
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) {
$current = $current->{$P};
}
}
// Evalutate :: function call on target current
if ($func && (method_exists($current, $func) || method_exists($current, '__call'))) {
$current = $current->{$func}();
}
$record[] = (string) $current;
}
if (isset($this->options['modify']) && is_callable($this->options['modify']))
$record = $this->options['modify']($record, $this->keys);
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,
GROUP_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, TICKET_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,
USER_ACCOUNT_TABLE, ORGANIZATION_TABLE, NOTE_TABLE
function DatabaseExporter($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'));
}
}
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
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'));
}