diff --git a/css/osticket.css b/css/osticket.css index 87e6c6aed4b382095e42199f41a00f35bea32621..8b986fef422772ae58c24cbaed324763e8171c03 100644 --- a/css/osticket.css +++ b/css/osticket.css @@ -43,3 +43,10 @@ display: block; opacity: 0.3; } + +div.section-break { + margin-top: 1em; + margin-bottom: 0.5em; + padding-top: 0.8em !important; + border-top: 1px solid #ccc; +} diff --git a/include/ajax.forms.php b/include/ajax.forms.php index ac49c8cdb053f0b055f888cd2d74c1cfb5dabca5..17cae9189bfcb7c049c0f0bd53977e41612d2c1c 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -15,12 +15,8 @@ class DynamicFormsAjaxAPI extends AjaxController { function getFormsForHelpTopic($topic_id, $client=false) { $topic = Topic::lookup($topic_id); - $form =DynamicForm::lookup($topic->ht['form_id']); - $set=$form; - if ($client) - include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php'); - else - include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php'); + if ($form =DynamicForm::lookup($topic->ht['form_id'])) + $form->render(!$client); } function getClientFormsForHelpTopic($topic_id) { @@ -66,8 +62,10 @@ class DynamicFormsAjaxAPI extends AjaxController { $static->data($data); $custom = array(); - foreach ($user->getDynamicData() as $cd) + foreach ($user->getDynamicData() as $cd) { + $cd->addMissingFields(); $custom[] = $cd->getForm(); + } include(STAFFINC_DIR . 'templates/user-info.tmpl.php'); } @@ -80,8 +78,9 @@ class DynamicFormsAjaxAPI extends AjaxController { $custom_data = $user->getDynamicData(); $custom = array(); foreach ($custom_data as $cd) { + $cd->addMissingFields(); $cf = $custom[] = $cd->getForm(); - $valid &= $cf->isValid(); + $valid &= $cd->isValid(); } if (!$valid) { diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 771dcf7f6ba2270a1ab3db6183da77b957c68b84..c664466fb1ad9330826d7029081bbac8da386308 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -17,6 +17,7 @@ if(!defined('INCLUDE_DIR')) die('403'); include_once(INCLUDE_DIR.'class.ticket.php'); +require_once(INCLUDE_DIR.'class.ajax.php'); class TicketsAjaxAPI extends AjaxController { @@ -77,7 +78,7 @@ class TicketsAjaxAPI extends AjaxController { $sql.=' OR dept_id IN ('.implode(',', db_input($depts)).')'; $sql.=' ) ' - .' GROUP BY email.value ' + .' GROUP BY email.address ' .' ORDER BY ticket.created LIMIT '.$limit; if(($res=db_query($sql)) && db_num_rows($res)) { @@ -88,11 +89,11 @@ class TicketsAjaxAPI extends AjaxController { return $this->json_encode($tickets); } - function search() { + function _search($req) { global $thisstaff, $cfg; $result=array(); - $select = 'SELECT count( DISTINCT ticket.ticket_id) as tickets '; + $select = 'SELECT DISTINCT ticket.ticket_id'; $from = ' FROM '.TICKET_TABLE.' ticket '; $where = ' WHERE 1 '; @@ -108,15 +109,15 @@ class TicketsAjaxAPI extends AjaxController { $where.=' ) '; //Department - if($_REQUEST['deptId']) - $where.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']); + if($req['deptId']) + $where.=' AND ticket.dept_id='.db_input($req['deptId']); //Help topic - if($_REQUEST['topicId']) - $where.=' AND ticket.topic_id='.db_input($_REQUEST['topicId']); + if($req['topicId']) + $where.=' AND ticket.topic_id='.db_input($req['topicId']); //Status - switch(strtolower($_REQUEST['status'])) { + switch(strtolower($req['status'])) { case 'open': $where.=' AND ticket.status="open" '; break; @@ -132,9 +133,9 @@ class TicketsAjaxAPI extends AjaxController { } //Assignee - if(isset($_REQUEST['assignee']) && strcasecmp($_REQUEST['status'], 'closed')) { - $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']); - $assignee = $_REQUEST['assignee']; + if(isset($req['assignee']) && strcasecmp($req['status'], 'closed')) { + $id=preg_replace("/[^0-9]/", "", $req['assignee']); + $assignee = $req['assignee']; $where.= ' AND ( ( ticket.status="open" '; if($assignee[0]=='t') $where.=' AND ticket.team_id='.db_input($id); @@ -145,19 +146,19 @@ class TicketsAjaxAPI extends AjaxController { $where.=')'; - if($_REQUEST['staffId'] && !$_REQUEST['status']) //Assigned TO + Closed By - $where.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") '; - elseif(isset($_REQUEST['staffId'])) // closed by any + if($req['staffId'] && !$req['status']) //Assigned TO + Closed By + $where.= ' OR (ticket.staff_id='.db_input($req['staffId']). ' AND ticket.status="closed") '; + elseif(isset($req['staffId'])) // closed by any $where.= ' OR ticket.status="closed" '; $where.= ' ) '; - } elseif($_REQUEST['staffId']) { - $where.=' AND (ticket.staff_id='.db_input($_REQUEST['staffId']).' AND ticket.status="closed") '; + } elseif($req['staffId']) { + $where.=' AND (ticket.staff_id='.db_input($req['staffId']).' AND ticket.status="closed") '; } //dates - $startTime =($_REQUEST['startDate'] && (strlen($_REQUEST['startDate'])>=8))?strtotime($_REQUEST['startDate']):0; - $endTime =($_REQUEST['endDate'] && (strlen($_REQUEST['endDate'])>=8))?strtotime($_REQUEST['endDate']):0; + $startTime =($req['startDate'] && (strlen($req['startDate'])>=8))?strtotime($req['startDate']):0; + $endTime =($req['endDate'] && (strlen($req['endDate'])>=8))?strtotime($req['endDate']):0; if( ($startTime && $startTime>time()) or ($startTime>$endTime && $endTime>0)) $startTime=$endTime=0; @@ -168,23 +169,65 @@ class TicketsAjaxAPI extends AjaxController { $where.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')'; //Query - if($_REQUEST['query']) { - $queryterm=db_real_escape($_REQUEST['query'], false); - - $from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )'; - $where.=" AND ( ticket.email LIKE '%$queryterm%'" - ." OR ticket.name LIKE '%$queryterm%'" - ." OR ticket.subject LIKE '%$queryterm%'" + if($req['query']) { + $queryterm=db_real_escape($req['query'], false); + + $from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )' + .' LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id + AND tentry.object_type="T") + LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id + AND tans.value_id IS NULL) + LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id + AND uentry.object_type="U") + LEFT JOIN '.FORM_ANSWER_TABLE.' uans ON (uans.entry_id = uentry.id + AND uans.value_id IS NULL) + LEFT JOIN '.USER_TABLE.' user ON (ticket.user_id = user.id) + LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)'; + + $where.=" AND ( uemail.address LIKE '%$queryterm%'" + ." OR user.name LIKE '%$queryterm%'" + ." OR tans.value LIKE '%$queryterm%'" + ." OR uans.value LIKE '%$queryterm%'" ." OR thread.title LIKE '%$queryterm%'" ." OR thread.body LIKE '%$queryterm%'" .' )'; } + // Dynamic fields + $dynfields='(SELECT entry.object_id, value, value_id FROM '.FORM_ANSWER_TABLE.' ans '. + 'LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '. + 'LEFT JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '. + 'WHERE field.name = %1$s AND entry.object_type="T")'; + foreach (TicketForm::getInstance()->getFields() as $f) { + if ($f->get('name') && isset($req[$f->getFormName()]) + && ($val = $req[$f->getFormName()])) { + $name = 'dyn_'.$f->get('id'); + $from .= ' LEFT JOIN '.sprintf($dynfields, db_input($f->get('name'))) + ." $name ON ($name.object_id = ticket.ticket_id)"; + $where .= " AND ($name.value_id = ".db_input($val) + . " OR $name.value LIKE '%".db_real_escape($val)."%')"; + } + } + $sql="$select $from $where"; - if(($tickets=db_result(db_query($sql)))) { - $result['success'] =sprintf("Search criteria matched %s - <a href='tickets.php?%s'>view</a>", - ($tickets>1?"$tickets tickets":"$tickets ticket"), - str_replace(array('&', '&'), array('&', '&'), $_SERVER['QUERY_STRING'])); + $res = db_query($sql); + while (list($tickets[]) = db_fetch_row($res)); + $tickets = array_filter($tickets); + + return $tickets; + } + + function search() { + $tickets = self::_search($_REQUEST); + + if (count($tickets)) { + $uid = md5($_SERVER['QUERY_STRING']); + $_SESSION["adv_$uid"] = $tickets; + $result['success'] =sprintf( + "Search criteria matched %d %s - <a href='tickets.php?%s'>view</a>", + count($tickets), (count($tickets)>1?"tickets":"ticket"), + 'advsid='.$uid + ); } else { $result['fail']='No tickets found matching your search criteria.'; } diff --git a/include/api.tickets.php b/include/api.tickets.php index b57f65125ec050cb0dff0f40f0c5d507476bd01c..81dac7a927134e11e090cc1e356b42e82064b915 100644 --- a/include/api.tickets.php +++ b/include/api.tickets.php @@ -21,9 +21,12 @@ class TicketApiController extends ApiController { if (isset($data['topicId'])) { $topic=Topic::lookup($data['topicId']); $form=DynamicForm::lookup($topic->ht['form_id']); - foreach ($form->getFields() as $field) + foreach ($form->getDynamicFields() as $field) $supported[] = $field->get('name'); } + $form = TicketForm::lookup()->instanciate(); + foreach ($form->getDynamicFields() as $field) + $supported[] = $field->get('name'); if(!strcasecmp($format, 'email')) { $supported = array_merge($supported, array('header', 'mid', @@ -98,16 +101,13 @@ class TicketApiController extends ApiController { # Create the ticket with the data (attempt to anyway) $errors = array(); - $topic=Topic::lookup($data['topicId']); - $form=DynamicForm::lookup($topic->ht['form_id'])->instanciate(); - # 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 ($topic=Topic::lookup($data['topicId'])) { + if ($form=DynamicForm::lookup($topic->ht['form_id'])) { + $form = $form->instanciate(); + if (!$form->isValid()) + $errors += $form->errors(); + } } - if (!$form->isValid()) - $errors = array_merge($errors, $form->errors()); $ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert); # Return errors (?) @@ -124,10 +124,10 @@ class TicketApiController extends ApiController { return $this->exerr(500, "Unable to create new ticket: unknown error"); } - # Save dynamic forms - foreach ($forms as $f) { - $f->setTicketId($ticket->getId()); - $f->save(); + # Save dynamic form + if (isset($form)) { + $form->setTicketId($ticket->getId()); + $form->save(); } return $ticket; diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 0418bab1e1f3f966464d987d656476ed8f1c5c4d..f38f09bf6f5a6cafd917e2ef7ec9ee7dfbdcbb7d 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -45,7 +45,8 @@ class DynamicForm extends VerySimpleModel { if (!isset($this->_fields)) { $this->_fields = array(); foreach ($this->getDynamicFields() as $f) - $this->_fields[] = $f->getImpl(); + // TODO: Index by field name or id + $this->_fields[$f->get('id')] = $f->getImpl($f); } return $this->_fields; } @@ -111,22 +112,24 @@ class DynamicForm extends VerySimpleModel { } return $inst; } - - static function addFormTypes($types) { - static::$types += $types; - } - - static function allTypes() { - return static::$types; - } } class UserForm extends DynamicForm { + static $instance; + static function objects() { $os = parent::objects(); return $os->filter(array('type'=>'U')); } + static function getInstance() { + if (!isset(static::$instance)) { + $o = static::objects(); + static::$instance = $o[0]->instanciate(); + } + return static::$instance; + } + function getStaticForm() { static $form = null; if (!$form) @@ -134,21 +137,45 @@ class UserForm extends DynamicForm { 'email' => new TextboxField(array( 'id'=>'email', 'label'=>'Email Address', 'required'=>true, 'validator' => 'email', 'configuration'=>array( - 'autocomplete'=>false, 'classes'=>array('typeahead'), + 'autocomplete'=>false, 'classes'=>'typeahead', 'size'=>40) )), 'name' => new TextboxField(array( 'id'=>'name', 'label'=>'Full Name', 'required'=>true, 'configuration' => array('size'=>40), )), - 'phone' => new PhoneField(array( - 'id'=>'c', 'label'=>'Phone Number', 'required'=>false, 'default'=>'', - )), ), 'User Information'); return $form; } } +class TicketForm extends DynamicForm { + static $instance; + + static function objects() { + $os = parent::objects(); + return $os->filter(array('type'=>'T')); + } + + static function getInstance() { + if (!isset(static::$instance)) { + $o = static::objects(); + static::$instance = $o[0]->instanciate(); + } + return static::$instance; + } +} +// Add fields from the standard ticket form to the ticket filterable fields +Filter::addSupportedMatches('Dynamic Fields', function() { + $matches = array(); + foreach (TicketForm::getInstance()->getFields() as $f) { + if (!$f->hasData()) + continue; + $matches['field.'.$f->get('id')] = $f->getLabel(); + } + return $matches; +}); + require_once(INCLUDE_DIR . "class.json.php"); class DynamicFormField extends VerySimpleModel { @@ -200,8 +227,8 @@ class DynamicFormField extends VerySimpleModel { * errors. If false, the errors were written into the received errors * array. */ - function setConfiguration($errors) { - $errors = $config = array(); + function setConfiguration(&$errors=array()) { + $config = array(); foreach ($this->getConfigurationForm() as $name=>$field) { $config[$name] = $field->getClean(); $errors = array_merge($errors, $field->errors()); @@ -212,6 +239,19 @@ class DynamicFormField extends VerySimpleModel { return count($errors) === 0; } + function isDeletable() { + return $this->get('edit_mask') & 1; + } + function isNameEditable() { + return $this->get('edit_mask') & 2; + } + function isPrivacyForced() { + return $this->get('edit_mask') & 4; + } + function isRequirementForced() { + return $this->get('edit_mask') & 8; + } + function delete() { // Don't really delete form fields as that will screw up the data // model. Instead, just drop the association with the form which @@ -265,6 +305,8 @@ class DynamicFormEntry extends VerySimpleModel { var $_values; var $_fields; var $_form; + var $_errors = false; + var $_clean = false; function getAnswers() { if (!isset($this->_values)) { @@ -283,6 +325,16 @@ class DynamicFormEntry extends VerySimpleModel { return $ans->getValue(); return null; } + function setAnswer($name, $value, $id=false) { + foreach ($this->getAnswers() as $ans) { + if ($ans->getField()->get('name') == $name) { + $ans->set('value', $value); + if ($id !== false) + $ans->set('value_id', $id); + break; + } + } + } function errors() { return $this->_errors; @@ -292,7 +344,7 @@ class DynamicFormEntry extends VerySimpleModel { function getInstructions() { return $this->getForm()->getInstructions(); } function getForm() { - if (!$this->_form) { + if (!isset($this->_form)) { $this->_form = DynamicForm::lookup($this->get('form_id')); if ($this->id) $this->_form->data($this); @@ -301,7 +353,7 @@ class DynamicFormEntry extends VerySimpleModel { } function getFields() { - if (!$this->_fields) { + if (!isset($this->_fields)) { $this->_fields = array(); foreach ($this->getAnswers() as $a) $this->_fields[] = $a->getField(); @@ -350,6 +402,10 @@ class DynamicFormEntry extends VerySimpleModel { $this->object_id = $user_id; } + function render($staff=true) { + return $this->getForm()->render($staff); + } + /** * addMissingFields * @@ -359,20 +415,26 @@ class DynamicFormEntry extends VerySimpleModel { * entry. */ function addMissingFields() { - foreach ($this->getForm()->getFields() as $field) { + foreach ($this->getForm()->getDynamicFields() as $field) { $found = false; foreach ($this->getAnswers() as $answer) { if ($answer->get('field_id') == $field->get('id')) { $found = true; break; } } - if (!$found) { + if (!$found && ($field = $field->getImpl($field)) + && !$field->isPresentationOnly()) { $a = DynamicFormEntryAnswer::create( array('field_id'=>$field->get('id'), 'entry_id'=>$this->id)); $a->field = $field; + $a->entry = $this; // Add to list of answers $this->_values[] = $a; - $a->save(); + $this->_fields[] = $field; + // Omit fields without data + if ($field->hasData()) + $a->save(); + unset($this->_form); } } } @@ -382,9 +444,18 @@ class DynamicFormEntry extends VerySimpleModel { $this->set('updated', new SqlFunction('NOW')); parent::save(); foreach ($this->getAnswers() as $a) { - $a->set('value', $a->getField()->to_database($a->getField()->getClean())); + $field = $a->getField(); + $val = $field->to_database($field->getClean()); + if (is_array($val)) { + $a->set('value', $val[0]); + $a->set('value_id', $val[1]); + } + else + $a->set('value', $val); $a->set('entry_id', $this->get('id')); - $a->save(); + // Don't save answers for presentation-only fields + if ($field->hasData() && !$field->isPresentationOnly()) + $a->save(); } $this->_values = array(); } @@ -393,6 +464,7 @@ class DynamicFormEntry extends VerySimpleModel { $inst = parent::create($ht); $inst->set('created', new SqlFunction('NOW')); foreach ($inst->getForm()->getFields() as $f) { + if (!$f->hasData()) continue; $a = DynamicFormEntryAnswer::create( array('field_id'=>$f->get('id'))); $a->field = $f; @@ -440,7 +512,8 @@ class DynamicFormEntryAnswer extends VerySimpleModel { function getField() { if (!isset($this->field)) { - $this->field = DynamicFormField::lookup($this->get('field_id'))->getImpl(); + $f = DynamicFormField::lookup($this->get('field_id')); + $this->field = $f->getImpl($f); $this->field->answer = $this; } return $this->field; @@ -448,10 +521,15 @@ class DynamicFormEntryAnswer extends VerySimpleModel { function getValue() { if (!$this->_value) - $this->_value = $this->getField()->to_php($this->get('value')); + $this->_value = $this->getField()->to_php( + $this->get('value'), $this->get('value_id')); return $this->_value; } + function getIdValue() { + return $this->get('value_id'); + } + function toString() { return $this->getField()->toString($this->getValue()); } @@ -490,7 +568,7 @@ class DynamicList extends VerySimpleModel { } function getPluralName() { - if ($name = $this->get('plural_name')) + if ($name = $this->get('name_plural')) return $name; else return $this->get('name') . 's'; @@ -530,13 +608,13 @@ class DynamicList extends VerySimpleModel { $selections = array(); foreach (DynamicList::objects() as $list) { $selections['list-'.$list->id] = - array('Selection: ' . $list->getPluralName(), + array($list->getPluralName(), SelectionField, $list->get('id')); } return $selections; } } -FormField::addFieldTypes(array(DynamicList, 'getSelections')); +FormField::addFieldTypes('Custom Lists', array(DynamicList, 'getSelections')); /** * Represents a single item in a dynamic list @@ -576,12 +654,14 @@ class DynamicListItem extends VerySimpleModel { } class SelectionField extends FormField { + function getListId() { + list(,$list_id) = explode('-', $this->get('type')); + return $list_id; + } + function getList() { - if (!$this->_list) { - $list_id = explode('-', $this->get('type')); - $list_id = $list_id[1]; - $this->_list = DynamicList::lookup($list_id); - } + if (!$this->_list) + $this->_list = DynamicList::lookup($this->getListId()); return $this->_list; } @@ -589,33 +669,35 @@ class SelectionField extends FormField { return new SelectionWidget($this); } - function parse($id) { - return $this->to_php($id); + function parse($value) { + return $this->to_php($value); } - function to_php($id) { - if (!$id) - return null; - list($id, $value) = explode(':', $id); - $item = DynamicListItem::lookup($id); + function to_php($value, $id=false) { + $item = DynamicListItem::lookup($id ? $id : $value); # Attempt item lookup by name too if (!$item) { - $item = DynamicListItem::objects()->filter(array( - 'value'=>$id, - 'list_id'=>$this->getList()->get('id'))); - $item = (count($item)) ? $item[0] : null; + $item = DynamicListItem::lookup(array( + 'value'=>$value, + 'list_id'=>$this->getListId())); } - return $item; + return ($item) ? $item : $id; } function to_database($item) { - if ($item && $item->get('id')) - return $item->id . ':' . $item->value; + if ($item instanceof DynamicListItem) + return array($item->value, $item->id); return null; } function toString($item) { - return ($item) ? $item->toString() : ''; + return ($item instanceof DynamicListItem) ? $item->toString() : $item; + } + + function validateEntry($item) { + parent::validateEntry($item); + if ($item && !$item instanceof DynamicListItem) + $this->_errors[] = 'Select a value from the list'; } function getConfigurationOptions() { @@ -627,21 +709,30 @@ class SelectionField extends FormField { 'hint'=>'Typeahead will work better for large lists')), ); } + + function getChoices() { + if (!$this->_choices) { + $this->_choices = array(); + foreach ($this->getList()->getItems() as $i) + $this->_choices[$i->get('id')] = $i->get('value'); + } + return $this->_choices; + } } class SelectionWidget extends ChoicesWidget { function render() { $config = $this->field->getConfiguration(); $value = false; - if (is_object($this->value) && get_class($this->value) == 'DynamicListItem') { + if ($this->value instanceof DynamicListItem) { // Loaded from database $value = $this->value->get('id'); $name = $this->value->get('value'); } elseif ($this->value) { // Loaded from POST $value = $this->value; - $name = DynamicListItem::lookup($this->value); - $name = ($name) ? $name->get('value') : null; + $name = DynamicListItem::lookup($value); + $name = ($name) ? $name->get('value') : $value; } if (!$config['typeahead']) { @@ -652,22 +743,20 @@ class SelectionWidget extends ChoicesWidget { $source = array(); foreach ($this->field->getList()->getItems() as $i) $source[] = array( - 'info' => $i->get('value'), - 'value' => strtolower($i->get('value').' '.$i->get('extra')), - 'id' => $i->get('id')); + 'value' => $i->get('value'), + 'info' => $i->get('value')." -- ".$i->get('extra'), + ); ?> <span style="display:inline-block"> - <input type="hidden" name="<?php echo $this->name; ?>" - value="<?php echo $value; ?>" /> - <input type="text" size="30" id="<?php echo $this->name; ?>" - value="<?php echo $name; ?>" /> + <input type="text" size="30" name="<?php echo $this->name; ?>" + value="<?php echo $name; ?>" autocomplete="off" /> <script type="text/javascript"> $(function() { - $('#<?php echo $this->name; ?>').typeahead({ + $('input[name=<?php echo $this->name; ?>]').typeahead({ source: <?php echo JsonDataEncoder::encode($source); ?>, + property: 'info', onselect: function(item) { - $('#<?php echo $this->name; ?>').val(item['info']) - $('input[name="<?php echo $this->name; ?>"]').val(item['id']) + $('input[name="<?php echo $this->name; ?>"]').val(item['value']) } }); }); @@ -675,15 +764,5 @@ class SelectionWidget extends ChoicesWidget { </span> <?php } - - function getChoices() { - if (!$this->_choices) { - $this->_choices = array(); - foreach ($this->field->getList()->getItems() as $i) - $this->_choices[$i->get('id')] = $i->get('value'); - } - return $this->_choices; - } } - ?> diff --git a/include/class.filter.php b/include/class.filter.php index 1f94120793d338ddb57be5979eb31a500d9c1ada..2ee4fbbf2e1a967b9ab32a0f29d7b7f872a0e962 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -18,6 +18,17 @@ class Filter { var $id; var $ht; + static $match_types = array( + 'Basic Fields' => array( + 'name' => 'Name', + 'email' => 'Email', + 'subject' => 'Subject', + 'body' => 'Body/Text', + 'reply-to' => 'Reply-To Email', + 'reply-to-name' => 'Reply-To Name', + ), + ); + function Filter($id) { $this->id=0; $this->load($id); @@ -299,13 +310,28 @@ class Filter { $ticket['cannedResponseId'] = $this->getCannedResponse(); } /* static */ function getSupportedMatches() { - return array( - 'name'=> 'Name', - 'email'=> 'Email', - 'subject'=> 'Subject', - 'body'=> 'Body/Text' - ); + foreach (static::$match_types as $k=>&$v) { + if (is_callable($v)) + $v = $v(); + } + unset($v); + return static::$match_types; + } + + static function addSupportedMatches($group, $callable) { + static::$match_types[$group] = $callable; } + + static function getSupportedMatchFields() { + $keys = array(); + foreach (static::getSupportedMatches() as $group=>$matches) { + foreach ($matches as $key=>$label) { + $keys[] = $key; + } + } + return $keys; + } + /* static */ function getSupportedMatchTypes() { return array( 'equal'=> 'Equal', @@ -370,7 +396,7 @@ class Filter { function save_rules($id,$vars,&$errors) { - $matches = array_keys(self::getSupportedMatches()); + $matches = array_keys(self::getSupportedMatchFields()); $types = array_keys(self::getSupportedMatchTypes()); $rules=array(); @@ -382,7 +408,9 @@ class Filter { $errors["rule_$i"]='Invalid match type selection'; elseif(!$vars["rule_v$i"]) $errors["rule_$i"]='Value required'; - elseif($vars["rule_w$i"]=='email' && $vars["rule_h$i"]=='equal' && !Validator::is_email($vars["rule_v$i"])) + elseif($vars["rule_w$i"]=='email' + && $vars["rule_h$i"]=='equal' + && !Validator::is_email($vars["rule_v$i"])) $errors["rule_$i"]='Valid email required for the match type'; else //for everything-else...we assume it's valid. $rules[]=array('what'=>$vars["rule_w$i"], @@ -640,22 +668,18 @@ class TicketFilter { * deal with the data in the incoming ticket (based on $vars) will be considered. * @see ::quickList() for more information. */ - function TicketFilter($origin, $vars=null) { + function TicketFilter($origin, $vars=array()) { //Normalize the target based on ticket's origin. $this->target = self::origin2target($origin); //Extract the vars we care about (fields we filter by!). - $this->vars = array_filter(array_map('trim', - array( - 'email' => $vars['email'], - 'subject' => $vars['subject'], - 'name' => $vars['name'], - 'body' => $vars['message'], - 'emailId' => $vars['emailId'], - 'reply-to' => @$vars['reply-to'], - 'reply-to-name' => @$vars['reply-to-name'], - ))); + $this->vars = array('body'=>$vars['message']); + $interest = Filter::getSupportedMatchFields(); + foreach ($vars as $k=>$v) { + if (in_array($k, $interest)) + $this->vars[$k] = trim($v); + } //Init filters. $this->build(); diff --git a/include/class.forms.php b/include/class.forms.php index cc5d07f07821a713fdb9370bc9954dd00662b883..b9a6a2dbe8225171c9e50131ea92135de309895a 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -20,7 +20,7 @@ */ class Form { var $fields = array(); - var $title = 'Unnamed Form'; + var $title = ''; var $instructions = ''; var $_errors; @@ -93,14 +93,21 @@ class FormField { ); var $_cform; + var $_clean; + var $_errors = array(); + var $parent; static $types = array( - 'text' => array('Short Answer', TextboxField), - 'memo' => array('Long Answer', TextareaField), - 'datetime' => array('Date and Time', DatetimeField), - 'phone' => array('Phone Number', PhoneField), - 'bool' => array('Checkbox', BooleanField), - 'choices' => array('Choices', ChoiceField), + 'Basic Fields' => array( + 'text' => array('Short Answer', TextboxField), + 'memo' => array('Long Answer', TextareaField), + 'thread' => array('Thread Entry', ThreadEntryField, false), + 'datetime' => array('Date and Time', DatetimeField), + 'phone' => array('Phone Number', PhoneField), + 'bool' => array('Checkbox', BooleanField), + 'choices' => array('Choices', ChoiceField), + 'break' => array('Section Break', SectionBreakField), + ), ); static $more_types = array(); @@ -114,20 +121,25 @@ class FormField { $this->ht['id'] = $uid++; } - static function addFieldTypes($callable) { - static::$more_types[] = $callable; + static function addFieldTypes($group, $callable) { + static::$more_types[$group] = $callable; } static function allTypes() { if (static::$more_types) { - foreach (static::$more_types as $c) - static::$types = array_merge(static::$types, - call_user_func($c)); + foreach (static::$more_types as $group=>$c) + static::$types[$group] = call_user_func($c); static::$more_types = array(); } return static::$types; } + static function getFieldType($type) { + foreach (static::allTypes() as $group=>$types) + if (isset($types[$type])) + return $types[$type]; + } + function get($what) { return $this->ht[$what]; } @@ -141,15 +153,16 @@ class FormField { * user-entered data. */ function getClean() { - $value = $this->getWidget()->value; - $value = $this->parse($value); - $this->validateEntry($value); - return $value; + if (!isset($this->_clean)) { + $value = $this->getWidget()->value; + $this->_clean = $this->parse($value); + $this->validateEntry($this->_clean); + } + return $this->_clean; } function errors() { - if (!$this->_errors) return array(); - else return $this->_errors; + return $this->_errors; } function isValidEntry() { @@ -168,14 +181,13 @@ class FormField { * $value - (string) input from the user */ function validateEntry($value) { + if (!$value && count($this->_errors)) + return; + # Validates a user-input into an instance of this field on a dynamic # form - if (!is_array($this->_errors)) { - $this->_errors = array(); - - if ($this->get('required') && !$value) - $this->_errors[] = $this->getLabel() . ' is a required field'; - } + if ($this->get('required') && !$value && $this->hasData()) + $this->_errors[] = sprintf('%s is a required field', $this->getLabel()); } /** @@ -253,13 +265,20 @@ class FormField { * For instance, if the value of this field is 'text', a TextField * instance will be returned. */ - function getImpl() { + function getImpl($parent=null) { // Allow registration with ::addFieldTypes and delayed calling - $types = static::allTypes(); - $clazz = $types[$this->get('type')][1]; + $this->parent = $parent; + $type = static::getFieldType($this->get('type')); + $clazz = $type[1]; return new $clazz($this->ht); } + function __call($what, $args) { + // XXX: Throw exception if $this->parent is not set + return call_user_func_array( + array($this->parent, $what), $args); + } + function getAnswer() { return $this->answer; } function getFormName() { @@ -269,8 +288,12 @@ class FormField { return $this->get('id'); } - function render() { - $this->getWidget()->render(); + function render($mode=null) { + $this->getWidget()->render($mode); + } + + function renderExtras($mode=null) { + return; } function getConfigurationOptions() { @@ -300,14 +323,49 @@ class FormField { return $this->_config; } + /** + * If the [Config] button should be shown to allow for the configuration + * of this field + */ function isConfigurable() { return true; } + /** + * Field type is changeable in the admin interface + */ + function isChangeable() { + return true; + } + + /** + * Field does not contain data that should be saved to the database. Ie. + * non data fields like section headers + */ + function hasData() { + return true; + } + + /** + * Returns true if the field/widget should be rendered as an entire + * block in the target form. + */ + function isBlockLevel() { + return false; + } + + /** + * Fields should not be saved with the dynamic data. It is assumed that + * some static processing will store the data elsewhere. + */ + function isPresentationOnly() { + return false; + } + function getConfigurationForm() { if (!$this->_cform) { - $types = static::allTypes(); - $clazz = $types[$this->get('type')][1]; + $type = static::getFieldType($this->get('type')); + $clazz = $type[1]; $T = new $clazz(); $this->_cform = $T->getConfigurationOptions(); } @@ -333,6 +391,10 @@ class TextboxField extends FormField { 'id'=>3, 'label'=>'Validator', 'required'=>false, 'default'=>'', 'choices' => array('phone'=>'Phone Number','email'=>'Email Address', 'ip'=>'IP Address', 'number'=>'Number', ''=>'None'))), + 'validator-error' => new TextboxField(array( + 'id'=>4, 'label'=>'Validation Error', 'default'=>'', + 'configuration'=>array('size'=>40), + 'hint'=>'Message shown to user if the input does not match the validator')), ); } @@ -354,6 +416,8 @@ class TextboxField extends FormField { $config = $this->getConfiguration(); $valid = $config['validator']; } + if (!$value || !isset($validators[$valid])) + return; $func = $validators[$valid]; if (is_array($func) && is_callable($func[0])) if (!call_user_func($func[0], $value)) @@ -442,6 +506,34 @@ class ChoiceField extends FormField { 'id'=>1, 'label'=>'Choices', 'required'=>false, 'default'=>'')), ); } + + function toString($value) { + $choices = $this->getChoices(); + if (isset($choices[$value])) + return $choices[$value]; + else + return $choices[$this->get('default')]; + } + + function getChoices() { + if ($this->_choices === null) { + // Allow choices to be set in this->ht (for configurationOptions) + $this->_choices = $this->get('choices'); + if (!$this->_choices) { + $this->_choices = array(); + $config = $this->getConfiguration(); + $choices = explode("\n", $config['choices']); + foreach ($choices as $choice) { + // Allow choices to be key: value + list($key, $val) = explode(':', $choice); + if ($val == null) + $val = $key; + $this->_choices[trim($key)] = trim($val); + } + } + } + return $this->_choices; + } } class DatetimeField extends FormField { @@ -516,6 +608,93 @@ class DatetimeField extends FormField { } } +/** + * This is kind-of a special field that doesn't have any data. It's used as + * a field to provide a horizontal section break in the display of a form + */ +class SectionBreakField extends FormField { + function getWidget() { + return new SectionBreakWidget($this); + } + + function hasData() { + return false; + } + + function isBlockLevel() { + return true; + } +} + +class ThreadEntryField extends FormField { + function getWidget() { + return new ThreadEntryWidget($this); + } + function isChangeable() { + return false; + } + function isBlockLevel() { + return true; + } + function isPresentationOnly() { + return true; + } + function renderExtras($mode=null) { + if ($mode == 'client') + $this->getWidget()->showAttachments(); + } +} + +class PriorityField extends ChoiceField { + function getWidget() { + $widget = parent::getWidget(); + if ($widget->value instanceof Priority) + $widget->value = $widget->value->getId(); + return $widget; + } + + function getChoices() { + $this->ht['default'] = 0; + + $sql = 'SELECT priority_id, priority_desc FROM '.PRIORITY_TABLE + .' ORDER BY priority_urgency DESC'; + $choices = array(0 => '— Default —'); + if (!($res = db_query($sql))) + return $choices; + + while ($row = db_fetch_row($res)) + $choices[$row[0]] = $row[1]; + return $choices; + } + + function parse($id) { + return $this->to_php(null, $id); + } + + function to_php($value, $id) { + return Priority::lookup($id); + } + + function to_database($prio) { + return ($prio instanceof Priority) + ? array($prio->getDesc(), $prio->getId()) + : $prio; + } + + function toString($value) { + return ($value instanceof Priority) ? $value->getDesc() : $value; + } + + function getConfigurationOptions() { + return array(); + } +} +FormField::addFieldTypes('Built-in Lists', function() { + return array( + 'priority' => array('Priority Level', PriorityField), + ); +}); + class Widget { function Widget() { # Not called in PHP5 @@ -546,7 +725,7 @@ class TextboxWidget extends Widget { if (isset($config['length'])) $maxlength = "maxlength=\"{$config['length']}\""; if (isset($config['classes'])) - $classes = 'class="'.implode(' ', $config['classes']).'"'; + $classes = 'class="'.$config['classes'].'"'; if (isset($config['autocomplete'])) $autocomplete = 'autocomplete="'.($config['autocomplete']?'on':'off').'"'; ?> @@ -604,7 +783,7 @@ class ChoicesWidget extends Widget { // Determine the value for the default (the one listed if nothing is // selected) $def_key = $this->field->get('default'); - $choices = $this->getChoices(); + $choices = $this->field->getChoices(); $have_def = isset($choices[$def_key]); if (!$have_def) $def_val = 'Select '.$this->field->get('label'); @@ -619,34 +798,14 @@ class ChoicesWidget extends Widget { foreach ($choices as $key=>$name) { if (!$have_def && $key == $def_key) continue; ?> - <option value="<?php echo $key; ?>" - <?php if ($this->value == $key) echo 'selected="selected"'; + <option value="<?php echo $key; ?>" <?php + if ($this->value == $key) echo 'selected="selected"'; ?>><?php echo $name; ?></option> <?php } ?> </select> </span> <?php } - - function getChoices() { - if ($this->_choices === null) { - // Allow choices to be set in this->ht (for configurationOptions) - $this->_choices = $this->field->get('choices'); - if (!$this->_choices) { - $this->_choices = array(); - $config = $this->field->getConfiguration(); - $choices = explode("\n", $config['choices']); - foreach ($choices as $choice) { - // Allow choices to be key: value - list($key, $val) = explode(':', $choice); - if ($val == null) - $val = $key; - $this->_choices[trim($key)] = trim($val); - } - } - } - return $this->_choices; - } } class CheckboxWidget extends Widget { @@ -730,4 +889,42 @@ class DatetimePickerWidget extends Widget { } } +class SectionBreakWidget extends Widget { + function render() { + ?><div class="form-header section-break"><h3><?php + echo Format::htmlchars($this->field->get('label')); + ?></h3><em><?php echo Format::htmlchars($this->field->get('hint')); + ?></em></div> + <?php + } +} + +class ThreadEntryWidget extends Widget { + function render($mode=null) { + echo '<strong>'.Format::htmlchars($this->field->get('label')); + ?></strong>: + <br/> + <textarea name="<?php echo $this->field->get('name'); ?>" + cols="21" rows="8" style="width:80%;"><?php echo + $this->value; ?></textarea> + <?php + } + + function showAttachments() { + global $cfg, $thisclient; + + if(($cfg->allowOnlineAttachments() + && !$cfg->allowAttachmentsOnlogin()) + || ($cfg->allowAttachmentsOnlogin() + && ($thisclient && $thisclient->isValid()))) { ?> + <hr/> + <div><strong>Attachments:</strong></div> + <div class="uploads"></div><br> + <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" /> + <font class="error"> <?php echo $errors['attachments']; ?></font> + <?php + } + } +} + ?> diff --git a/include/class.orm.php b/include/class.orm.php index 86d3015f1525dff2dd90edea0448309901ac6fbf..50aae3b237f454ec97d26928ed6bbdf7a1ae179b 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -151,8 +151,6 @@ class VerySimpleModel { function save($refetch=false) { $pk = static::$meta['pk']; - if (!$this->isValid()) - return false; if (!is_array($pk)) $pk=array($pk); if ($this->__new__) $sql = 'INSERT INTO '.static::$meta['table']; @@ -632,7 +630,9 @@ class SqlCompiler { list($field, $op) = $this->getField($field, $model); // Allow operators to be callable rather than sprintf // strings - if (is_callable($op)) + if ($value === null) + $filter[] = sprintf('%s IS NULL', $field); + elseif (is_callable($op)) $filter[] = call_user_func($op, $field, $value); else $filter[] = sprintf($op, $field, $this->input($value)); diff --git a/include/class.ticket.php b/include/class.ticket.php index 12c30c02acad45938333a2e7de30ef0453cca320..ef82b7104e9f6fdf5a90ef64f34739f6a203b403 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -111,7 +111,7 @@ class Ticket { foreach (DynamicFormEntry::forTicket($this->getId()) as $form) foreach ($form->getAnswers() as $answer) $this->_answers[$answer->getField()->get('name')] = - $answer->toString(); + $answer->getValue(); } function reload() { @@ -216,7 +216,7 @@ class Ticket { function getName(){ if ($o = $this->getOwner()) - return $o->getFullName(); + return $o->getName(); return null; } @@ -288,11 +288,17 @@ class Ticket { } function getPriorityId() { - return $this->ht['priority_id']; + global $cfg; + + if ($a = $this->_answers['priority']) + return $a->getId(); + return $cfg->getDefaultPriorityId(); } - function getPriority() { //TODO: Make it an obj. - return $this->ht['priority_desc']; + function getPriority() { + if ($a = $this->_answers['priority']) + return $a->getDesc(); + return '...ummm...'; } function getPhone() { @@ -565,20 +571,6 @@ class Ticket { return $this->lastMsgId=$msgid; } - function setPriority($priorityId) { - - //XXX: what happens to SLA priority??? - - if(!$priorityId || $priorityId==$this->getPriorityId()) - return ($priorityId); - - $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() ' - .', priority_id='.db_input($priorityId) - .' WHERE ticket_id='.db_input($this->getId()); - - return (($res=db_query($sql)) && db_affected_rows($res)); - } - //DeptId can NOT be 0. No orphans please! function setDeptId($deptId) { @@ -1664,7 +1656,6 @@ class Ticket { $fields=array(); $fields['topicId'] = array('type'=>'int', 'required'=>1, 'error'=>'Help topic required'); - $fields['priorityId'] = array('type'=>'int', 'required'=>1, 'error'=>'Priority required'); $fields['slaId'] = array('type'=>'int', 'required'=>0, 'error'=>'Select SLA'); $fields['duedate'] = array('type'=>'date', 'required'=>0, 'error'=>'Invalid date - must be MM/DD/YY'); @@ -1687,7 +1678,6 @@ class Ticket { if($errors) return false; $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() ' - .' ,priority_id='.db_input($vars['priorityId']) .' ,topic_id='.db_input($vars['topicId']) .' ,sla_id='.db_input($vars['slaId']) .' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL'); @@ -1895,6 +1885,23 @@ class Ticket { } } + // Create and verify the dynamic form entry for the new ticket + $form = TicketForm::getInstance(); + // If submitting via email, ensure we have a subject and such + foreach ($form->getFields() as $field) { + $fname = $field->get('name'); + if ($fname && isset($vars[$fname]) && !$field->value) + $field->value = $vars[$fname]; + } + + // Don't enforce form validation for email + if (!$form->isValid() && strtolower($origin) != 'email') + $errors += $form->errors(); + + // Unpack dynamic variables into $vars for filter application + foreach ($form->getFields() as $f) + $vars['field.'.$f->get('id')] = $f->toString($f->getClean()); + //Init ticket filters... $ticket_filter = new TicketFilter($origin, $vars); // Make sure email contents should not be rejected @@ -1930,7 +1937,6 @@ class Ticket { # TODO: Return error message $errors['err']=$errors['origin'] = 'Invalid origin given'; } - $fields['priorityId'] = array('type'=>'int', 'required'=>0, 'error'=>'Invalid Priority'); if(!Validator::process($fields, $vars, $errors) && !$errors['err']) $errors['err'] ='Missing or invalid data - check the errors and try again'; @@ -1945,16 +1951,19 @@ class Ticket { $errors['duedate']='Due date must be in the future'; } - //Any error above is fatal. - if($errors) return 0; - # Identify the user creating the ticket $user_info = UserForm::getStaticForm()->getClean(); + // Data is slightly different between HTTP posts and emails if (isset($vars['emailId']) || !isset($user_info['email'])) $user_info = $vars; + elseif (!UserForm::getStaticForm()->isValid()) + $errors['user'] = 'Incomplete client information'; $user = User::fromForm($user_info); $user_email = UserEmail::ensure($user_info['email']); + //Any error above is fatal. + if($errors) return 0; + # Perform ticket filter actions on the new ticket arguments if ($ticket_filter) $ticket_filter->apply($vars); @@ -1964,13 +1973,13 @@ class Ticket { // OK...just do it. $deptId=$vars['deptId']; //pre-selected Dept if any. - $priorityId=$vars['priorityId']; $source=ucfirst($vars['source']); $topic=NULL; // Intenal mapping magic...see if we need to override anything if(isset($vars['topicId']) && ($topic=Topic::lookup($vars['topicId']))) { //Ticket created via web by user/or staff $deptId=$deptId?$deptId:$topic->getDeptId(); - $priorityId=$priorityId?$priorityId:$topic->getPriorityId(); + if (!$form->getAnswer('priority')) + $form->setAnswer('priority', null, $topic->getPriorityId()); if($autorespond) $autorespond=$topic->autoRespond(); $source=$vars['source']?$vars['source']:'Web'; @@ -1988,13 +1997,15 @@ class Ticket { }elseif($vars['emailId'] && !$vars['deptId'] && ($email=Email::lookup($vars['emailId']))) { //Emailed Tickets $deptId=$email->getDeptId(); - $priorityId=$priorityId?$priorityId:$email->getPriorityId(); + if (!$form->getAnswer('priority')) + $form->setAnswer('priority', null, $email->getPriorityId()); if($autorespond) $autorespond=$email->autoRespond(); $email=null; $source='Email'; } //Last minute checks - $priorityId=$priorityId?$priorityId:$cfg->getDefaultPriorityId(); + if (!$form->getAnswer('priority')) + $form->setAnswer('priority', null, $cfg->getDefaultPriorityId()); $deptId=$deptId?$deptId:$cfg->getDefaultDeptId(); $topicId=$vars['topicId']?$vars['topicId']:0; $ipaddress=$vars['ip']?$vars['ip']:$_SERVER['REMOTE_ADDR']; @@ -2008,7 +2019,6 @@ class Ticket { .' ,ticketID='.db_input($extId) .' ,dept_id='.db_input($deptId) .' ,topic_id='.db_input($topicId) - .' ,priority_id='.db_input($priorityId) .' ,ip_address='.db_input($ipaddress) .' ,source='.db_input($source); @@ -2029,6 +2039,10 @@ class Ticket { //TODO: RETHING what happens if this fails?? [At the moment on failure random ID is used...making stuff usable] } + // Save the (common) dynamic form + $form->setTicketId($id); + $form->save(); + $dept = $ticket->getDept(); //post the message. @@ -2100,11 +2114,6 @@ class Ticket { if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other'))) $errors['source']='Invalid source - '.Format::htmlchars($vars['source']); - if(!$vars['issue']) - $errors['issue']='Summary of the issue required'; - else - $vars['message']=$vars['issue']; - if(!($ticket=Ticket::create($vars, $errors, 'staff', false, (!$vars['assignId'])))) return false; diff --git a/include/class.topic.php b/include/class.topic.php index d924f2ab7f1f4e3afec14e4cde5f20ebb069705a..8f1eb7365d1a323363c34744d12f0156648b11f7 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -209,15 +209,6 @@ class Topic { elseif(($tid=self::getIdByName($vars['topic'], $vars['pid'])) && $tid!=$id) $errors['topic']='Topic already exists'; - if (!$vars['form_id']) - $errors['form_id'] = 'You must select a form'; - else { - $form=DynamicForm::lookup($vars['form_id']); - foreach (array('subject') as $f) - if (!$form->hasField($f)) - $errors['form_id']="Form must define the '$f' field"; - } - if(!$vars['dept_id']) $errors['dept_id']='You must select a department'; diff --git a/include/client/open.inc.php b/include/client/open.inc.php index ac8a9353d4c585acdd610d65d6d22f8c9d533d04..7bb1d959951f2946ab421bf2954e30ff18db5482 100644 --- a/include/client/open.inc.php +++ b/include/client/open.inc.php @@ -39,7 +39,9 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; <font class="error">* <?php echo $errors['topicId']; ?></font> </td> </tr> - <?php UserForm::getStaticForm()->render(false, 'Your Information'); ?> +<?php + UserForm::getStaticForm()->render(false, 'Your Information'); + TicketForm::getInstance()->render(false); ?> </tbody> <tbody id="dynamic-form"> <?php if ($forms) { @@ -49,32 +51,6 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; } ?> </tbody> <tbody> - <tr><td colspan="2"><hr /></td></tr> - <tr> - <td class="required">Message:</td> - <td> - <div style="margin-bottom:0.5em;"><em>Please provide as much detail as possible so we can best assist you.</em> <font class="error">* <?php echo $errors['message']; ?></font> - </div> - <textarea id="message" cols="60" rows="8" name="message" - class="richtext ifhtml draft" - data-draft-namespace="ticket.client" - data-draft-object-id="<?php echo substr(session_id(), -12); ?>" - ><?php echo $info['message']; ?></textarea> - </td> - </tr> - - <?php if(($cfg->allowOnlineAttachments() && !$cfg->allowAttachmentsOnlogin()) - || ($cfg->allowAttachmentsOnlogin() && ($thisclient && $thisclient->isValid()))) { ?> - <tr> - <td>Attachments:</td> - <td> - <div class="uploads"></div><br> - <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" /> - <font class="error"> <?php echo $errors['attachments']; ?></font> - </td> - </tr> - <tr><td colspan=2> </td></tr> - <?php } ?> <?php if($cfg->allowPriorityChange() && ($priorities=Priority::getPriorities())) { ?> <tr> diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php index 49ff0169731bbb1d1c4e15f512dd698bcb04e1ef..1e4e79e8c2c486c5ab32a39cf9ab2c81dfaf6efd 100644 --- a/include/client/templates/dynamic-form.tmpl.php +++ b/include/client/templates/dynamic-form.tmpl.php @@ -30,14 +30,22 @@ if ($field->get('private')) continue; ?> - <tr><td class="<?php if ($field->get('required')) echo 'required'; ?>"> - <?php echo Format::htmlchars($field->get('label')); ?>:</td> - <td><?php $field->render(); ?> + <tr> + <?php if ($field->isBlockLevel()) { ?> + <td colspan="2"> + <?php + } + else { ?> + <td class="<?php if ($field->get('required')) echo 'required'; ?>"> + <?php echo Format::htmlchars($field->get('label')); ?>:</td><td> + <?php + } + $field->render('client'); ?> <?php if ($field->get('required')) { ?> <font class="error">*</font> <?php } - if ($field->get('hint')) { ?> + if ($field->get('hint') && !$field->isBlockLevel()) { ?> <br /><em style="color:gray;display:inline-block"><?php echo Format::htmlchars($field->get('hint')); ?></em> <?php @@ -45,7 +53,9 @@ foreach ($field->errors() as $e) { ?> <br /> <font class="error"><?php echo $e; ?></font> - <?php } ?> + <?php } + $field->renderExtras('client'); + ?> </td> </tr> <?php diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index 04898d5e43e5a27e02907087079e118bb572251a..d38d113cedc3fb6e744640ffd224090a0e01b970 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -46,14 +46,14 @@ $qselect='SELECT ticket.ticket_id,ticket.ticketID,ticket.dept_id,isanswered, ' $dynfields='(SELECT entry.object_id, value FROM '.FORM_ANSWER_TABLE.' ans '. 'LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '. 'LEFT JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '. - 'WHERE field.name = "%1$s" entry.object_type="T") %1$s ON ticket.ticket_id = %1$s.ticket_id '; + 'WHERE field.name = "%1$s" AND entry.object_type="T")'; $subject_sql = sprintf($dynfields, 'subject'); $qfrom='FROM '.TICKET_TABLE.' ticket ' .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.dept_id) ' .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id' .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id' - .' LEFT JOIN '.$subject_sql; + .' LEFT JOIN '.$subject_sql.' subject ON ticket.ticket_id = subject.object_id '; $qwhere =' WHERE email.address='.db_input($thisclient->getEmail()); diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml index 868b76422ce8d669cb96ce74150338311e291f23..1de219b60033189782e9bd1d3dcaae1a85a4a066 100644 --- a/include/i18n/en_US/form.yaml +++ b/include/i18n/en_US/form.yaml @@ -1,44 +1,83 @@ # -# Default (dynamic) form configuration +# Default (dynamic) form configuration. This data is used as the initial, +# minimal data for dynamic forms that ships with the system. # # Fields: +# id: Used only if associated with a help topic +# title: Bold section title of the form +# instructions: Title deck, detailed instructions on entering form data +# notes: Notes for the form, shown under the fields +# deletable: True if the form can be removed from the system +# fields: List of fields for the form +# type: Field type (short name) (eg. 'text', 'memo', 'phone', ...) +# label: Field label shown to the user +# name: Name used with the data internally. This is especially +# useful for page and email templates, where %{ ticket.<name> } +# will be used to retrieve the data from the field. +# hint: Help text shown with the field +# edit_mask: Mask out edits to the field (1=>delete, 2=>change name, +# 4=>privacy setting, 8=>requirement setting) +# private: True if the field should be hidden from the client +# required: True if entry for the field is required +# configuration: Field-specific configuration +# size: (text) width of the field +# length: (text) maximum size of the data in the field +# cols: (memo) width of the textarea +# rows: (memo) height of the textarea +# --- - id: 1 - title: User Information + type: U # notrans + title: Client Details + deletable: false fields: - - type: text # notrans - name: email # notrans - label: Email Address - required: true - sort: 10 - configuration: - size: 40 - length: 40 - validator: email # notrans - - - type: text # notrans - name: name # notrans - label: Full Name - required: true - sort: 20 - configuration: - size: 40 - length: 40 - - type: phone # notrans name: phone # notrans label: Phone Number required: false - sort: 30 + sort: 1 + + - type: memo # notrans + name: notes + label: Internal Notes + required: false + private: true + sort: 2 + configuration: + rows: 4 + cols: 40 - id: 2 - title: Ticket Details + type: T # notrans + title: Standard Ticket Form + notes: | + This form will be attached to every ticket, regardless of its source. + You can add any fields to this form and they will be available to all + tickets, and will be searchable with advanced search and filterable. + deletable: false fields: - type: text # notrans name: subject # notrans label: Subject hint: Issue Summary - sort: 10 + required: true + edit_mask: 15 + sort: 1 configuration: - size: 40 - length: 50 + size: 40 + length: 50 + + - type: thread # notrans + name: message # notrans + label: Issue + hint: Details on the reason(s) for opening the ticket. + required: true + edit_mask: 15 + sort: 2 + + - type: priority # notrans + name: priority # notrans + label: Priority Level + required: false + edit_mask: 3 + sort: 3 diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php index 4af7cde38f102f5d5ee9e2ad57305b1817c11ed6..34ba48ffdf9be605772bddbd8c1fb4b92e26d388 100644 --- a/include/staff/dynamic-form.inc.php +++ b/include/staff/dynamic-form.inc.php @@ -16,7 +16,7 @@ if($form && $_REQUEST['a']!='add') { $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="?" method="post" id="save"> +<form action="?id=<?php echo urlencode($_REQUEST['id']); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> @@ -43,12 +43,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); echo $info['instructions']; ?></textarea> </td> </tr> - <tr> - <td width="180">Internal Notes:</td> - <td><textarea name="notes" rows="4" cols="80"><?php - echo $info['notes']; ?></textarea> - </td> - </tr> </tbody> </table> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> @@ -78,11 +72,22 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <td>Email Address</td><td>Short Answer</td><td>email</td> <td><input type="checkbox" disabled="disabled"/></td> <td><input type="checkbox" disabled="disabled" checked="checked"/></td></tr> + <?php + $uform = UserForm::objects()->all(); + $ftypes = FormField::allTypes(); + foreach ($uform[0]->getFields() as $f) { + if ($f->get('private')) continue; + ?> <tr> <td><input type="checkbox" disabled="disabled"/></td> - <td>Phone Number</td><td>Phone Number</td><td>phone</td> + <td><?php echo $f->get('label'); ?></td> + <td><?php $t=FormField::getFieldType($f->get('type')); echo $t[0]; ?></td> + <td><?php echo $f->get('name'); ?></td> <td><input type="checkbox" disabled="disabled"/></td> - <td><input type="checkbox" disabled="disabled"/></td></tr> + <td><input type="checkbox" disabled="disabled" + <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td></tr> + + <?php } ?> </tbody> <thead> <tr> @@ -102,9 +107,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <tbody class="sortable-rows" data-sort="sort-"> <?php if ($form) foreach ($form->getFields() as $f) { $id = $f->get('id'); + $deletable = ($f->get('editable') & 1) ? 'disabled="disabled"' : ''; + $force_name = ($f->get('editable') & 2) ? 'disabled="disabled"' : ''; + $force_privacy = ($f->get('editable') & 4) ? 'disabled="disabled"' : ''; + $force_required = ($f->get('editable') & 8) ? 'disabled="disabled"' : ''; $errors = $f->errors(); ?> <tr> - <td><input type="checkbox" name="delete-<?php echo $id; ?>"/> + <td><input type="checkbox" name="delete-<?php echo $id; ?>" + <?php echo $deletable; ?>/> <input type="hidden" name="sort-<?php echo $id; ?>" value="<?php echo $f->get('sort'); ?>"/> <font class="error"><?php @@ -113,11 +123,18 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td><input type="text" size="32" name="label-<?php echo $id; ?>" value="<?php echo $f->get('label'); ?>"/></td> - <td><select name="type-<?php echo $id; ?>"> - <?php foreach (FormField::allTypes() as $type=>$nfo) { ?> + <td><select name="type-<?php echo $id; ?>" <?php + if (!$f->isChangeable()) echo 'disabled="disabled"'; ?>> + <?php foreach (FormField::allTypes() as $group=>$types) { + ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php + foreach ($types as $type=>$nfo) { + if ($f->get('type') != $type + && isset($nfo[2]) && !$nfo[2]) continue; ?> <option value="<?php echo $type; ?>" <?php if ($f->get('type') == $type) echo 'selected="selected"'; ?>> <?php echo $nfo[0]; ?></option> + <?php } ?> + </optgroup> <?php } ?> </select> <?php if ($f->isConfigurable()) { ?> @@ -133,15 +150,24 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php } ?></td> <td> <input type="text" size="20" name="name-<?php echo $id; ?>" - value="<?php echo $f->get('name'); ?>"/> + value="<?php echo $f->get('name'); ?>" <?php echo $force_name ?>/> <font class="error"><?php if ($errors['name']) echo '<br/>'; echo $errors['name']; ?></font> </td> <td><input type="checkbox" name="private-<?php echo $id; ?>" - <?php if ($f->get('private')) echo 'checked="checked"'; ?>/></td> + <?php if ($f->get('private')) echo 'checked="checked"'; ?> + <?php echo $force_privacy ?>/></td> <td><input type="checkbox" name="required-<?php echo $id; ?>" - <?php if ($f->get('required')) echo 'checked="checked"'; ?>/></td> + <?php if ($f->get('required')) echo 'checked="checked"'; ?> + <?php echo $force_required ?>/> + <?php if (!$f->get('editable')) { ?> + <input type="hidden" name="private-<?php echo $id; ?>" value="<?php + echo ($f->get('private')) ? 'on' : ''; ?>" /> + <input type="hidden" name="required-<?php echo $id; ?>" value="<?php + echo ($f->get('required')) ? 'on' : ''; ?>" /> + <?php + } ?></td> </tr> <?php } @@ -150,17 +176,35 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td> <td><input type="text" size="32" name="label-new-<?php echo $i; ?>"/></td> <td><select name="type-new-<?php echo $i; ?>"> - <?php foreach (FormField::allTypes() as $type=>$nfo) { ?> + <?php foreach (FormField::allTypes() as $group=>$types) { + ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php + foreach ($types as $type=>$nfo) { ?> <option value="<?php echo $type; ?>"> <?php echo $nfo[0]; ?></option> + <?php } ?> + </optgroup> <?php } ?> </select></td> <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"/></td> - <td><input type="checkbox" name="private-new-<?php echo $i; ?>"/></td> + <td><input type="checkbox" name="private-new-<?php echo $i; ?>" + <?php if ($form && $form->get('type') == 'U') + echo 'checked="checked"'; ?>/></td> <td><input type="checkbox" name="required-new-<?php echo $i; ?>"/></td> </tr> <?php } ?> </tbody> + <tbody> + <tr> + <th colspan="7"> + <em><strong>Internal Notes:</strong> be liberal, they're internal</em> + </th> + </tr> + <tr> + <td colspan="7"><textarea name="notes" rows="6" cols="80" style="width:95%"><?php + echo $info['notes']; ?></textarea> + </td> + </tr> + </tbody> </table> <p style="padding-left:225px;"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> diff --git a/include/staff/dynamic-forms.inc.php b/include/staff/dynamic-forms.inc.php index e1c590831ecc218c805feede51946006b30677ad..7c590d435d522c37eddaccfff32e14abb99f22a7 100644 --- a/include/staff/dynamic-forms.inc.php +++ b/include/staff/dynamic-forms.inc.php @@ -7,13 +7,50 @@ <?php $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; -$count = DynamicForm::objects()->count(); +$count = DynamicForm::objects()->filter(array('type'=>'G'))->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); $pageNav->setURL('forms.php'); $showing=$pageNav->showing().' forms'; ?> <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> + <thead> + <tr> + <th width="7"> </th> + <th>Client Information Form <em>Added to all clients</em></th> + <th>Last Updated</th> + </tr> + </thead> + <tbody> + <?php foreach (UserForm::objects()->order_by('title') + ->limit($pageNav->getLimit()) + ->offset($pageNav->getStart()) as $form) { ?> + <tr> + <td/> + <td><a href="?id=<?php echo $form->get('id'); ?>"><?php echo $form->get('title'); ?></a></td> + <td><?php echo $form->get('updated'); ?></td> + </tr> + <?php } ?> + </tbody> + <thead> + <tr> + <th width="7"> </th> + <th>Common Ticket Form <em>Added to all tickets</em></th> + <th>Last Updated</th> + </tr> + </thead> + <tbody> + <?php foreach (TicketForm::objects()->order_by('title') + ->limit($pageNav->getLimit()) + ->offset($pageNav->getStart()) as $form) { ?> + <tr> + <td/> + <td><a href="?id=<?php echo $form->get('id'); ?>"><?php echo $form->get('title'); ?></a></td> + <td><?php echo $form->get('updated'); ?></td> + </tr> + <?php } ?> + </tbody> + <tbody> <caption><?php echo $showing; ?></caption> <thead> <tr> @@ -23,7 +60,8 @@ $showing=$pageNav->showing().' forms'; </tr> </thead> <tbody> - <?php foreach (DynamicForm::objects()->order_by('title') + <?php foreach (DynamicForm::objects()->filter(array('type'=>'G')) + ->order_by('title') ->limit($pageNav->getLimit()) ->offset($pageNav->getStart()) as $form) { ?> <tr> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index b029fbb682a8ebfdebf386027d561c772aba2382..37ac6c69925bf4dc15711f0a76767803aeacc66a 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -125,11 +125,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <select name="rule_w<?php echo $i; ?>"> <option value="">— Select One ‐</option> <?php - foreach($matches as $k=>$v){ - $sel=($info["rule_w$i"]==$k)?'selected="selected"':''; - echo sprintf('<option value="%s" %s>%s</option>',$k,$sel,$v); - } - ?> + foreach ($matches as $group=>$ms) { ?> + <optgroup label="<?php echo $group; ?>"><?php + foreach ($ms as $k=>$v) { + $sel=($info["rule_w$i"]==$k)?'selected="selected"':''; + echo sprintf('<option value="%s" %s>%s</option>',$k,$sel,$v); + } ?> + </optgroup> + <?php } ?> </select> <select name="rule_h<?php echo $i; ?>"> <option value="0">— Select One ‐</option> diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index 8aa9be725e396a07c0da9cf07b58a38c46fe8e12..319ce28509d32c1118180f3e1cf359b87b2c00eb 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -92,8 +92,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <tr> <td><strong>Dynamic Form</strong>:</td> <td><select name="form_id"> - <option value="0">— Select a Form —</option> - <?php foreach (DynamicForm::objects() as $group) { ?> + <option value="0">— No Extra Fields —</option> + <?php foreach (DynamicForm::objects()->filter(array('type'=>'G')) as $group) { ?> <option value="<?php echo $group->get('id'); ?>" <?php if ($group->get('id') == $info['form_id']) echo 'selected="selected"'; ?>> @@ -101,7 +101,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </option> <?php } ?> </select> - <em>Information for tickets associated with this help topic</em> + <em>Extra information for tickets associated with this help topic</em> <span class="error"> <?php echo $errors['form_id']; ?></span> </td> </tr> diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php index e54d5bfc1395a52fff47f07649aa5b0a3c1646cf..4a67b38e2a94a664e86d2702e442dbdd56c9704c 100644 --- a/include/staff/templates/dynamic-form.tmpl.php +++ b/include/staff/templates/dynamic-form.tmpl.php @@ -7,14 +7,21 @@ } foreach ($form->getFields() as $field) { ?> - <tr><td class="multi-line <?php if ($field->get('required')) echo 'required'; ?>"> - <?php echo Format::htmlchars($field->get('label')); ?>:</td> - <td><?php $field->render(); ?> + <tr><?php if ($field->isBlockLevel()) { ?> + <td colspan="2" class="<?php if ($field->get('required')) echo 'required'; ?>"> + <?php + } + else { ?> + <td class="multi-line <?php if ($field->get('required')) echo 'required'; ?>"> + <?php echo Format::htmlchars($field->get('label')); ?>:</td> + <td><?php + } + $field->render(); ?> <?php if ($field->get('required')) { ?> <font class="error">*</font> <?php } - if ($field->get('hint')) { ?> + if ($field->get('hint') && !$field->isBlockLevel()) { ?> <br /><em style="color:gray;display:inline-block"><?php echo Format::htmlchars($field->get('hint')); ?></em> <?php diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php index 643e147c0bb6e535846038f1a81f99460053afd7..e2d7864a42c710fd50c049d15e655cacfd00c9cf 100644 --- a/include/staff/ticket-edit.inc.php +++ b/include/staff/ticket-edit.inc.php @@ -51,25 +51,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); <font class="error"><b>*</b> <?php echo $errors['topicId']; ?></font> </td> </tr> - <tr> - <td width="160" class="required"> - Priority Level: - </td> - <td> - <select name="priorityId"> - <option value="" selected >— Select Priority —</option> - <?php - if($priorities=Priority::getPriorities()) { - foreach($priorities as $id =>$name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, ($info['priorityId']==$id)?'selected="selected"':'',$name); - } - } - ?> - </select> - <font class="error">* <?php echo $errors['priorityId']; ?></font> - </td> - </tr> <tr> <td width="160"> SLA Plan: @@ -111,7 +92,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); <tbody id="dynamic-form"> <?php if ($forms) foreach ($forms as $form) { - include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php'); + $form->render(true); } ?> </tbody> <tbody> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index e72514b938c083373a151c45559b05dd4721884f..46356bbba95148e310f473abe0f21a259698c45b 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -88,25 +88,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <font class="error"><b>*</b> <?php echo $errors['topicId']; ?></font> </td> </tr> - <tr> - <td width="160"> - Priority: - </td> - <td> - <select name="priorityId"> - <option value="0" selected >— System Default —</option> - <?php - if($priorities=Priority::getPriorities()) { - foreach($priorities as $id =>$name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, ($info['priorityId']==$id)?'selected="selected"':'',$name); - } - } - ?> - </select> - <font class="error"> <?php echo $errors['priorityId']; ?></font> - </td> - </tr> <tr> <td width="160"> SLA Plan: @@ -178,29 +159,16 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> </tr> <?php - } ?> + } + TicketForm::getInstance()->render(true); + ?> </tbody> <tbody id="dynamic-form"> - <?php if ($form) - include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php'); + <?php + if ($form) $form->render(true); ?> </tbody> <tbody> - <tr> - <th colspan="2"> - <em><strong>Issue</strong>: The user will be able to see the issue summary below and any associated responses.</em> - </th> - </tr> - <tr> - <td colspan=2> - <div><em><strong>Issue</strong>: Details on the reason(s) for opening the ticket.</em> <font class="error">* <?php echo $errors['issue']; ?></font></div> - <textarea class="richtext ifhtml draft draft-delete" - placeholder="Details on the reason(s) for opening the ticket." - data-draft-namespace="ticket.staff" name="issue" - cols="21" rows="8" style="width:80%;" - ><?php echo $info['issue']; ?></textarea> - </td> - </tr> <?php //is the user allowed to post replies?? if($thisstaff->canPostReply()) { diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index e99560eb6e8b2c2ecb8f789ecf3a2acb2dad51fc..a3ef79d5d4d29f0752ef720c41cd769ab318d6df 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -140,7 +140,7 @@ if($ticket->isOverdue()) </tr> </table> </td> - <td width="50%"> + <td width="50%" style="vertical-align:top"> <table border="0" cellspacing="" cellpadding="4" width="100%"> <tr> <th width="100">Client:</th> @@ -177,15 +177,11 @@ if($ticket->isOverdue()) </td> </tr> <tr> - <th>Default Email:</th> + <th>Email:</th> <td> <?php echo $ticket->getEmail(); ?> </td> </tr> - <tr> - <th>Phone:</th> - <td><?php echo $ticket->getPhoneNumber(); ?></td> - </tr> <tr> <th>Source:</th> <td><?php @@ -193,8 +189,6 @@ if($ticket->isOverdue()) if($ticket->getIP()) echo ' <span class="faded">('.$ticket->getIP().')</span>'; - - ?> </td> </tr> @@ -285,7 +279,7 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) { // array('email', ...)))); $answers = array_filter($form->getAnswers(), function ($a) { return !in_array($a->getField()->get('name'), - array('email','subject','name','phone')); + array('email','subject','name','priority')); }); if (count($answers) == 0) continue; @@ -293,7 +287,8 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) { </tr><tr> <td colspan="2"> <table cellspacing="0" cellpadding="4" width="100%" border="0"> - <?php foreach($answers as $a) { ?> + <?php foreach($answers as $a) { + if (!$a->toString()) continue; ?> <tr> <th width="100"><?php echo $a->getField()->get('label'); diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 5d1ea62383fb4cf523b6723e88da82a4afc15d99..43e68f52567066a78290809100ee96418a4c27d8 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -49,7 +49,7 @@ switch(strtolower($_REQUEST['status'])){ //Status is overloaded $results_type='Answered Tickets'; break; default: - if(!$search) + if(!$search && !isset($_REQUEST['advsid'])) $_REQUEST['status']=$status='open'; } @@ -109,90 +109,36 @@ if($search): if($searchTerm){ $qstr.='&query='.urlencode($searchTerm); $queryterm=db_real_escape($searchTerm,false); //escape the term ONLY...no quotes. - if(is_numeric($searchTerm)){ + if (is_numeric($searchTerm)) { $qwhere.=" AND ticket.ticketID LIKE '$queryterm%'"; - }elseif(strpos($searchTerm,'@') && Validator::is_email($searchTerm)){ //pulling all tricks! + } elseif (strpos($searchTerm,'@') && Validator::is_email($searchTerm)) { + //pulling all tricks! # XXX: What about searching for email addresses in the body of # the thread message $qwhere.=" AND email.address='$queryterm'"; - }else{//Deep search! + } else {//Deep search! //This sucks..mass scan! search anything that moves! + require_once(INCLUDE_DIR.'ajax.tickets.php'); - $deep_search=true; - } - } - // OwnerId - if ($_REQUEST['ownerId']) { - $qwhere .= ' AND ticket.user_id='.db_input($_REQUEST['ownerId']); - } - //department - if($_REQUEST['deptId'] && in_array($_REQUEST['deptId'],$thisstaff->getDepts())) { - //This is dept based search..perm taken care above..put the sucker in. - $qwhere.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']); - $qstr.='&deptId='.urlencode($_REQUEST['deptId']); - } - - //Help topic - if($_REQUEST['topicId']) { - $qwhere.=' AND ticket.topic_id='.db_input($_REQUEST['topicId']); - $qstr.='&topicId='.urlencode($_REQUEST['topicId']); - } - - //Assignee - if(isset($_REQUEST['assignee']) && strcasecmp($_REQUEST['status'], 'closed')) { - $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']); - $assignee = $_REQUEST['assignee']; - $qstr.='&assignee='.urlencode($_REQUEST['assignee']); - $qwhere.= ' AND ( - ( ticket.status="open" '; - - if($assignee[0]=='t') - $qwhere.=' AND ticket.team_id='.db_input($id); - elseif($assignee[0]=='s') - $qwhere.=' AND ticket.staff_id='.db_input($id); - elseif(is_numeric($id)) - $qwhere.=' AND ticket.staff_id='.db_input($id); - - $qwhere.=' ) '; - - if($_REQUEST['staffId'] && !$_REQUEST['status']) { //Assigned TO + Closed By - $qwhere.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") '; - $qstr.='&staffId='.urlencode($_REQUEST['staffId']); - }elseif(isset($_REQUEST['staffId'])) { - $qwhere.= ' OR ticket.status="closed" '; - $qstr.='&staffId='.urlencode($_REQUEST['staffId']); - } - - $qwhere.= ' ) '; - } elseif($_REQUEST['staffId']) { - $qwhere.=' AND (ticket.staff_id='.db_input($_REQUEST['staffId']).' AND ticket.status="closed") '; - $qstr.='&staffId='.urlencode($_REQUEST['staffId']); - } - - //dates - $startTime =($_REQUEST['startDate'] && (strlen($_REQUEST['startDate'])>=8))?strtotime($_REQUEST['startDate']):0; - $endTime =($_REQUEST['endDate'] && (strlen($_REQUEST['endDate'])>=8))?strtotime($_REQUEST['endDate']):0; - if( ($startTime && $startTime>time()) or ($startTime>$endTime && $endTime>0)){ - $errors['err']='Entered date span is invalid. Selection ignored.'; - $startTime=$endTime=0; - }else{ - //Have fun with dates. - if($startTime){ - $qwhere.=' AND ticket.created>=FROM_UNIXTIME('.$startTime.')'; - $qstr.='&startDate='.urlencode($_REQUEST['startDate']); - - } - if($endTime){ - $qwhere.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')'; - $qstr.='&endDate='.urlencode($_REQUEST['endDate']); + $tickets = TicketsAjaxApi::_search(array('query'=>$queryterm)); + if (count($tickets)) + $qwhere .= ' AND ticket.ticket_id IN ('. + implode(',',db_input($tickets)).')'; } } endif; -$sortOptions=array('date'=>'ticket.created','ID'=>'ticketID','pri'=>'priority_urgency','name'=>'name.value', - 'subj'=>'subject.value','status'=>'ticket.status','assignee'=>'assigned','staff'=>'staff', - 'dept'=>'dept_name'); +if ($_REQUEST['advsid'] && isset($_SESSION['adv_'.$_REQUEST['advsid']])) { + $qstr.='advsid='.$_REQUEST['advsid']; + $qwhere .= ' AND ticket.ticket_id IN ('. implode(',', + db_input($_SESSION['adv_'.$_REQUEST['advsid']])).')'; +} + +$sortOptions=array('date'=>'ticket.created','ID'=>'ticketID', + 'pri'=>'priority_urgency','name'=>'user.name','subj'=>'subject.value', + 'status'=>'ticket.status','assignee'=>'assigned','staff'=>'staff', + 'dept'=>'dept_name'); $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); @@ -241,11 +187,12 @@ $$x=' class="'.strtolower($order).'" '; if($_GET['limit']) $qstr.='&limit='.urlencode($_GET['limit']); -$dynfields='(SELECT entry.object_id, value FROM '.FORM_ANSWER_TABLE.' ans '. +$dynfields='(SELECT entry.object_id, value, value_id FROM '.FORM_ANSWER_TABLE.' ans '. 'LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '. 'LEFT JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '. 'WHERE field.name = "%1$s" AND entry.object_type="T")'; $subject_sql=sprintf($dynfields, 'subject'); +$prio_sql=sprintf($dynfields, 'priority'); $qselect ='SELECT DISTINCT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,ticket.staff_id,ticket.team_id ' .' ,subject.value as subject' @@ -257,7 +204,8 @@ $qfrom=' FROM '.TICKET_TABLE.' ticket '. ' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'. ' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'. ' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id '. - ' LEFT JOIN '.$subject_sql.' subject ON subject.object_id = ticket.ticket_id'; + ' LEFT JOIN '.$subject_sql.' subject ON subject.object_id = ticket.ticket_id'. + ' LEFT JOIN '.$prio_sql.' tprio ON tprio.object_id = ticket.ticket_id'; $sjoin=''; if($search && $deep_search) { @@ -282,7 +230,7 @@ $qselect.=' ,count(attach.attach_id) as attachments ' .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned ' .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic '; -$qfrom.=' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (ticket.priority_id=pri.priority_id) ' +$qfrom.=' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (tprio.value_id=pri.priority_id) ' .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW() AND tlock.staff_id!='.db_input($thisstaff->getId()).') ' .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) ' @@ -644,6 +592,17 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting.. <span>TO</span> <input class="dp" type="input" size="20" name="endDate"> </fieldset> + <fieldset> + <?php + foreach (TicketForm::getInstance()->getFields() as $f) { + if (in_array($f->get('type'), array('text', 'memo', 'phone', 'thread'))) + continue; + elseif (!$f->hasData()) + continue; + ?><label><?php echo $f->getLabel(); ?>:</label> + <div style="display:inline-block;width: 12.5em;"><?php $f->render(); ?></div> + <?php } ?> + </fieldset> <p> <span class="buttons"> <input type="submit" value="Search"> diff --git a/include/upgrader/streams/core/d51f303a-DYNAMICF.patch.sql b/include/upgrader/streams/core/d51f303a-DYNAMICF.patch.sql index 940b9a12b3d260e5ab8c5925f11254be49484726..5fa04072673d4cd67d9d26bfc8c4f412f47cc597 100644 --- a/include/upgrader/streams/core/d51f303a-DYNAMICF.patch.sql +++ b/include/upgrader/streams/core/d51f303a-DYNAMICF.patch.sql @@ -10,48 +10,28 @@ * fields are dropped from the ticket table. */ -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_formset`; -CREATE TABLE `%TABLE_PREFIX%dynamic_formset` ( - `id` int(11) unsigned auto_increment, - `title` varchar(255) NOT NULL, - `instructions` varchar(512), - `notes` text, - `created` datetime NOT NULL, - `updated` datetime NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_formset_sections`; -CREATE TABLE `%TABLE_PREFIX%dynamic_formset_sections` ( - `id` int(11) unsigned NOT NULL auto_increment, - `formset_id` int(11) NOT NULL, - `section_id` int(11) NOT NULL, - `title` varchar(255), - `instructions` text, - -- Allow more than one form, sorted in this order - `sort` int(11) NOT NULL DEFAULT 1, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_form_section`; -CREATE TABLE `%TABLE_PREFIX%dynamic_form` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%form`; +CREATE TABLE `%TABLE_PREFIX%form` ( `id` int(11) unsigned NOT NULL auto_increment, + `type` char(1) NOT NULL DEFAULT 'G', + `deletable` tinyint(1) NOT NULL DEFAULT 1, `title` varchar(255) NOT NULL, `instructions` varchar(512), `notes` text, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_form_field`; -CREATE TABLE `%TABLE_PREFIX%dynamic_form_field` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%form_field`; +CREATE TABLE `%TABLE_PREFIX%form_field` ( `id` int(11) unsigned NOT NULL auto_increment, - `section_id` int(11) unsigned NOT NULL, + `form_id` int(11) unsigned NOT NULL, `type` varchar(255) NOT NULL DEFAULT 'text', `label` varchar(255) NOT NULL, `required` tinyint(1) NOT NULL DEFAULT 0, `private` tinyint(1) NOT NULL DEFAULT 0, + `edit_mask` tinyint(1) NOT NULL DEFAULT 1, `name` varchar(64) NOT NULL, `configuration` text, `sort` int(11) unsigned NOT NULL, @@ -59,65 +39,33 @@ CREATE TABLE `%TABLE_PREFIX%dynamic_form_field` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - --- Create a default form to mimic the previous default form of osTicket < 1.7.1 -INSERT INTO `%TABLE_PREFIX%dynamic_form_section` SET - `id` = 1, `title` = 'User Information', `created` = NOW(), - `updated` = NOW(); -INSERT INTO `%TABLE_PREFIX%dynamic_form_section` SET - `id` = 2, `title` = 'Ticket Details', `created` = NOW(), - `updated` = NOW(); - -INSERT INTO `%TABLE_PREFIX%dynamic_formset` SET - `id` = 1, `title` = 'Default', `created` = NOW(), `updated` = NOW(); - -INSERT INTO `%TABLE_PREFIX%dynamic_formset_sections` SET - `formset_id` = 1, `section_id` = 1, `sort` = 10; -INSERT INTO `%TABLE_PREFIX%dynamic_formset_sections` SET - `formset_id` = 1, `section_id` = 2, `sort` = 20; - -INSERT INTO `%TABLE_PREFIX%dynamic_form_field` SET - `section_id` = 1, `type` = 'text', `label` = 'Email Address', - `required` = 1, `configuration` = '{"size":40,"length":120,"validator":"email"}', - `name` = 'email', `sort` = 10, `created` = NOW(), `updated` = NOW(); -INSERT INTO `%TABLE_PREFIX%dynamic_form_field` SET - `section_id` = 1, `type` = 'text', `label` = 'Full Name', - `required` = 1, `configuration` = '{"size":40,"length":32}', - `name` = 'name', `sort` = 20, `created` = NOW(), `updated` = NOW(); -INSERT INTO `%TABLE_PREFIX%dynamic_form_field` SET - `section_id` = 1, `type` = 'phone', `label` = 'Phone Number', - `name` = 'phone', `sort` = 30, `created` = NOW(), `updated` = NOW(); - -INSERT INTO `%TABLE_PREFIX%dynamic_form_field` SET - `section_id` = 2, `type` = 'text', `label` = 'Subject', - `hint` = 'Issue summary', `required` = 1, - `configuration` = '{"size":40,"length":64}', - `name` = 'subject', `sort` = 10, `created` = NOW(), `updated` = NOW(); - -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_form_entry`; -CREATE TABLE `%TABLE_PREFIX%dynamic_form_entry` ( +) DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `%TABLE_PREFIX%form_entry`; +CREATE TABLE `%TABLE_PREFIX%form_entry` ( `id` int(11) unsigned NOT NULL auto_increment, - `section_id` int(11) unsigned NOT NULL, - `ticket_id` int(11) unsigned, + `form_id` int(11) unsigned NOT NULL, + `object_id` int(11) unsigned, + `object_type` char(1) NOT NULL DEFAULT 'T', `sort` int(11) unsigned NOT NULL DEFAULT 1, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), KEY `ticket_dyn_form_lookup` (`ticket_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_form_entry_values`; -CREATE TABLE `%TABLE_PREFIX%dynamic_form_entry_values` ( - -- references dynamic_form_entry.id +DROP TABLE IF EXISTS `%TABLE_PREFIX%form_entry_values`; +CREATE TABLE `%TABLE_PREFIX%form_entry_values` ( + -- references form_entry.id `entry_id` int(11) unsigned NOT NULL, `field_id` int(11) unsigned NOT NULL, `value` text, + `value_id` int(11), PRIMARY KEY (`entry_id`, `field_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_list`; -CREATE TABLE `%TABLE_PREFIX%dynamic_list` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%list`; +CREATE TABLE `%TABLE_PREFIX%list` ( `id` int(11) unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL, `name_plural` varchar(255), @@ -126,10 +74,10 @@ CREATE TABLE `%TABLE_PREFIX%dynamic_list` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -DROP TABLE IF EXISTS `%TABLE_PREFIX%dynamic_list_items`; -CREATE TABLE `%TABLE_PREFIX%dynamic_list_items` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%list_items`; +CREATE TABLE `%TABLE_PREFIX%list_items` ( `id` int(11) unsigned NOT NULL auto_increment, `list_id` int(11), `value` varchar(255) NOT NULL, @@ -137,68 +85,94 @@ CREATE TABLE `%TABLE_PREFIX%dynamic_list_items` ( `extra` varchar(255), `sort` int(11) NOT NULL DEFAULT 1, PRIMARY KEY (`id`), - KEY `dynamic_list_item_lookup` (`list_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `list_item_lookup` (`list_id`) +) DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `%TABLE_PREFIX%user`; +CREATE TABLE `%TABLE_PREFIX%user` ( + `id` int(10) unsigned NOT NULL auto_increment, + `default_email_id` int(10) NOT NULL, + `name` varchar(128) NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `%TABLE_PREFIX%user_email`; +CREATE TABLE `%TABLE_PREFIX%user_email` ( + `id` int(10) unsigned NOT NULL auto_increment, + `user_id` int(10) unsigned NOT NULL, + `address` varchar(128) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `address` (`address`) +); + +ALTER TABLE `%TABLE_PREFIX%filter_rule` + CHANGE `what` `what` varchar(32) NOT NULL; ALTER TABLE `%TABLE_PREFIX%help_topic` ADD `formset_id` int(11) unsigned NOT NULL default '0' AFTER `sla_id`; --- All help topics will link to the default formset -UPDATE `%TABLE_PREFIX%help_topic` SET `formset_id` = 1; -- Port data from the ticket table -- 1. Create form entries for each ticket -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry` ( +INSERT INTO `%TABLE_PREFIX%form_entry` ( `section_id`, `ticket_id`, `sort`, `created`, `updated`) SELECT 1, `ticket_id`, 10, `created`, `updated` FROM `%TABLE_PREFIX%ticket`; -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry` ( - `section_id`, `ticket_id`, `sort`, `created`, `updated`) - SELECT 2, `ticket_id`, 20, `created`, `updated` - FROM `%TABLE_PREFIX%ticket`; - --- 2. Copy Name, Email, and Phone from the ticket table to section #1 -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry_values` ( - `field_id`, `entry_id`, `value`) - SELECT A3.`field_id`, A2.`id`, A1.`name` - FROM `%TABLE_PREFIX%ticket` A1 - INNER JOIN `%TABLE_PREFIX%dynamic_form_entry` A2 ON (A1.`ticket_id` - = A2.`ticket_id` AND A2.`section_id` = 1), - INNER JOIN `%TABLE_PREFIX%dynamic_form_field` A3 ON (A2.`section_id` - = A3.`section_id`) - WHERE A3.`name` = 'name' AND LENGTH(A1.`name`); - -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry_values` ( +-- 2. Copy subject lines from the ticket table into section #2 +INSERT INTO `%TABLE_PREFIX%form_entry_values` ( `field_id`, `entry_id`, `value`) - SELECT A3.`field_id`, A2.`id`, A1.`email` + SELECT A3.`field_id`, A2.`id`, A1.`subject` FROM `%TABLE_PREFIX%ticket` A1 - INNER JOIN `%TABLE_PREFIX%dynamic_form_entry` A2 ON (A1.`ticket_id` - = A2.`ticket_id` AND A2.`section_id` = 1), - INNER JOIN `%TABLE_PREFIX%dynamic_form_field` A3 ON (A2.`section_id` + INNER JOIN `%TABLE_PREFIX%form_entry` A2 ON (A1.`ticket_id` + = A2.`ticket_id` AND A2.`section_id` = 2), + INNER JOIN `%TABLE_PREFIX%form_field` A3 ON (A2.`section_id` = A3.`section_id`) - WHERE A3.`name` = 'email' AND LENGTH(A1.`email`); + WHERE A3.`name` = 'subject'; -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry_values` ( +-- TODO: Move this to a client info dynamic entry +-- 3. Copy Phone from the ticket table to section #1 +INSERT INTO `%TABLE_PREFIX%form_entry_values` ( `field_id`, `entry_id`, `value`) SELECT A3.`field_id`, A2.`id`, CONCAT(A1.`phone`, 'X', A1.`phone_ext`) FROM `%TABLE_PREFIX%ticket` A1 - INNER JOIN `%TABLE_PREFIX%dynamic_form_entry` A2 ON (A1.`ticket_id` + INNER JOIN `%TABLE_PREFIX%form_entry` A2 ON (A1.`ticket_id` = A2.`ticket_id` AND A2.`section_id` = 1), - INNER JOIN `%TABLE_PREFIX%dynamic_form_field` A3 ON (A2.`section_id` + INNER JOIN `%TABLE_PREFIX%form_field` A3 ON (A2.`section_id` = A3.`section_id`) WHERE A3.`name` = 'phone' AND LENGTH(A1.`phone`); --- 3. Copy subject lines from the ticket table into section #2 -INSERT INTO `%TABLE_PREFIX%dynamic_form_entry_values` ( - `field_id`, `entry_id`, `value`) - SELECT A3.`field_id`, A2.`id`, A1.`subject` +-- 4. Create <user> accounts for everybody +-- - Start with creating email addresses for the accounts +INSERT INTO `%TABLE_PREFIX%user_email` (`address`) + SELECT DISTINCT `email` FROM `%TABLE_PREFIX%ticket`; + +-- - Then create the accounts and link the `default_email`s +INSERT INTO `%TABLE_PREFIX%user` (`first`, `default_email_id`) + SELECT MAX(`name`), A2.`id` FROM `%TABLE_PREFIX%ticket` A1 - INNER JOIN `%TABLE_PREFIX%dynamic_form_entry` A2 ON (A1.`ticket_id` - = A2.`ticket_id` AND A2.`section_id` = 2), - INNER JOIN `%TABLE_PREFIX%dynamic_form_field` A3 ON (A2.`section_id` - = A3.`section_id`) - WHERE A3.`name` = 'subject'; + INNER JOIN `%TABLE_PREFIX%user_email` A2 ON (A1.`email` = A2.`address`); + GROUP BY A2.`id` + +-- - Now link the user and user_email tables +ALTER TABLE `%TABLE_PREFIX%user` ADD KEY `def_eml_id` (`default_email_id`, `id`); +UPDATE `%TABLE_PREFIX%user_email` A1 + SET user_id = ( + SELECT A2.`id` FROM `%TABLE_PREFIX%user` A2 + WHERE `default_email_id` = A1.`id`); +ALTER TABLE `%TABLE_PREFIX%user` DROP INDEX `def_eml_id`; + +-- - Update the ticket table +ALTER TABLE `%TABLE_PREFIX%ticket` + ADD `user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `ticket_id`, + ADD `user_email_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `user_id`; + +UPDATE `%TABLE_PREFIX%ticket` A1 + JOIN `%TABLE_PREFIX%user_email` A2 ON A2.`address` = A1.`email` + SET `user_id` = A2.`user_id`, + `user_email_id` = A2.`id`; -- 4. Remove columns from ticket table ALTER TABLE `%TABLE_PREFIX%ticket` diff --git a/include/upgrader/streams/core/d51f303a-DYNAMICF.task.php b/include/upgrader/streams/core/d51f303a-DYNAMICF.task.php new file mode 100644 index 0000000000000000000000000000000000000000..98d128fbdaed3a1a4a57fe3af06cd0fbb99c591e --- /dev/null +++ b/include/upgrader/streams/core/d51f303a-DYNAMICF.task.php @@ -0,0 +1,21 @@ +<?php + +/* + * Loads the initial data for dynamic forms into the system. This is + * preferred over providing the data inside the SQL scripts + */ + +class DynamicFormLoader extends MigrationTask { + var $description = "Loading initial data for dynamic forms"; + + function run($max_time) { + $i18n = new Internationalization('en_US'); + $forms = $i18n->getTemplate('form.yaml')->getData(); + foreach ($forms as $f) + DynamicForm::create($f); + } +} + +return 'DynamicFormLoader'; + +?> diff --git a/open.php b/open.php index 1d1511c3a70d932e393a743319dc3b16a5a204c7..1370e4c9c9581c459bbc034435f0ce7840b77f48 100644 --- a/open.php +++ b/open.php @@ -30,24 +30,15 @@ if($_POST): $errors['captcha']='Invalid - try again!'; } - $interest=array('name','email','subject'); - if ($topic=Topic::lookup($vars['topicId'])) { - $form=DynamicForm::lookup($topic->ht['form_id'])->instanciate(); - # Collect name, email, and subject address for banning and such - foreach ($form->getAnswers() as $answer) { - $fname = $answer->getField()->get('name'); - if (in_array($fname, $interest)) - # XXX: Assigning to _POST not considered great PHP - # coding style - $vars[$fname] = $answer->getField()->getClean(); + $interest = array('subject'); + if ($topic = Topic::lookup($vars['topicId'])) { + if ($form = DynamicForm::lookup($topic->ht['form_id'])) { + $form = $form->instanciate(); + if (!$form->isValid()) + $errors += $form->errors(); } - if (!$form->isValid()) - $errors = array_merge($errors, $form->errors()); } - $form2 = UserForm::getStaticForm(); - if (!$form2->isValid()) - $errors += $form2->errors(); - if(!$errors && $cfg->allowOnlineAttachments() && $_FILES['attachments']) + if (!$errors && $cfg->allowOnlineAttachments() && $_FILES['attachments']) $vars['files'] = AttachmentFile::format($_FILES['attachments'], true); //Ticket::create...checks for errors.. @@ -55,9 +46,11 @@ if($_POST): $msg='Support ticket request created'; Draft::deleteForNamespace('ticket.client.'.substr(session_id(), -12)); # TODO: Save dynamic form(s) - $form->setTicketId($ticket->getId()); - $form->save(); - $ticket->loadDynamicData(); + if (isset($form)) { + $form->setTicketId($ticket->getId()); + $form->save(); + $ticket->loadDynamicData(); + } //Logged in...simply view the newly created ticket. if($thisclient && $thisclient->isValid()) { if(!$cfg->showRelatedTickets()) diff --git a/scp/css/scp.css b/scp/css/scp.css index 8f58ac452a133ffc7b8afd98616a5c81ed9dd366..070aafeeb36dc4f87fc8e40ce13c9a0c55cc7cdf 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -567,23 +567,27 @@ a.print { border:1px solid #f00; } -.form_table th { +.form_table th, div.section-break { text-align:left; border:1px solid #ccc; background:#eee; padding:0; + padding:5px; +} + +div.section-break h3 { + margin: 0; + padding: 0; } .form_table th h4 { margin:0; - padding:5px; color:#fff; background:#929292; } .form_table th em { display:block; - padding:5px; color:#000; } @@ -1197,13 +1201,13 @@ time { background:#f8f8f8; border:2px solid #2a67ac; display:none; - z-index:1200; + z-index:5; box-shadow: 0 5px 60px #001; border-radius: 5px; } .redactor_air { - z-index: 1201 !important; + z-index: 6 !important; } .dialog#advanced-search { @@ -1405,7 +1409,7 @@ ul.progress li.no small {color:red;} width: 100%; height: 100%; background: #000; - z-index: 1000; + z-index: 1; -webkit-transform: translate3d(0,0,0); } diff --git a/scp/forms.php b/scp/forms.php index 399d8b5d0266cb593ffed9dd38bd2613afbbb641..a26aa7dfc155774f69d12ee46fc0e965a121a148 100644 --- a/scp/forms.php +++ b/scp/forms.php @@ -18,20 +18,24 @@ if($_POST) { $form->save(); foreach ($form->getDynamicFields() as $field) { $id = $field->get('id'); - if ($_POST["delete-$id"] == 'on') { + if ($_POST["delete-$id"] == 'on' && $field->isDeletable()) { $field->delete(); + // Don't bother updating the field continue; } - foreach (array('sort','label','type','name') as $f) - if (isset($_POST["$f-$id"])) - $field->set($f, $_POST["$f-$id"]); + if (isset($_POST["type-$id"]) && $field->isChangeable()) + $field->set('type', $_POST["type-$id"]); + if (isset($_POST["name-$id"]) && $field->isNameEditable()) + $field->set('name', $_POST["name-$id"]); # TODO: make sure all help topics still have all required fields - $field->set('required', $_POST["required-$id"] == 'on' ? 1 : 0); - $field->set('private', $_POST["private-$id"] == 'on' ? 1 : 0); - # Core fields are forced required and public - if (in_array($field->get('name'), $required)) { - $field->set('required', 1); - $field->set('private', 0); + if (!$field->isRequirementForced()) + $field->set('required', $_POST["required-$id"] == 'on' ? 1 : 0); + if (!$field->isPrivacyForced()) + $field->set('private', $_POST["private-$id"] == 'on' ? 1 : 0); + foreach (array('sort','label') as $f) { + if (isset($_POST["$f-$id"])) { + $field->set($f, $_POST["$f-$id"]); + } } if ($field->isValid()) $field->save(); diff --git a/scp/tickets.php b/scp/tickets.php index 296bbc7b273159e7f42127ed0d874d36cc65e97b..c5ade077c4704b8c3b8a00f49785016af493e1cd 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -205,7 +205,6 @@ if($_POST && !$errors): $_REQUEST['a'] = null; //Clear edit action - going back to view. //Check to make sure the staff STILL has access post-update (e.g dept change). foreach ($forms as $f) $f->save(); - $ticket->setOwner($form2->getClean()); if(!$ticket->checkStaffAccess($thisstaff)) $ticket=null; } elseif(!$errors['err']) { @@ -460,22 +459,20 @@ if($_POST && !$errors): $ticket=null; $interest=array('name','email','subject'); if ($topic=Topic::lookup($_POST['topicId'])) { - $form = DynamicForm::lookup($topic->ht['form_id']); - $form = $form->instanciate(); - # Collect name, email, and subject address for banning and such - foreach ($form->getAnswers() as $answer) { - $fname = $answer->getField()->get('name'); - if (in_array($fname, $interest)) - # XXX: Assigning to _POST not considered great PHP - # coding style - $_POST[$fname] = $answer->getField()->getClean(); + if ($form = DynamicForm::lookup($topic->ht['form_id'])) { + $form = $form->instanciate(); + # Collect name, email, and subject address for banning and such + foreach ($form->getAnswers() as $answer) { + $fname = $answer->getField()->get('name'); + if (in_array($fname, $interest)) + # XXX: Assigning to _POST not considered great PHP + # coding style + $_POST[$fname] = $answer->getField()->getClean(); + } + if (!$form->isValid()) + $errors = array_merge($errors, $form->errors()); } - if (!$form->isValid()) - $errors = array_merge($errors, $form->errors()); } - $form2 = UserForm::getStaticForm(); - if (!$form2->isValid()) - $errors = array_merge($errors, $form2->errors()); if(!$thisstaff || !$thisstaff->canCreateTickets()) { $errors['err']='You do not have permission to create tickets. Contact admin for such access'; } else { @@ -487,9 +484,11 @@ if($_POST && !$errors): $msg='Ticket created successfully'; $_REQUEST['a']=null; # TODO: Save dynamic form(s) - $form->setTicketId($ticket->getId()); - $form->save(); - $ticket->loadDynamicData(); + if (isset($form)) { + $form->setTicketId($ticket->getId()); + $form->save(); + $ticket->loadDynamicData(); + } if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) $ticket=null; Draft::deleteForNamespace('ticket.staff%', $thisstaff->getId()); diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 8faf7ac5c8ac6144c53cb58eec674aea6564db2c..78d3a68c539b938ed128f383b9e0ca6ad1c6502c 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -173,7 +173,8 @@ INSERT INTO `%TABLE_PREFIX%config` (`namespace`, `key`, `value`) VALUES DROP TABLE IF EXISTS `%TABLE_PREFIX%form`; CREATE TABLE `%TABLE_PREFIX%form` ( `id` int(11) unsigned NOT NULL auto_increment, - `type` char(1) NOT NULL DEFAULT 'T', + `type` char(1) NOT NULL DEFAULT 'G', + `deletable` tinyint(1) NOT NULL DEFAULT 1, `title` varchar(255) NOT NULL, `instructions` varchar(512), `notes` text, @@ -190,6 +191,7 @@ CREATE TABLE `%TABLE_PREFIX%form_field` ( `label` varchar(255) NOT NULL, `required` tinyint(1) NOT NULL DEFAULT 0, `private` tinyint(1) NOT NULL DEFAULT 0, + `edit_mask` tinyint(1) NOT NULL DEFAULT 1, `name` varchar(64) NOT NULL, `configuration` text, `sort` int(11) unsigned NOT NULL, @@ -218,6 +220,7 @@ CREATE TABLE `%TABLE_PREFIX%form_entry_values` ( `entry_id` int(11) unsigned NOT NULL, `field_id` int(11) unsigned NOT NULL, `value` text, + `value_id` int(11), PRIMARY KEY (`entry_id`, `field_id`) ) DEFAULT CHARSET=utf8; @@ -349,7 +352,7 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_rule`; CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `id` int(11) unsigned NOT NULL auto_increment, `filter_id` int(10) unsigned NOT NULL default '0', - `what` enum('name','email','subject','body','header') NOT NULL, + `what` varchar(32) NOT NULL, `how` enum('equal','not_equal','contains','dn_contain','starts','ends') NOT NULL, `val` varchar(255) NOT NULL, `isactive` tinyint(1) unsigned NOT NULL DEFAULT '1', @@ -756,8 +759,7 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%user`; CREATE TABLE `%TABLE_PREFIX%user` ( `id` int(10) unsigned NOT NULL auto_increment, `default_email_id` int(10) NOT NULL, - `first` varchar(64) NOT NULL, - `last` varchar(64), + `name` varchar(128) NOT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`)