Skip to content

Commit

Permalink
Dynamic data for osTicket
Browse files Browse the repository at this point in the history
*This is a major redesign / rework of the osTicket base*

This patch drops the concept of static ticket metadata and allows for an
admin-configurable arbitrary data that is attachable to tickets

The system is architected such that the base osTicket install now comes with
a "default" form that has fields for subject, name, email, and phone number.
This form is editable to allow for the addition of arbitrary other fields;
however, the basic fields must remain in order to be associated with a
help-topic and attached to a ticket.

This concept can be expanded to allow for arbitrary data associated with
registered clients or ticket thread items.

Forms are comprised of sections. Sections have a title and instructions
properties and a list of fields. Fields have various implementations to
represent different data such as text, long answer, phone number, datetime,
yes/no, and selections, and are configurable to define the look and feel and
interpretation of the respective form field.

Dropdown lists are represented as "Dynamic Lists", which are
admin-configurable lists of items. Dropdowns can be optionally represented
as Bootstrap typeahead fields.

This also adds the start of a simple ORM which will hopefully be expanded in
the future to support multiple database platforms. Currently, only MySQL is
implemented.
  • Loading branch information
Jared Hancock committed Oct 9, 2013
1 parent 3e3544c commit 9e75169
Show file tree
Hide file tree
Showing 53 changed files with 4,755 additions and 339 deletions.
3 changes: 3 additions & 0 deletions ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ function clientLoginPage($msg='Unauthorized') {
url_post('^(?P<id>\d+)/attach$', 'uploadInlineImageClient'),
url_get('^(?P<namespace>[\w.]+)$', 'getDraftClient'),
url_post('^(?P<namespace>[\w.]+)$', 'createDraftClient')
)),
url('^/form/', patterns('ajax.forms.php:DynamicFormsAjaxAPI',
url_get('^help-topic/(?P<id>\d+)$', 'getClientFormsForHelpTopic')
))
);
print $dispatcher->resolve($ost->get_path_info());
Expand Down
11 changes: 11 additions & 0 deletions bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ function defineTables($prefix) {
define('TICKET_PRIORITY_TABLE',$prefix.'ticket_priority');
define('PRIORITY_TABLE',TICKET_PRIORITY_TABLE);

define('FORM_SEC_TABLE',$prefix.'form_section');
define('FORM_FIELD_TABLE',$prefix.'form_field');
define('FORMSET_TABLE',$prefix.'formset');
define('FORMSET_SEC_TABLE',$prefix.'formset_sections');

define('LIST_TABLE',$prefix.'list');
define('LIST_ITEM_TABLE',$prefix.'list_items');

define('FORM_ENTRY_TABLE',$prefix.'form_entry');
define('FORM_ANSWER_TABLE',$prefix.'form_entry_values');

define('TOPIC_TABLE',$prefix.'help_topic');
define('SLA_TABLE', $prefix.'sla');

Expand Down
46 changes: 46 additions & 0 deletions include/ajax.forms.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

require_once(INCLUDE_DIR . 'class.topic.php');
require_once(INCLUDE_DIR . 'class.dynamic_forms.php');

class DynamicFormsAjaxAPI extends AjaxController {
function getForm($form_id) {
$form = DynamicFormSection::lookup($form_id);
if (!$form) return;

foreach ($form->getFields() as $field) {
$field->render();
}
}

function getFormsForHelpTopic($topic_id, $client=false) {
$topic = Topic::lookup($topic_id);
foreach (DynamicFormset::lookup($topic->ht['formset_id'])->getForms() as $form) {
$set=$form;
$form=$form->getForm();
if ($client)
include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php');
else
include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php');
}
}

function getClientFormsForHelpTopic($topic_id) {
return $this->getFormsForHelpTopic($topic_id, true);
}

function getFieldConfiguration($field_id) {
$field = DynamicFormField::lookup($field_id);
include(STAFFINC_DIR . 'templates/dynamic-field-config.tmpl.php');
}

function saveFieldConfiguration($field_id) {
$field = DynamicFormField::lookup($field_id);
if (!$field->setConfiguration())
include(STAFFINC_DIR . 'templates/dynamic-field-config.tmpl.php');
else
$field->save();
}
}

?>
26 changes: 17 additions & 9 deletions include/ajax.tickets.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ function lookup() {
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$tickets=array();

$sql='SELECT DISTINCT ticketID, email'
.' FROM '.TICKET_TABLE
.' WHERE ticketID LIKE \''.db_input($_REQUEST['q'], false).'%\'';
$sql='SELECT DISTINCT ticketID, email.value AS email'
.' FROM '.TICKET_TABLE.' ticket'
.' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.ticket_id = ticket.ticket_id '
.' LEFT JOIN '.FORM_ANSWER_TABLE.' email ON email.entry_id = entry.id '
.' LEFT JOIN '.FORM_FIELD_TABLE.' field ON email.field_id = field.id '
.' WHERE field.name = "email"'
.' AND ticketID LIKE \''.db_input($_REQUEST['q'], false).'%\'';

$sql.=' AND ( staff_id='.db_input($thisstaff->getId());

Expand All @@ -43,7 +47,7 @@ function lookup() {
$sql.=' OR dept_id IN ('.implode(',', db_input($depts)).')';

$sql.=' ) '
.' ORDER BY created LIMIT '.$limit;
.' ORDER BY ticket.created LIMIT '.$limit;

if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($id, $email)=db_fetch_row($res))
Expand All @@ -60,9 +64,13 @@ function lookupByEmail() {
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$tickets=array();

$sql='SELECT email, count(ticket_id) as tickets '
.' FROM '.TICKET_TABLE
.' WHERE email LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' ';
$sql='SELECT email.value AS email, count(ticket.ticket_id) as tickets '
.' FROM '.TICKET_TABLE.' ticket'
.' JOIN '.FORM_ENTRY_TABLE.' entry ON entry.ticket_id = ticket.ticket_id '
.' JOIN '.FORM_ANSWER_TABLE.' email ON email.entry_id = entry.id '
.' JOIN '.FORM_FIELD_TABLE.' field ON email.field_id = field.id '
.' WHERE field.name = "email"'
.' AND email.value LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' ';

$sql.=' AND ( staff_id='.db_input($thisstaff->getId());

Expand All @@ -73,8 +81,8 @@ function lookupByEmail() {
$sql.=' OR dept_id IN ('.implode(',', db_input($depts)).')';

$sql.=' ) '
.' GROUP BY email '
.' ORDER BY created LIMIT '.$limit;
.' GROUP BY email.value '
.' ORDER BY ticket.created LIMIT '.$limit;

if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($email, $count)=db_fetch_row($res))
Expand Down
33 changes: 31 additions & 2 deletions include/api.tickets.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ class TicketApiController extends ApiController {
# Supported arguments -- anything else is an error. These items will be
# inspected _after_ the fixup() method of the ApiXxxDataParser classes
# so that all supported input formats should be supported
function getRequestStructure($format) {
function getRequestStructure($format, $data=null) {
$supported = array(
"alert", "autorespond", "source", "topicId",
"name", "email", "subject", "phone", "phone_ext",
"attachments" => array("*" =>
array("name", "type", "data", "encoding")
),
"message", "ip", "priorityId"
);
# Fetch dynamic form field names for the given help topic and add
# the names to the supported request structure
if (isset($data['topicId'])) {
$topic=Topic::lookup($data['topicId']);
$formset=DynamicFormset::lookup($topic->ht['formset_id']);
foreach ($formset->getForms() as $form)
foreach ($form->getForm()->getFields() as $field)
$supported[] = $field->get('name');
}

if(!strcasecmp($format, 'email')) {
$supported = array_merge($supported, array('header', 'mid',
Expand Down Expand Up @@ -90,6 +98,21 @@ function createTicket($data) {

# Create the ticket with the data (attempt to anyway)
$errors = array();

$topic=Topic::lookup($data['topicId']);
$forms=DynamicFormset::lookup($topic->ht['formset_id'])->getForms();
foreach ($forms as $idx=>$f) {
$forms[$idx] = $form = $f->getForm()->instanciate($f->sort);
# Collect name, email address, and subject for banning and such
foreach ($form->getFields() as $field) {
$fname = $field->get('name');
if ($fname && isset($data[$fname]))
$field->value = $data[$fname];
}
if (!$form->isValid())
$errors = array_merge($errors, $form->errors());
}

$ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert);
# Return errors (?)
if (count($errors)) {
Expand All @@ -105,6 +128,12 @@ function createTicket($data) {
return $this->exerr(500, "Unable to create new ticket: unknown error");
}

# Save dynamic forms
foreach ($forms as $f) {
$f->set('ticket_id', $ticket->getId());
$f->save();
}

return $ticket;
}

Expand Down
4 changes: 2 additions & 2 deletions include/class.api.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ function getEmailRequest() {
* Structure to validate the request against -- must be overridden to be
* useful
*/
function getRequestStructure($format) { return array(); }
function getRequestStructure($format, $data=null) { return array(); }
/**
* Simple validation that makes sure the keys of a parsed request are
* expected. It is assumed that the functions actually implementing the
Expand Down Expand Up @@ -266,7 +266,7 @@ function validateRequestStructure($data, $structure, $prefix="") {
function validate(&$data, $format) {
return $this->validateRequestStructure(
$data,
$this->getRequestStructure($format)
$this->getRequestStructure($format, $data)
);
}

Expand Down
42 changes: 28 additions & 14 deletions include/class.client.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ function load($id=0, $email=null) {
if(!$id && !($id=$this->getId()))
return false;

$sql='SELECT ticket_id, ticketID, name, email, phone, phone_ext '
.' FROM '.TICKET_TABLE
.' WHERE ticketID='.db_input($id);
$sql='SELECT ticket.ticket_id, ticketID, email.value as email, phone.value as phone '
.' FROM '.TICKET_TABLE.' ticket '
.' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.ticket_id = ticket.ticket_id '
.' LEFT JOIN '.FORM_ANSWER_TABLE.' email ON email.entry_id = entry.id '
.' LEFT JOIN '.FORM_FIELD_TABLE.' field1 ON email.field_id = field1.id '
.' LEFT JOIN '.FORM_ANSWER_TABLE.' phone ON email.entry_id = entry.id '
.' LEFT JOIN '.FORM_FIELD_TABLE.' field2 ON phone.field_id = field2.id '
.' WHERE field1.name = "email" AND field2.name="phone" AND ticketID='.db_input($id);

if($email)
$sql.=' AND email='.db_input($email);
$sql.=' AND email.value = '.db_input($email);

if(!($res=db_query($sql)) || !db_num_rows($res))
return NULL;
Expand All @@ -53,12 +59,17 @@ function load($id=0, $email=null) {
$this->id = $this->ht['ticketID']; //placeholder
$this->ticket_id = $this->ht['ticket_id'];
$this->ticketID = $this->ht['ticketID'];
$this->fullname = ucfirst($this->ht['name']);

$entry = DynamicFormEntry::forTicket($this->ticket_id);
foreach ($entry as $form)
if ($form->getAnswer('name'))
$this->fullname = $form->getAnswer('name');

$this->username = $this->ht['email'];
$this->email = $this->ht['email'];

$this->stats = array();

return($this->id);
}

Expand Down Expand Up @@ -93,7 +104,7 @@ function getPhone() {
function getPhoneExt() {
return $this->ht['phone_ext'];
}

function getTicketID() {
return $this->ticketID;
}
Expand All @@ -120,9 +131,12 @@ function getNumClosedTickets() {

/* ------------- Static ---------------*/
function getLastTicketIdByEmail($email) {
$sql='SELECT ticketID FROM '.TICKET_TABLE
.' WHERE email='.db_input($email)
.' ORDER BY created '
$sql='SELECT ticket.ticketID '.TICKET_TABLE.' ticket '
.' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.ticket_id = ticket.ticket_id '
.' LEFT JOIN '.FORM_ANSWER_TABLE.' email ON email.entry_id = entry.id '
.' LEFT JOIN '.FORM_FIELD_TABLE.' field ON email.field_id = field.id '
.' WHERE field.name = "email" AND email.value = '.db_input($email)
.' ORDER BY ticket.created '
.' LIMIT 1';
if(($res=db_query($sql)) && db_num_rows($res))
list($tid) = db_fetch_row($res);
Expand Down Expand Up @@ -175,12 +189,12 @@ function lookupByEmail($email) {
//See if we can fetch local ticket id associated with the ID given
if(($ticket=Ticket::lookupByExtId($ticketID, $email)) && $ticket->getId()) {
//At this point we know the ticket ID is valid.
//TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets??
//TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets??
//Check the email given.

# Require auth token for automatic logins (GET METHOD).
if (!strcasecmp($ticket->getEmail(), $email) && (!$auto_login || $auth === $ticket->getAuthToken())) {

//valid match...create session goodies for the client.
$user = new ClientSession($email,$ticket->getExtId());
$_SESSION['_client'] = array(); //clear.
Expand All @@ -193,7 +207,7 @@ function lookupByEmail($email) {
//Log login info...
$msg=sprintf('%s/%s logged in [%s]', $ticket->getEmail(), $ticket->getExtId(), $_SERVER['REMOTE_ADDR']);
$ost->logDebug('User login', $msg);

//Regenerate session ID.
$sid=session_id(); //Current session id.
session_regenerate_id(TRUE); //get new ID.
Expand All @@ -202,7 +216,7 @@ function lookupByEmail($email) {

return $user;

}
}
}

//If we get to this point we know the login failed.
Expand Down
Loading

0 comments on commit 9e75169

Please sign in to comment.