From f7384359b09be1d29107397e087167d6351f46c6 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Tue, 8 Oct 2013 23:09:30 +0000 Subject: [PATCH] Significant fixes made after final feature review --- assets/default/css/theme.css | 4 +- include/ajax.content.php | 15 +- include/ajax.forms.php | 47 +++---- include/ajax.tickets.php | 18 ++- include/ajax.users.php | 2 +- include/class.client.php | 23 +-- include/class.config.php | 16 +-- include/class.dynamic_forms.php | 96 +++++++++---- include/class.filter.php | 2 +- include/class.forms.php | 131 +++++++++++------- include/class.staff.php | 3 +- include/class.ticket.php | 39 ++++-- include/class.topic.php | 2 +- include/class.user.php | 97 +++++++++++-- include/client/open.inc.php | 40 ++---- .../client/templates/dynamic-form.tmpl.php | 18 +-- include/client/tickets.inc.php | 2 +- include/i18n/en_US/form.yaml | 35 ++++- include/i18n/en_US/help_topic.yaml | 4 +- include/staff/dynamic-form.inc.php | 81 +++++------ include/staff/dynamic-forms.inc.php | 57 +++++++- include/staff/dynamic-list.inc.php | 19 +-- include/staff/dynamic-lists.inc.php | 57 +++++++- include/staff/settings-emails.inc.php | 17 +++ include/staff/settings-system.inc.php | 30 ++-- include/staff/settings-tickets.inc.php | 7 - include/staff/templates/user-info.tmpl.php | 1 - include/staff/ticket-open.inc.php | 3 +- open.php | 11 ++ scp/css/scp.css | 6 +- scp/forms.php | 34 ++++- scp/js/scp.js | 9 +- scp/lists.php | 24 ++++ scp/tickets.php | 7 +- setup/doc/forms.md | 51 +++++++ setup/doc/orm.md | 11 ++ 36 files changed, 705 insertions(+), 314 deletions(-) create mode 100644 setup/doc/forms.md create mode 100644 setup/doc/orm.md diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index 4a1af12ac..bacbd67a0 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -532,9 +532,11 @@ body { width: 140px; float: left; } +label.required { + font-weight: bold; +} #ticketForm div label.required, #clientLogin div label.required { - font-weight: bold; text-align: left; } #ticketForm div input, diff --git a/include/ajax.content.php b/include/ajax.content.php index 197d75ffe..2451880ac 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -49,7 +49,8 @@ class ContentAjaxAPI extends AjaxController { <tr><td width="130">%{ticket.id}</td><td>Ticket ID (internal ID)</td></tr> <tr><td>%{ticket.number}</td><td>Ticket number (external ID)</td></tr> <tr><td>%{ticket.email}</td><td>Email address</td></tr> - <tr><td>%{ticket.name}</td><td>Full name</td></tr> + <tr><td>%{ticket.name}</td><td>Full name — + <em>see name expansion</em></td></tr> <tr><td>%{ticket.subject}</td><td>Subject</td></tr> <tr><td>%{ticket.phone}</td><td>Phone number | ext</td></tr> <tr><td>%{ticket.status}</td><td>Status</td></tr> @@ -80,6 +81,18 @@ class ContentAjaxAPI extends AjaxController { <tr><td>%{reset_link}</td> <td>Reset link used by the password reset feature</td></tr> </table> + <table width="100%" border="0" cellspacing=1 cellpadding=1> + <tr><td colspan="2"><b>Name Expansion</b></td></tr> + <tr><td>.first</td><td>First Name</td></tr> + <tr><td>.middle</td><td>Middle Name(s)</td></tr> + <tr><td>.last</td><td>Last Name</td></tr> + <tr><td>.full</td><td>First Last</td></tr> + <tr><td>.legal</td><td>First M. Last</td></tr> + <tr><td>.short</td><td>First L.</td></tr> + <tr><td>.formal</td><td>Mr. Last</td></tr> + <tr><td>.shortformal</td><td>F. Last</td></tr> + <tr><td>.lastfirst</td><td>Last, First</td></tr> + </table> </td> </tr> </table> diff --git a/include/ajax.forms.php b/include/ajax.forms.php index 27fac6672..a04c327a0 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -15,7 +15,8 @@ class DynamicFormsAjaxAPI extends AjaxController { function getFormsForHelpTopic($topic_id, $client=false) { $topic = Topic::lookup($topic_id); - if ($form =DynamicForm::lookup($topic->ht['form_id'])) + if ($topic->ht['form_id'] + && ($form = DynamicForm::lookup($topic->ht['form_id']))) $form->render(!$client); } @@ -36,31 +37,21 @@ class DynamicFormsAjaxAPI extends AjaxController { $field->save(); } - function _getUserForms() { - $static = new Form(array( - 'name' => new TextboxField(array( - 'label'=>'Full Name', 'required'=>true, 'configuration'=>array('size'=>40)) - ), - 'email' => new TextboxField(array( - 'label'=>'Default Email', 'required'=>true, 'configuration'=>array( - 'validator'=>'email', 'size'=>40)) - ), - )); - - return $static; - } - function getUserInfo($user_id) { $user = User::lookup($user_id); - $static = $this->_getUserForms(); $data = $user->ht; $data['email'] = $user->default_email->address; - $static->data($data); $custom = array(); foreach ($user->getDynamicData() as $cd) { $cd->addMissingFields(); + foreach ($cd->getFields() as $f) { + if ($f->get('name') == 'name') + $f->value = $user->getFullName(); + elseif ($f->get('name') == 'email') + $f->value = $user->getEmail(); + } $custom[] = $cd->getForm(); } @@ -69,11 +60,10 @@ class DynamicFormsAjaxAPI extends AjaxController { function saveUserInfo($user_id) { $user = User::lookup($user_id); - $static = $this->_getUserForms(); - $valid = $static->isValid(); $custom_data = $user->getDynamicData(); $custom = array(); + $valid = true; foreach ($custom_data as $cd) { $cd->addMissingFields(); $cf = $custom[] = $cd->getForm(); @@ -85,15 +75,20 @@ class DynamicFormsAjaxAPI extends AjaxController { return; } - $data = $static->getClean(); - $user->name = $data['name']; - $user->default_email->address = $data['email']; - $user->default_email->save(); - $user->save(); - // Save custom data - foreach ($custom_data as $cd) + foreach ($custom_data as $cd) { + foreach ($cd->getFields() as $f) { + if ($f->get('name') == 'name') { + $user->name = $f->getClean(); + $user->save(); + } + elseif ($f->get('name') == 'email') { + $user->default_email->address = $f->getClean(); + $user->default_email->save(); + } + } $cd->save(); + } } } diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index c664466fb..0f373efd6 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -49,9 +49,14 @@ class TicketsAjaxAPI extends AjaxController { .' ORDER BY ticket.created LIMIT '.$limit; if(($res=db_query($sql)) && db_num_rows($res)) { - while(list($id, $email)=db_fetch_row($res)) - $tickets[] = array('id'=>$id, 'email'=>$email, 'value'=>$id, 'info'=>"$id - $email"); + while(list($id, $email)=db_fetch_row($res)) { + $info = "$id - $email"; + $tickets[] = array('id'=>$id, 'email'=>$email, 'value'=>$id, + 'info'=>$info, 'matches'=>$_REQUEST['q']); + } } + if (!$tickets) + return self::lookupByEmail(); return $this->json_encode($tickets); } @@ -67,7 +72,11 @@ class TicketsAjaxAPI extends AjaxController { .' FROM '.TICKET_TABLE.' ticket' .' JOIN '.USER_TABLE.' user ON user.id = ticket.user_id' .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id' - .' WHERE email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' '; + .' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_id = user.id + AND entry.object_type=\'U\') + LEFT JOIN '.FORM_ANSWER_TABLE.' data ON (data.entry_id = entry.id)' + .' WHERE (email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' + OR data.value LIKE \'%'.db_input($_REQUEST['q'], false).'%\')'; $sql.=' AND ( staff_id='.db_input($thisstaff->getId()); @@ -83,7 +92,8 @@ class TicketsAjaxAPI extends AjaxController { if(($res=db_query($sql)) && db_num_rows($res)) { while(list($email, $count)=db_fetch_row($res)) - $tickets[] = array('email'=>$email, 'value'=>$email, 'info'=>"$email ($count)"); + $tickets[] = array('email'=>$email, 'value'=>$email, + 'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']); } return $this->json_encode($tickets); diff --git a/include/ajax.users.php b/include/ajax.users.php index 46e868827..a075ff421 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -31,7 +31,7 @@ class UsersAjaxAPI extends AjaxController { $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $users=array(); - $sql='SELECT DISTINCT email.address, concat_ws(" ", first, last) as name ' + $sql='SELECT DISTINCT email.address, name ' .' FROM '.USER_TABLE.' user ' .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id ' .' WHERE email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' ' diff --git a/include/class.client.php b/include/class.client.php index 3c4ce5243..3ee32c8f7 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -24,6 +24,8 @@ class Client { var $username; var $email; + var $_answers; + var $ticket_id; var $ticketID; @@ -40,15 +42,10 @@ class Client { if(!$id && !($id=$this->getId())) return false; - $sql='SELECT ticket.ticket_id, ticketID, email.address as email, phone.value as phone ' + $sql='SELECT ticket.ticket_id, ticketID, email.address as email ' .' 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 '.FORM_ENTRY_TABLE.' entry ON - (entry.object_id = ticket.ticket_id AND entry.object_type = "T")' - .' LEFT JOIN '.FORM_ANSWER_TABLE.' phone ON phone.entry_id = entry.id' - .' LEFT JOIN '.FORM_FIELD_TABLE.' field ON - (phone.field_id = field.id AND field.name="phone")' .' WHERE ticketID='.db_input($id); if($email) @@ -73,6 +70,14 @@ class Client { return($this->id); } + function loadDynamicData() { + $this->_answers = array(); + foreach (DynamicFormEntry::forClient($this->getId()) as $form) + foreach ($form->getAnswers() as $answer) + $this->_answers[$answer->getField()->get('name')] = + $answer->getValue(); + } + function reload() { return $this->load(); } @@ -98,11 +103,7 @@ class Client { } function getPhone() { - return $this->ht['phone']; - } - - function getPhoneExt() { - return $this->ht['phone_ext']; + return $this->_answers['phone']; } function getTicketID() { diff --git a/include/class.config.php b/include/class.config.php index 5d0c8160f..acd72ca30 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -146,6 +146,7 @@ class OsticketConfig extends Config { 'allow_email_attachments' => true, 'allow_online_attachments' => true, 'allow_online_attachments_onlogin' => false, + 'name_format' => 'full', # First Last ); function OsticketConfig($section=null) { @@ -326,6 +327,10 @@ class OsticketConfig extends Config { return $this->get('autolock_minutes'); } + function getDefaultNameFormat() { + return $this->get('name_format'); + } + function getDefaultDeptId() { return $this->get('default_dept_id'); } @@ -519,11 +524,6 @@ class OsticketConfig extends Config { return ($this->get('enable_mail_polling')); } - function allowPriorityChange() { - return ($this->get('allow_priority_change')); - } - - function useEmailPriority() { return ($this->get('use_email_priority')); } @@ -768,7 +768,6 @@ class OsticketConfig extends Config { $f['helpdesk_url']=array('type'=>'string', 'required'=>1, 'error'=>'Helpdesk URl required'); $f['helpdesk_title']=array('type'=>'string', 'required'=>1, 'error'=>'Helpdesk title required'); $f['default_dept_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Dept. required'); - $f['default_template_id']=array('type'=>'int', 'required'=>1, 'error'=>'You must select template.'); $f['staff_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes'); $f['client_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes'); //Date & Time Options @@ -789,10 +788,10 @@ class OsticketConfig extends Config { 'helpdesk_title'=>$vars['helpdesk_title'], 'helpdesk_url'=>$vars['helpdesk_url'], 'default_dept_id'=>$vars['default_dept_id'], - 'default_template_id'=>$vars['default_template_id'], 'max_page_size'=>$vars['max_page_size'], 'log_level'=>$vars['log_level'], 'log_graceperiod'=>$vars['log_graceperiod'], + 'name_format'=>$vars['name_format'], 'passwd_reset_period'=>$vars['passwd_reset_period'], 'staff_max_logins'=>$vars['staff_max_logins'], 'staff_login_timeout'=>$vars['staff_login_timeout'], @@ -861,7 +860,6 @@ class OsticketConfig extends Config { 'default_sla_id'=>$vars['default_sla_id'], 'max_open_tickets'=>$vars['max_open_tickets'], 'autolock_minutes'=>$vars['autolock_minutes'], - 'allow_priority_change'=>isset($vars['allow_priority_change'])?1:0, 'use_email_priority'=>isset($vars['use_email_priority'])?1:0, 'enable_captcha'=>isset($vars['enable_captcha'])?1:0, 'log_ticket_activity'=>isset($vars['log_ticket_activity'])?1:0, @@ -889,6 +887,7 @@ class OsticketConfig extends Config { function updateEmailsSettings($vars, &$errors) { $f=array(); + $f['default_template_id']=array('type'=>'int', 'required'=>1, 'error'=>'You must select template.'); $f['default_email_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default email required'); $f['alert_email_id']=array('type'=>'int', 'required'=>1, 'error'=>'Selection required'); $f['admin_email']=array('type'=>'email', 'required'=>1, 'error'=>'System admin email required'); @@ -903,6 +902,7 @@ class OsticketConfig extends Config { return false; return $this->updateAll(array( + 'default_template_id'=>$vars['default_template_id'], 'default_email_id'=>$vars['default_email_id'], 'alert_email_id'=>$vars['alert_email_id'], 'default_smtp_id'=>$vars['default_smtp_id'], diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 7f034a05e..a72a568a2 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -61,8 +61,10 @@ class DynamicForm extends VerySimpleModel { // Multiple inheritance -- delegate to Form function __call($what, $args) { - return call_user_func_array( - array($this->getForm(), $what), $args); + $delegate = array($this->getForm(), $what); + if (!is_callable($delegate)) + throw new Exception($what.': Call to non-existing function'); + return call_user_func_array($delegate, $args); } function hasField($name) { @@ -77,11 +79,16 @@ class DynamicForm extends VerySimpleModel { function getForm() { if (!$this->_form) { $fields = $this->getFields(); - $this->_form = new Form($fields, $this->title, $this->instructions); + $this->_form = new Form($fields, false, array( + 'title'=>$this->title, 'instructions'=>$this->instructions)); } return $this->_form; } + function isDeletable() { + return $this->get('deletable'); + } + function instanciate($sort=1) { return DynamicFormEntry::create(array( 'form_id'=>$this->get('id'), 'sort'=>$sort)); @@ -93,10 +100,17 @@ class DynamicForm extends VerySimpleModel { } } - function save() { + function save($refetch=false) { if (count($this->dirty)) $this->set('updated', new SqlFunction('NOW')); - return parent::save(); + return parent::save($refetch); + } + + function delete() { + if (!$this->isDeletable()) + return false; + else + return parent::delete(); } static function create($ht=false) { @@ -122,6 +136,22 @@ class UserForm extends DynamicForm { return $os->filter(array('type'=>'U')); } + function getFields() { + $fields = parent::getFields(); + foreach ($fields as $f) { + if ($f->get('name') == 'email') { + $f->getConfiguration(); + $f->_config['classes'] = 'auto email typeahead'; + $f->_config['autocomplete'] = false; + } + elseif ($f->get('name') == 'name') { + $f->getConfiguration(); + $f->_config['classes'] = 'auto name'; + } + } + return $fields; + } + static function getInstance() { if (!isset(static::$instance)) { $o = static::objects(); @@ -129,24 +159,6 @@ class UserForm extends DynamicForm { } return static::$instance; } - - function getStaticForm() { - static $form = null; - if (!$form) - $form = new Form(array( - 'email' => new TextboxField(array( - 'id'=>'email', 'label'=>'Email Address', 'required'=>true, - 'validator' => 'email', 'configuration'=>array( - 'autocomplete'=>false, 'classes'=>'typeahead', - 'size'=>40) - )), - 'name' => new TextboxField(array( - 'id'=>'name', 'label'=>'Full Name', 'required'=>true, - 'configuration' => array('size'=>40), - )), - ), 'User Information'); - return $form; - } } class TicketForm extends DynamicForm { @@ -166,7 +178,7 @@ class TicketForm extends DynamicForm { } } // Add fields from the standard ticket form to the ticket filterable fields -Filter::addSupportedMatches('Dynamic Fields', function() { +Filter::addSupportedMatches('Custom Fields', function() { $matches = array(); foreach (TicketForm::getInstance()->getFields() as $f) { if (!$f->hasData()) @@ -240,7 +252,7 @@ class DynamicFormField extends VerySimpleModel { } function isDeletable() { - return $this->get('edit_mask') & 1; + return ($this->get('edit_mask') & 1) == 0; } function isNameForced() { return $this->get('edit_mask') & 2; @@ -376,7 +388,8 @@ class DynamicFormEntry extends VerySimpleModel { if (!$this->_clean) { $this->_clean = array(); foreach ($this->getFields() as $field) - $this->_clean[$field->get('id')] = $field->getClean(); + $this->_clean[$field->get('id')] + = $this->_clean[$field->get('name')] = $field->getClean(); } return $this->_clean; } @@ -432,10 +445,18 @@ class DynamicFormEntry extends VerySimpleModel { $this->_values[] = $a; $this->_fields[] = $field; // Omit fields without data - if ($field->hasData()) + // For user entries, the name and email fields should not be + // saved with the rest of the data + if (!($this->object_type == 'U' + && in_array($field->get('name'), array('name','email'))) + && $field->hasData()) $a->save(); - unset($this->_form); + $this->_form = null; } + // Sort the form the way it is declared to be sorted + usort($this->_fields, function($a, $b) { + return $a->get('sort') - $b->get('sort'); + }); } } @@ -445,6 +466,9 @@ class DynamicFormEntry extends VerySimpleModel { parent::save(); foreach ($this->getAnswers() as $a) { $field = $a->getField(); + if ($this->object_type == 'U' + && in_array($field->get('name'), array('name','email'))) + continue; $val = $field->to_database($field->getClean()); if (is_array($val)) { $a->set('value', $val[0]); @@ -460,6 +484,12 @@ class DynamicFormEntry extends VerySimpleModel { $this->_values = array(); } + function delete() { + foreach ($this->getAnswers() as $a) + $a->delete(); + return parent::delete(); + } + static function create($ht=false) { $inst = parent::create($ht); $inst->set('created', new SqlFunction('NOW')); @@ -598,6 +628,16 @@ class DynamicList extends VerySimpleModel { return parent::save($refetch); } + function delete() { + $fields = DynamicFormField::objects()->filter(array( + 'type'=>'list-'.$this->id))->count(); + if ($fields == 0) + return parent::delete(); + else + // Refuse to delete lists that are in use by fields + return false; + } + static function create($ht=false) { $inst = parent::create($ht); $inst->set('created', new SqlFunction('NOW')); diff --git a/include/class.filter.php b/include/class.filter.php index 2ee4fbbf2..81c6a39fe 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -176,7 +176,7 @@ class Filter { function addRule($what, $how, $val,$extra=array()) { - $rule= array_merge($extra,array('w'=>$what, 'h'=>$how, 'v'=>$val)); + $rule= array_merge($extra,array('what'=>$what, 'how'=>$how, 'val'=>$val)); $rule['filter_id']=$this->getId(); return FilterRule::create($rule,$errors); # nolint diff --git a/include/class.forms.php b/include/class.forms.php index da4ed00a9..963abcb64 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -20,18 +20,25 @@ */ class Form { var $fields = array(); - var $title = ''; + var $title = 'Unnamed'; var $instructions = ''; var $_errors; + var $_source = false; function Form() { call_user_func_array(array($this, '__construct'), func_get_args()); } - function __construct($fields=array(), $title=false, $instructions=false) { + function __construct($fields=array(), $source=null, $options=array()) { $this->fields = $fields; - $this->title = $title; - $this->instructions = $instructions; + foreach ($fields as $f) + $f->setForm($this); + if (isset($options['title'])) + $this->title = $options['title']; + if (isset($options['instructions'])) + $this->instructions = $options['instructions']; + // Use POST data if source was not specified + $this->_source = ($source) ? $source : $_POST; } function data($source) { foreach ($this->fields as $name=>$f) @@ -44,6 +51,7 @@ class Form { } function getTitle() { return $this->title; } function getInstructions() { return $this->instructions; } + function getSource() { return $this->_source; } function isValid() { if (!is_array($this->_errors)) { @@ -59,8 +67,10 @@ class Form { function getClean() { if (!$this->_clean) { $this->_clean = array(); - foreach ($this->getFields() as $key=>$field) - $this->_clean[$key] = $field->getClean(); + foreach ($this->getFields() as $key=>$field) { + $this->_clean[$key] = $this->_clean[$field->get('name')] + = $field->getClean(); + } } return $this->_clean; } @@ -85,6 +95,8 @@ class Form { require_once(INCLUDE_DIR . "class.json.php"); class FormField { + static $widget = false; + var $ht = array( 'label' => 'Unlabeled', 'required' => false, @@ -92,10 +104,13 @@ class FormField { 'configuration' => array(), ); + var $_form; var $_cform; var $_clean; var $_errors = array(); + var $_widget; var $parent; + var $presentation_only = false; static $types = array( 'Basic Fields' => array( @@ -111,9 +126,6 @@ class FormField { ); static $more_types = array(); - function FormField() { - call_user_func_array(array($this, '__construct'), func_get_args()); - } function __construct($options=array()) { static $uid = 100; $this->ht = array_merge($this->ht, $options); @@ -271,11 +283,14 @@ class FormField { $clazz = $type[1]; $inst = new $clazz($this->ht); $inst->parent = $parent; + $inst->setForm($this->_form); return $inst; } function __call($what, $args) { // XXX: Throw exception if $this->parent is not set + if (!$this->parent) + throw new Exception($what.': Call to undefined function'); // BEWARE: DynamicFormField has a __call() which will create a new // FormField instance and invoke __call() on it or bounce // immediately back @@ -292,6 +307,26 @@ class FormField { return $this->get('id'); } + function setForm($form) { + $this->_form = $form; + } + function getForm() { + return $this->_form; + } + /** + * Returns the data source for this field. If created from a form, the + * data source from the form is returned. Otherwise, if the request is a + * POST, then _POST is returned. + */ + function getSource() { + if ($this->_form) + return $this->_form->getSource(); + elseif ($_SERVER['REQUEST_METHOD'] == 'POST') + return $_POST; + else + return array(); + } + function render($mode=null) { $this->getWidget()->render($mode); } @@ -363,7 +398,7 @@ class FormField { * some static processing will store the data elsewhere. */ function isPresentationOnly() { - return false; + return $this->presentation_only; } function getConfigurationForm() { @@ -376,12 +411,17 @@ class FormField { return $this->_cform; } + function getWidget() { + if (!static::$widget) + throw new Exception('Widget not defined for this field'); + if (!isset($this->_widget)) + $this->_widget = new static::$widget($this); + return $this->_widget;; + } } class TextboxField extends FormField { - function getWidget() { - return new TextboxWidget($this); - } + static $widget = 'TextboxWidget'; function getConfigurationOptions() { return array( @@ -430,9 +470,8 @@ class TextboxField extends FormField { } class TextareaField extends FormField { - function getWidget() { - return new TextareaWidget($this); - } + static $widget = 'TextareaWidget'; + function getConfigurationOptions() { return array( 'cols' => new TextboxField(array( @@ -446,6 +485,8 @@ class TextareaField extends FormField { } class PhoneField extends FormField { + static $widget = 'PhoneNumberWidget'; + function validateEntry($value) { parent::validateEntry($value); # Run validator against $this->value for email type @@ -459,9 +500,6 @@ class PhoneField extends FormField { $this->_errors[] = "Enter a phone number for the extension"; } } - function getWidget() { - return new PhoneNumberWidget($this); - } function toString($value) { list($phone, $ext) = explode("X", $value, 2); @@ -473,9 +511,7 @@ class PhoneField extends FormField { } class BooleanField extends FormField { - function getWidget() { - return new CheckboxWidget($this); - } + static $widget = 'CheckboxWidget'; function getConfigurationOptions() { return array( @@ -500,9 +536,7 @@ class BooleanField extends FormField { } class ChoiceField extends FormField { - function getWidget() { - return new ChoicesWidget($this); - } + static $widget = 'ChoicesWidget'; function getConfigurationOptions() { return array( @@ -541,9 +575,7 @@ class ChoiceField extends FormField { } class DatetimeField extends FormField { - function getWidget() { - return new DatetimePickerWidget($this); - } + static $widget = 'DatetimePickerWidget'; function to_database($value) { // Store time in gmt time, unix epoch format @@ -617,9 +649,7 @@ class DatetimeField extends FormField { * a field to provide a horizontal section break in the display of a form */ class SectionBreakField extends FormField { - function getWidget() { - return new SectionBreakWidget($this); - } + static $widget = 'SectionBreakWidget'; function hasData() { return false; @@ -631,9 +661,8 @@ class SectionBreakField extends FormField { } class ThreadEntryField extends FormField { - function getWidget() { - return new ThreadEntryWidget($this); - } + static $widget = 'ThreadEntryWidget'; + function isChangeable() { return false; } @@ -700,24 +729,22 @@ FormField::addFieldTypes('Built-in Lists', function() { }); class Widget { - function Widget() { - # Not called in PHP5 - call_user_func_array(array(&$this, '__construct'), func_get_args()); - } function __construct($field) { $this->field = $field; $this->name = $field->getFormName(); - if ($_SERVER['REQUEST_METHOD'] == 'POST') - $this->value = $this->getValue(); - elseif (is_object($field->getAnswer())) + $this->value = $this->getValue(); + if (!isset($this->value) && is_object($field->getAnswer())) $this->value = $field->getAnswer()->getValue(); - if (!$this->value && $field->value) + if (!isset($this->value) && $field->value) $this->value = $field->value; } function getValue() { - return $_POST[$this->name]; + $data = $this->field->getSource(); + if (!isset($data[$this->name])) + return null; + return $data[$this->name]; } } @@ -775,9 +802,13 @@ class PhoneNumberWidget extends Widget { } function getValue() { - $ext = $_POST["{$this->name}-ext"]; + $data = $this->field->getSource(); + $base = parent::getValue(); + if ($base === null) + return $base; + $ext = $data["{$this->name}-ext"]; if ($ext) $ext = 'X'.$ext; - return parent::getValue() . $ext; + return $base . $ext; } } @@ -832,8 +863,9 @@ class CheckboxWidget extends Widget { } function getValue() { - if (count($_POST)) - return @in_array($this->field->get('id'), $_POST[$this->name]); + $data = $this->field->getSource(); + if (count($data)) + return @in_array($this->field->get('id'), $data[$this->name]); return parent::getValue(); } } @@ -886,9 +918,10 @@ class DatetimePickerWidget extends Widget { * time value into a single date and time string value. */ function getValue() { + $data = $this->field->getSource(); $datetime = parent::getValue(); - if ($datetime && isset($_POST[$this->name . ':time'])) - $datetime .= ' ' . $_POST[$this->name . ':time']; + if ($datetime && isset($data[$this->name . ':time'])) + $datetime .= ' ' . $data[$this->name . ':time']; return $datetime; } } diff --git a/include/class.staff.php b/include/class.staff.php index a1fe8e7c8..93708bc0c 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -19,6 +19,7 @@ include_once(INCLUDE_DIR.'class.error.php'); include_once(INCLUDE_DIR.'class.team.php'); include_once(INCLUDE_DIR.'class.group.php'); include_once(INCLUDE_DIR.'class.passwd.php'); +include_once(INCLUDE_DIR.'class.user.php'); class Staff { @@ -165,7 +166,7 @@ class Staff { } function getName() { - return ucfirst($this->ht['firstname'].' '.$this->ht['lastname']); + return new PersonsName($this->ht['firstname'].' '.$this->ht['lastname']); } function getFirstName() { diff --git a/include/class.ticket.php b/include/class.ticket.php index 9bf716e39..6edb55e8b 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -84,6 +84,7 @@ class Ticket { $this->id = $this->ht['ticket_id']; $this->number = $this->ht['ticketID']; + $this->_answers = array(); $this->loadDynamicData(); @@ -105,11 +106,12 @@ class Ticket { } function loadDynamicData() { - $this->_answers = array(); - foreach (DynamicFormEntry::forTicket($this->getId()) as $form) - foreach ($form->getAnswers() as $answer) - $this->_answers[$answer->getField()->get('name')] = - $answer->getValue(); + if (!$this->_answers) { + foreach (DynamicFormEntry::forTicket($this->getId()) as $form) + foreach ($form->getAnswers() as $answer) + $this->_answers[$answer->getField()->get('name')] = + $answer->getValue(); + } } function reload() { @@ -1642,6 +1644,9 @@ class Ticket { //delete just orphaned ticket thread & associated attachments. $this->getThread()->delete(); + foreach (DynamicFormEntry::forTicket($this->getId()) as $form) + $form->delete(); + return true; } @@ -1852,10 +1857,12 @@ class Ticket { function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { global $ost, $cfg, $thisclient, $_FILES; - // Drop extra whitespace - foreach (array('phone', 'subject') as $f) - if (isset($vars[$f])) - $vars[$f] = trim($vars[$f]); + // Identify the user creating the ticket and unpack user information + // fields into local scope for filtering and banning purposes + $user_form = UserForm::getInstance(); + $user_info = $user_form->getClean(); + if ($user_form->isValid()) + $vars += $user_info; //Check for 403 if ($vars['email'] && Validator::is_email($vars['email'])) { @@ -1949,19 +1956,21 @@ class Ticket { $errors['duedate']='Due date must be in the future'; } - # 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'])) + if ((isset($vars['emailId']) && $vars['emailId']) + || !isset($user_info['email']) || !$user_info['email']) { $user_info = $vars; - elseif (!UserForm::getStaticForm()->isValid()) + } + elseif (!$user_form->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; + $user = User::fromForm($user_info); + $user_email = UserEmail::ensure($user_info['email']); + # Perform ticket filter actions on the new ticket arguments if ($ticket_filter) $ticket_filter->apply($vars); diff --git a/include/class.topic.php b/include/class.topic.php index 8f1eb7365..688cb7aaa 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -227,7 +227,7 @@ class Topic { .',page_id='.db_input($vars['page_id']) .',isactive='.db_input($vars['isactive']) .',ispublic='.db_input($vars['ispublic']) - .',noautoresp='.db_input(isset($vars['noautoresp'])?1:0) + .',noautoresp='.db_input(isset($vars['noautoresp']) && $vars['noautoresp']?1:0) .',notes='.db_input(Format::sanitize($vars['notes'])); //Auto assign ID is overloaded... diff --git a/include/class.user.php b/include/class.user.php index 8a01c159c..b38ec2528 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -79,10 +79,17 @@ class User extends UserModel { if (!$user) { $user = User::create(array('name'=>$data['name'], 'default_email'=> - UserEmail::create(array('address'=>$data['email'])) + UserEmail::create(array('address'=>$data['email'])) )); $user->save(); $user->emails->add($user->default_email); + + // Attach initial custom fields + $uf = UserForm::getInstance(); + foreach ($uf->getFields() as $f) + if (isset($data[$f->get('name')])) + $uf->setAnswer($f->get('name'), $data[$f->get('name')]); + $uf->save(); } return $user; @@ -104,11 +111,9 @@ class User extends UserModel { $data = DynamicFormEntry::forClient($this->id); if (!$data[0]) { $data = array(); - foreach (UserForm::objects() as $f) { - $g = $f->instanciate(); - $g->setClientId($this->id); - $data[] = $g; - } + $g = UserForm::getInstance(); + $g->setClientId($this->id); + $data[] = $g; } return $data; } @@ -136,6 +141,18 @@ class PersonsName { var $parts; var $name; + static $formats = array( + 'first' => array("First", 'getFirst'), + 'last' => array("Last", 'getLast'), + 'full' => array("First Last", 'getFull'), + 'legal' => array("First M. Last", 'getLegal'), + 'lastfirst' => array("Last, First", 'getLastFirst'), + 'formal' => array("Mr. Last", 'getFormal'), + 'short' => array("First L.", 'getShort'), + 'shortformal' => array("F. Last", 'getShortFormal'), + 'complete' => array("Mr. First M. Last Sr.", 'getComplete'), + ); + function __construct($name) { $this->parts = static::splitName($name); $this->name = $name; @@ -149,6 +166,14 @@ class PersonsName { return $this->parts['last']; } + function getMiddle() { + return $this->parts['middle']; + } + + function getMiddleInitial() { + return mb_substr($this->parts['middle'],0,1).'.'; + } + function getFormal() { return trim($this->parts['salutation'].' '.$this->parts['last']); } @@ -157,9 +182,26 @@ class PersonsName { return trim($this->parts['first'].' '.$this->parts['last']); } + function getLegal() { + $parts = array( + $this->parts['first'], + mb_substr($this->parts['middle'],0,1), + $this->parts['last'], + ); + if ($parts[1]) $parts[1] .= '.'; + return implode(' ', array_filter($parts)); + } + function getComplete() { - return trim($this->parts['salutation'].' '.$this->parts['first'] - .' '.$this->parts['last'].' '.$this->parts['suffix']); + $parts = array( + $this->parts['salutation'], + $this->parts['first'], + mb_substr($this->parts['middle'],0,1), + $this->parts['last'], + $this->parts['suffix'] + ); + if ($parts[2]) $parts[2] .= '.'; + return implode(' ', array_filter($parts)); } function getLastFirst() { @@ -169,8 +211,32 @@ class PersonsName { return $name; } + function getShort() { + return $this->parts['first'].' '.mb_substr($this->parts['last'],0,1).'.'; + } + + function getShortFormal() { + return mb_substr($this->parts['first'],0,1).'. '.$this->parts['last']; + } + + function getOriginal() { + return $this->name; + } + + function asVar() { + return $this->__toString(); + } + function __toString() { - return $this->getLastFirst(); + global $cfg; + $format = $cfg->getDefaultNameFormat(); + list(,$func) = static::$formats[$format]; + if (!$func) $func = 'getFull'; + return call_user_func(array($this, $func)); + } + + static function allFormats() { + return static::$formats; } /** @@ -208,12 +274,19 @@ class PersonsName { $start = ($results['salutation']) ? 2 : 1; $end = ($results['suffix']) ? $size - 2 : $size - 1; - $last = ''; + $middle = array(); for ($i = $start; $i <= $end; $i++) { - $last .= ' '.$r[$i]; + $middle[] = $r[$i]; + } + if (count($middle) > 1) { + $results['last'] = array_pop($middle); + $results['middle'] = implode(' ', $middle); + } + else { + $results['last'] = $middle[0]; + $results['middle'] = ''; } - $results['last'] = trim($last); return $results; } diff --git a/include/client/open.inc.php b/include/client/open.inc.php index 7bb1d9599..b9ba42e24 100644 --- a/include/client/open.inc.php +++ b/include/client/open.inc.php @@ -4,8 +4,7 @@ $info=array(); if($thisclient && $thisclient->isValid()) { $info=array('name'=>$thisclient->getName(), 'email'=>$thisclient->getEmail(), - 'phone'=>$thisclient->getPhone(), - 'phone_ext'=>$thisclient->getPhoneExt()); + 'phone'=>$thisclient->getPhone()); } $info=($_POST && $errors)?Format::htmlchars($_POST):$info; @@ -40,40 +39,23 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; </td> </tr> <?php - UserForm::getStaticForm()->render(false, 'Your Information'); + if (!$thisclient) { + UserForm::getInstance()->render(false, 'Your Information'); + } + else { ?> + <tr><td colspan="2"><hr /></td></tr> + <tr><td>Email:</td><td><?php echo $thisclient->getEmail(); ?></td></tr> + <tr><td>Client:</td><td><?php echo $thisclient->getName(); ?></td></tr> + <?php } TicketForm::getInstance()->render(false); ?> </tbody> <tbody id="dynamic-form"> - <?php if ($forms) { - foreach ($forms as $form) { - include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php'); - } + <?php if ($form) { + include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php'); } ?> </tbody> <tbody> <?php - if($cfg->allowPriorityChange() && ($priorities=Priority::getPriorities())) { ?> - <tr> - <td>Ticket Priority:</td> - <td> - <select id="priority" name="priorityId"> - <?php - if(!$info['priorityId']) - $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default. - 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> - <?php - } - ?> - <?php if($cfg && $cfg->isCaptchaEnabled() && (!$thisclient || !$thisclient->isValid())) { if($_POST && $errors && !$errors['captcha']) $errors['captcha']='Please re-enter the text again'; diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php index 1e4e79e8c..5eac1d38a 100644 --- a/include/client/templates/dynamic-form.tmpl.php +++ b/include/client/templates/dynamic-form.tmpl.php @@ -14,19 +14,6 @@ // 'private' are not included in the output for clients global $thisclient; foreach ($form->getFields() as $field) { - if ($thisclient) { - switch ($field->get('name')) { - case 'name': - $field->value = $thisclient->getName(); - break; - case 'email': - $field->value = $thisclient->getEmail(); - break; - case 'phone': - $field->value = $thisclient->getPhone(); - break; - } - } if ($field->get('private')) continue; ?> @@ -36,8 +23,9 @@ <?php } else { ?> - <td class="<?php if ($field->get('required')) echo 'required'; ?>"> - <?php echo Format::htmlchars($field->get('label')); ?>:</td><td> + <td><label for="<?php echo $field->getFormname(); ?>" class="<?php + if ($field->get('required')) echo 'required'; ?>"> + <?php echo Format::htmlchars($field->get('label')); ?>:</label></td><td> <?php } $field->render('client'); ?> diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index d38d113ce..7c2c6e564 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -69,7 +69,7 @@ if($search) { } else {//Deep search! $queryterm=db_real_escape($_REQUEST['q'],false); //escape the term ONLY...no quotes. $qwhere.=' AND ( ' - ." ticket.subject LIKE '%$queryterm%'" + ." subject.value LIKE '%$queryterm%'" ." OR thread.body LIKE '%$queryterm%'" .' ) '; $deep_search=true; diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml index 745948812..69b3ae30b 100644 --- a/include/i18n/en_US/form.yaml +++ b/include/i18n/en_US/form.yaml @@ -28,28 +28,50 @@ --- - id: 1 type: U # notrans - title: Client Details + title: Contact Information deletable: false fields: + - type: text # notrans + name: email # notrans + label: Email Address + required: true + sort: 1 + edit_mask: 15 + configuration: + size: 40 + length: 64 + validator: email # notrans + + - type: text # notrans + name: name # notrans + label: Full Name + required: true + sort: 2 + edit_mask: 15 + configuration: + size: 40 + length: 64 + - type: phone # notrans name: phone # notrans label: Phone Number required: false - sort: 1 + sort: 3 - type: memo # notrans name: notes label: Internal Notes required: false private: true - sort: 2 + sort: 4 configuration: rows: 4 cols: 40 - id: 2 type: T # notrans - title: Standard Ticket Form + title: Ticket Details + instructions: Please Describe Your Issue 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 @@ -58,8 +80,7 @@ fields: - type: text # notrans name: subject # notrans - label: Subject - hint: Issue Summary + label: Issue Summary required: true edit_mask: 15 sort: 1 @@ -69,7 +90,7 @@ - type: thread # notrans name: message # notrans - label: Issue + label: Issue Details hint: Details on the reason(s) for opening the ticket. required: true edit_mask: 15 diff --git a/include/i18n/en_US/help_topic.yaml b/include/i18n/en_US/help_topic.yaml index 8ee30e318..fe5294319 100644 --- a/include/i18n/en_US/help_topic.yaml +++ b/include/i18n/en_US/help_topic.yaml @@ -23,7 +23,7 @@ dept_id: 1 sla_id: 1 priority_id: 2 - form_id: 1 + form_id: 0 topic: Support notes: | Tickets that primarily concern the support deparment @@ -33,7 +33,7 @@ dept_id: 1 sla_id: 0 priority_id: 2 - form_id: 1 + form_id: 0 topic: Billing notes: | Tickets that primarily concern the sales and billing deparments diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php index bdb38041c..4e48664ab 100644 --- a/include/staff/dynamic-form.inc.php +++ b/include/staff/dynamic-form.inc.php @@ -20,13 +20,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> - <h2>Dynamic Form</h2> + <h2>Custom Form</h2> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em>Dynamic forms are used to allow custom data to be + <em>Custom forms are used to allow custom data to be associated with tickets</em> </th> </tr> @@ -57,22 +57,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <th></th> <th>Label</th> <th>Type</th> - <th>Name</th> - <th>Private</th> + <th>Internal</th> <th>Required</th> + <th>Name</th> + <th>Delete</th> </tr> </thead> <tbody> - <tr> - <td><input type="checkbox" disabled="disabled"/></td> - <td>Full Name</td><td>Short Answer</td><td>name</td> - <td><input type="checkbox" disabled="disabled"/></td> - <td><input type="checkbox" disabled="disabled" checked="checked"/></td></tr> - <tr> - <td><input type="checkbox" disabled="disabled"/></td> - <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(); @@ -80,13 +71,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); if ($f->get('private')) continue; ?> <tr> - <td><input type="checkbox" disabled="disabled"/></td> + <td></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" - <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td></tr> + <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td> + <td><?php echo $f->get('name'); ?></td> + <td><input type="checkbox" disabled="disabled"/></td></tr> <?php } ?> </tbody> @@ -98,31 +90,25 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </th> </tr> <tr> - <th>Delete</th> + <th>Sort</th> <th>Label</th> <th>Type</th> - <th>Name</th> - <th>Private</th> + <th>Internal</th> <th>Required</th> + <th>Name</th> + <th>Delete</th> </tr> </thead> <tbody class="sortable-rows" data-sort="sort-"> <?php if ($form) foreach ($form->getFields() as $f) { $id = $f->get('id'); - $deletable = $f->isDeletable() ? 'disabled="disabled"' : ''; + $deletable = !$f->isDeletable() ? 'disabled="disabled"' : ''; $force_name = $f->isNameForced() ? 'disabled="disabled"' : ''; $force_privacy = $f->isPrivacyForced() ? 'disabled="disabled"' : ''; $force_required = $f->isRequirementForced() ? 'disabled="disabled"' : ''; $errors = $f->errors(); ?> <tr> - <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 - if ($errors['sort']) echo '<br/>'; echo $errors['sort']; - ?></font> - </td> + <td><i class="icon-sort"></i></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 @@ -140,7 +126,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php } ?> </select> <?php if ($f->isConfigurable()) { ?> - <a class="action-button" style="float:none" + <a class="action-button" style="float:none;overflow:inherit" href="ajax.php/form/field-config/<?php echo $f->get('id'); ?>" onclick="javascript: @@ -150,6 +136,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); return false; "><i class="icon-edit"></i> Config</a> <?php } ?></td> + <td><input type="checkbox" name="private-<?php echo $id; ?>" + <?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"'; ?> + <?php echo $force_required ?>/> + </td> <td> <input type="text" size="20" name="name-<?php echo $id; ?>" value="<?php echo $f->get('name'); ?>" <?php echo $force_name ?>/> @@ -157,41 +150,35 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); 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"'; ?> - <?php echo $force_privacy ?>/></td> - <td><input type="checkbox" name="required-<?php echo $id; ?>" - <?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> + <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'); ?>"/> + </td> </tr> <?php } for ($i=0; $i<$newcount; $i++) { ?> - <td><em>add</em> + <td><em>+</em> <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 $group=>$types) { ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php - foreach ($types as $type=>$nfo) { ?> + foreach ($types as $type=>$nfo) { + if (isset($nfo[2]) && !$nfo[2]) continue; ?> <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; ?>" <?php if ($form && $form->get('type') == 'U') echo 'checked="checked"'; ?>/></td> <td><input type="checkbox" name="required-new-<?php echo $i; ?>"/></td> + <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"/></td> + <td></td> </tr> <?php } ?> </tbody> @@ -208,7 +195,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> </tbody> </table> -<p style="padding-left:225px;"> +<p class="centered"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> <input type="reset" name="reset" value="Reset"> <input type="button" name="cancel" value="Cancel" onclick='window.location.href="?"'> diff --git a/include/staff/dynamic-forms.inc.php b/include/staff/dynamic-forms.inc.php index 7c590d435..0b4a8e154 100644 --- a/include/staff/dynamic-forms.inc.php +++ b/include/staff/dynamic-forms.inc.php @@ -1,8 +1,8 @@ <div style="width:700;padding-top:5px; float:left;"> - <h2>Dynamic Forms</h2> + <h2>Custom Forms</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> - <b><a href="forms.php?a=add" class="Icon">Add New Dynamic Form</a></b></div> + <b><a href="forms.php?a=add" class="Icon">Add New Custom Form</a></b></div> <div class="clear"></div> <?php @@ -13,6 +13,10 @@ $pageNav->setURL('forms.php'); $showing=$pageNav->showing().' forms'; ?> +<form action="forms.php" method="POST" name="forms"> +<?php csrf_token(); ?> +<input type="hidden" name="do" value="mass_process" > +<input type="hidden" id="action" name="a" value="" > <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> <thead> <tr> @@ -63,17 +67,62 @@ $showing=$pageNav->showing().' forms'; <?php foreach (DynamicForm::objects()->filter(array('type'=>'G')) ->order_by('title') ->limit($pageNav->getLimit()) - ->offset($pageNav->getStart()) as $form) { ?> + ->offset($pageNav->getStart()) as $form) { + $sel=false; + if($ids && in_array($form->get('id'),$ids)) + $sel=true; ?> <tr> - <td/> + <td><?php if ($form->isDeletable()) { ?> + <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $form->get('id'); ?>" + <?php echo $sel?'checked="checked"':''; ?>> + <?php } ?></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> + <tfoot> + <tr> + <td colspan="3"> + <?php if($count){ ?> + Select: + <a id="selectAll" href="#ckb">All</a> + <a id="selectNone" href="#ckb">None</a> + <a id="selectToggle" href="#ckb">Toggle</a> + <?php }else{ + echo 'No extra forms defined yet — add one!'; + } ?> + </td> + </tr> + </tfoot> </table> <?php if ($count) //Show options.. echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; ?> +<p class="centered" id="actions"> + <input class="button" type="submit" name="delete" value="Delete"> +</p> +</form> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3>Please Confirm</h3> + <a class="close" href="">×</a> + <hr/> + <p class="confirm-action" style="display:none;" id="delete-confirm"> + <font color="red"><strong>Are you sure you want to DELETE selected forms?</strong></font> + <br><br>Deleted forms CANNOT be recovered. + </p> + <div>Please confirm to continue.</div> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" value="No, Cancel" class="close"> + </span> + <span class="buttons" style="float:right"> + <input type="button" value="Yes, Do it!" class="confirm"> + </span> + </p> + <div class="clear"></div> +</div> diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index 3496251ff..b137edb7a 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -2,13 +2,13 @@ $info=array(); if($list && $_REQUEST['a']!='add') { - $title = 'Update dynamic list'; + $title = 'Update custom list'; $action = 'update'; $submit_text='Save Changes'; $info = $list->ht; $newcount=2; } else { - $title = 'Add new dynamic list'; + $title = 'Add new custom list'; $action = 'add'; $submit_text='Add List'; $newcount=4; @@ -20,13 +20,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> - <h2>Dynamic List</h2> + <h2>Custom List</h2> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em>Dynamic lists are used to provide selection boxes for dynamic forms</em> + <em>Custom lists are used to provide drop-down lists for custom forms</em> </th> </tr> </thead> @@ -69,9 +69,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </th> </tr> <tr> - <th>Delete</th> + <th></th> <th>Value</th> <th>Extra <em style="display:inline">— abbreviations and such</em></th> + <th>Delete</th> </tr> </thead> <tbody <?php if ($info['sort_mode'] == 'SortCol') { ?> @@ -84,22 +85,24 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $id = $i->get('id'); ?> <tr> <td><?php echo $icon; ?> - <input type="checkbox" name="delete-<?php echo $id; ?>"/> <input type="hidden" name="sort-<?php echo $id; ?>" value="<?php echo $i->get('sort'); ?>"/></td> <td><input type="text" size="40" name="value-<?php echo $id; ?>" value="<?php echo $i->get('value'); ?>"/></td> <td><input type="text" size="30" name="extra-<?php echo $id; ?>" value="<?php echo $i->get('extra'); ?>"/></td> + <td> + <input type="checkbox" name="delete-<?php echo $id; ?>"/></td> </tr> <?php } } for ($i=0; $i<$newcount; $i++) { ?> <tr> - <td><?php echo $icon; ?> <em>add</em> + <td><?php echo $icon; ?> <em>+</em> <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td> <td><input type="text" size="40" name="value-new-<?php echo $i; ?>"/></td> <td><input type="text" size="30" name="extra-new-<?php echo $i; ?>"/></td> + <td></td> </tr> <?php } ?> </tbody> @@ -117,7 +120,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tbody> </table> </table> -<p style="padding-left:225px;"> +<p class="centered"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> <input type="reset" name="reset" value="Reset"> <input type="button" name="cancel" value="Cancel" onclick='window.location.href="?"'> diff --git a/include/staff/dynamic-lists.inc.php b/include/staff/dynamic-lists.inc.php index a5742f3fd..67510792d 100644 --- a/include/staff/dynamic-lists.inc.php +++ b/include/staff/dynamic-lists.inc.php @@ -1,8 +1,8 @@ <div style="width:700;padding-top:5px; float:left;"> - <h2>Dynamic Lists</h2> + <h2>Custom Lists</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> - <b><a href="lists.php?a=add" class="Icon">Add New Dynamic List</a></b></div> + <b><a href="lists.php?a=add" class="Icon">Add New Custom List</a></b></div> <div class="clear"></div> <?php @@ -13,6 +13,10 @@ $pageNav->setURL('lists.php'); $showing=$pageNav->showing().' dynamic lists'; ?> +<form action="lists.php" method="POST" name="lists"> +<?php csrf_token(); ?> +<input type="hidden" name="do" value="mass_process" > +<input type="hidden" id="action" name="a" value="" > <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> <caption><?php echo $showing; ?></caption> <thead> @@ -26,9 +30,14 @@ $showing=$pageNav->showing().' dynamic lists'; <tbody> <?php foreach (DynamicList::objects()->order_by('name') ->limit($pageNav->getLimit()) - ->offset($pageNav->getStart()) as $list) { ?> + ->offset($pageNav->getStart()) as $list) { + $sel = false; + if ($ids && in_array($form->get('id'),$ids)) + $sel = true; ?> <tr> - <td/> + <td> + <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $list->get('id'); ?>" + <?php echo $sel?'checked="checked"':''; ?>></td> <td><a href="?id=<?php echo $list->get('id'); ?>"><?php echo $list->get('name'); ?></a></td> <td><?php echo $list->get('created'); ?></td> <td><?php echo $list->get('updated'); ?></td> @@ -36,8 +45,48 @@ $showing=$pageNav->showing().' dynamic lists'; <?php } ?> </tbody> + <tfoot> + <tr> + <td colspan="4"> + <?php if($count){ ?> + Select: + <a id="selectAll" href="#ckb">All</a> + <a id="selectNone" href="#ckb">None</a> + <a id="selectToggle" href="#ckb">Toggle</a> + <?php } else { + echo 'No custom lists defined yet — add one!'; + } ?> + </td> + </tr> + </tfoot> </table> <?php if ($count) //Show options.. echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; ?> + +<p class="centered" id="actions"> + <input class="button" type="submit" name="delete" value="Delete"> +</p> +</form> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3>Please Confirm</h3> + <a class="close" href="">×</a> + <hr/> + <p class="confirm-action" style="display:none;" id="delete-confirm"> + <font color="red"><strong>Are you sure you want to DELETE selected lists?</strong></font> + <br><br>Deleted forms CANNOT be recovered. + </p> + <div>Please confirm to continue.</div> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" value="No, Cancel" class="close"> + </span> + <span class="buttons" style="float:right"> + <input type="button" value="Yes, Do it!" class="confirm"> + </span> + </p> + <div class="clear"></div> +</div> diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index e433cd7a2..f8b96f04b 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -15,6 +15,23 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) </tr> </thead> <tbody> + <tr> + <td width="180" class="required">Default Email Templates:</td> + <td> + <select name="default_template_id"> + <option value="">— Select Default Template —</option> + <?php + $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE isactive=1 ORDER BY name'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while (list($id, $name) = db_fetch_row($res)){ + $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?> + <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> + <?php + } + } ?> + </select> <font class="error">* <?php echo $errors['default_template_id']; ?></font> + </td> + </tr> <tr> <td width="180" class="required">Default System Email:</td> <td> diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index 8dd170e41..838b4c0fc 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -54,23 +54,6 @@ $gmtime = Misc::gmtime(); </select> <font class="error">* <?php echo $errors['default_dept_id']; ?></font> </td> </tr> - <tr> - <td width="220" class="required">Default Email Templates:</td> - <td> - <select name="default_template_id"> - <option value="">— Select Default Template —</option> - <?php - $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE isactive=1 ORDER BY name'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while (list($id, $name) = db_fetch_row($res)){ - $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?> - <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> - <?php - } - } ?> - </select> <font class="error">* <?php echo $errors['default_template_id']; ?></font> - </td> - </tr> <tr><td>Default Page Size:</td> <td> @@ -112,6 +95,19 @@ $gmtime = Misc::gmtime(); </select> </td> </tr> + <tr> + <td width="180">Default Name Formatting:</td> + <td> + <select name="name_format"> +<?php foreach (PersonsName::allFormats() as $n=>$f) { + list($desc, $func) = $f; + $selected = ($config['name_format'] == $n) ? 'selected="selected"' : ''; ?> + <option value="<?php echo $n; ?>" <?php echo $selected; + ?>><?php echo $desc; ?></option> +<?php } ?> + </select> + </td> + </tr> <tr> <th colspan="2"> <em><b>Authentication Settings</b></em> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index d360b4d29..6585e8aaa 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -77,13 +77,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) </td> </tr> <tr> - <td width="180">Web Tickets Priority:</td> - <td> - <input type="checkbox" name="allow_priority_change" value="1" <?php echo $config['allow_priority_change'] ?'checked="checked"':''; ?>> - <em>(Allow user to override/set priority)</em> - </td> - </tr> - <tr> <td width="180">Emailed Tickets Priority:</td> <td> <input type="checkbox" name="use_email_priority" value="1" <?php echo $config['use_email_priority'] ?'checked="checked"':''; ?> > diff --git a/include/staff/templates/user-info.tmpl.php b/include/staff/templates/user-info.tmpl.php index 3cfb9d467..b9802b8f0 100644 --- a/include/staff/templates/user-info.tmpl.php +++ b/include/staff/templates/user-info.tmpl.php @@ -18,7 +18,6 @@ <table width="100%"> <?php echo csrf_token(); - $static->render(); foreach ($custom as $form) $form->render(); ?> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index 46356bbba..826d19e79 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -18,7 +18,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </thead> <tbody> <?php - UserForm::getStaticForm()->render(); + $uf = UserForm::objects(); + $uf[0]->render(); if($cfg->notifyONNewStaffTicket()) { ?> <tr> <td width="160">Alert:</td> diff --git a/open.php b/open.php index 1370e4c9c..468f700b5 100644 --- a/open.php +++ b/open.php @@ -38,6 +38,13 @@ if($_POST): $errors += $form->errors(); } } + // Don't process contact information for logged-in clients + if (!$thisclient) { + $contact_form = UserForm::getInstance(); + if (!$contact_form->isValid()) + $errors += $contact_form->errors(); + } + if (!$errors && $cfg->allowOnlineAttachments() && $_FILES['attachments']) $vars['files'] = AttachmentFile::format($_FILES['attachments'], true); @@ -51,6 +58,10 @@ if($_POST): $form->save(); $ticket->loadDynamicData(); } + if (isset($contact_form)) { + $contact_form->setClientId($ticket->getOwnerId()); + $contact_form->save(); + } //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 070aafeeb..e255d2e59 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -581,11 +581,15 @@ div.section-break h3 { } .form_table th h4 { - margin:0; + margin: -0.3em -0.3em; color:#fff; background:#929292; + padding: 0.3em; } +.form_table th em:not(:first-child) { + margin-top: 0.6em; +} .form_table th em { display:block; color:#000; diff --git a/scp/forms.php b/scp/forms.php index a26aa7dfc..5c0a2ad04 100644 --- a/scp/forms.php +++ b/scp/forms.php @@ -9,13 +9,14 @@ if($_REQUEST['id'] && !($form=DynamicForm::lookup($_REQUEST['id']))) if($_POST) { $fields = array('title', 'notes', 'instructions'); $required = array('subject'); + $max_sort = 0; switch(strtolower($_POST['do'])) { case 'update': foreach ($fields as $f) if (isset($_POST[$f])) $form->set($f, $_POST[$f]); if ($form->isValid()) - $form->save(); + $form->save(true); foreach ($form->getDynamicFields() as $field) { $id = $field->get('id'); if ($_POST["delete-$id"] == 'on' && $field->isDeletable()) { @@ -25,7 +26,7 @@ if($_POST) { } if (isset($_POST["type-$id"]) && $field->isChangeable()) $field->set('type', $_POST["type-$id"]); - if (isset($_POST["name-$id"]) && $field->isNameEditable()) + if (isset($_POST["name-$id"]) && !$field->isNameForced()) $field->set('name', $_POST["name-$id"]); # TODO: make sure all help topics still have all required fields if (!$field->isRequirementForced()) @@ -39,6 +40,8 @@ if($_POST) { } if ($field->isValid()) $field->save(); + // Keep track of the last sort number + $max_sort = max($max_sort, $field->get('sort')); } break; case 'add': @@ -47,7 +50,30 @@ if($_POST) { 'instructions'=>$_POST['instructions'], 'notes'=>$_POST['notes'])); if ($form->isValid()) - $form->save(); + $form->save(true); + break; + + case 'mass_process': + if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { + $errors['err'] = 'You must select at least one API key'; + } else { + $count = count($_POST['ids']); + switch(strtolower($_POST['a'])) { + case 'delete': + $i=0; + foreach($_POST['ids'] as $k=>$v) { + if(($t=DynamicForm::lookup($v)) && $t->delete()) + $i++; + } + if ($i && $i==$count) + $msg = 'Selected custom forms deleted successfully'; + elseif ($i > 0) + $warn = "$i of $count selected forms deleted"; + elseif (!$errors['err']) + $errors['err'] = 'Unable to delete selected custom forms'; + break; + } + } break; } @@ -57,7 +83,7 @@ if($_POST) { continue; $field = DynamicFormField::create(array( 'form_id'=>$form->get('id'), - 'sort'=>$_POST["sort-new-$i"], + 'sort'=>$_POST["sort-new-$i"] ? $_POST["sort-new-$i"] : $max_sort++, 'label'=>$_POST["label-new-$i"], 'type'=>$_POST["type-new-$i"], 'name'=>$_POST["name-new-$i"], diff --git a/scp/js/scp.js b/scp/js/scp.js index d4e90e1b8..961e9e932 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -295,13 +295,14 @@ $(document).ready(function(){ }); }, onselect: function (obj) { + $('#basic-ticket-search').val(obj.value); $('#basic-ticket-search').closest('form').submit(); }, - property: "value" + property: "matches" }); /* Typeahead user lookup */ - $('#email.typeahead').typeahead({ + $('.email.typeahead').typeahead({ source: function (typeahead, query) { if(query.length > 2) { $.ajax({ @@ -314,9 +315,9 @@ $(document).ready(function(){ } }, onselect: function (obj) { - var fObj=$('#email.typeahead').closest('form'); + var fObj=$('.email.typeahead').closest('form'); if(obj.name) - $('#name', fObj).val(obj.name); + $('.auto.name', fObj).val(obj.name); }, property: "email" }); diff --git a/scp/lists.php b/scp/lists.php index acd3cc9c0..81cf2663d 100644 --- a/scp/lists.php +++ b/scp/lists.php @@ -37,6 +37,30 @@ if($_POST) { if ($list->isValid()) $list->save(true); break; + + case 'mass_process': + if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { + $errors['err'] = 'You must select at least one API key'; + } else { + $count = count($_POST['ids']); + switch(strtolower($_POST['a'])) { + case 'delete': + $i=0; + foreach($_POST['ids'] as $k=>$v) { + if(($t=DynamicList::lookup($v)) && $t->delete()) + $i++; + } + if ($i && $i==$count) + $msg = 'Selected custom lists deleted successfully'; + elseif ($i > 0) + $warn = "$i of $count selected lists deleted"; + elseif (!$errors['err']) + $errors['err'] = 'Unable to delete selected custom lists' + .' — they may be in use on a custom form'; + break; + } + } + break; } if ($list) { diff --git a/scp/tickets.php b/scp/tickets.php index c5ade077c..067df4f70 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -483,14 +483,15 @@ if($_POST && !$errors): if(($ticket=Ticket::open($vars, $errors))) { $msg='Ticket created successfully'; $_REQUEST['a']=null; - # TODO: Save dynamic form(s) + # Save extra dynamic form(s) if (isset($form)) { $form->setTicketId($ticket->getId()); $form->save(); - $ticket->loadDynamicData(); } - if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) + if (!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) $ticket=null; + else + $ticket->loadDynamicData(); Draft::deleteForNamespace('ticket.staff%', $thisstaff->getId()); } elseif(!$errors['err']) { $errors['err']='Unable to create the ticket. Correct the error(s) and try again'; diff --git a/setup/doc/forms.md b/setup/doc/forms.md new file mode 100644 index 000000000..c3545096c --- /dev/null +++ b/setup/doc/forms.md @@ -0,0 +1,51 @@ +osTicket Forms API +================== + +osTicket now includes a (relatively) complete forms API. Forms can be +created as objects and used to render their corresponding widgets to the +screen without actually writing and HTML. + +Defining a form +--------------- +Instanciate a form from a list of fields. PHP unfortunately does not allow +one to create a class and instanciate a list of instances into a static +property. Because of this limitation, the field list is specified at the +form instance construction time by passing the list of fields into the +constructor. + +The simplest way to create forms is to instanciate the Form instance +directly: + + $form = new Form(array( + 'email' => new TextboxField(array('label'=>'Email Address')), + ); + +The form can then be rendered to HTML and sent to the user. Later the form +can be recreated after a POST and the data from the request will +automatically be placed into the form. Check if the form is valid: + + if ($form->isValid()) + $object->update($form->getClean()); + +The `getClean()` method will return a hash array, where the keys are the +keys in the field array passed to the form constructor, and the values are +the cleaned values from the form fields based on the data from the request. + +To create a class that defines the fields statically, one might write a +trampoline constructor: + + class UserForm extends Form { + function __construct() { + $args = func_get_args(); + $fields = array( + 'email' => new TextboxField(array( + 'label'=>'Email Address') + ), + ); + array_unshift($args, $fields); + call_user_func_array(array('parent','__construct'), $args); + } + } + +Here, the fields are defined statically in the constructor. Do not bother +trying to specify the fields in a static property. You'll end up crying. diff --git a/setup/doc/orm.md b/setup/doc/orm.md new file mode 100644 index 000000000..3a77fc7de --- /dev/null +++ b/setup/doc/orm.md @@ -0,0 +1,11 @@ +osTicket ORM +============ + +Creating a new model +-------------------- +Eventually, we will have scripts to create the orm model from a mockup file +with field definitions, complete with validation routines. Currently, the +model validation must be written manually, and the fields are assumed based +on the fields available in the database on fetch, and the name of the field +updated on update. + -- GitLab