diff --git a/bootstrap.php b/bootstrap.php index d2afa2243f7bfa2914da15428c3f8e2c9b197fcb..2085a4fbc1909d4c00ab79424e405f4c6ebd78b0 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -116,6 +116,7 @@ class Bootstrap { define('FORM_ANSWER_TABLE',$prefix.'form_entry_values'); define('TOPIC_TABLE',$prefix.'help_topic'); + define('TOPIC_FORM_TABLE',$prefix.'help_topic_form'); define('SLA_TABLE', $prefix.'sla'); define('EMAIL_TABLE',$prefix.'email'); @@ -124,6 +125,7 @@ class Bootstrap { define('FILTER_TABLE', $prefix.'filter'); define('FILTER_RULE_TABLE', $prefix.'filter_rule'); + define('FILTER_ACTION_TABLE', $prefix.'filter_action'); define('PLUGIN_TABLE', $prefix.'plugin'); define('SEQUENCE_TABLE', $prefix.'sequence'); diff --git a/include/ajax.filter.php b/include/ajax.filter.php new file mode 100644 index 0000000000000000000000000000000000000000..5ef5ec82cc95fbec05d727dfdae8ba071ba32af3 --- /dev/null +++ b/include/ajax.filter.php @@ -0,0 +1,28 @@ +<?php + +require_once(INCLUDE_DIR . 'class.filter.php'); + +class FilterAjaxAPI extends AjaxController { + + function getFilterActionForm($type) { + if (!($A = FilterAction::lookupByType($type))) + Http::response(404, 'No such filter action type'); + + $form = $A->getConfigurationForm(); + ?> + <div style="position:relative"> + <div class="pull-right" style="position:absolute;top:2px;right:2px;"> + <a href="#" title="<?php echo __('clear'); ?>" onclick="javascript: + if (!confirm(__('You sure?'))) + return false; + $(this).closest('tr').fadeOut(400, function() { $(this).hide(); }); + return false;"><i class="icon-trash"></i></a> + </div> + <?php + include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php'; + ?> + </div> + <?php + } + +} diff --git a/include/ajax.forms.php b/include/ajax.forms.php index 6c35f1bb2fe2055d220c89c583261a189223ddc0..169290c945bac96986ef2ef222710691256851b5 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -24,13 +24,13 @@ class DynamicFormsAjaxAPI extends AjaxController { $_SESSION[':form-data'] = array_merge($_SESSION[':form-data'], $_GET); } - if ($form = $topic->getForm()) { + foreach ($topic->getForms() as $form) { ob_start(); $form->getForm($_SESSION[':form-data'])->render(!$client); - $html = ob_get_clean(); + $html .= ob_get_clean(); ob_start(); print $form->getMedia(); - $media = ob_get_clean(); + $media .= ob_get_clean(); } return $this->encode(array( 'media' => $media, @@ -140,5 +140,23 @@ class DynamicFormsAjaxAPI extends AjaxController { array('id'=>$field->ajaxUpload(true)) ); } + + function getAllFields($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!$form = DynamicForm::lookup($id)) + Http::response(400, 'No such form'); + + ob_start(); + include STAFFINC_DIR . 'templates/dynamic-form-fields-view.tmpl.php'; + $html = ob_get_clean(); + + return $this->encode(array( + 'success'=>true, + 'html' => $html, + )); + } } ?> diff --git a/include/class.banlist.php b/include/class.banlist.php index 939ae179b1cc142950daf65cd4c25f301c99b651..ae7b894479f4e3c56c51c30487bf1cd8fc16e2c2 100644 --- a/include/class.banlist.php +++ b/include/class.banlist.php @@ -16,15 +16,15 @@ require_once "class.filter.php"; class Banlist { - + function add($email,$submitter='') { return self::getSystemBanList()->addRule('email','equal',$email); } - + function remove($email) { return self::getSystemBanList()->removeRule('email','equal',$email); } - + function isbanned($email) { return TicketFilter::isBanned($email); } @@ -49,7 +49,9 @@ class Banlist { 'name' => 'SYSTEM BAN LIST', 'isactive' => 1, 'match_all_rules' => false, - 'reject_ticket' => true, + 'actions' => array( + 'Nreject', + ), 'rules' => array(), 'notes' => __('Internal list for email banning. Do not remove') ), $errors); diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 181c1bdbd524bc8c3c2582c874b03ca127738ba9..e620669c9339a1dcfe1f5f31e56b530f14c0909a 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -32,6 +32,11 @@ class DynamicForm extends VerySimpleModel { 'table' => FORM_SEC_TABLE, 'ordering' => array('title'), 'pk' => array('id'), + 'joins' => array( + 'fields' => array( + 'reverse' => 'DynamicFormField.form', + ), + ), ); // Registered form types @@ -96,8 +101,8 @@ class DynamicForm extends VerySimpleModel { } - function getTitle() { return $this->get('title'); } - function getInstructions() { return $this->get('instructions'); } + function getTitle() { return $this->getLocal('title'); } + function getInstructions() { return $this->getLocal('instructions'); } function getForm($source=false) { if (!$this->_form || $source) { @@ -112,6 +117,14 @@ class DynamicForm extends VerySimpleModel { return $this->get('deletable'); } + function disableFields(array $ids) { + foreach ($this->getFields() as $F) { + if (in_array($F->get('id'), $ids)) { + $F->disable(); + } + } + } + function instanciate($sort=1) { return DynamicFormEntry::create(array( 'form_id'=>$this->get('id'), 'sort'=>$sort)); @@ -138,7 +151,9 @@ class DynamicForm extends VerySimpleModel { $this->set('updated', new SqlFunction('NOW')); if (isset($this->dirty['notes'])) $this->notes = Format::sanitize($this->notes); - return parent::save($refetch); + if ($rv = parent::save($refetch | $this->dirty)) + return $this->saveTranslations(); + return $rv; } function delete() { @@ -182,6 +197,53 @@ class DynamicForm extends VerySimpleModel { return $inst; } + function saveTranslations($vars=false) { + global $thisstaff; + + $vars = $vars ?: $_POST; + $tags = array( + 'title' => $this->getTranslateTag('title'), + 'instructions' => $this->getTranslateTag('instructions'), + ); + $rtags = array_flip($tags); + $translations = CustomDataTranslation::allTranslations($tags, 'phrase'); + foreach ($translations as $t) { + $T = $rtags[$t->object_hash]; + $content = @$vars['trans'][$t->lang][$T]; + if (!isset($content)) + continue; + + // Content is not new and shouldn't be added below + unset($vars['trans'][$t->lang][$T]); + + $t->text = $content; + $t->agent_id = $thisstaff->getId(); + $t->updated = SqlFunction::NOW(); + if (!$t->save()) + return false; + } + // New translations (?) + foreach ($vars['trans'] as $lang=>$parts) { + if (!Internationalization::isLanguageInstalled($lang)) + continue; + foreach ($parts as $T => $content) { + $content = trim($content); + if (!$content) + continue; + $t = CustomDataTranslation::create(array( + 'type' => 'phrase', + 'object_hash' => $tags[$T], + 'lang' => $lang, + 'text' => $content, + 'agent_id' => $thisstaff->getId(), + 'updated' => SqlFunction::NOW(), + )); + if (!$t->save()) + return false; + } + } + return true; + } static function getCrossTabQuery($object_type, $object_id='object_id', $exclude=array()) { @@ -422,6 +484,7 @@ class DynamicFormField extends VerySimpleModel { ); var $_field; + var $_disabled = false; const FLAG_ENABLED = 0x00001; const FLAG_EXT_STORED = 0x00002; // Value stored outside of form_entry_value @@ -525,6 +588,12 @@ class DynamicFormField extends VerySimpleModel { function isEditable() { return $this->hasFlag(self::FLAG_MASK_EDIT); } + function disable() { + $this->_disabled = true; + } + function isEnabled() { + return !$this->_disabled && $this->hasFlag(self::FLAG_ENABLED); + } function hasFlag($flag) { return (isset($this->flags) && ($this->flags & $flag) != 0); @@ -638,19 +707,19 @@ class DynamicFormField extends VerySimpleModel { return $this->hasFlag(self::FLAG_CLOSE_REQUIRED); } function isEditableToStaff() { - return $this->hasFlag(self::FLAG_ENABLED) + return $this->isEnabled() && $this->hasFlag(self::FLAG_AGENT_EDIT); } function isVisibleToStaff() { - return $this->hasFlag(self::FLAG_ENABLED) + return $this->isEnabled() && $this->hasFlag(self::FLAG_AGENT_VIEW); } function isEditableToUsers() { - return $this->hasFlag(self::FLAG_ENABLED) + return $this->isEnabled() && $this->hasFlag(self::FLAG_CLIENT_EDIT); } function isVisibleToUsers() { - return $this->hasFlag(self::FLAG_ENABLED) + return $this->isEnabled() && $this->hasFlag(self::FLAG_CLIENT_VIEW); } @@ -689,10 +758,10 @@ class DynamicFormField extends VerySimpleModel { $this->save(); } - function save() { + function save($refetch=false) { if (count($this->dirty)) $this->set('updated', new SqlFunction('NOW')); - return parent::save(); + return parent::save($this->dirty || $refetch); } static function create($ht=false) { @@ -722,7 +791,7 @@ class DynamicFormEntry extends VerySimpleModel { 'pk' => array('id'), 'select_related' => array('form'), 'fields' => array('id', 'form_id', 'object_type', 'object_id', - 'sort', 'updated', 'created'), + 'sort', 'extra', 'updated', 'created'), 'joins' => array( 'form' => array( 'null' => true, @@ -785,6 +854,10 @@ class DynamicFormEntry extends VerySimpleModel { $this->_form = DynamicForm::lookup($this->get('form_id')); if ($this->_form && isset($this->id)) $this->_form->data($this); + if (isset($this->extra)) { + $x = JsonDataParser::decode($this->extra) ?: array(); + $this->_form->disableFields($x['disable'] ?: array()); + } } return $this->_form; } @@ -958,6 +1031,7 @@ class DynamicFormEntry extends VerySimpleModel { } } if (!$found && ($fImpl = $field->getImpl($field)) + && $field->isEnabled() && !$fImpl->isPresentationOnly()) { $a = DynamicFormEntryAnswer::create( array('field_id'=>$field->get('id'), 'entry_id'=>$this->id)); diff --git a/include/class.email.php b/include/class.email.php index ab0727f7cbf4c5cf941ebbf1893fc6873f6eb79f..31c3dda775dd5f0b8aef0c6300e3a03f6f088add 100644 --- a/include/class.email.php +++ b/include/class.email.php @@ -59,6 +59,19 @@ class EmailModel extends VerySimpleModel { static function getPermissions() { return self::$perms; } + + static function getAddresses($options=array()) { + $objects = static::objects(); + if ($options['smtp']) + $objects = $objects->filter(array('smtp_active'=>true)); + + $addresses = array(); + foreach ($objects->values_flat('email_id', 'email') as $row) { + list($id, $email) = $row; + $addresses[$id] = $email; + } + return $addresses; + } } RolePermission::register(/* @trans */ 'Miscellaneous', EmailModel::getPermissions()); diff --git a/include/class.filter.php b/include/class.filter.php index acc3a3590a7921d1bee8599417c20fd1714e011a..7a2ced7e2d7012b0980ba0940ea5a6d2e03f7c38 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -14,6 +14,8 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +require_once INCLUDE_DIR . 'class.filter_action.php'; + class Filter { var $id; @@ -293,47 +295,28 @@ class Filter { return $match; } + + function getActions() { + return FilterAction::objects()->filter(array( + 'filter_id'=>$this->getId() + )); + } /** * If the matches() method returns TRUE, send the initial ticket to this * method to apply the filter actions defined */ - function apply(&$ticket, $info=null) { - # TODO: Disable alerting - # XXX: Does this imply turning it on as well? (via ->sendAlerts()) - if ($this->disableAlerts()) $ticket['autorespond']=false; - # Set owning department (?) - if ($this->getDeptId()) $ticket['deptId']=$this->getDeptId(); - # Set ticket priority (?) - if ($this->getPriorityId()) $ticket['priorityId']=$this->getPriorityId(); - # Set SLA plan (?) - if ($this->getSLAId()) $ticket['slaId']=$this->getSLAId(); - # Set status - if ($this->getStatusId()) $ticket['statusId']=$this->getStatusId(); - # Auto-assign to (?) - # XXX: Unset the other (of staffId or teamId) (?) - if ($this->getStaffId()) $ticket['staffId']=$this->getStaffId(); - elseif ($this->getTeamId()) $ticket['teamId']=$this->getTeamId(); - # Override name with reply-to information from the TicketFilter - # match - if ($this->useReplyToEmail() && $info['reply-to']) { - $changed = $info['reply-to'] != $ticket['email'] - || ($info['reply-to-name'] && $ticket['name'] != $info['reply-to-name']); - $ticket['email'] = $info['reply-to']; - if ($info['reply-to-name']) - $ticket['name'] = $info['reply-to-name']; - if ($changed) - throw new FilterDataChanged(); + function apply(&$ticket, $vars) { + foreach ($this->getActions() as $a) { + $a->setFilter($this); + $a->apply($ticket, $vars); } + } - # Use canned response. - if ($this->getCannedResponse()) - $ticket['cannedResponseId'] = $this->getCannedResponse(); - - # Apply help topic - if ($this->getHelpTopic()) - $ticket['topicId'] = $this->getHelpTopic(); + function getVars() { + return $this->vars; } - static function getSupportedMatches() { + + static function getSupportedMatches() { foreach (static::$match_types as $k=>&$v) { if (is_callable($v[0])) $v[0] = $v[0](); @@ -518,17 +501,9 @@ class Filter { .',name='.db_input($vars['name']) .',execorder='.db_input($vars['execorder']) .',email_id='.db_input($emailId) - .',dept_id='.db_input($vars['dept_id']) - .',status_id='.db_input($vars['status_id']) - .',priority_id='.db_input($vars['priority_id']) - .',sla_id='.db_input($vars['sla_id']) - .',topic_id='.db_input($vars['topic_id']) .',match_all_rules='.db_input($vars['match_all_rules']) .',stop_onmatch='.db_input(isset($vars['stop_onmatch'])?1:0) .',reject_ticket='.db_input(isset($vars['reject_ticket'])?1:0) - .',use_replyto_email='.db_input(isset($vars['use_replyto_email'])?1:0) - .',disable_autoresponder='.db_input(isset($vars['disable_autoresponder'])?1:0) - .',canned_response_id='.db_input($vars['canned_response_id']) .',notes='.db_input(Format::sanitize($vars['notes'])); @@ -558,8 +533,40 @@ class Filter { # Don't care about errors stashed in $xerrors $xerrors = array(); self::save_rules($id,$vars,$xerrors); - - return true; + self::save_actions($id, $vars, $errors); + + return count($errors) == 0; + } + + function save_actions($id, $vars, &$errors) { + if (!is_array(@$vars['actions'])) + return; + + foreach ($vars['actions'] as $sort=>$v) { + $info = substr($v, 1); + switch ($v[0]) { + case 'N': # new filter action + $I = FilterAction::create(array( + 'type'=>$info, + 'filter_id'=>$id, + 'sort' => (int) $sort, + )); + $I->setConfiguration($errors, $vars); + $I->save(); + break; + case 'I': # exiting filter action + if ($I = FilterAction::lookup($info)) { + $I->setConfiguration($errors, $vars); + $I->sort = (int) $sort; + $I->save(); + } + break; + case 'D': # deleted filter action + if ($I = FilterAction::lookup($info)) + $I->delete(); + break; + } + } } } @@ -779,8 +786,6 @@ class TicketFilter { */ function apply(&$ticket) { foreach ($this->getMatchingFilterList() as $filter) { - if ($filter->rejectOnMatch()) - throw new RejectedException($filter, $ticket); $filter->apply($ticket, $this->vars); if ($filter->stopOnMatch()) break; } diff --git a/include/class.filter_action.php b/include/class.filter_action.php new file mode 100644 index 0000000000000000000000000000000000000000..f2367bc77b1f63e5a1d5eeb11a6d7b35af3b2013 --- /dev/null +++ b/include/class.filter_action.php @@ -0,0 +1,513 @@ +<?php + +require_once INCLUDE_DIR . 'class.orm.php'; + +class FilterAction extends VerySimpleModel { + static $meta = array( + 'table' => FILTER_ACTION_TABLE, + 'pk' => array('id'), + 'ordering' => array('sort'), + ); + + static $registry = array(); + static $registry_group = array(); + + var $_impl; + var $_config; + var $_filter; + + function getId() { + return $this->id; + } + + function setFilter($filter) { + $this->_filter = $filter; + } + function getFilter() { + return $this->_filter; + } + + function getConfiguration() { + if (!$this->_config) { + $this->_config = $this->get('configuration'); + if (is_string($this->_config)) + $this->_config = JsonDataParser::parse($this->_config); + elseif (!$this->_config) + $this->_config = array(); + foreach ($this->getImpl()->getConfigurationOptions() as $name=>$field) + if (!isset($this->_config[$name])) + $this->_config[$name] = $field->get('default'); + } + return $this->_config; + } + + function setConfiguration(&$errors=array(), $source=false) { + $config = array(); + foreach ($this->getImpl()->getConfigurationForm($source ?: $_POST) + ->getFields() as $name=>$field) { + if (!$field->hasData()) + continue; + $config[$name] = $field->to_php($field->getClean()); + $errors = array_merge($errors, $field->errors()); + } + if (count($errors) === 0) + $this->set('configuration', JsonDataEncoder::encode($config)); + return count($errors) === 0; + } + + function getImpl() { + if (!isset($this->_impl)) { + if (!($I = self::lookupByType($this->type, $this))) + throw new Exception(sprintf( + '%s: No such filter action registered', $this->type)); + $this->_impl = $I; + } + return $this->_impl; + } + + function apply(&$ticket, array $info) { + return $this->getImpl()->apply($ticket, $info); + } + + function save($refetch=false) { + if ($this->dirty) + $this->updated = SqlFunction::NOW(); + return parent::save($refetch || $this->dirty); + } + + static function register($class, $group=false) { + if (!$class::$type) + throw new Exception('Filter actions must specify ::$type'); + elseif (!is_subclass_of($class, 'TriggerAction')) + throw new Exception('Filter actions must extend from TriggerAction'); + + self::$registry[$class::$type] = $class; + self::$registry_group[$group ?: ''][$class::$type] = $class; + } + + static function lookupByType($type, $thisObj=false) { + if (!isset(self::$registry[$type])) + return null; + + $class = self::$registry[$type]; + return new $class($thisObj); + } + + static function allRegistered($group=false) { + $types = array(); + foreach (self::$registry_group as $group=>$actions) { + $G = $group ? __($group) : ''; + foreach ($actions as $type=>$class) { + $types[$G][$type] = __($class::getName()); + } + } + return $types; + } +} + +abstract class TriggerAction { + static $type = false; + static $flags = 0; + + const FLAG_MULTI_USE = 0x0001; // Action can be used multiple times + + var $action; + + function __construct($action=false) { + $this->action = $action; + } + + function getConfiguration() { + if ($this->action) + return $this->action->getConfiguration(); + return array(); + } + + function getConfigurationForm($source=false) { + if (!$this->_cform) { + $config = $this->getConfiguration(); + $options = $this->getConfigurationOptions(); + // Find a uid offset for this guy + $uid = 1000; + foreach (FilterAction::$registry as $type=>$class) { + $uid += 100; + if ($type == $this->getType()) + break; + } + // Ensure IDs are unique + foreach ($options as $f) { + $f->set('id', $uid++); + } + $this->_cform = new Form($options, $source); + if (!$source) { + foreach ($this->_cform->getFields() as $name=>$f) { + if ($config && isset($config[$name])) + $f->value = $config[$name]; + elseif ($f->get('default')) + $f->value = $f->get('default'); + } + } + } + return $this->_cform; + } + + function hasFlag($flag) { + return static::$flags & $flag > 0; + } + + static function getType() { return static::$type; } + static function getName() { return __(static::$name); } + + abstract function apply(&$ticket, array $info); + abstract function getConfigurationOptions(); +} + +class FA_RejectTicket extends TriggerAction { + static $type = 'reject'; + static $name = /* @trans */ 'Reject Ticket'; + + function apply(&$ticket, array $info) { + throw new RejectedException($this->action->getFilter(), $ticket); + } + + function getConfigurationOptions() { + return array( + '' => new FreeTextField(array( + 'configuration' => array( + 'content' => sprintf('<span style="color:red"><b>%s</b></span>', + __('Reject Ticket')), + ) + )), + ); + } +} +FilterAction::register('FA_RejectTicket', /* @trans */ 'Ticket'); + +class FA_UseReplyTo extends TriggerAction { + static $type = 'replyto'; + static $name = /* @trans */ 'Use Reply-To Email'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['enable'] && $info['reply-to']) { + $ticket['email'] = $info['reply-to']; + if ($info['reply-to-name']) + $ticket['name'] = $info['reply-to-name']; + } + } + + function getConfigurationOptions() { + return array( + '' => new FreeTextField(array( + 'configuration' => array( + 'content' => __('<strong>Use</strong> the Reply-To email header') + ) + )), + ); + } +} +FilterAction::register('FA_UseReplyTo', /* @trans */ 'Communication'); + +class FA_DisableAutoResponse extends TriggerAction { + static $type = 'noresp'; + static $name = /* @trans */ "Disable autoresponse"; + + function apply(&$ticket, array $info) { + # TODO: Disable alerting + # XXX: Does this imply turning it on as well? (via ->sendAlerts()) + $config = $this->getConfiguration(); + if ($config['enable']) { + $ticket['autorespond']=false; + } + } + + function getConfigurationOptions() { + return array( + '' => new FreeTextField(array( + 'configuration' => array( + 'content' => __('<strong>Disable</strong> new ticket auto-response') + ), + )), + ); + } +} +FilterAction::register('FA_DisableAutoResponse', /* @trans */ 'Communication'); + +class FA_AutoCannedResponse extends TriggerAction { + static $type = 'canned'; + static $name = /* @trans */ "Attach Canned Response"; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['canned_id']) { + $ticket['cannedResponseId'] = $config['canned_id']; + } + } + + function getConfigurationOptions() { + $sql='SELECT canned_id, title, isenabled FROM '.CANNED_TABLE .' ORDER by title'; + $choices = array(false => '— '.__('None').' —'); + if ($res=db_query($sql)) { + while (list($id, $title, $isenabled)=db_fetch_row($res)) { + if (!$isenabled) + $title .= ' ' . __('(disabled)'); + $choices[$id] = $title; + } + } + return array( + 'canned_id' => new ChoiceField(array( + 'default' => false, + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AutoCannedResponse', /* @trans */ 'Communication'); + +class FA_RouteDepartment extends TriggerAction { + static $type = 'dept'; + static $name = /* @trans */ 'Set Department'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['dept_id']) + $ticket['deptId'] = $config['dept_id']; + } + + function getConfigurationOptions() { + return array( + 'dept_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => Dept::getDepartments(), + )), + ); + } +} +FilterAction::register('FA_RouteDepartment', /* @trans */ 'Ticket'); + +class FA_AssignPriority extends TriggerAction { + static $type = 'pri'; + static $name = /* @trans */ "Set Priority"; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['priority']) + $ticket['priority_id'] = $config['priority']->getId(); + } + + function getConfigurationOptions() { + $sql = 'SELECT priority_id, priority_desc FROM '.PRIORITY_TABLE + .' ORDER BY priority_urgency DESC'; + $choices = array(); + if ($res = db_query($sql)) { + while ($row = db_fetch_row($res)) + $choices[$row[0]] = $row[1]; + } + return array( + 'priority' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignPriority', /* @trans */ 'Ticket'); + +class FA_AssignSLA extends TriggerAction { + static $type = 'sla'; + static $name = /* @trans */ 'Set SLA Plan'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['sla_id']) + $ticket['slaId'] = $config['sla_id']; + } + + function getConfigurationOptions() { + $choices = SLA::getSLAs(); + return array( + 'sla_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignSLA', /* @trans */ 'Ticket'); + +class FA_AssignTeam extends TriggerAction { + static $type = 'team'; + static $name = /* @trans */ 'Assign Team'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['team_id']) + $ticket['teamId'] = $config['team_id']; + } + + function getConfigurationOptions() { + $choices = Team::getTeams(); + return array( + 'team_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignTeam', /* @trans */ 'Ticket'); + +class FA_AssignAgent extends TriggerAction { + static $type = 'agent'; + static $name = /* @trans */ 'Assign Agent'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['staff_id']) + $ticket['staffId'] = $config['staff_id']; + } + + function getConfigurationOptions() { + $choices = Staff::getStaffMembers(); + return array( + 'staff_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignAgent', /* @trans */ 'Ticket'); + +class FA_AssignTopic extends TriggerAction { + static $type = 'topic'; + static $name = /* @trans */ 'Set Help Topic'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['topic_id']) + $ticket['topicId'] = $config['topic_id']; + } + + function getConfigurationOptions() { + $choices = HelpTopic::getAllHelpTopics(); + return array( + 'topic_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignTopic', /* @trans */ 'Ticket'); + +class FA_SetStatus extends TriggerAction { + static $type = 'status'; + static $name = /* @trans */ 'Set Ticket Status'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['status_id']) + $ticket['statusId'] = $config['status_id']; + } + + function getConfigurationOptions() { + $choices = array(); + foreach (TicketStatusList::getStatuses() as $S) { + // TODO: Move this to TicketStatus::getName + $name = $S->getName(); + if (!($isenabled = $S->isEnabled())) + $name.=' '.__('(disabled)'); + $choices[$S->getId()] = $name; + } + return array( + 'status_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_SetStatus', /* @trans */ 'Ticket'); + +class FA_SendEmail extends TriggerAction { + static $type = 'email'; + static $name = /* @trans */ 'Send an Email'; + static $flags = TriggerAction::FLAG_MULTI_USE; + + function apply(&$ticket, array $info) { + global $ost; + + $config = $this->getConfiguration(); + $info = array('subject' => $config['subject'], + 'message' => $config['message']); + $info = $ost->replaceTemplateVariables( + $info, array('ticket' => $ticket) + ); + + // Honor FROM address settings + if (!$config['from'] || !($mailer = Email::lookup($config['from']))) + $mailer = new Mailer(); + + // Honor %{user} variable + $to = $config['recipients']; + $replacer = new VariableReplacer(); + $replacer->assign(array( + 'user' => sprintf('%s <%s>', $ticket['name'], $ticket['email']) + )); + $to = $replacer->replaceVars($to); + + $mailer->send($to, $info['subject'], $info['message']); + } + + function getConfigurationOptions() { + $choices = array('' => __('Default System Email')); + $choices += EmailModel::getAddresses(); + + return array( + 'recipients' => new TextboxField(array( + 'label' => __('Recipients'), 'required' => true, + 'configuration' => array( + 'size' => 80, 'length' => 1000, + ), + 'validators' => function($self, $value) { + if (!($mails = Mail_RFC822::parseAddressList($value)) || PEAR::isError($mails)) + $self->addError('Unable to parse address list. ' + .'Use commas to separate addresses.'); + + $valid = array('user',); + foreach ($mails as $M) { + // Check placeholders like '%{user}' + $P = array(); + if (preg_match('`%\{([^}]+)\}`', $M->mailbox, $P)) { + if (!in_array($P[1], $valid)) + $self->addError(sprintf('%s: Not a valid variable', $P[0])); + } + elseif ($M->host == 'localhost' || !$M->mailbox) { + $self->addError(sprintf(__('%s: Not a valid email address'), + $M->mailbox . '@' . $M->host)); + } + } + } + )), + 'subject' => new TextboxField(array( + 'configuration' => array( + 'size' => 80, + 'placeholder' => __('Subject') + ), + )), + 'message' => new TextareaField(array( + 'configuration' => array( + 'placeholder' => __('Message'), + 'html' => true, + ), + )), + 'from' => new ChoiceField(array( + 'label' => __('From Email'), + 'choices' => $choices, + 'default' => '', + )), + ); + } +} +FilterAction::register('FA_SendEmail', /* @trans */ 'Communication'); diff --git a/include/class.forms.php b/include/class.forms.php index b101009c42239d20e358a75ec799fd67b50f312d..dfccf962c5d1e307fec038fd028f400c889de122 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2759,11 +2759,18 @@ class FreeTextField extends FormField { class FreeTextWidget extends Widget { function render($options=array()) { $config = $this->field->getConfiguration(); - ?><div class=""><h3><?php - echo Format::htmlchars($this->field->getLocal('label')); - ?></h3><em><?php - echo Format::htmlchars($this->field->getLocal('hint')); - ?></em><div><?php + ?><div class=""><?php + if ($label = $this->field->getLocal('label')) { ?> + <h3><?php + echo Format::htmlchars($label); + ?></h3><?php + } + if ($hint = $this->field->getLocal('hint')) { ?> + <em><?php + echo Format::htmlchars($hint); + ?></em><?php + } ?> + <div><?php echo Format::viewableImages($config['content']); ?></div> </div> <?php diff --git a/include/class.ticket.php b/include/class.ticket.php index 1dd62e97cf49c0be0a0a727260f54062f8d0c8b3..07c8bfebdf10e7a520974ab0732204a9040f7701 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -526,6 +526,23 @@ class Ticket { return $this->ht['status_id']; } + /** + * setStatusId + * + * Forceably set the ticket status ID to the received status ID. No + * checks are made. Use ::setStatus() to change the ticket status + */ + // XXX: Use ::setStatus to change the status. This can be used as a + // fallback if the logic in ::setStatus fails. + function setStatusId($id) { + $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '. + ' ,status_id='.db_input($status->getId()) . + ' WHERE ticket_id='.db_input($this->getId()); + + if (!db_query($sql) || !db_affected_rows()) + return false; + } + function getStatus() { if (!$this->status && $this->getStatusId()) @@ -1076,7 +1093,7 @@ class Ticket { function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) { global $thisstaff; - if (!$thisstaff || !($role=$thisstaff->getRole($this->getDeptId()))) + if ($thisstaff && !($role=$thisstaff->getRole($this->getDeptId()))) return false; if ($status && is_numeric($status)) @@ -1085,19 +1102,21 @@ class Ticket { if (!$status || !$status instanceof TicketStatus) return false; - // Double check permissions - switch ($status->getState()) { - case 'closed': - if (!($role->hasPerm(TicketModel::PERM_CLOSE))) + // Double check permissions (when changing status) + if ($role && $this->getStatusId()) { + switch ($status->getState()) { + case 'closed': + if (!($role->hasPerm(TicketModel::PERM_CLOSE))) + return false; + break; + case 'deleted': + // XXX: intercept deleted status and do hard delete + if ($role->hasPerm(TicketModel::PERM_DELETE)) + return $this->delete($comments); + // Agent doesn't have permission to delete tickets return false; - break; - case 'deleted': - // XXX: intercept deleted status and do hard delete - if ($role->hasPerm(TicketModel::PERM_DELETE)) - return $this->delete($comments); - // Agent doesn't have permission to delete tickets - return false; - break; + break; + } } if ($this->getStatusId() == $status->getId()) @@ -1244,10 +1263,14 @@ class Ticket { return false; //bail out...missing stuff. } - $options = array( - 'inreplyto'=>$message->getEmailMessageId(), - 'references'=>$message->getEmailReferences(), - 'thread'=>$message); + $options = array(); + if ($message instanceof ThreadEntry) { + $options += array( + 'inreplyto'=>$message->getEmailMessageId(), + 'references'=>$message->getEmailReferences(), + 'thread'=>$message + ); + } //Send auto response - if enabled. if($autorespond @@ -1275,7 +1298,7 @@ class Ticket { $recipients=$sentlist=array(); //Exclude the auto responding email just incase it's from staff member. - if ($message->isAutoReply()) + if ($message instanceof ThreadEntry && $message->isAutoReply()) $sentlist[] = $this->getEmail(); //Alert admin?? @@ -2019,7 +2042,7 @@ class Ticket { return $message; } - function postCannedReply($canned, $msgId, $alert=true) { + function postCannedReply($canned, $message, $alert=true) { global $ost, $cfg; if((!is_object($canned) && !($canned=Canned::lookup($canned))) || !$canned->isEnabled()) @@ -2036,7 +2059,7 @@ class Ticket { $response = new TextThreadEntryBody( $this->replaceVars($canned->getPlainText())); - $info = array('msgId' => $msgId, + $info = array('msgId' => $message instanceof ThreadEntry ? $message->getId() : 0, 'poster' => __('SYSTEM (Canned Reply)'), 'response' => $response, 'cannedattachments' => $files); @@ -2715,15 +2738,11 @@ class Ticket { } } - if (!$form->isValid($field_filter('ticket'))) - $errors += $form->errors(); - if ($vars['uid']) $user = User::lookup($vars['uid']); $id=0; $fields=array(); - $fields['message'] = array('type'=>'*', 'required'=>1, 'error'=>__('Message content is required')); switch (strtolower($origin)) { case 'web': $fields['topicId'] = array('type'=>'int', 'required'=>1, 'error'=>__('Select a help topic')); @@ -2756,22 +2775,45 @@ class Ticket { $errors['duedate']=__('Due date must be in the future'); } + $topic_forms = array(); if (!$errors) { - # Perform ticket filter actions on the new ticket arguments - $__form = null; + // Handle the forms associate with the help topics. Instanciate the + // entries, disable and track the requested disabled fields. if ($vars['topicId']) { - if (($__topic=Topic::lookup($vars['topicId'])) - && ($__form = $__topic->getForm()) - ) { - $__form = $__form->instanciate(); - $__form->setSource($vars); + if ($__topic=Topic::lookup($vars['topicId'])) { + foreach ($__topic->getForms() as $idx=>$__F) { + $disabled = array(); + foreach ($__F->getFields() as $field) { + if (!$field->isEnabled() && $field->hasFlag(DynamicFormField::FLAG_ENABLED)) + $disabled[] = $field->get('id'); + } + // Special handling for the ticket form — disable fields + // requested to be disabled as per the help topic. + if ($__F->get('type') == 'T') { + foreach ($form->getFields() as $field) { + if (false !== array_search($field->get('id'), $disabled)) + $field->disable(); + } + $form->sort = $idx; + $__F = $form; + } + else { + $__F = $__F->instanciate($idx); + $__F->setSource($vars); + $topic_forms[] = $__F; + } + // Track fields currently disabled + $__F->extra = JsonDataEncoder::encode(array( + 'disable' => $disabled + )); + } } } try { $vars = self::filterTicketData($origin, $vars, - array($form, $__form), $user); + array_merge(array($form), $topic_forms), $user); } catch (RejectedException $ex) { return $reject_ticket( @@ -2823,12 +2865,13 @@ class Ticket { } } + if (!$form->isValid($field_filter('ticket'))) + $errors += $form->errors(); + if ($vars['topicId']) { if ($topic=Topic::lookup($vars['topicId'])) { - if ($topic_form = $topic->getForm()) { - $TF = $topic_form->getForm($vars); - $topic_form = $topic_form->instanciate(); - $topic_form->setSource($vars); + foreach ($topic_forms as $topic_form) { + $TF = $topic_form->getForm()->getForm($vars); if (!$TF->isValid($field_filter('topic'))) $errors = array_merge($errors, $TF->errors()); } @@ -2972,7 +3015,7 @@ class Ticket { $form->save(); // Save the form data from the help-topic form, if any - if ($topic_form) { + foreach ($topic_forms as $topic_form) { $topic_form->setTicketId($id); $topic_form->save(); } @@ -3032,8 +3075,12 @@ class Ticket { // Apply requested status — this should be done AFTER assignment, // because if it is requested to be closed, it should not cause the // ticket to be reopened for assignment. - if ($statusId) - $ticket->setStatus($statusId, false, $errors, false); + if ($statusId) { + if (!$ticket->setStatus($statusId, false, $errors, false)) { + // Tickets _must_ have a status. Forceably set one here + $ticket->setStatusId($cfg->getDefaultTicketStatusId()); + } + } /********** double check auto-response ************/ //Override auto responder if the FROM email is one of the internal emails...loop control. @@ -3044,12 +3091,12 @@ class Ticket { # not have a return 'ping' message if (isset($vars['flags']) && $vars['flags']['bounce']) $autorespond = false; - if ($autorespond && $message->isAutoReply()) + if ($autorespond && $message instanceof ThreadEntry && $message->isAutoReply()) $autorespond = false; //post canned auto-response IF any (disables new ticket auto-response). if ($vars['cannedResponseId'] - && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) { + && $ticket->postCannedReply($vars['cannedResponseId'], $message, $autorespond)) { $ticket->markUnAnswered(); //Leave the ticket as unanswred. $autorespond = false; } @@ -3061,7 +3108,7 @@ class Ticket { //Don't send alerts to staff when the message is a bounce // this is necessary to avoid possible loop (especially on new ticket) - if ($alertstaff && $message->isBounce()) + if ($alertstaff && $message instanceof ThreadEntry && $message->isBounce()) $alertstaff = false; /***** See if we need to send some alerts ****/ diff --git a/include/class.topic.php b/include/class.topic.php index 5adc575635e0fc40f214778ad4da2d2622948177..eda6a2a69b3c9e59af82745855cf30ef5d62c8d4 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -52,11 +52,15 @@ class Topic extends VerySimpleModel { 'priority_id' => 'Priority.priority_id', ), ), + 'forms' => array( + 'reverse' => 'TopicFormModel.topic', + 'null' => true, + ), ), ); var $page; - var $form; + var $_forms; const DISPLAY_DISABLED = 2; @@ -129,19 +133,15 @@ class Topic extends VerySimpleModel { return $this->page; } - function getFormId() { - return $this->form_id; - } - - function getForm() { - $id = $this->getFormId(); - - if ($id == self::FORM_USE_PARENT && ($p = $this->getParent())) - $this->form = $p->getForm(); - elseif ($id && !$this->form) - $this->form = DynamicForm::lookup($id); - - return $this->form; + function getForms() { + if (!isset($this->_forms)) { + foreach ($this->forms->select_related('form') as $F) { + $extra = JsonDataParser::decode($F->extra) ?: array(); + $F->form->disableFields($extra['disable'] ?: array()); + $this->_forms[] = $F->form; + } + } + return $this->_forms; } function autoRespond() { @@ -426,12 +426,69 @@ class Topic extends VerySimpleModel { $errors['err']=sprintf(__('Unable to update %s.'), __('this help topic')) .' '.__('Internal error occurred'); } - if (!$cfg || $cfg->getTopicSortMode() == 'a') { - static::updateSortOrder(); + if ($rv) { + if (!$cfg || $cfg->getTopicSortMode() == 'a') { + static::updateSortOrder(); + } + $this->updateForms($vars, $errors); } return $rv; } + function updateForms($vars, &$errors) { + $find_disabled = function($form) use ($vars) { + $fields = $vars['fields']; + $disabled = array(); + foreach ($form->fields->values_flat('id') as $row) { + list($id) = $row; + if (false === ($idx = array_search($id, $fields))) { + $disabled[] = $id; + } + } + return $disabled; + }; + + // Consider all the forms in the request + $current = array(); + if (is_array($form_ids = $vars['forms'])) { + $forms = TopicFormModel::objects() + ->select_related('form') + ->filter(array('topic_id' => $this->getId())); + foreach ($forms as $F) { + if (false !== ($idx = array_search($F->form_id, $form_ids))) { + $current[] = $F->form_id; + $F->sort = $idx + 1; + $F->extra = JsonDataEncoder::encode( + array('disable' => $find_disabled($F->form)) + ); + $F->save(); + unset($form_ids[$idx]); + } + elseif ($F->form->get('type') != 'T') { + $F->delete(); + } + } + foreach ($form_ids as $sort=>$id) { + if (!($form = DynamicForm::lookup($id))) { + continue; + } + elseif (in_array($id, $current)) { + // Don't add a form more than once + continue; + } + TopicFormModel::create(array( + 'topic_id' => $this->getId(), + 'form_id' => $id, + 'sort' => $sort + 1, + 'extra' => JsonDataEncoder::encode( + array('disable' => $find_disabled($form)) + ) + ))->save(); + } + } + return true; + } + function save($refetch=false) { if ($this->dirty) $this->updated = SqlFunction::NOW(); @@ -473,3 +530,19 @@ class Topic extends VerySimpleModel { // Add fields from the standard ticket form to the ticket filterable fields Filter::addSupportedMatches(/* @trans */ 'Help Topic', array('topicId' => 'Topic ID'), 100); + +class TopicFormModel extends VerySimpleModel { + static $meta = array( + 'table' => TOPIC_FORM_TABLE, + 'pk' => array('id'), + 'ordering' => array('sort'), + 'joins' => array( + 'topic' => array( + 'constraint' => array('topic_id' => 'Topic.topic_id'), + ), + 'form' => array( + 'constraint' => array('form_id' => 'DynamicForm.id'), + ), + ), + ); +} diff --git a/include/class.variable.php b/include/class.variable.php index 41bcb619e5587ddce96dce8bdb16d1e5aec7994b..ffb850ec8f264dd2e15ada81e37e90b268c0ccec 100644 --- a/include/class.variable.php +++ b/include/class.variable.php @@ -79,6 +79,9 @@ class VariableReplacer { return $this->getVar($rv, $part); } + if (is_array($obj) && isset($obj[$v])) + return $obj[$v]; + if (!$var || !method_exists($obj, 'getVar')) return ""; @@ -112,8 +115,13 @@ class VariableReplacer { $parts = explode('.', $var, 2); if($parts && ($obj=$this->getObj($parts[0]))) return $this->getVar($obj, $parts[1]); - elseif($parts[0] && @isset($this->variables[$parts[0]])) //root override + elseif($parts[0] && @isset($this->variables[$parts[0]])) { //root override + if (is_array($this->variables[$parts[0]]) + && isset($this->variables[$parts[0]][$parts[1]])) + return $this->variables[$parts[0]][$parts[1]]; + return $this->variables[$parts[0]]; + } //Unknown object or variable - leavig it alone. $this->setError(sprintf(__('Unknown object for "%s" tag'), $var)); diff --git a/include/client/open.inc.php b/include/client/open.inc.php index 821aa6ef9cc8661e5126b070dc5d35fedc09d1df..e29f2b651be929de68e8d50f759e7bef41c28c33 100644 --- a/include/client/open.inc.php +++ b/include/client/open.inc.php @@ -13,11 +13,14 @@ $form = null; if (!$info['topicId']) $info['topicId'] = $cfg->getDefaultTopicId(); +$forms = array(); if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) { - $form = $topic->getForm(); - if ($_POST && $form) { - $form = $form->instanciate(); - $form->isValidForClient(); + foreach ($topic->getForms() as $F) { + if ($_POST) { + $F = $F->instanciate(); + $F->isValidForClient(); + } + $forms[] = $F; } } @@ -29,9 +32,26 @@ if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) { <input type="hidden" name="a" value="open"> <table width="800" cellpadding="1" cellspacing="0" border="0"> <tbody> +<?php + if (!$thisclient) { + $uform = UserForm::getUserForm()->getForm($_POST); + if ($_POST) $uform->isValid(); + $uform->render(false); + } + else { ?> + <tr><td colspan="2"><hr /></td></tr> + <tr><td><?php echo __('Email'); ?>:</td><td><?php echo $thisclient->getEmail(); ?></td></tr> + <tr><td><?php echo __('Client'); ?>:</td><td><?php echo $thisclient->getName(); ?></td></tr> + <?php } ?> + </tbody> + <tbody> + <tr><td colspan="2"><hr /> + <div class="form-header" style="margin-bottom:0.5em"> + <b><?php echo __('Help Topic'); ?></b> + </div> + </td></tr> <tr> - <td class="required"><?php echo __('Help Topic');?>:</td> - <td> + <td colspan="2"> <select id="topicId" name="topicId" onchange="javascript: var data = $(':input[name]', '#dynamic-form').serialize(); $.ajax( @@ -59,28 +79,21 @@ if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) { <font class="error">* <?php echo $errors['topicId']; ?></font> </td> </tr> -<?php - if (!$thisclient) { - $uform = UserForm::getUserForm()->getForm($_POST); - if ($_POST) $uform->isValid(); - $uform->render(false); - } - else { ?> - <tr><td colspan="2"><hr /></td></tr> - <tr><td><?php echo __('Email'); ?>:</td><td><?php echo $thisclient->getEmail(); ?></td></tr> - <tr><td><?php echo __('Client'); ?>:</td><td><?php echo $thisclient->getName(); ?></td></tr> - <?php } ?> </tbody> <tbody id="dynamic-form"> - <?php if ($form) { + <?php foreach ($forms as $form) { + $hasFields = false; + foreach ($form->getFields() as $f) { + if ($f->isVisibleToUsers()) { + $hasFields = true; + break; + } + } + if (!$hasFields) + continue; include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php'); } ?> </tbody> - <tbody><?php - $tform = TicketForm::getInstance()->getForm($_POST); - if ($_POST) $tform->isValid(); - $tform->render(false); ?> - </tbody> <tbody> <?php if($cfg && $cfg->isCaptchaEnabled() && (!$thisclient || !$thisclient->isValid())) { diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php index a333ac2eef0d7aff31ee0a35d7cc18f4717c3bc5..6cf54466b08a0622693126a915ed503cf9dbba86 100644 --- a/include/client/templates/dynamic-form.tmpl.php +++ b/include/client/templates/dynamic-form.tmpl.php @@ -8,7 +8,7 @@ <?php print ($form instanceof DynamicFormEntry) ? $form->getForm()->getMedia() : $form->getMedia(); ?> <h3><?php echo Format::htmlchars($form->getTitle()); ?></h3> - <em><?php echo Format::htmlchars($form->getInstructions()); ?></em> + <div><?php echo Format::display($form->getInstructions()); ?></div> </div> </td></tr> <?php diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php index 2d6e64f496f86f99f3743d34869ed8b328c95723..1939cda0e211de50eaddf249e51243853750ad7a 100644 --- a/include/staff/dynamic-form.inc.php +++ b/include/staff/dynamic-form.inc.php @@ -7,9 +7,20 @@ if($form && $_REQUEST['a']!='add') { $url = "?id=".urlencode($_REQUEST['id']); $submit_text=__('Save Changes'); $info = $form->ht; - $trans['title'] = $form->getTranslateTag('title'); - $trans['instructions'] = $form->getTranslateTag('instructions'); + $trans = array( + 'title' => $form->getTranslateTag('title'), + 'instructions' => $form->getTranslateTag('instructions'), + ); $newcount=2; + $translations = CustomDataTranslation::allTranslations($trans, 'phrase'); + $_keys = array_flip($trans); + foreach ($translations as $t) { + if (!Internationalization::isLanguageInstalled($t->lang)) + continue; + // Create keys of [trans][de_DE][title] for instance + $info['trans'][$t->lang][$_keys[$t->object_hash]] + = Format::viewableImages($t->text); + } } else { $title = __('Add new custom form section'); $action = 'add'; @@ -39,23 +50,65 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </thead> <tbody style="vertical-align:top"> <tr> - <td width="180" class="required"><?php echo __('Title'); ?>:</td> - <td><input type="text" name="title" size="40" - data-translate-tag="<?php echo $trans['title']; ?>" + <td colspan="2"> + <table class="full-width"><tbody><tr><td style="vertical-align:top"> +<?php +$langs = Internationalization::getConfiguredSystemLanguages(); +if ($form && count($langs) > 1) { ?> + <ul class="vertical tabs" id="translations"> + <li class="empty"><i class="icon-globe" title="This content is translatable"></i></li> +<?php foreach ($langs as $tag=>$nfo) { ?> + <li class="<?php if ($tag == $cfg->getPrimaryLanguage()) echo "active"; + ?>"><a href="#translation-<?php echo $tag; ?>" title="<?php + echo Internationalization::getLanguageDescription($tag); + ?>"><span class="flag flag-<?php echo strtolower($nfo['flag']); ?>"></span> + </a></li> +<?php } ?> + </ul> +<?php +} ?> + </td> + <td id="translations_container"> + <div id="translation-<?php echo $cfg->getPrimaryLanguage(); ?>" class="tab_content" + lang="<?php echo $cfg->getPrimaryLanguage(); ?>"> + <div class="required"><?php echo __('Title'); ?>:</div> + <div> + <input type="text" name="title" size="60" value="<?php echo $info['title']; ?>"/> <i class="help-tip icon-question-sign" href="#form_title"></i> - <font class="error"><?php - if ($errors['title']) echo '<br/>'; echo $errors['title']; ?></font> - </td> - </tr> - <tr> - <td width="180"><?php echo __('Instructions'); ?>:</td> - <td><textarea name="instructions" rows="3" cols="40" - data-translate-tag="<?php echo $trans['instructions']; ?>"><?php - echo $info['instructions']; ?></textarea> + <div class="error"><?php + if ($errors['title']) echo '<br/>'; echo $errors['title']; ?></div> + </div> + <div style="margin-top: 8px"><?php echo __('Instructions'); ?>: <i class="help-tip icon-question-sign" href="#form_instructions"></i> - </td> - </tr> + </div> + <textarea name="instructions" rows="3" cols="40" class="richtext"><?php + echo $info['instructions']; ?></textarea> + </div> + +<?php if ($langs && $form) { + foreach ($langs as $tag=>$nfo) { + if ($tag == $cfg->getPrimaryLanguage()) + continue; ?> + <div id="translation-<?php echo $tag; ?>" class="tab_content" + style="display:none;" lang="<?php echo $tag; ?>"> + <div> + <div class="required"><?php echo __('Title'); ?>:</div> + <input type="text" name="trans[<?php echo $tag; ?>][title]" size="60" + value="<?php echo $info['trans'][$tag]['title']; ?>"/> + <i class="help-tip icon-question-sign" href="#form_title"></i> + </div> + <div style="margin-top: 8px"><?php echo __('Instructions'); ?>: + <i class="help-tip icon-question-sign" href="#form_instructions"></i> + </div> + <textarea name="trans[<?php echo $tag; ?>][instructions]" cols="21" rows="12" + style="width:100%" class="richtext"><?php + echo $info['trans'][$tag]['instructions']; ?></textarea> + </div> +<?php } +} ?> + </td></tr></tbody></table> + </td></tr> </tbody> </table> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index f09731af64a0cc7f5455b3677b408bb208af29aa..61e563bc077c5ec1a4eeef94191e6408ab981b67 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -167,209 +167,87 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> <tr> <th colspan="2"> - <em><strong><?php echo __('Filter Actions');?></strong>: <?php - echo __('Can be overwridden by other filters depending on processing order.');?> </em> + <em><strong><?php echo __('Filter Actions');?></strong>: + <div><?php + echo __('Can be overwridden by other filters depending on processing order.'); + ?><br/><?php + echo __('Actions are executed in the order declared below'); + ?></em> </th> </tr> - <tr> - <td width="180"> - <?php echo __('Reject Ticket');?>: - </td> - <td> - <input type="checkbox" name="reject_ticket" value="1" <?php echo $info['reject_ticket']?'checked="checked"':''; ?> > - <strong><font class="error"><?php echo __('Reject Ticket');?></font></strong> - <i class="help-tip icon-question-sign" href="#reject_ticket"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Reply-To Email');?>: - </td> - <td> - <input type="checkbox" name="use_replyto_email" value="1" <?php echo $info['use_replyto_email']?'checked="checked"':''; ?> > - <?php echo __('<strong>Use</strong> Reply-To Email');?> <em>(<?php echo __('if available');?>)</em> - <i class="help-tip icon-question-sign" href="#reply_to_email"></i></em> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Ticket auto-response');?>: - </td> - <td> - <input type="checkbox" name="disable_autoresponder" value="1" <?php echo $info['disable_autoresponder']?'checked="checked"':''; ?> > - <?php echo __('<strong>Disable</strong> auto-response.');?> - <i class="help-tip icon-question-sign" href="#ticket_auto_response"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Canned Response');?>: - </td> - <td> - <select name="canned_response_id"> - <option value="">— <?php echo __('None');?> —</option> - <?php - $sql='SELECT canned_id, title, isenabled FROM '.CANNED_TABLE .' ORDER by title'; - if ($res=db_query($sql)) { - while (list($id, $title, $isenabled)=db_fetch_row($res)) { - $selected=($info['canned_response_id'] && - $id==$info['canned_response_id']) - ? 'selected="selected"' : ''; - - if (!$isenabled) - $title .= ' ' . __('(disabled)'); - - echo sprintf('<option value="%d" %s>%s</option>', - $id, $selected, $title); - } - } - ?> - </select> - <i class="help-tip icon-question-sign" href="#canned_response"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Department');?>: - </td> - <td> - <select name="dept_id"> - <option value="">— <?php echo __('Default');?> —</option> - <?php - foreach (Dept::getDepartments() as $id=>$name) { - $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - ?> - </select> - <span class="error">* <?php echo $errors['dept_id']; ?></span> <i class="help-tip icon-question-sign" href="#department"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Status'); ?>: - </td> - <td> - <span> - <select name="status_id"> - <option value="">— <?php echo __('Default'); ?> —</option> - <?php - foreach (TicketStatusList::getStatuses() as $status) { - $name = $status->getName(); - if (!($isenabled = $status->isEnabled())) - $name.=' '.__('(disabled)'); - - echo sprintf('<option value="%d" %s %s>%s</option>', - $status->getId(), - ($info['status_id'] == $status->getId()) - ? 'selected="selected"' : '', - $isenabled ? '' : 'disabled="disabled"', - $name - ); - } - ?> - </select> - - <span class="error"><?php echo $errors['status_id']; ?></span> - <i class="help-tip icon-question-sign" href="#status"></i> - </span> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Priority');?>: - </td> - <td> - <select name="priority_id"> - <option value="">— <?php echo __('Default');?> —</option> - <?php - $sql='SELECT priority_id,priority_desc FROM '.PRIORITY_TABLE.' pri ORDER by priority_urgency DESC'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$name)=db_fetch_row($res)){ - $selected=($info['priority_id'] && $id==$info['priority_id'])?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - } - ?> - </select> - <span class="error">* <?php echo $errors['priority_id']; ?></span> - <i class="help-tip icon-question-sign" href="#priority"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('SLA Plan');?>: - </td> - <td> - <select name="sla_id"> - <option value="0">— <?php echo __('System Default');?> —</option> - <?php - if($slas=SLA::getSLAs()) { - foreach($slas as $id =>$name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, ($info['sla_id']==$id)?'selected="selected"':'',$name); - } - } - ?> - </select> - <span class="error"> <?php echo $errors['sla_id']; ?></span> - <i class="help-tip icon-question-sign" href="#sla_plan"></i> - </td> - </tr> - <tr> - <td width="180"> - <?php echo __('Auto-assign To');?>: - </td> - <td> - <select name="assign"> - <option value="0">— <?php echo __('Unassigned');?> —</option> - <?php - if (($users=Staff::getStaffMembers())) { - echo '<OPTGROUP label="'.__('Agents').'">'; - foreach($users as $id => $name) { - $name = new PersonsName($name); - $k="s$id"; - $selected = ($info['assign']==$k || $info['staff_id']==$id)?'selected="selected"':''; - ?> - <option value="<?php echo $k; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> - <?php - } - echo '</OPTGROUP>'; - } - $sql='SELECT team_id, isenabled, name FROM '.TEAM_TABLE .' ORDER BY name'; - if ($teams = Team::getTeams()) { - echo '<OPTGROUP label="'.__('Teams').'">'; - foreach ($teams as $id=>$name) { - $k="t$id"; - $selected = ($info['assign']==$k || $info['team_id']==$id)?'selected="selected"':''; - ?> - <option value="<?php echo $k; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> - <?php - } - echo '</OPTGROUP>'; - } - ?> - </select> - <span class="error"> <?php echo - $errors['assign']; ?></span><i class="help-tip icon-question-sign" href="#auto_assign"></i> + </tbody> + <tbody id="dynamic-actions" class="sortable-rows"> +<?php +$existing = array(); +if ($filter) { foreach ($filter->getActions() as $A) { + $existing[] = $A->type; +?> + <tr style="background-color:white"><td><i class="icon-bolt icon-large icon-muted"></i> + <?php echo $A->getImpl()->getName(); ?>:</td> + <td><div style="position:relative"><?php + $form = $A->getImpl()->getConfigurationForm($_POST ?: false); + // XXX: Drop this when the ORM supports proper caching + $form->isValid(); + include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php'; +?> + <input type="hidden" name="actions[]" value="I<?php echo $A->getId(); ?>"/> + <div class="pull-right" style="position:absolute;top:2px;right:2px;"> + <a href="#" title="<?php echo __('clear'); ?>" onclick="javascript: + if (!confirm(__('You sure?'))) + return false; + $(this).closest('td').find('input[name=\'actions[]\']') + .val(function(i,v) { return 'D' + v.substring(1); }); + $(this).closest('tr').fadeOut(400, function() { $(this).hide(); }); + return false;"><i class="icon-trash"></i></a> + </div> +</div> </td> </tr> +<?php } } ?> + </tbody> + <tbody> <tr> - <td width="180"> - <?php echo __('Help Topic'); ?> - </td> + <td><strong><i class="icon-plus-sign"></i> + <?php echo __('Add'); ?>: + </strong></td> <td> - <select name="topic_id"> - <option value="0" selected="selected">— <?php - echo __('Unchanged'); ?> —</option> - <?php - foreach (Topic::getAllHelpTopics(true) as $id=>$name) { - $selected=($info['topic_id'] && $id==$info['topic_id'])?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - ?> + <select name="new-action" id="new-action-select" + onchange="javascript: $('#new-action-btn').trigger('click');"> + <option value=""><?php echo __('— Select an Action —'); ?></option> +<?php +$current_group = ''; +foreach (FilterAction::allRegistered() as $group=>$actions) { + if ($group && $current_group != $group) { + if ($current_group) echo '</optgroup>'; + $current_group = $group; + ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php + } + foreach ($actions as $type=>$name) { +?> + <option data-title="<?php echo $name; ?>" value="<?php echo $type; ?>" + data-multi-use="<?php echo $mu = FilterAction::lookupByType($type)->hasFlag(TriggerAction::FLAG_MULTI_USE); ?> " <?php + if (in_array($type, $existing) && !$mu) echo 'disabled="disabled"'; + ?>><?php echo $name; ?></option> +<?php } +} ?> </select> - <span class="error"><?php echo $errors['topic_id']; ?></span><i class="help-tip icon-question-sign" href="#help_topic"></i> + <input id="new-action-btn" type="button" value="<?php echo __('Add'); ?>" + onclick="javascript: + var dropdown = $('#new-action-select'), selected = dropdown.find(':selected'); + dropdown.val(''); + $('#dynamic-actions') + .append($('<tr></tr>') + .append($('<td></td>') + .text(selected.data('title') + ':') + ).append($('<td></td>') + .append($('<em></em>').text(__('Loading ...'))) + .load('ajax.php/filter/action/' + selected.val() + '/config', function() { + if (!selected.data('multiUse')) selected.prop('disabled', true); + }) + ) + ).append( + $('<input>').attr({type:'hidden',name:'actions[]',value:'N'+selected.val()}) + );"/> </td> </tr> <tr> @@ -392,3 +270,12 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="filters.php"'> </p> </form> +<script type="text/javascript"> + var fixHelper = function(e, ui) { + ui.children().each(function() { + $(this).width($(this).width()); + }); + return ui; + }; + $('#dynamic-actions').sortable({helper: fixHelper, opacity: 0.5}); +</script> diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index 6fb6371bc77cd7a61e03ee1830c950cf2ea72504..cf43338fa5abf33fc15477418cc5d485d6a48653 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -10,6 +10,7 @@ if($topic && $_REQUEST['a']!='add') { $info['pid']=$topic->getPid(); $trans['name'] = $topic->getTranslateTag('name'); $qs += array('id' => $topic->getId()); + $forms = $topic->getForms(); } else { $title=__('Add New Help Topic'); $action='create'; @@ -21,22 +22,31 @@ if($topic && $_REQUEST['a']!='add') { } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> + +<h2 style="font-weight: normal"><?php echo $title; ?> + <i class="help-tip icon-question-sign" href="#help_topic_information"></i> + </h2> +<?php if ($topic) { ?> + <div class="big"><strong><?php echo $topic->getLocal('topic'); ?></strong></div> +<?php } ?> + +<br/> + +<ul class="tabs" id="topic-tabs"> + <li class="active"><a href="#info"><i class="icon-info-sign"></i> Help Topic Information</a></li> + <li><a href="#routing"><i class="icon-ticket"></i> New ticket options</a></li> + <li><a href="#forms"><i class="icon-paste"></i> Forms</a></li> +</ul> + <form action="helptopics.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> - <h2><?php echo __('Help Topic');?></h2> - <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th colspan="2"> - <h4><?php echo $title; ?></h4> - <em><?php echo __('Help Topic Information');?> - <i class="help-tip icon-question-sign" href="#help_topic_information"></i></em> - </th> - </tr> - </thead> + +<div id="topic-tabs_container"> +<div class="tab_content" id="info"> + <table class="table" border="0" cellspacing="0" cellpadding="2"> <tbody> <tr> <td width="180" class="required"> @@ -53,8 +63,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php echo __('Status');?>: </td> <td> - <input type="radio" name="isactive" value="1" <?php echo $info['isactive']?'checked="checked"':''; ?>><?php echo __('Active'); ?> - <input type="radio" name="isactive" value="0" <?php echo !$info['isactive']?'checked="checked"':''; ?>><?php echo __('Disabled'); ?> + <input type="radio" name="isactive" value="1" <?php echo $info['isactive']?'checked="checked"':''; ?>> <?php echo __('Active'); ?> + <input type="radio" name="isactive" value="0" <?php echo !$info['isactive']?'checked="checked"':''; ?>> <?php echo __('Disabled'); ?> <span class="error">* </span> <i class="help-tip icon-question-sign" href="#status"></i> </td> </tr> @@ -63,8 +73,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php echo __('Type');?>: </td> <td> - <input type="radio" name="ispublic" value="1" <?php echo $info['ispublic']?'checked="checked"':''; ?>><?php echo __('Public'); ?> - <input type="radio" name="ispublic" value="0" <?php echo !$info['ispublic']?'checked="checked"':''; ?>><?php echo __('Private/Internal'); ?> + <input type="radio" name="ispublic" value="1" <?php echo $info['ispublic']?'checked="checked"':''; ?>> <?php echo __('Public'); ?> + <input type="radio" name="ispublic" value="0" <?php echo !$info['ispublic']?'checked="checked"':''; ?>> <?php echo __('Private/Internal'); ?> <span class="error">* </span> <i class="help-tip icon-question-sign" href="#type"></i> </td> </tr> @@ -87,28 +97,26 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> </tr> - <tr><th colspan="2"><em><?php echo __('New ticket options');?></em></th></tr> - <tr> - <td><strong><?php echo __('Custom Form'); ?></strong>:</td> - <td><select name="form_id"> - <option value="0" <?php -if ($info['form_id'] == '0') echo 'selected="selected"'; - ?>>— <?php echo __('None'); ?> —</option> - <option value="<?php echo Topic::FORM_USE_PARENT; ?>" <?php -if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"'; - ?>>— <?php echo __('Use Parent Form'); ?> —</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"'; ?>> - <?php echo $group->get('title'); ?> - </option> - <?php } ?> - </select> - <span class="error"> <?php echo $errors['form_id']; ?></span> - <i class="help-tip icon-question-sign" href="#custom_form"></i> - </td> - </tr> + </tbody> + </table> + + <div style="padding:8px 3px;border-bottom: 2px dotted #ddd;"> + <strong class="big"><?php echo __('Internal Notes');?></strong><br/> + <?php echo __("be liberal, they're internal.");?> + </div> + + <textarea class="richtext no-bar" name="notes" cols="21" + rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea> + +</div> + +<div class="hidden tab_content" id="routing"> +<div style="padding:8px 0;border-bottom: 2px dotted #ddd;"> +<div><b class="big"><?php echo __('New ticket options');?></b></div> +</div> + + <table class="table" border="0" cellspacing="0" cellpadding="2"> + <tbody> <tr> <td width="180" class="required"> <?php echo __('Department'); ?>: @@ -127,6 +135,62 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"'; <i class="help-tip icon-question-sign" href="#department"></i> </td> </tr> + <tr class="border"> + <td> + <?php echo __('Ticket Number Format'); ?>: + </td> + <td> + <label> + <input type="radio" name="custom-numbers" value="0" <?php echo !$info['custom-numbers']?'checked="checked"':''; ?> + onchange="javascript:$('#custom-numbers').hide();"> <?php echo __('System Default'); ?> + </label> <label> + <input type="radio" name="custom-numbers" value="1" <?php echo $info['custom-numbers']?'checked="checked"':''; ?> + onchange="javascript:$('#custom-numbers').show(200);"> <?php echo __('Custom'); ?> + </label> <i class="help-tip icon-question-sign" href="#custom_numbers"></i> + </td> + </tr> + </tbody> + <tbody id="custom-numbers" style="<?php if (!$info['custom-numbers']) echo 'display:none'; ?>"> + <tr> + <td style="padding-left:20px"> + <?php echo __('Format'); ?>: + </td> + <td> + <input type="text" name="number_format" value="<?php echo $info['number_format']; ?>"/> + <span class="faded"><?php echo __('e.g.'); ?> <span id="format-example"><?php + if ($info['custom-numbers']) { + if ($info['sequence_id']) + $seq = Sequence::lookup($info['sequence_id']); + if (!isset($seq)) + $seq = new RandomSequence(); + echo $seq->current($info['number_format']); + } ?></span></span> + <div class="error"><?php echo $errors['number_format']; ?></div> + </td> + </tr> + <tr> +<?php $selected = 'selected="selected"'; ?> + <td style="padding-left:20px"> + <?php echo __('Sequence'); ?>: + </td> + <td> + <select name="sequence_id"> + <option value="0" <?php if ($info['sequence_id'] == 0) echo $selected; + ?>>— <?php echo __('Random'); ?> —</option> +<?php foreach (Sequence::objects() as $s) { ?> + <option value="<?php echo $s->id; ?>" <?php + if ($info['sequence_id'] == $s->id) echo $selected; + ?>><?php echo $s->name; ?></option> +<?php } ?> + </select> + <button class="action-button pull-right" onclick="javascript: + $.dialog('ajax.php/sequence/manage', 205); + return false; + "><i class="icon-gear"></i> <?php echo __('Manage'); ?></button> + </td> + </tr> + </tbody> + <tbody> <tr> <td width="180"> <?php echo __('Status'); ?>: @@ -266,75 +330,95 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"'; <i class="help-tip icon-question-sign" href="#ticket_auto_response"></i> </td> </tr> - <tr> - <td> - <?php echo __('Ticket Number Format'); ?>: - </td> - <td> - <label> - <input type="radio" name="custom-numbers" value="0" <?php echo !$info['custom-numbers']?'checked="checked"':''; ?> - onchange="javascript:$('#custom-numbers').hide();"> <?php echo __('System Default'); ?> - </label> <label> - <input type="radio" name="custom-numbers" value="1" <?php echo $info['custom-numbers']?'checked="checked"':''; ?> - onchange="javascript:$('#custom-numbers').show(200);"> <?php echo __('Custom'); ?> - </label> <i class="help-tip icon-question-sign" href="#custom_numbers"></i> - </td> - </tr> </tbody> - <tbody id="custom-numbers" style="<?php if (!$info['custom-numbers']) echo 'display:none'; ?>"> - <tr> - <td style="padding-left:20px"> - <?php echo __('Format'); ?>: - </td> - <td> - <input type="text" name="number_format" value="<?php echo $info['number_format']; ?>"/> - <span class="faded"><?php echo __('e.g.'); ?> <span id="format-example"><?php - if ($info['custom-numbers']) { - if ($info['sequence_id']) - $seq = Sequence::lookup($info['sequence_id']); - if (!isset($seq)) - $seq = new RandomSequence(); - echo $seq->current($info['number_format']); - } ?></span></span> - <div class="error"><?php echo $errors['number_format']; ?></div> - </td> - </tr> + </table> +</div> + +<div class="hidden tab_content" id="forms"> + <table id="topic-forms" class="table" border="0" cellspacing="0" cellpadding="2"> + +<?php +$current_forms = array(); +foreach ($forms as $F) { + $current_forms[] = $F->id; ?> + <tbody data-form-id="<?php echo $F->get('id'); ?>"> <tr> -<?php $selected = 'selected="selected"'; ?> - <td style="padding-left:20px"> - <?php echo __('Sequence'); ?>: - </td> - <td> - <select name="sequence_id"> - <option value="0" <?php if ($info['sequence_id'] == 0) echo $selected; - ?>>— <?php echo __('Random'); ?> —</option> -<?php foreach (Sequence::objects() as $s) { ?> - <option value="<?php echo $s->id; ?>" <?php - if ($info['sequence_id'] == $s->id) echo $selected; - ?>><?php echo $s->name; ?></option> + <td class="handle" colspan="6"> + <input type="hidden" name="forms[]" value="<?php echo $F->get('id'); ?>" /> + <div class="pull-right"> + <i class="icon-large icon-move icon-muted"></i> +<?php if ($F->get('type') != 'T') { ?> + <a href="#" title="<?php echo __('Delete'); ?>" onclick="javascript: + if (confirm(__('You sure?'))) + var tbody = $(this).closest('tbody'); + tbody.fadeOut(function(){this.remove()}); + $(this).closest('form') + .find('[name=form_id] [value=' + tbody.data('formId') + ']') + .prop('disabled', false); + return false;"><i class="icon-large icon-trash"></i></a> <?php } ?> - </select> - <button class="action-button pull-right" onclick="javascript: - $.dialog('ajax.php/sequence/manage', 205); - return false; - "><i class="icon-gear"></i> <?php echo __('Manage'); ?></button> + </div> + <div><strong><?php echo Format::htmlchars($F->getLocal('title')); ?></strong></div> + <div><?php echo Format::display($F->getLocal('instructions')); ?></div> </td> </tr> - </tbody> - <tbody> <tr> - <th colspan="2"> - <em><strong><?php echo __('Internal Notes');?></strong>: <?php echo __("be liberal, they're internal.");?></em> - </th> + <th><?php echo __('Enable'); ?></th> + <th><?php echo __('Label'); ?></th> + <th><?php echo __('Type'); ?></th> + <th><?php echo __('Visibility'); ?></th> + <th><?php echo __('Variable'); ?></th> </tr> + <?php + foreach ($F->getFields() as $f) { ?> <tr> - <td colspan=2> - <textarea class="richtext no-bar" name="notes" cols="21" - rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea> - </td> + <td><input type="checkbox" name="fields[]" value="<?php + echo $f->get('id'); ?>" <?php + if ($f->isEnabled()) echo 'checked="checked"'; ?>/></td> + <td><?php echo $f->get('label'); ?></td> + <td><?php $t=FormField::getFieldType($f->get('type')); echo __($t[0]); ?></td> + <td><?php echo $f->getVisibilityDescription(); ?></td> + <td><?php echo $f->get('name'); ?></td> </tr> + <?php } ?> </tbody> -</table> + <?php } ?> + </table> + + <br/> + <strong><?php echo __('Add Custom Form'); ?></strong>: + <select name="form_id" onchange="javascript: + event.preventDefault(); + var $this = $(this), + val = $this.val(); + if (!val) return; + $.ajax({ + url: 'ajax.php/form/' + val + '/fields/view', + dataType: 'json', + success: function(json) { + if (json.success) { + $(json.html).appendTo('#topic-forms').effect('highlight'); + $this.find(':selected').prop('disabled', true); + } + } + });"> + <option value=""><?php echo '— '.__('Add a custom form') . ' —'; ?></option> + <?php foreach (DynamicForm::objects()->filter(array('type'=>'G')) as $F) { ?> + <option value="<?php echo $F->get('id'); ?>" + <?php if (in_array($F->id, $current_forms)) + echo 'disabled="disabled"'; ?> + <?php if ($F->get('id') == $info['form_id']) + echo 'selected="selected"'; ?>> + <?php echo $F->getLocal('title'); ?> + </option> + <?php } ?> + </select> + <span class="error"> <?php echo $errors['form_id']; ?></span> + <i class="help-tip icon-question-sign" href="#custom_form"></i> +</div> + +</div> + <p style="text-align:center;"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> <input type="reset" name="reset" value="<?php echo __('Reset');?>"> @@ -355,4 +439,19 @@ $(function() { $('[name=sequence_id]').on('change', update_example); $('[name=number_format]').on('keyup', update_example); }); +$('table#topic-forms').sortable({ + items: 'tbody', + handle: 'td.handle', + tolerance: 'pointer', + forcePlaceholderSize: true, + helper: function(e, ui) { + ui.children().each(function() { + $(this).children().each(function() { + $(this).width($(this).width()); + }); + }); + ui=ui.clone().css({'background-color':'white', 'opacity':0.8}); + return ui; + } +}).disableSelection(); </script> diff --git a/include/staff/page.inc.php b/include/staff/page.inc.php index cbc2d2526f1ed37e65952a3efb4e309208825c63..6326500aba5bd1b6d545eb1f8eae59bb7645cbda 100644 --- a/include/staff/page.inc.php +++ b/include/staff/page.inc.php @@ -116,10 +116,11 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <li><a href="#notes"><?php echo __('Internal Notes'); ?></a></li> </ul> <div class="tab_content active" id="content"> +<table class="full-width"><tbody><tr><td style="vertical-align:top"> <?php $langs = Internationalization::getConfiguredSystemLanguages(); if ($page && count($langs) > 1) { ?> - <ul class="vertical left tabs"> + <ul class="vertical tabs" id="translations"> <li class="empty"><i class="icon-globe" title="This content is translatable"></i></li> <?php foreach ($langs as $tag=>$nfo) { ?> <li class="<?php if ($tag == $cfg->getPrimaryLanguage()) echo "active"; @@ -131,15 +132,18 @@ if ($page && count($langs) > 1) { ?> </ul> <?php } ?> - <div id="msg_info" style="margin:0 55px"> +</td> +<td id="translations_container" style="padding-left: 10px"> + <div id="msg_info"> <em><i class="icon-info-sign"></i> <?php echo __( 'Ticket variables are only supported in thank-you pages.' - ); ?></em></div> + ); ?></em> + </div> - <div id="translation-<?php echo $cfg->getPrimaryLanguage(); ?>" class="tab_content" style="margin:0 45px" + <div id="translation-<?php echo $cfg->getPrimaryLanguage(); ?>" class="tab_content" lang="<?php echo $cfg->getPrimaryLanguage(); ?>"> - <textarea name="body" cols="21" rows="12" style="width:98%;" class="richtext draft" + <textarea name="body" cols="21" rows="12" style="width:100%" class="richtext draft" <?php list($draft, $attrs) = Draft::getDraftAndDataAttrs('page', $info['id'], $info['body']); echo $attrs; ?>><?php echo $draft ?: $info['body']; ?></textarea> @@ -150,17 +154,18 @@ if ($page && count($langs) > 1) { ?> if ($tag == $cfg->getPrimaryLanguage()) continue; ?> <div id="translation-<?php echo $tag; ?>" class="tab_content" - style="display:none;margin:0 45px" lang="<?php echo $tag; ?>"> + style="display:none;" lang="<?php echo $tag; ?>"> <textarea name="trans[<?php echo $tag; ?>][body]" cols="21" rows="12" - style="width:98%;" class="richtext draft" + style="width:100%" class="richtext draft" <?php list($draft, $attrs) = Draft::getDraftAndDataAttrs('page', $info['id'].'.'.$tag, $info['trans'][$tag]); echo $attrs; ?>><?php echo $draft ?: $info['trans'][$tag]; ?></textarea> </div> <?php } } ?> +</td></tr></tbody></table> - <div class="error" style="margin: 5px 55px"><?php echo $errors['body']; ?></div> + <div class="error" style="margin: 5px 0"><?php echo $errors['body']; ?></div> <div class="clear"></div> </div> <div class="tab_content" style="display:none" id="notes"> diff --git a/include/staff/templates/dynamic-form-simple.tmpl.php b/include/staff/templates/dynamic-form-simple.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..1c31b8fed135211e77fe932922e44f989cabaeda --- /dev/null +++ b/include/staff/templates/dynamic-form-simple.tmpl.php @@ -0,0 +1,32 @@ + <?php + echo $form->getMedia(); + foreach ($form->getFields() as $name=>$f) { ?> + <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; + ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> + <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> + <label for="<?php echo $f->getWidget()->name; ?>"> + <?php if ($f->get('label')) { ?> + <?php echo Format::htmlchars($f->get('label')); ?>: + <?php } ?> + <?php if ($f->get('required')) { ?> + <span class="error">*</span> + <?php } ?> + </label> + <?php + if ($f->get('hint')) { ?> + <br/><em style="color:gray;display:inline-block"><?php + echo Format::htmlchars($f->get('hint')); ?></em> + <?php + } ?> + </div><div> + <?php + $f->render(); + ?> + </div> + <?php + foreach ($f->errors() as $e) { ?> + <div class="error"><?php echo $e; ?></div> + <?php } ?> + </div> + <?php } + ?> diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php index 487ce9a56f863a735abe6808f29a95c56bf2dc9f..5002700fce79396f2f86098a030ac1c061cdb357 100644 --- a/include/staff/templates/dynamic-form.tmpl.php +++ b/include/staff/templates/dynamic-form.tmpl.php @@ -20,8 +20,7 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?> <?php } ?> <?php if ($form->getTitle()) { ?> <tr><th colspan="2"> - <em><strong><?php echo Format::htmlchars($form->getTitle()); ?></strong>: - <?php echo Format::htmlchars($form->getInstructions()); ?> + <em> <?php if ($options['mode'] == 'edit') { ?> <div class="pull-right"> <?php if ($options['entry'] @@ -32,12 +31,17 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?> <?php } ?> <i class="icon-sort" title="Drag to Sort"></i> </div> -<?php } ?></em> +<?php } ?> + <strong><?php echo Format::htmlchars($form->getTitle()); ?></strong>: + <div><?php echo Format::display($form->getInstructions()); ?></div> + </em> </th></tr> <?php } foreach ($form->getFields() as $field) { try { + if (!$field->isEnabled()) + continue; if ($options['mode'] == 'edit' && !$field->isEditableToStaff()) continue; } diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index fcebc13d474f66469e7eb2e188f55505de61e165..e9edb4643ade0c9aa8dbf48f3b3c498c641a4ddc 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -9,12 +9,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); if (!$info['topicId']) $info['topicId'] = $cfg->getDefaultTopicId(); -$form = null; +$forms = array(); if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) { - $form = $topic->getForm(); - if ($_POST && $form) { - $form = $form->instanciate(); - $form->isValid(); + foreach ($topic->getForms() as $F) { + if ($_POST) { + $F = $F->instanciate(); + $F->isValidForClient(); + } + $forms[] = $F; } } @@ -156,9 +158,9 @@ if ($_POST) $id, ($info['topicId']==$id)?'selected="selected"':'', $selected, $name); } - if (count($topics) == 1 && !$form) { + if (count($topics) == 1 && !$forms) { if (($T = Topic::lookup($id))) - $form = $T->getForm(); + $forms = $T->getForms(); } } ?> @@ -260,18 +262,21 @@ if ($_POST) </tbody> <tbody id="dynamic-form"> <?php - if ($form) { + foreach ($forms as $form) { + $hasFields = false; + foreach ($form->getFields() as $f) { + if ($f->isVisibleToStaff()) { + $hasFields = true; + break; + } + } + if (!$hasFields) + continue; print $form->getForm()->getMedia(); include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php'); } ?> </tbody> - <tbody> <?php - $tform = TicketForm::getInstance()->getForm($_POST); - if ($_POST) $tform->isValid(); - $tform->render(true); - ?> - </tbody> <tbody> <?php //is the user allowed to post replies?? diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index de9ccf80153ce21876688d343459a1cb08dc6b17..5bda5f6374998dd31bf662c8fe8a37b3e8375eb1 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -466,7 +466,7 @@ $tcount+= $ticket->getNumNotes(); $msgId = $entry['id']; } } else { - echo '<p>'.__('Error fetching ticket thread - get technical help.').'</p>'; + echo '<p><em>'.__('No entries have been posted to this ticket.').'</em></p>'; }?> <div class="clear" style="padding-bottom:10px;"></div> <?php if($errors['err']) { ?> diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index 209fca107da75265e35d6cd2f12d71d5e89213a5..3cbda6fa0ad2b31ab58b8b2c3201f386774c454a 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -5cd0a25a54fd27ed95f00d62edda4c6d +a22c2b4ff54ce5aa61e94124a73e6eac diff --git a/include/upgrader/streams/core/5cd0a25a-a22c2b4f.cleanup.sql b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.cleanup.sql new file mode 100644 index 0000000000000000000000000000000000000000..dd4a3f887dee2d7a24c557287cf94052c4fd2640 --- /dev/null +++ b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.cleanup.sql @@ -0,0 +1,21 @@ +/** + * @signature 0e47d678f50874fa0d33e1e3759f657e + * @version v1.9.6 + * @title Make fields disable-able per help topic + */ + +ALTER TABLE `%TABLE_PREFIX%help_topic` + DROP `form_id` int(10) unsigned NOT NULL default '0'; + +ALTER TABLE `%TABLE_PREFIX%filter` + DROP `reject_ticket`, + DROP `use_replyto_email`, + DROP `disable_autoresponder`, + DROP `canned_response_id`, + DROP `status_id`, + DROP `priority_id`, + DROP `dept_id`, + DROP `staff_id`, + DROP `team_id`, + DROP `sla_id`, + DROP `form_id`; diff --git a/include/upgrader/streams/core/5cd0a25a-a22c2b4f.patch.sql b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..509c83519cff637457c87a20df6b68c1db00de6a --- /dev/null +++ b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.patch.sql @@ -0,0 +1,138 @@ +/** + * @signature a22c2b4ff54ce5aa61e94124a73e6eac + * @version v1.9.6 + * @title Make fields disable-able per help topic + * + * This patch adds the ability to associate more than one extra form with a + * help topic, allows specifying the sort order of each form, including the + * main ticket details forms, and also allows disabling any of the fields on + * any of the associated forms, including the issue details field. + * + * This patch migrates the columnar layout of the %filter table into a new + * %filter_action table. The cleanup portion of the script will drop the old + * columns from the %filter table. + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_action`; +CREATE TABLE `%TABLE_PREFIX%filter_action` ( + `id` int(11) unsigned NOT NULL auto_increment, + `filter_id` int(10) unsigned NOT NULL, + `sort` int(10) unsigned NOT NULL default 0, + `type` varchar(24) NOT NULL, + `configuration` text, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `filter_id` (`filter_id`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'reject', '', `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `reject_ticket` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'replyto', '{"enable":true}', `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `use_replyto_email` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'noresp', '{"enable":true}', `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `disable_autoresponder` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'canned', CONCAT('{"canned_id":',`canned_response_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `canned_response_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'dept', CONCAT('{"dept_id":',`dept_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `dept_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'pri', CONCAT('{"priority_id":',`priority_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `priority_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'sla', CONCAT('{"sla_id":',`sla_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `sla_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'team', CONCAT('{"team_id":',`team_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `team_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'agent', CONCAT('{"staff_id":',`staff_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `staff_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'topic', CONCAT('{"topic_id":',`topic_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `topic_id` != 0; + +INSERT INTO `%TABLE_PREFIX%filter_action` + (`filter_id`, `type`, `configuration`, `updated`) + SELECT `id`, 'status', CONCAT('{"status_id":',`status_id`,'}'), `updated` + FROM `%TABLE_PREFIX%filter` + WHERE `status_id` != 0; + +ALTER TABLE `%TABLE_PREFIX%form` + ADD `pid` int(10) unsigned DEFAULT NULL AFTER `id`, + ADD `name` varchar(64) NOT NULL DEFAULT '' AFTER `instructions`; + +ALTER TABLE `%TABLE_PREFIX%form_entry` + ADD `extra` text AFTER `sort`; + +CREATE TABLE `%TABLE_PREFIX%help_topic_form` ( + `id` int(11) unsigned NOT NULL auto_increment, + `topic_id` int(11) unsigned NOT NULL default 0, + `form_id` int(10) unsigned NOT NULL default 0, + `sort` int(10) unsigned NOT NULL default 1, + `extra` text, + PRIMARY KEY (`topic_id`, `form_id`) +) DEFAULT CHARSET=utf8; + +-- Handle A4 / A3 / A2 / A1 help topics. For these, consider the forms +-- associated with each, which should sort above the ticket details form, as +-- the graphical interface rendered it suchly. Then, consider cascaded +-- forms, where the parent form was specified on a child. +insert into `%TABLE_PREFIX%help_topic_form` + (`topic_id`, `form_id`, `sort`) + select A1.topic_id, case + when A3.form_id = 4294967295 then A4.form_id + when A2.form_id = 4294967295 then A3.form_id + when A1.form_id = 4294967295 then A2.form_id + else COALESCE(A4.form_id, A3.form_id, A2.form_id, A1.form_id) end as form_id, 1 as `sort` + from `%TABLE_PREFIX%help_topic` A1 + left join `%TABLE_PREFIX%help_topic` A2 on (A2.topic_id = A1.topic_pid) + left join `%TABLE_PREFIX%help_topic` A3 on (A3.topic_id = A2.topic_pid) + left join `%TABLE_PREFIX%help_topic` A4 on (A4.topic_id = A3.topic_pid) + having `form_id` > 0 + union + select A2.topic_id, id as `form_id`, 2 as `sort` + from `%TABLE_PREFIX%form` A1 + join `%TABLE_PREFIX%help_topic` A2 + where A1.`type` = 'T'; + +ALTER TABLE `%TABLE_PREFIX%help_topic` + DROP `form_id` int(10) unsigned NOT NULL default '0'; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = 'a22c2b4ff54ce5aa61e94124a73e6eac' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/streams/core/5cd0a25a-a22c2b4f.task.php b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.task.php new file mode 100644 index 0000000000000000000000000000000000000000..0379bd4becece7cb4cf4688acfc891b1c08098ca --- /dev/null +++ b/include/upgrader/streams/core/5cd0a25a-a22c2b4f.task.php @@ -0,0 +1,14 @@ +<?php + +class InstructionPorter extends MigrationTask { + var $description = "Converting custom form instructions to HTML"; + + function run($max_time) { + foreach (DynamicForm::objects() as $F) { + $F->instructions = Format::htmlchars($F->get('instructions')); + $F->save(); + } + } +} + +return 'InstructionsPorter'; diff --git a/scp/ajax.php b/scp/ajax.php index 131243adeb18c39b487631bcb9c5aee7b50044c1..140085b3910165086b67e1e9f4d61428777569e2 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -57,7 +57,11 @@ $dispatcher = patterns('', url_post('^field-config/(?P<id>\d+)$', 'saveFieldConfiguration'), url_delete('^answer/(?P<entry>\d+)/(?P<field>\d+)$', 'deleteAnswer'), url_post('^upload/(\d+)?$', 'upload'), - url_post('^upload/(\w+)?$', 'attach') + url_post('^upload/(\w+)?$', 'attach'), + url_get('^(?P<id>\d+)/fields/view$', 'getAllFields') + )), + url('^/filter/', patterns('ajax.filter.php:FilterAjaxAPI', + url_get('^action/(?P<type>\w+)/config$', 'getFilterActionForm') )), url('^/list/', patterns('ajax.forms.php:DynamicFormsAjaxAPI', url_get('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'getListItemProperties'), diff --git a/scp/css/scp.css b/scp/css/scp.css index 0d76bf2ba467afd9ff21f10e99c9f039bdc3fb2e..ebb60c9ba34d2b21761a3fdb59f148524e7ef8cc 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -51,6 +51,10 @@ div#header a { clear:both; } +.big { + font-size: 110%; +} + .faded { color:#666; } @@ -589,6 +593,27 @@ a.print { padding:2px; } +.table { + width: 100%; + border-collapse: collapse; + margin-top:3px; +} + +.table tr.header td, +.table th { + font-weight: bold; + text-align: left; + height: 24px; + background: #f0f0f0; +} + +.table tr { + border-bottom:1px dotted #ddd; +} +.table td:not(:empty) { + height: 24px; +} + .form_table { margin-top:3px; border-left:1px solid #ddd; @@ -607,10 +632,16 @@ table.fixed { border-collapse: collapse; width: 100%; } -table.fixed td { +table.fixed > thead > tr > th, +table.fixed > thead > tr > td, +table.fixed > tbody > tr > td, +table.fixed > tr > td { width: 180px; } -table.fixed td + td { +table.fixed > thead > tr > th + th, +table.fixed > thead > tr > td + td, +table.fixed > tbody > tr > td + td, +table.fixed > tr > td + td { width: auto; } @@ -1459,12 +1490,12 @@ time { border:none; } -.dialog .custom-field .field-label { +.custom-field .field-label { margin-left: 3px; margin-right: 3px; } -.dialog .custom-field + .custom-field { - margin-top: 8px; +.custom-field + .custom-field { + margin-top: 5px; } .dialog label.fixed-size { width:100px; @@ -2102,3 +2133,11 @@ td.indented { position: relative; top: -1px; } + +#topic-forms tbody + tbody td.handle { + padding-top: 15px; +} + +#dynamic-actions > tr > td { + padding: 5px; +} diff --git a/scp/css/translatable.css b/scp/css/translatable.css index d949d2d4fafac616e4fd4764847044e7116b90cf..3bece152ac700f39a6e7ff6404e3e7ae14814044 100644 --- a/scp/css/translatable.css +++ b/scp/css/translatable.css @@ -100,13 +100,14 @@ div.translatable { box-shadow: inset 0 1px 1px rgba(0,0,0,0.05); display: inline-block; white-space: nowrap; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; border-right: none; - padding: 0 5px 2px; + padding: 1px 5px 1px; margin-left: 2px; width: auto; background-color: white; + line-height: 16px; } div.translatable.textarea { border: 1px solid #bbb; diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 124b65afbb7316e046f27a427bd569048d25a17f..d988d02b0c802d1f5ba76f71d4921283fb026c84 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -115,10 +115,12 @@ 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, + `pid` int(10) unsigned DEFAULT NULL, `type` varchar(8) NOT NULL DEFAULT 'G', `deletable` tinyint(1) NOT NULL DEFAULT 1, `title` varchar(255) NOT NULL, `instructions` varchar(512), + `name` varchar(64) NOT NULL DEFAULT '', `notes` text, `created` datetime NOT NULL, `updated` datetime NOT NULL, @@ -151,6 +153,7 @@ CREATE TABLE `%TABLE_PREFIX%form_entry` ( `object_id` int(11) unsigned, `object_type` char(1) NOT NULL DEFAULT 'T', `sort` int(11) unsigned NOT NULL DEFAULT 1, + `extra` text, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), @@ -299,20 +302,6 @@ CREATE TABLE `%TABLE_PREFIX%filter` ( `status` int(11) unsigned NOT NULL DEFAULT '0', `match_all_rules` tinyint(1) unsigned NOT NULL default '0', `stop_onmatch` tinyint(1) unsigned NOT NULL default '0', - `reject_ticket` tinyint(1) unsigned NOT NULL default '0', - `use_replyto_email` tinyint(1) unsigned NOT NULL default '0', - `disable_autoresponder` tinyint(1) unsigned NOT NULL default '0', - `canned_response_id` int(11) unsigned NOT NULL default '0', - `email_id` int(10) unsigned NOT NULL default '0', - `status_id` int(10) unsigned NOT NULL default '0', - `priority_id` int(10) unsigned NOT NULL default '0', - `dept_id` int(10) unsigned NOT NULL default '0', - `staff_id` int(10) unsigned NOT NULL default '0', - `team_id` int(10) unsigned NOT NULL default '0', - `sla_id` int(10) unsigned NOT NULL default '0', - `form_id` int(11) unsigned NOT NULL default '0', - `topic_id` int(11) unsigned NOT NULL default '0', - `ext_id` varchar(11), `target` ENUM( 'Any', 'Web', 'Email', 'API' ) NOT NULL DEFAULT 'Any', `name` varchar(32) NOT NULL default '', `notes` text, @@ -323,6 +312,18 @@ CREATE TABLE `%TABLE_PREFIX%filter` ( KEY `email_id` (`email_id`) ) DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_action`; +CREATE TABLE `%TABLE_PREFIX%filter_action` ( + `id` int(11) unsigned NOT NULL auto_increment, + `filter_id` int(10) unsigned NOT NULL, + `sort` int(10) unsigned NOT NULL default 0, + `type` varchar(24) NOT NULL, + `configuration` text, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `filter_id` (`filter_id`) +) DEFAULT CHARSET=utf8; + DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_rule`; CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `id` int(11) unsigned NOT NULL auto_increment, @@ -443,7 +444,6 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( `team_id` int(10) unsigned NOT NULL default '0', `sla_id` int(10) unsigned NOT NULL default '0', `page_id` int(10) unsigned NOT NULL default '0', - `form_id` int(10) unsigned NOT NULL default '0', `sequence_id` int(10) unsigned NOT NULL DEFAULT '0', `sort` int(10) unsigned NOT NULL default '0', `topic` varchar(32) NOT NULL default '', @@ -461,6 +461,16 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( KEY `page_id` (`page_id`) ) DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `%TABLE_PREFIX%help_topic_form`; +CREATE TABLE `%TABLE_PREFIX%help_topic_form` ( + `id` int(11) unsigned NOT NULL auto_increment, + `topic_id` int(11) unsigned NOT NULL default 0, + `form_id` int(10) unsigned NOT NULL default 0, + `sort` int(10) unsigned NOT NULL default 1, + `extra` text, + PRIMARY KEY (`topic_id`, `form_id`) +) DEFAULT CHARSET=utf8; + DROP TABLE IF EXISTS `%TABLE_PREFIX%organization`; CREATE TABLE `%TABLE_PREFIX%organization` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT,