diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 27e4a0afe186e550db4ae74051f9fea50fa40b09..a2bef2a62da7e27e60559d17c4f7bece216fe8ab 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -271,7 +271,7 @@ class TicketForm extends DynamicForm { } } // Add fields from the standard ticket form to the ticket filterable fields -Filter::addSupportedMatches('Custom Fields', function() { +Filter::addSupportedMatches('Ticket Data', function() { $matches = array(); foreach (TicketForm::getInstance()->getFields() as $f) { if (!$f->hasData()) diff --git a/include/class.filter.php b/include/class.filter.php index a17962cf2e9c44e50f44c1b1f3571c3fd4eadd0c..8854ea89e03095d003f2cb3bea407c8b5e062a91 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -19,11 +19,11 @@ class Filter { var $ht; static $match_types = array( - 'Basic Fields' => array( + 'User Information' => array( 'name' => 'Name', 'email' => 'Email', - 'subject' => 'Subject', - 'body' => 'Body/Text', + ), + 'Email Meta-Data' => array( 'reply-to' => 'Reply-To Email', 'reply-to-name' => 'Reply-To Name', ), @@ -242,12 +242,14 @@ class Filter { $how = array( # how => array(function, null or === this, null or !== this) - 'equal' => array('strcmp', 0), - 'not_equal' => array('strcmp', null, 0), - 'contains' => array('strpos', null, false), - 'dn_contain'=> array('strpos', false), - 'starts' => array('strpos', 0), - 'ends' => array('endsWith', true) + '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), ); $match = false; @@ -260,12 +262,8 @@ class Filter { foreach ($this->getRules() as $rule) { if (!isset($how[$rule['h']])) continue; list($func, $pos, $neg) = $how[$rule['h']]; - # TODO: convert $what and $rule['v'] to mb_strtoupper and do - # case-sensitive, binary-safe comparisons. Would be really - # nice to do $rule['v'] on the database side for - # performance -- but ::getFlatRules() is a blocker - $result = call_user_func($func, strtoupper($what[$rule['w']]), - strtoupper($rule['v'])); + + $result = call_user_func($func, $what[$rule['w']], $rule['v']); if (($pos === null && $result !== $neg) or ($result === $pos)) { # Match. $match = true; @@ -341,7 +339,9 @@ class Filter { 'contains'=> 'Contains', 'dn_contain'=> 'Does Not Contain', 'starts'=> 'Starts With', - 'ends'=> 'Ends With' + 'ends'=> 'Ends With', + 'match'=> 'Matches Regex', + 'not_match'=> 'Does Not Match Regex', ); } @@ -404,6 +404,14 @@ class Filter { $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; + } + if(!$vars["rule_w$i"] || !in_array($vars["rule_w$i"],$matches)) $errors["rule_$i"]='Invalid match selection'; elseif(!$vars["rule_h$i"] || !in_array($vars["rule_h$i"],$types)) @@ -414,6 +422,12 @@ class Filter { && $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)', + preg_last_error()); + + else //for everything-else...we assume it's valid. $rules[]=array('what'=>$vars["rule_w$i"], 'how'=>$vars["rule_h$i"],'val'=>$vars["rule_v$i"]); @@ -902,13 +916,17 @@ class TicketFilter { * Returns TRUE if the haystack ends with needle and FALSE otherwise. * Thanks, http://stackoverflow.com/a/834355 */ -function endsWith($haystack, $needle) +function iendsWith($haystack, $needle) { - $length = strlen($needle); + $length = mb_strlen($needle); if ($length == 0) { return true; } - return (substr($haystack, -$length) === $needle); + return (strcasecmp(mb_substr($haystack, -$length), $needle) === 0); +} + +function pregMatchB($subject, $pattern) { + return preg_match($pattern, $subject); } ?> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index 37ac6c69925bf4dc15711f0a76767803aeacc66a..74e72106859d68501fbc6e490d79a9ddf90a4869 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -121,7 +121,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); for($i=1; $i<=$n; $i++){ ?> <tr id="r<?php echo $i; ?>"> <td colspan="2"> - <div style="width:700px; float:left;"> + <div> <select name="rule_w<?php echo $i; ?>"> <option value="">— Select One ‐</option> <?php @@ -143,15 +143,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> </select> - <input type="text" size="30" name="rule_v<?php echo $i; ?>" value="<?php echo $info["rule_v$i"]; ?>"> + <input type="text" size="60" name="rule_v<?php echo $i; ?>" value="<?php echo $info["rule_v$i"]; ?>"> <span class="error"> <?php echo $errors["rule_$i"]; ?></span> - </div> <?php if($info["rule_w$i"] || $info["rule_h$i"] || $info["rule_v$i"]){ ?> <div style="float:right;text-align:right;padding-right:20px;"><a href="#" class="clearrule">(clear)</a></div> <?php } ?> - <div class="clear"></div> + </div> </td> </tr> <?php diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index 6dad7bd6c28059aac49c966dd472d51ddcf6e66a..3ac67bf82846c663c880c6fc441e5e108e310989 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -f1ccd3bb620e314b0ae1dbd0a1a99177 +f5692e24c7afba7ab6168dde0b3bb3c8 diff --git a/include/upgrader/streams/core/ed60ba20-934954de.patch.sql b/include/upgrader/streams/core/ed60ba20-934954de.patch.sql index a9b2ec364841d77ef62fe4f13b01367796d5da0f..39c210f4d1d7d9ffa65150243cb4cf81db96d552 100644 --- a/include/upgrader/streams/core/ed60ba20-934954de.patch.sql +++ b/include/upgrader/streams/core/ed60ba20-934954de.patch.sql @@ -17,11 +17,8 @@ UPDATE `%TABLE_PREFIX%filter_rule` -- [#331](https://github.com/osTicket/osTicket-1.8/issues/331) -- Previously there was no primary key on the %ticket_email_info table, so -- clean up any junk records before adding one -DELETE FROM `%TABLE_PREFIX%ticket_email_info` WHERE - `message_id` = 0 OR `message_id` IS NULL; ALTER TABLE `%TABLE_PREFIX%ticket_email_info` CHANGE `message_id` `thread_id` int(11) unsigned NOT NULL, - ADD PRIMARY KEY (`thread_id`), DROP INDEX `message_id`, ADD INDEX `email_mid` (`email_mid`); diff --git a/include/upgrader/streams/core/f1ccd3bb-f5692e24.cleanup.sql b/include/upgrader/streams/core/f1ccd3bb-f5692e24.cleanup.sql new file mode 100644 index 0000000000000000000000000000000000000000..109cf6c02c663533571b1e15c28086e213ddbcae --- /dev/null +++ b/include/upgrader/streams/core/f1ccd3bb-f5692e24.cleanup.sql @@ -0,0 +1,18 @@ +/** + * @version v1.8.1 + * @signature f5692e24c7afba7ab6168dde0b3bb3c8 + * @title Add regex field to ticket filters + * + * This fixes a glitch introduced @934954de8914d9bd2bb8343e805340ae where + * a primary key was added to the %ticket_email_info table so that deleting + * can be supported in a clustered environment. The patch added the + * `thread_id` column as the primary key, which was incorrect, because the + * `thread_id` may be null when rejected emails are recorded so they are + * never considered again if found in the inbox. + */ + +-- Add the primary key. The PK on `thread_id` would have been removed in the +-- task if it existed +ALTER TABLE `%TABLE_PREFIX%ticket_email_info` + ADD `id` int(11) unsigned not null auto_increment FIRST, + ADD PRIMARY KEY (`id`); diff --git a/include/upgrader/streams/core/f1ccd3bb-f5692e24.patch.sql b/include/upgrader/streams/core/f1ccd3bb-f5692e24.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..fab41f1bc758901bfc0a7537643556a1a90dadb9 --- /dev/null +++ b/include/upgrader/streams/core/f1ccd3bb-f5692e24.patch.sql @@ -0,0 +1,46 @@ +/** + * @version v1.8.1 + * @signature f5692e24c7afba7ab6168dde0b3bb3c8 + * @title Add regex field to ticket filters + * + * This fixes a glitch introduced @934954de8914d9bd2bb8343e805340ae where + * a primary key was added to the %ticket_email_info table so that deleting + * can be supported in a clustered environment. The patch added the + * `thread_id` column as the primary key, which was incorrect, because the + * `thread_id` may be null when rejected emails are recorded so they are + * never considered again if found in the inbox. + */ + +-- [#479](https://github.com/osTicket/osTicket-1.8/issues/479) +-- Add (not)_match to the filter_rule `how` +ALTER TABLE `%TABLE_PREFIX%filter_rule` + CHANGE `how` `how` enum('equal','not_equal','contains','dn_contain','starts','ends','match','not_match') + NOT NULL; + +-- Allow `isactive` to be `-1` for collaborators, which might indicate +-- something like 'unsubscribed' +ALTER TABLE `%TABLE_PREFIX%ticket_collaborator` + CHANGE `isactive` `isactive` tinyint(1) NOT NULL DEFAULT '1'; + +-- There is no `subject` available in the filter::apply method for anything but email +UPDATE `%TABLE_PREFIX%filter_rule` + SET `what` = CONCAT('field.', ( + SELECT field.`id` FROM `%TABLE_PREFIX%form_field` field + JOIN `%TABLE_PREFIX%form` form ON (field.form_id = form.id) + WHERE field.`name` = 'subject' AND form.`type` = 'T' + )) + WHERE `what` = 'subject'; + +-- There is no `body` available in the filter::apply method for anything but emails +UPDATE `%TABLE_PREFIX%filter_rule` + SET `what` = CONCAT('field.', ( + SELECT field.`id` FROM `%TABLE_PREFIX%form_field` field + JOIN `%TABLE_PREFIX%form` form ON (field.form_id = form.id) + WHERE field.`name` = 'message' AND form.`type` = 'T' + )) + WHERE `what` = 'body'; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = 'f5692e24c7afba7ab6168dde0b3bb3c8' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/streams/core/f1ccd3bb-f5692e24.task.php b/include/upgrader/streams/core/f1ccd3bb-f5692e24.task.php new file mode 100644 index 0000000000000000000000000000000000000000..fd58fe77064f1a81e41e6dc7a31ba0daac3a1de9 --- /dev/null +++ b/include/upgrader/streams/core/f1ccd3bb-f5692e24.task.php @@ -0,0 +1,27 @@ +<?php + +/* + * Drops the `thread_id` primary key on the ticket_email_info table if it + * exists + */ + +class DropTicketEmailInfoPk extends MigrationTask { + var $description = "Reticulating splines"; + + function run($max_time) { + $sql = 'SELECT `INDEX_NAME` FROM information_schema.statistics + WHERE table_schema = '.db_input(DBNAME) + .' AND table_name = '.db_input(TICKET_EMAIL_INFO_TABLE) + .' AND column_name = '.db_input('thread_id'); + if ($name = db_result(db_query($sql))) { + if ($name == 'PRIMARY') { + db_query('ALTER TABLE `'.TICKET_EMAIL_INFO_TABLE + .'` DROP PRIMARY KEY'); + } + } + } +} + +return 'DropTicketEmailInfoPk'; + +?> diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 4b51a63c9410e742ac716eb759a61670ef968799..85f06999698f21093930575429e967ff7e28ab29 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -276,7 +276,7 @@ CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `id` int(11) unsigned NOT NULL auto_increment, `filter_id` int(10) unsigned NOT NULL default '0', `what` varchar(32) NOT NULL, - `how` enum('equal','not_equal','contains','dn_contain','starts','ends') NOT NULL, + `how` enum('equal','not_equal','contains','dn_contain','starts','ends','match','not_match') NOT NULL, `val` varchar(255) NOT NULL, `isactive` tinyint(1) unsigned NOT NULL DEFAULT '1', `notes` tinytext NOT NULL, @@ -572,10 +572,11 @@ CREATE TABLE `%TABLE_PREFIX%ticket_lock` ( DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_email_info`; CREATE TABLE `%TABLE_PREFIX%ticket_email_info` ( + `id` int(11) unsigned NOT NULL auto_increment, `thread_id` int(11) unsigned NOT NULL, `email_mid` varchar(255) NOT NULL, `headers` text, - PRIMARY KEY (`thread_id`), + PRIMARY KEY (`id`), KEY `email_mid` (`email_mid`) ) DEFAULT CHARSET=utf8; @@ -631,7 +632,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_thread` ( CREATE TABLE `%TABLE_PREFIX%ticket_collaborator` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `isactive` tinyint(1) unsigned NOT NULL DEFAULT '1', + `isactive` tinyint(1) NOT NULL DEFAULT '1', `ticket_id` int(11) unsigned NOT NULL DEFAULT '0', `user_id` int(11) unsigned NOT NULL DEFAULT '0', -- M => (message) clients, N => (note) 3rd-Party, R => (reply) external authority