diff --git a/bootstrap.php b/bootstrap.php index 63d71fe8754d51c5d110ec1d9fa447dc0bfefdac..2085a4fbc1909d4c00ab79424e405f4c6ebd78b0 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -125,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..7c34b54428e4f1078ef2f210be1bc5c2f82ce3c4 --- /dev/null +++ b/include/ajax.filter.php @@ -0,0 +1,15 @@ +<?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(); + include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php'; + } + +} diff --git a/include/class.filter.php b/include/class.filter.php index acc3a3590a7921d1bee8599417c20fd1714e011a..c29df8dc579a52aa071fe6c806521676bb2c5b92 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,23 @@ 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(); + foreach ($this->getActions() as $a) { + $a->apply($ticket, $info); } - - # Use canned response. - if ($this->getCannedResponse()) - $ticket['cannedResponseId'] = $this->getCannedResponse(); - - # Apply help topic - if ($this->getHelpTopic()) - $ticket['topicId'] = $this->getHelpTopic(); } - static function getSupportedMatches() { + + static function getSupportedMatches() { foreach (static::$match_types as $k=>&$v) { if (is_callable($v[0])) $v[0] = $v[0](); @@ -518,17 +496,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,9 +528,37 @@ class Filter { # Don't care about errors stashed in $xerrors $xerrors = array(); self::save_rules($id,$vars,$xerrors); + self::save_actions($id, $vars, $errors); return true; } + + function save_actions($id, $vars, &$errors) { + if (!is_array(@$vars['actions'])) + return; + + foreach ($vars['actions'] as $v) { + $info = substr($v, 1); + switch ($v[0]) { + case 'N': # new filter action + $I = FilterAction::create(array( + 'type'=>$info, + 'filter_id'=>$id, + )); + $I->setConfiguration($errors); + $I->save(); + break; + case 'I': # exiting filter action + if ($I = FilterAction::lookup($info)) + $I->setConfiguration() && $I->save(); + break; + case 'D': # deleted filter action + if ($I = FilterAction::lookup($info)) + $I->delete(); + break; + } + } + } } class FilterRule { diff --git a/include/class.filter_action.php b/include/class.filter_action.php new file mode 100644 index 0000000000000000000000000000000000000000..b54eabb68db7c6fe819a6b7c848eb7f451cc6e32 --- /dev/null +++ b/include/class.filter_action.php @@ -0,0 +1,395 @@ +<?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(); + + var $_impl; + var $_config; + + function getId() { + return $this->id; + } + + 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($source=false, &$errors=array()) { + $config = array(); + foreach ($this->getImpl()->getConfigurationForm($source ?: $_POST) + ->getFields() as $name=>$field) { + $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, $type=false) { + // TODO: Check if $class implements TriggerAction + self::$registry[$type ?: $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() { + $types = array(); + foreach (self::$registry as $type=>$class) { + $types[$type] = $class::getName(); + } + return $types; + } +} + +abstract class TriggerAction { + 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; + } + + static function getType() { return static::$type; } + static function getName() { return __(static::$name); } + + abstract function apply(&$ticket, array $info); + abstract function getConfigurationOptions(); +} + +class FA_UseReplyTo extends TriggerAction { + static $type = 'replyto'; + static $name = /* trans */ '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( + 'enable' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('Use the Reply-To email header') + ) + )), + ); + } +} +FilterAction::register('FA_UseReplyTo'); + +class FA_DisableAutoResponse extends TriggerAction { + static $type = 'noresp'; + static $name = /* trans */ "Ticket auto-response"; + + 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( + 'enable' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('<strong>Disable</strong> auto-response') + ), + )), + ); + } +} +FilterAction::register('FA_DisableAutoResponse'); + +class FA_AutoCannedResponse extends TriggerAction { + static $type = 'canned'; + static $name = /* trans */ "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'); + +class FA_RouteDepartment extends TriggerAction { + static $type = 'dept'; + static $name = /* trans */ 'Department'; + + function apply(&$ticket, array $info) { + $config = $this->getConfiguration(); + if ($config['dept_id']) + $ticket['deptId'] = $config['dept_id']; + } + + function getConfigurationOptions() { + $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name'; + $choices = array(); + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$name)=db_fetch_row($res)){ + $choices[$id] = $name; + } + } + return array( + 'dept_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_RouteDepartment'); + +class FA_AssignPriority extends TriggerAction { + static $type = 'pri'; + static $name = /* trans */ "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'); + +class FA_AssignSLA extends TriggerAction { + static $type = 'sla'; + static $name = /* trans */ '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'); + +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() { + $sql='SELECT team_id, isenabled, name FROM '.TEAM_TABLE .' ORDER BY name'; + $choices = array(); + if(($res=db_query($sql)) && db_num_rows($res)){ + while (list($id, $isenabled, $name) = db_fetch_row($res)){ + if (!$isenabled) + $name .= ' '.__('(disabled)'); + $choices[$id] = $name; + } + } + return array( + 'team_id' => new ChoiceField(array( + 'configuration' => array('prompt' => __('Unchanged')), + 'choices' => $choices, + )), + ); + } +} +FilterAction::register('FA_AssignTeam'); + +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'); + +class FA_AssignTopic extends TriggerAction { + static $type = 'topic'; + static $name = /* trans */ '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'); + +class FA_SetStatus extends TriggerAction { + static $type = 'status'; + static $name = /* trans */ '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'); diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index f09731af64a0cc7f5455b3677b408bb208af29aa..5ef7d36a1482f441ddb470a6a4f45fb6465477d4 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -181,195 +181,65 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <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"> +<?php +$existing = array(); +if ($filter) { foreach ($filter->getActions() as $A) { + $existing[] = $A->type; +?> + <tr><td><?php echo $A->getImpl()->getName(); ?>:</td> + <td><div style="position:relative"><?php + $form = $A->getImpl()->getConfigurationForm(); + 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> + <?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 foreach (FilterAction::allRegistered() as $type=>$name) { + if (in_array($type, $existing)) + continue; +?> + <option data-title="<?php echo $name; ?>" value="<?php echo $type; ?>"><?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 selected = $('#new-action-select').find(':selected'); + $('#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() { + selected.prop('disabled', true); + }) + ) + ).append( + $('<input>').attr({type:'hidden',name:'actions[]',value:'N'+selected.val()}) + );"/> </td> </tr> <tr> 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..896805c45c48f6ace493b9786f0abd00e804cbc7 --- /dev/null +++ b/include/staff/templates/dynamic-form-simple.tmpl.php @@ -0,0 +1,33 @@ + <?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 } + ?> + </form> diff --git a/include/upgrader/streams/core/b26f29a6-00000000.cleanup.sql b/include/upgrader/streams/core/b26f29a6-00000000.cleanup.sql new file mode 100644 index 0000000000000000000000000000000000000000..d40218224f680e4852eb29ca6589d0eec15f02be --- /dev/null +++ b/include/upgrader/streams/core/b26f29a6-00000000.cleanup.sql @@ -0,0 +1,11 @@ +ALTER TABLE `%TABLE_PREFIX%filter` + 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/b26f29a6-00000000.patch.sql b/include/upgrader/streams/core/b26f29a6-00000000.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..821b2cbbe3b2a28762263ddd1c9bf9a8a8f91c2c --- /dev/null +++ b/include/upgrader/streams/core/b26f29a6-00000000.patch.sql @@ -0,0 +1,86 @@ +/** + * @version v1.9.5 + * @signature 00000000000000000000000000000000 + * @title Add flexible filter actions + * + * 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`, '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; + +-- Set new schema signature +UPDATE `%TABLE_PREFIX%config` + SET `value` = '00000000000000000000000000000000' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/scp/ajax.php b/scp/ajax.php index 79dd8e1b384456df05a05373c4887f99cd7d0098..140085b3910165086b67e1e9f4d61428777569e2 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -60,6 +60,9 @@ $dispatcher = patterns('', 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'), url_post('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'saveListItemProperties') diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 738df56ce9521c2a087d5aa04f326b89cff14f0d..669f071e10ed72c9ab76baac3b8fd8e959b15524 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -326,6 +326,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,