Skip to content
Snippets Groups Projects
class.filter.php 28.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
    <?php
    /*********************************************************************
        class.filter.php
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        Peter Rotich <peter@osticket.com>
    
        Copyright (c)  2006-2013 osTicket
    
    Jared Hancock's avatar
    Jared Hancock committed
        http://www.osticket.com
    
        Released under the GNU General Public License WITHOUT ANY WARRANTY.
        See LICENSE.TXT for details.
    
        vim: expandtab sw=4 ts=4 sts=4:
    **********************************************************************/
    
    require_once INCLUDE_DIR . 'class.filter_action.php';
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    class Filter {
    
        var $id;
        var $ht;
    
    
        static $match_types = array(
    
            /* @trans */ 'User Information' => array(
                array('name'      =>    /* @trans */ 'Name',
                    'email'     =>      /* @trans */ 'Email',
    
            /* @trans */ 'Email Meta-Data' => array(
                array('reply-to'  =>    /* @trans */ 'Reply-To Email',
                    'reply-to-name' =>  /* @trans */ 'Reply-To Name',
                    'addressee' =>      /* @trans */ 'Addressee (To and Cc)',
    
        function Filter($id) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->id=0;
            $this->load($id);
        }
    
        function load($id=0) {
    
            if(!$id && !($id=$this->getId()))
                return false;
    
            $sql='SELECT filter.*,count(rule.id) as rule_count '
    
                .' FROM '.FILTER_TABLE.' filter '
                .' LEFT JOIN '.FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) '
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' WHERE filter.id='.db_input($id)
                .' GROUP BY filter.id';
    
            if(!($res=db_query($sql)) || !db_num_rows($res))
                return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->ht=db_fetch_array($res);
    
            $this->id=$this->ht['id'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            return true;
        }
    
        function reload() {
            return $this->load($this->getId());
        }
    
    
        function getId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->id;
        }
    
    
        function getTarget() {
            return $this->ht['target'];
        }
    
        function getName() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['name'];
        }
    
    
        function getNotes() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['notes'];
        }
    
    
        function getInfo() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return  $this->ht;
        }
    
    
        function getNumRules() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['rule_count'];
        }
    
    
        function getExecOrder() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['execorder'];
        }
    
    
        function getEmailId() {
            return $this->ht['email_id'];
        }
    
    
        function isActive() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($this->ht['isactive']);
        }
    
        function isSystemBanlist() {
            return !strcasecmp($this->getName(),'SYSTEM BAN LIST');
        }
    
    
        function getDeptId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['dept_id'];
        }
    
    
        function getStatusId() {
            return $this->ht['status_id'];
        }
    
    
        function getPriorityId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['priority_id'];
        }
    
    
        function getSLAId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['sla_id'];
        }
    
    
        function getStaffId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['staff_id'];
        }
    
    
        function getTeamId() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->ht['team_id'];
        }
    
    
        function getCannedResponse() {
    
            return $this->ht['canned_response_id'];
    
        function getHelpTopic() {
            return $this->ht['topic_id'];
        }
    
    
        function stopOnMatch() {
    
            return ($this->ht['stop_onmatch']);
    
        function matchAllRules() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($this->ht['match_all_rules']);
        }
    
    
        function rejectOnMatch() {
            return ($this->ht['reject_ticket']);
    
        function useReplyToEmail() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($this->ht['use_replyto_email']);
        }
    
    
        function disableAlerts() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($this->ht['disable_autoresponder']);
        }
    
        function sendAlerts() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return (!$this->disableAlerts());
        }
    
    
        function getRules() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (!$this->ht['rules']) {
                $rules=array();
                //We're getting the rules...live because it gets cleared on update.
    
                $sql='SELECT * FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($this->getId());
                if(($res=db_query($sql)) && db_num_rows($res)) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    while($row=db_fetch_array($res))
                        $rules[]=array('w'=>$row['what'],'h'=>$row['how'],'v'=>$row['val']);
                }
                $this->ht['rules'] = $rules;
            }
            return $this->ht['rules'];
        }
    
    
        function getFlatRules() { //Format used on html... I'm ashamed
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $info=array();
    
            if(($rules=$this->getRules())) {
                foreach($rules as $k=>$rule) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $i=$k+1;
                    $info["rule_w$i"]=$rule['w'];
                    $info["rule_h$i"]=$rule['h'];
                    $info["rule_v$i"]=$rule['v'];
                }
            }
            return $info;
        }
    
        function addRule($what, $how, $val,$extra=array()) {
    
            $errors = array();
    
            $rule= array_merge($extra,array('what'=>$what, 'how'=>$how, 'val'=>$val));
    
    Jared Hancock's avatar
    Jared Hancock committed
            $rule['filter_id']=$this->getId();
    
    
            return FilterRule::create($rule,$errors);
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function removeRule($what, $how, $val) {
    
    
            $sql='DELETE FROM '.FILTER_RULE_TABLE
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' WHERE filter_id='.db_input($this->getId())
                .' AND what='.db_input($what)
                .' AND how='.db_input($how)
                .' AND val='.db_input($val);
    
            return (db_query($sql) && db_affected_rows());
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getRule($id) {
            return $this->getRuleById($id);
        }
    
        function getRuleById($id) {
            return FilterRule::lookup($id,$this->getId());
        }
    
        function containsRule($what, $how, $val) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (isset($this->ht['rules'])) {
    
                $match = array("w"=>$what, "h"=>$how, "v"=>$val);
    
    Jared Hancock's avatar
    Jared Hancock committed
                foreach ($this->ht['rules'] as $rule) {
    
                    if ($match == $rule)
                        return true;
    
    Jared Hancock's avatar
    Jared Hancock committed
            } else {
                # Fetch from database
                return 0 != db_count(
    
                    "SELECT COUNT(*) FROM ".FILTER_RULE_TABLE
    
    Jared Hancock's avatar
    Jared Hancock committed
                   ." WHERE filter_id=".db_input($this->id)
                   ." AND what=".db_input($what)." AND how=".db_input($how)
                   ." AND val=".db_input($val)
                );
            }
        }
        /**
         * Simple true/false if the rules defined for this filter match the
         * incoming email
         *
    
         * $info is an ARRAY, which has valid keys
         *   email - FROM email address of the ticket owner
         *   name - name of ticket owner
         *   subject - subject line of the ticket
         *   body - body content of the message (no attachments, please)
    
    Jared Hancock's avatar
    Jared Hancock committed
         *   reply-to - reply-to email address
         *   reply-to-name - name of sender to reply-to
         *   headers - array of email headers
    
         *   emailId - osTicket system email id
    
    Peter Rotich's avatar
    Peter Rotich committed
        function matches($what) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            if(!$what || !is_array($what)) return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
            $how = array(
                # how => array(function, null or === this, null or !== this)
    
                'equal'     => array('strcasecmp', 0),
                'not_equal' => array('strcasecmp', null, 0),
                'contains'  => array('stripos', null, false),
                'dn_contain'=> array('stripos', false),
                'starts'    => array('stripos', 0),
                'ends'      => array('iendsWith', true),
                'match'     => array('pregMatchB', 1),
                'not_match' => array('pregMatchB', null, 0),
    
    Jared Hancock's avatar
    Jared Hancock committed
            $match = false;
    
            # Respect configured filter email-id
    
            if ($this->getEmailId()
    
                    && !strcasecmp($this->getTarget(), 'Email')
    
    Peter Rotich's avatar
    Peter Rotich committed
                    && $this->getEmailId() != $what['emailId'])
    
    Jared Hancock's avatar
    Jared Hancock committed
            foreach ($this->getRules() as $rule) {
    
                if (!isset($how[$rule['h']])) continue;
    
    Jared Hancock's avatar
    Jared Hancock committed
                list($func, $pos, $neg) = $how[$rule['h']];
    
    
                $result = call_user_func($func, $what[$rule['w']], $rule['v']);
    
    Jared Hancock's avatar
    Jared Hancock committed
                if (($pos === null && $result !== $neg) or ($result === $pos)) {
                    # Match.
                    $match = true;
                    if (!$this->matchAllRules()) break;
                } else {
                    # No match. Continue?
    
                    if ($this->matchAllRules()) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                        $match = false;
                        break;
                    }
                }
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $match;
        }
    
    
        function getActions() {
            return FilterAction::objects()->filter(array(
                'filter_id'=>$this->getId()
            ));
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
         * If the matches() method returns TRUE, send the initial ticket to this
         * method to apply the filter actions defined
         */
    
        function apply(&$ticket, $vars) {
    
            foreach ($this->getActions() as $a) {
    
                $a->setFilter($this);
                $a->apply($ticket, $vars);
    
        function getVars() {
            return $this->vars;
        }
    
    
        static function getSupportedMatches() {
    
            foreach (static::$match_types as $k=>&$v) {
    
                if (is_callable($v[0]))
                    $v[0] = $v[0]();
    
            uasort(static::$match_types, function($a, $b) { return $a[1] - $b[1]; });
            return array_map(function($a) { return $a[0]; }, static::$match_types);
    
        static function addSupportedMatches($group, $callable, $order=10) {
            static::$match_types[$group] = array($callable, $order);
    
    
        static function getSupportedMatchFields() {
            $keys = array();
            foreach (static::getSupportedMatches() as $group=>$matches) {
                foreach ($matches as $key=>$label) {
                    $keys[] = $key;
                }
            }
            return $keys;
        }
    
    
        /* static */ function getSupportedMatchTypes() {
            return array(
    
                'equal'=>       __('Equal'),
                'not_equal'=>   __('Not Equal'),
                'contains'=>    __('Contains'),
                'dn_contain'=>  __('Does Not Contain'),
                'starts'=>      __('Starts With'),
                'ends'=>        __('Ends With'),
                'match'=>       __('Matches Regex'),
                'not_match'=>   __('Does Not Match Regex'),
    
        function update($vars,&$errors) {
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!Filter::save($this->getId(),$vars,$errors))
                return false;
    
            $this->reload();
    
    Jared Hancock's avatar
    Jared Hancock committed
            return true;
        }
    
    
        function delete() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $id=$this->getId();
    
            $sql='DELETE FROM '.FILTER_TABLE.' WHERE id='.db_input($id).' LIMIT 1';
            if(db_query($sql) && ($num=db_affected_rows())) {
                db_query('DELETE FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id));
    
    Jared Hancock's avatar
    Jared Hancock committed
            }
    
            return $num;
        }
    
        /** static functions **/
    
        function getTargets() {
            return array(
    
                    'Any' => __('Any'),
                    'Web' => __('Web Forms'),
                    'API' => __('API Calls'),
                    'Email' => __('Emails'));
    
        }
    
        function create($vars,&$errors) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return Filter::save(0,$vars,$errors);
        }
    
    
        function getIdByName($name) {
    
            $sql='SELECT id FROM '.FILTER_TABLE.' WHERE name='.db_input($name);
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(($res=db_query($sql)) && db_num_rows($res))
                list($id)=db_fetch_row($res);
    
            return $id;
        }
    
    
        function lookup($id) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return ($id && is_numeric($id) && ($f= new Filter($id)) && $f->getId()==$id)?$f:null;
        }
    
    
        function validate_rules($vars,&$errors) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return self::save_rules(0,$vars,$errors);
        }
    
    
        function save_rules($id,$vars,&$errors) {
    
            $matches = array_keys(self::getSupportedMatchFields());
    
            $types = array_keys(self::getSupportedMatchTypes());
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $rules=array();
            for($i=1; $i<=25; $i++) { //Expecting no more than 25 rules...
    
                if($vars["rule_w$i"] || $vars["rule_h$i"]) {
    
                    // Check for REGEX compile errors
                    if (in_array($vars["rule_h$i"], array('match','not_match'))) {
                        $wrapped = "/".$vars["rule_v$i"]."/iu";
                        if (false === @preg_match($vars["rule_v$i"], ' ')
                                && (false !== @preg_match($wrapped, ' ')))
                            $vars["rule_v$i"] = $wrapped;
                    }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
                    if(!$vars["rule_w$i"] || !in_array($vars["rule_w$i"],$matches))
    
                        $errors["rule_$i"]=__('Invalid match selection');
    
    Jared Hancock's avatar
    Jared Hancock committed
                    elseif(!$vars["rule_h$i"] || !in_array($vars["rule_h$i"],$types))
    
                        $errors["rule_$i"]=__('Invalid match type selection');
    
    Jared Hancock's avatar
    Jared Hancock committed
                    elseif(!$vars["rule_v$i"])
    
                        $errors["rule_$i"]=__('Value required');
    
                    elseif($vars["rule_w$i"]=='email'
                            && $vars["rule_h$i"]=='equal'
                            && !Validator::is_email($vars["rule_v$i"]))
    
                        $errors["rule_$i"]=__('Valid email required for the match type');
    
                    elseif (in_array($vars["rule_h$i"], array('match','not_match'))
                            && (false === @preg_match($vars["rule_v$i"], ' ')))
    
                        $errors["rule_$i"] = sprintf(__('Regex compile error: (#%s)'),
    
    Jared Hancock's avatar
    Jared Hancock committed
                    else //for everything-else...we assume it's valid.
    
                        $rules[]=array('what'=>$vars["rule_w$i"],
    
                            'how'=>$vars["rule_h$i"],'val'=>trim($vars["rule_v$i"]));
    
                }elseif($vars["rule_v$i"]) {
    
                    $errors["rule_$i"]=__('Incomplete selection');
    
    Jared Hancock's avatar
    Jared Hancock committed
                }
            }
    
            if(!$rules && is_array($vars["rules"]))
                # XXX: Validation bypass
                $rules = $vars["rules"];
            elseif(!$rules && !$errors)
    
                $errors['rules']=__('You must set at least one rule.');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if($errors) return false;
    
            if(!$id) return true; //When ID is 0 then assume it was just validation...
    
    
            //Clear existing rules...we're doing mass replace on each save!!
    
            db_query('DELETE FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id));
    
    Jared Hancock's avatar
    Jared Hancock committed
            $num=0;
            foreach($rules as $rule) {
                $rule['filter_id']=$id;
                if(FilterRule::create($rule, $errors))
                    $num++;
            }
    
    
            return $num;
    
        function save($id,$vars,&$errors) {
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!$vars['execorder'])
    
                $errors['execorder'] = __('Order required');
    
    Jared Hancock's avatar
    Jared Hancock committed
            elseif(!is_numeric($vars['execorder']))
    
                $errors['execorder'] = __('Must be numeric value');
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!$vars['name'])
    
                $errors['name'] = __('Name required');
    
    Jared Hancock's avatar
    Jared Hancock committed
            elseif(($sid=self::getIdByName($vars['name'])) && $sid!=$id)
    
                $errors['name'] = __('Name already in use');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if(!$errors && !self::validate_rules($vars,$errors) && !$errors['rules'])
    
                $errors['rules'] = __('Unable to validate rules as entered');
    
            $targets = self::getTargets();
            if(!$vars['target'])
    
                $errors['target'] = __('Target required');
    
            else if(!is_numeric($vars['target']) && !$targets[$vars['target']])
    
                $errors['target'] = __('Unknown or invalid target');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            if($errors) return false;
    
    
            $emailId = 0;
            if(is_numeric($vars['target'])) {
                $emailId = $vars['target'];
                $vars['target'] = 'Email';
            }
    
            $sql=' updated=NOW() '
                .',isactive='.db_input($vars['isactive'])
                .',target='.db_input($vars['target'])
                .',name='.db_input($vars['name'])
                .',execorder='.db_input($vars['execorder'])
                .',email_id='.db_input($emailId)
                .',match_all_rules='.db_input($vars['match_all_rules'])
                .',stop_onmatch='.db_input(isset($vars['stop_onmatch'])?1:0)
    
                .',notes='.db_input(Format::sanitize($vars['notes']));
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($id) {
    
                $sql='UPDATE '.FILTER_TABLE.' SET '.$sql.' WHERE id='.db_input($id);
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(!db_query($sql))
    
                    $errors['err']=sprintf(__('Unable to update %s.'), __('this ticket filter'))
                       .' '.__('Internal error occurred');
    
    Jared Hancock's avatar
    Jared Hancock committed
            }else{
    
                $sql='INSERT INTO '.FILTER_TABLE.' SET '.$sql.',created=NOW() ';
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(!db_query($sql) || !($id=db_insert_id()))
    
                    $errors['err']=sprintf(__('Unable to add %s.'), __('this ticket filter'))
                       .' '.__('Internal error occurred');
    
    Jared Hancock's avatar
    Jared Hancock committed
            }
    
            if($errors || !$id) return false;
    
            //Success with update/create...save the rules. We can't recover from any errors at this point.
    
    Jared Hancock's avatar
    Jared Hancock committed
            # Don't care about errors stashed in $xerrors
    
            $xerrors = array();
            self::save_rules($id,$vars,$xerrors);
    
            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) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                if (is_array($v)) {
                    $info = $v['type'];
                    $sort = $v['sort'] ?: $sort;
                    $action = 'N';
                } else {
                    $action = $v[0];
                    $info = substr($v, 1);
                }
    
                switch ($action) {
    
                case 'N': # new filter action
                    $I = FilterAction::create(array(
                        'type'=>$info,
                        'filter_id'=>$id,
    
                    $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;
    
                    break;
                case 'D': # deleted filter action
                    if ($I = FilterAction::lookup($info))
                        $I->delete();
                    break;
                }
            }
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
    }
    
    class FilterRule {
    
        var $id;
        var $ht;
    
        var $filter;
    
    
        function FilterRule($id,$filterId=0) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->id=0;
            $this->load($id,$filterId);
        }
    
        function load($id,$filterId=0) {
    
    
            $sql='SELECT rule.* FROM '.FILTER_RULE_TABLE.' rule '
    
    Jared Hancock's avatar
    Jared Hancock committed
                .' WHERE rule.id='.db_input($id);
            if($filterId)
                $sql.=' AND rule.filter_id='.db_input($filterId);
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!($res=db_query($sql)) || !db_num_rows($res))
                return false;
    
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->ht=db_fetch_array($res);
            $this->id=$this->ht['id'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->filter=null;
    
            return true;
        }
    
        function reload() {
            return $this->load($this->getId());
        }
    
        function getId() {
            return $this->id;
        }
    
        function isActive() {
            return ($this->ht['isactive']);
        }
    
        function getHashtable() {
            return $this->ht;
        }
    
        function getInfo() {
            return $this->getHashtable();
        }
    
        function getFilterId() {
            return $this->ht['filter_id'];
        }
    
        function getFilter() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(!$this->filter && $this->getFilterId())
                $this->filter = Filter::lookup($this->getFilterId());
    
            return $this->filter;
        }
    
        function update($vars,&$errors) {
            if(!$this->save($this->getId(),$vars,$errors))
                return false;
    
            $this->reload();
            return true;
        }
    
    
        function delete() {
    
            $sql='DELETE FROM '.FILTER_RULE_TABLE.' WHERE id='.db_input($this->getId()).' AND filter_id='.db_input($this->getFilterId());
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return (db_query($sql) && db_affected_rows());
        }
    
        /* static */ function create($vars,&$errors) {
            return self::save(0,$vars,$errors);
        }
    
        /* static private */ function save($id,$vars,&$errors) {
            if(!$vars['filter_id'])
    
                $errors['err']=__('Parent filter ID required');
    
    Jared Hancock's avatar
    Jared Hancock committed
    
    
            if($errors) return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql=' updated=NOW() '.
    
                 ',what='.db_input($vars['what']).
                 ',how='.db_input($vars['how']).
                 ',val='.db_input($vars['val']).
    
    Jared Hancock's avatar
    Jared Hancock committed
                 ',isactive='.db_input(isset($vars['isactive'])?$vars['isactive']:1);
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(isset($vars['notes']))
                $sql.=',notes='.db_input($vars['notes']);
    
            if($id) {
    
                $sql='UPDATE '.FILTER_RULE_TABLE.' SET '.$sql.' WHERE id='.db_input($id).' AND filter_id='.db_input($vars['filter_id']);
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(db_query($sql))
                    return true;
    
            } else {
    
                $sql='INSERT INTO '.FILTER_RULE_TABLE.' SET created=NOW(), filter_id='.db_input($vars['filter_id']).', '.$sql;
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(db_query($sql) && ($id=db_insert_id()))
                    return $id;
            }
    
            return false;
        }
    
        /* static */ function lookup($id,$filterId=0) {
            return ($id && is_numeric($id) && ($r= new FilterRule($id,$filterId)) && $r->getId()==$id)?$r:null;
        }
    
    }
    
    /**
    
     * Applies rules defined in the admin control panel > Settings tab > "Ticket Filters". Each
    
    Jared Hancock's avatar
    Jared Hancock committed
     * filter can have up to 25 rules (*currently). This will attempt to match
    
     * the incoming tickets against the defined rules, and, if the email matches,
     * the ticket will be modified as described in the filter actions.
    
    class TicketFilter {
    
        var $target;
        var $vars;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        /**
    
         * Construct a list of filters to handle a new ticket
         * taking into account the source/origin of the ticket.
    
         * $vars is an ARRAY, which has valid keys
         *  *email - email address of user
         *   name - name of user
         *   subject - subject of the ticket
         *   emailId - id of osTicket's system email (for emailed tickets)
    
    Jared Hancock's avatar
    Jared Hancock committed
         *  ---------------
         *  @see Filter::matches() for a complete list of supported keys
         */
    
        function TicketFilter($origin, $vars=array()) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Normalize the target based on ticket's origin.
    
            $this->target = self::origin2target($origin);
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Extract the vars we care about (fields we filter by!).
    
            $this->vars = array('body'=>$vars['message']);
            $interest = Filter::getSupportedMatchFields();
    
            // emailId is always significant to the filter process
            $interest[] = 'emailId';
    
            foreach ($vars as $k=>$v) {
                if (in_array($k, $interest))
                    $this->vars[$k] = trim($v);
            }
    
            if (isset($vars['recipients']) && $vars['recipients']) {
    
                foreach ($vars['recipients'] as $r) {
                    $this->vars['addressee'][] = $r['name'];
                    $this->vars['addressee'][] = $r['email'];
                }
                $this->vars['addressee'] = implode(' ', $this->vars['addressee']);
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
             //Init filters.
    
            //Clear any memoized filters
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->filters = array();
    
    
            //Query DB for "possibly" matching filters.
    
            $res = $this->getAllActive();
    
            if($res) {
                while (list($id) = db_fetch_row($res))
    
                    $this->filters[] = new Filter($id);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->filters;
        }
    
    
        function getTarget() {
            return $this->target;
        }
    
    
         * Fetches the short list of filters that match the ticket vars received in the
    
         * constructor. This function is memoized so subsequent calls will
         * return immediately.
         */
        function getMatchingFilterList() {
    
                $this->short_list = array();
                foreach ($this->filters as $filter)
    
                    if ($filter->matches($this->vars))
    
                        $this->short_list[] = $filter;
            }
    
            return $this->short_list;
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        /**
         * Determine if any filters match the received email, and if so, apply
         * actions defined in those filters to the ticket-to-be-created.
    
         *
         * Throws:
         * RejectedException if the email should not be acceptable. If the email
         * should be rejected, the first filter that matches and has reject
         * ticket set is returned.
    
    Jared Hancock's avatar
    Jared Hancock committed
         */
        function apply(&$ticket) {
    
            foreach ($this->getMatchingFilterList() as $filter) {
    
                $filter->apply($ticket, $this->vars);
    
                if ($filter->stopOnMatch()) break;
    
        function getAllActive() {
    
    
            $sql='SELECT id FROM '.FILTER_TABLE
                .' WHERE isactive=1 '
    
    Peter Rotich's avatar
    Peter Rotich committed
                .'  AND target IN ("Any", '.db_input($this->getTarget()).') ';
    
    Peter Rotich's avatar
    Peter Rotich committed
            #Take into account email ID.
            if($this->vars['emailId'])
                $sql.=' AND (email_id=0 OR email_id='.db_input($this->vars['emailId']).')';
    
            $sql.=' ORDER BY execorder';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return db_query($sql);
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        /**
         * Simple true/false if the headers of the email indicate that the email
         * is an automatic response.
         *
         * Thanks to http://wiki.exim.org/EximAutoReply
         * X-Auto-Response-Supress is outlined here,
         *    http://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx
         */
    
        /* static */
    
        function isAutoReply($headers) {
    
    
            if($headers && !is_array($headers))
                $headers = Mail_Parse::splitHeaders($headers);
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $auto_headers = array(
    
                'Auto-Submitted'    => array('AUTO-REPLIED', 'AUTO-GENERATED'),
    
    Jared Hancock's avatar
    Jared Hancock committed
                'Precedence'        => array('AUTO_REPLY', 'BULK', 'JUNK', 'LIST'),
    
                'X-Precedence'      => array('AUTO_REPLY', 'BULK', 'JUNK', 'LIST'),
    
    Jared Hancock's avatar
    Jared Hancock committed
                'X-Autoreply'       => 'YES',
    
                'X-Auto-Response-Suppress' => array('ALL', 'DR', 'RN', 'NRN', 'OOF', 'AutoReply'),
    
                'X-Autoresponse'    => '*',
                'X-AutoReply-From'  => '*',
                'X-Autorespond'     => '*',
                'X-Mail-Autoreply'  => '*',
                'X-Autogenerated'   => 'REPLY',
    
                'X-AMAZON-MAIL-RELAY-TYPE' => 'NOTIFICATION',
    
    Jared Hancock's avatar
    Jared Hancock committed
            foreach ($auto_headers as $header=>$find) {
    
                if(!isset($headers[$header])) continue;
    
                $value = strtoupper($headers[$header]);
                # Search text must be found at the beginning of the header
                # value. This is especially import for something like the
                # subject line, where something like an autoreponse may
                # appear somewhere else in the value.
    
                if (is_array($find)) {
                    foreach ($find as $f)
                        if (strpos($value, $f) === 0)
                            return true;
    
                } elseif ($find === '*') {
                    return true;
    
                } elseif (strpos($value, $find) === 0) {
                    return true;
    
        static function isBounce($headers) {
    
    
            if($headers && !is_array($headers))
                $headers = Mail_Parse::splitHeaders($headers);
    
            $bounce_headers = array(
    
                'From'  => array('stripos',
                            array('MAILER-DAEMON', '<>'), null, false),
                'Subject'   => array('stripos',
    
                    array('DELIVERY FAILURE', 'DELIVERY STATUS',
                        'UNDELIVERABLE:', 'Undelivered Mail Returned'), 0),
    
                'Return-Path'   => array('strcmp', array('<>'), 0),
                'Content-Type'  => array('stripos', array('report-type=delivery-status'), null, false),
    
                'X-Failed-Recipients' => array('strpos', array('@'), null, false)
    
            );
    
            foreach ($bounce_headers as $header => $find) {
                if(!isset($headers[$header])) continue;
    
    
                @list($func, $searches, $pos, $neg) = $find;
    
                if(!($value = $headers[$header]) || !is_array($searches))
                    continue;
    
                foreach ($searches as $f) {
                    $result = call_user_func($func, $value, $f);
                    if (($pos === null && $result !== $neg) or ($result === $pos))
                        return true;
    
    Jared Hancock's avatar
    Jared Hancock committed
            return false;
        }
    
        /**
         * Normalize ticket source to supported filter target
    
         *
         */
        function origin2target($origin) {
            $sources=array('web' => 'Web', 'email' => 'Email', 'phone' => 'Web', 'staff' => 'Web', 'api' => 'API');
    
            return $sources[strtolower($origin)];
        }
    
    class RejectedException extends Exception {
        var $filter;
    
        function __construct(Filter $filter, $vars) {
    
            parent::__construct('Ticket rejected by a filter');
            $this->filter = $filter;
    
            $this->vars = $vars;
    
        }
    
        function getRejectingFilter() {
            return $this->filter;
        }
    
    
        function get($what) {
            return $this->vars[$what];
        }
    
    class FilterDataChanged extends Exception {
        var $data;
    
        function __construct($data) {
             parent::__construct('Ticket filter data changed');
             $this->data = $data;
        }
    
        function getData() {
            return $this->data;
        }
    }
    
    /**
     * Function: endsWith
     *
     * Returns TRUE if the haystack ends with needle and FALSE otherwise.
     * Thanks, http://stackoverflow.com/a/834355
     */
    
    function iendsWith($haystack, $needle)
    
        $length = mb_strlen($needle);
    
        if ($length == 0) {
            return true;
        }
    
    
        return (strcasecmp(mb_substr($haystack, -$length), $needle) === 0);
    }
    
    function pregMatchB($subject, $pattern) {
        return preg_match($pattern, $subject);