From 0185a2790767caab93f2c504ad38d7edca70d043 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sat, 15 Jun 2013 09:35:55 -0400
Subject: [PATCH] Implemented date, currentdate and index - RFC5260 (#1488120)

---
 plugins/managesieve/Changelog                     |    1 
 plugins/managesieve/skins/larry/managesieve.css   |   11 +
 plugins/managesieve/localization/en_US.inc        |   23 +++
 plugins/managesieve/skins/classic/managesieve.css |    8 +
 plugins/managesieve/managesieve.js                |   56 +++++---
 plugins/managesieve/managesieve.php               |  249 ++++++++++++++++++++++++++++++++++-------
 6 files changed, 281 insertions(+), 67 deletions(-)

diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog
index a5f3250..a2bdf68 100644
--- a/plugins/managesieve/Changelog
+++ b/plugins/managesieve/Changelog
@@ -1,6 +1,7 @@
 - Add vacation-seconds extension support (RFC 6131)
 - Several script parser code improvements
 - Support string list arguments in filter form (#1489018)
+- Support date, currendate and index tests - RFC5260 (#1488120)
 
 * version 6.2 [2013-02-17]
 -----------------------------------------------------------
diff --git a/plugins/managesieve/localization/en_US.inc b/plugins/managesieve/localization/en_US.inc
index 9fe9434..72bbf9d 100644
--- a/plugins/managesieve/localization/en_US.inc
+++ b/plugins/managesieve/localization/en_US.inc
@@ -83,13 +83,13 @@
 $labels['countislessthan'] = 'count is less than';
 $labels['countislessthanequal'] = 'count is less than or equal to';
 $labels['countequals'] = 'count is equal to';
-$labels['countnotequals'] = 'count does not equal';
+$labels['countnotequals'] = 'count is not equal to';
 $labels['valueisgreaterthan'] = 'value is greater than';
 $labels['valueisgreaterthanequal'] = 'value is greater than or equal to';
 $labels['valueislessthan'] = 'value is less than';
 $labels['valueislessthanequal'] = 'value is less than or equal to';
 $labels['valueequals'] = 'value is equal to';
-$labels['valuenotequals'] = 'value does not equal';
+$labels['valuenotequals'] = 'value is not equal to';
 $labels['setflags'] = 'Set flags to the message';
 $labels['addflags'] = 'Add flags to the message';
 $labels['removeflags'] = 'Remove flags from the message';
@@ -121,6 +121,22 @@
 $labels['usedata'] = 'Use following data in the filter:';
 $labels['nextstep'] = 'Next Step';
 $labels['...'] = '...';
+$labels['currdate'] = 'Current date';
+$labels['datetest'] = 'Date';
+$labels['dateheader'] = 'header:';
+$labels['year'] = 'year';
+$labels['month'] = 'month';
+$labels['day'] = 'day';
+$labels['date'] = 'date (yyyy-mm-dd)';
+$labels['julian'] = 'date (julian)';
+$labels['hour'] = 'hour';
+$labels['minute'] = 'minute';
+$labels['second'] = 'second';
+$labels['time'] = 'time (hh:mm:ss)';
+$labels['iso8601'] = 'date (ISO8601)';
+$labels['std11'] = 'date (RFC2822)';
+$labels['zone'] = 'time-zone';
+$labels['weekday'] = 'weekday (0-6)';
 $labels['advancedopts'] = 'Advanced options';
 $labels['body'] = 'Body';
 $labels['address'] = 'address';
@@ -140,6 +156,8 @@
 $labels['octet'] = 'strict (octet)';
 $labels['asciicasemap'] = 'case insensitive (ascii-casemap)';
 $labels['asciinumeric'] = 'numeric (ascii-numeric)';
+$labels['index'] = 'index:';
+$labels['indexlast'] = 'backwards';
 
 $messages = array();
 $messages['filterunknownerror'] = 'Unknown server error.';
@@ -173,5 +191,6 @@
 $messages['namereserved'] = 'Reserved name.';
 $messages['setexist'] = 'Set already exists.';
 $messages['nodata'] = 'At least one position must be selected!';
+$messages['invaliddateformat'] = 'Invalid date or date part format';
 
 ?>
diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js
index 3a87a9f..f6bf4b4 100644
--- a/plugins/managesieve/managesieve.js
+++ b/plugins/managesieve/managesieve.js
@@ -578,27 +578,32 @@
     header = document.getElementById('custom_header' + id + '_list'),
     mod = document.getElementById('rule_mod' + id),
     trans = document.getElementById('rule_trans' + id),
-    comp = document.getElementById('rule_comp' + id);
+    comp = document.getElementById('rule_comp' + id),
+    datepart = document.getElementById('rule_date_part' + id),
+    dateheader = document.getElementById('rule_date_header_div' + id),
+    h = obj.value;
 
-  if (obj.value == 'size') {
+  if (h == 'size') {
     size.style.display = 'inline';
-    op.style.display = 'none';
-    header.style.display = 'none';
-    mod.style.display = 'none';
-    trans.style.display = 'none';
-    comp.style.display = 'none';
+    $.each([op, header, mod, trans, comp], function() { this.style.display = 'none'; });
   }
   else {
-    header.style.display = obj.value != '...' ? 'none' : 'inline-block';
+    header.style.display = h != '...' ? 'none' : 'inline-block';
     size.style.display = 'none';
     op.style.display = 'inline';
     comp.style.display = '';
-    mod.style.display = obj.value == 'body' ? 'none' : 'block';
-    trans.style.display = obj.value == 'body' ? 'block' : 'none';
+    mod.style.display = h == 'body' || h == 'currentdate' || h == 'date' ? 'none' : 'block';
+    trans.style.display = h == 'body' ? 'block' : 'none';
   }
 
-  rule_op_select(op, id, obj.value);
-  obj.style.width = obj.value == '...' ? '40px' : '';
+  if (datepart)
+    datepart.style.display = h == 'currentdate' || h == 'date' ? 'inline' : 'none';
+  if (dateheader)
+    dateheader.style.display = h == 'date' ? '' : 'none';
+
+  rule_op_select(op, id, h);
+  rule_mod_select(id, h);
+  obj.style.width = h == '...' ? '40px' : '';
 };
 
 function rule_op_select(obj, id, header)
@@ -619,12 +624,19 @@
   target.style.display = obj.value != 'content' ? 'none' : 'inline';
 };
 
-function rule_mod_select(id)
+function rule_mod_select(id, header)
 {
   var obj = document.getElementById('rule_mod_op' + id),
-    target = document.getElementById('rule_mod_type' + id);
+    target = document.getElementById('rule_mod_type' + id),
+    index = document.getElementById('rule_index_div' + id);
+
+  if (!header)
+    header = document.getElementById('header' + id).value;
 
   target.style.display = obj.value != 'address' && obj.value != 'envelope' ? 'none' : 'inline';
+
+  if (index)
+    index.style.display = header != 'body' && header != 'currentdate' && header != 'size' && obj.value != 'envelope'  ? '' : 'none';
 };
 
 function rule_join_radio(value)
@@ -649,7 +661,7 @@
 function action_type_select(id)
 {
   var obj = document.getElementById('action_type' + id),
-    enabled = {},
+    v = obj.value(), enabled = {},
     elems = {
       mailbox: document.getElementById('action_mailbox' + id),
       target: document.getElementById('action_target' + id),
@@ -660,25 +672,25 @@
       notify: document.getElementById('action_notify' + id)
     };
 
-  if (obj.value == 'fileinto' || obj.value == 'fileinto_copy') {
+  if (v == 'fileinto' || v == 'fileinto_copy') {
     enabled.mailbox = 1;
   }
-  else if (obj.value == 'redirect' || obj.value == 'redirect_copy') {
+  else if (v == 'redirect' || v == 'redirect_copy') {
     enabled.target = 1;
   }
-  else if (obj.value.match(/^reject|ereject$/)) {
+  else if (v.match(/^reject|ereject$/)) {
     enabled.target_area = 1;
   }
-  else if (obj.value.match(/^(add|set|remove)flag$/)) {
+  else if (v.match(/^(add|set|remove)flag$/)) {
     enabled.flags = 1;
   }
-  else if (obj.value == 'vacation') {
+  else if (v == 'vacation') {
     enabled.vacation = 1;
   }
-  else if (obj.value == 'set') {
+  else if (v == 'set') {
     enabled.set = 1;
   }
-  else if (obj.value == 'notify') {
+  else if (v == 'notify') {
     enabled.notify = 1;
   }
 
diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php
index 0dfe05a..d514c28 100644
--- a/plugins/managesieve/managesieve.php
+++ b/plugins/managesieve/managesieve.php
@@ -12,8 +12,8 @@
  *
  * Configuration (see config.inc.php.dist)
  *
- * Copyright (C) 2008-2012, The Roundcube Dev Team
- * Copyright (C) 2011-2012, Kolab Systems AG
+ * Copyright (C) 2008-2013, The Roundcube Dev Team
+ * Copyright (C) 2011-2013, Kolab Systems AG
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2
@@ -662,6 +662,10 @@
             $body_trans     = rcube_utils::get_input_value('_rule_trans', rcube_utils::INPUT_POST);
             $body_types     = rcube_utils::get_input_value('_rule_trans_type', rcube_utils::INPUT_POST, true);
             $comparators    = rcube_utils::get_input_value('_rule_comp', rcube_utils::INPUT_POST);
+            $indexes        = rcube_utils::get_input_value('_rule_index', rcube_utils::INPUT_POST);
+            $lastindexes    = rcube_utils::get_input_value('_rule_index_last', rcube_utils::INPUT_POST);
+            $dateheaders    = rcube_utils::get_input_value('_rule_date_header', rcube_utils::INPUT_POST);
+            $dateparts      = rcube_utils::get_input_value('_rule_date_part', rcube_utils::INPUT_POST);
             $act_types      = rcube_utils::get_input_value('_action_type', rcube_utils::INPUT_POST, true);
             $mailboxes      = rcube_utils::get_input_value('_action_mailbox', rcube_utils::INPUT_POST, true);
             $act_targets    = rcube_utils::get_input_value('_action_target', rcube_utils::INPUT_POST, true);
@@ -732,6 +736,82 @@
                         else
                             $this->form['tests'][$i]['arg'] .= $m[1];
                     }
+                    else if ($header == 'currentdate') {
+                        $datepart = $this->strip_value($dateparts[$idx]);
+
+                        if (preg_match('/^not/', $operator))
+                            $this->form['tests'][$i]['not'] = true;
+                        $type = preg_replace('/^not/', '', $operator);
+
+                        if ($type == 'exists') {
+                            $this->errors['tests'][$i]['op'] = true;
+                        }
+
+                        $this->form['tests'][$i]['test'] = 'currentdate';
+                        $this->form['tests'][$i]['type'] = $type;
+                        $this->form['tests'][$i]['part'] = $datepart;
+                        $this->form['tests'][$i]['arg']  = $target;
+
+                        if ($type != 'exists') {
+                            if (!count($target)) {
+                                $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty');
+                            }
+                            else if ($type != 'regex' && $type != 'matches') {
+                                foreach ($target as $arg) {
+                                    if (!$this->validate_date_part($datepart, $arg)) {
+                                        $this->errors['tests'][$i]['target'] = $this->gettext('invaliddateformat');
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else if ($header == 'date') {
+                        $datepart    = $this->strip_value($dateparts[$idx]);
+                        $dateheader  = $this->strip_value($dateheaders[$idx]);
+                        $index       = $this->strip_value($indexes[$idx]);
+                        $indexlast   = $this->strip_value($lastindexes[$idx]);
+
+                        if (preg_match('/^not/', $operator))
+                            $this->form['tests'][$i]['not'] = true;
+                        $type = preg_replace('/^not/', '', $operator);
+
+                        if ($type == 'exists') {
+                            $this->errors['tests'][$i]['op'] = true;
+                        }
+
+                        if (!empty($index) && $mod != 'envelope') {
+                            $this->form['tests'][$i]['index'] = intval($index);
+                            $this->form['tests'][$i]['last']  = !empty($indexlast);
+                        }
+
+                        if (empty($dateheader)) {
+                            $dateheader = 'Date';
+                        }
+                        else if (!preg_match('/^[\x21-\x39\x41-\x7E]+$/i', $dateheader)) {
+                            $this->errors['tests'][$i]['dateheader'] = $this->gettext('forbiddenchars');
+                        }
+
+                        $this->form['tests'][$i]['test']   = 'date';
+                        $this->form['tests'][$i]['type']   = $type;
+                        $this->form['tests'][$i]['part']   = $datepart;
+                        $this->form['tests'][$i]['arg']    = $target;
+                        $this->form['tests'][$i]['header'] = $dateheader;
+
+                        if ($type != 'exists') {
+                            if (!count($target)) {
+                                $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty');
+                            }
+                            else if ($type != 'regex' && $type != 'matches') {
+                                foreach ($target as $arg) {
+                                    if (!$this->validate_date_part($datepart, $arg)) {
+                                        $this->errors['tests'][$i]['target'] = $this->gettext('invaliddateformat');
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
                     else if ($header == 'body') {
                         $trans      = $this->strip_value($body_trans[$idx]);
                         $trans_type = $this->strip_value($body_types[$idx], true);
@@ -766,12 +846,19 @@
                     }
                     else {
                         $cust_header = $headers = $this->strip_value(array_shift($cust_headers));
-                        $mod      = $this->strip_value($mods[$idx]);
-                        $mod_type = $this->strip_value($mod_types[$idx]);
+                        $mod         = $this->strip_value($mods[$idx]);
+                        $mod_type    = $this->strip_value($mod_types[$idx]);
+                        $index       = $this->strip_value($indexes[$idx]);
+                        $indexlast   = $this->strip_value($lastindexes[$idx]);
 
                         if (preg_match('/^not/', $operator))
                             $this->form['tests'][$i]['not'] = true;
                         $type = preg_replace('/^not/', '', $operator);
+
+                        if (!empty($index) && $mod != 'envelope') {
+                            $this->form['tests'][$i]['index'] = intval($index);
+                            $this->form['tests'][$i]['last']  = !empty($indexlast);
+                        }
 
                         if ($header == '...') {
                             if (!count($headers))
@@ -1304,35 +1391,40 @@
         // headers select
         $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id,
             'onchange' => 'rule_header_select(' .$id .')'));
-        foreach($this->headers as $name => $val)
+
+        foreach ($this->headers as $name => $val)
             $select_header->add(rcube::Q($this->gettext($name)), Q($val));
+        $select_header->add(rcube::Q($this->gettext('...')), '...');
         if (in_array('body', $this->exts))
             $select_header->add(rcube::Q($this->gettext('body')), 'body');
         $select_header->add(rcube::Q($this->gettext('size')), 'size');
-        $select_header->add(rcube::Q($this->gettext('...')), '...');
-
-        // TODO: list arguments
-        $aout = '';
-
-        if ((isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope')))
-            && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)
-        ) {
-            $aout .= $select_header->show($rule['arg1']);
+        if (in_array('date', $this->exts)) {
+            $select_header->add(rcube::Q($this->gettext('datetest')), 'date');
+            $select_header->add(rcube::Q($this->gettext('currdate')), 'currentdate');
         }
-        else if ((isset($rule['test']) && $rule['test'] == 'exists')
-            && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)
-        ) {
-            $aout .= $select_header->show($rule['arg']);
-        }
-        else if (isset($rule['test']) && $rule['test'] == 'size')
-            $aout .= $select_header->show('size');
-        else if (isset($rule['test']) && $rule['test'] == 'body')
-            $aout .= $select_header->show('body');
-        else if (isset($rule['test']) && $rule['test'] != 'true')
-            $aout .= $select_header->show('...');
-        else
-            $aout .= $select_header->show();
 
+        if (isset($rule['test'])) {
+            if (in_array($rule['test'], array('header', 'address', 'envelope'))
+                && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)
+            ) {
+                $test = $rule['arg1'];
+            }
+            else if ($rule['test'] == 'exists'
+                && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)
+            ) {
+                $test = $rule['arg'];
+            }
+            else if (in_array($rule['test'], array('size', 'body', 'date', 'currentdate'))) {
+                $test = $rule['test'];
+            }
+            else if ($rule['test'] != 'true') {
+                $test = '...';
+            }
+        }
+
+        $aout = $select_header->show($test);
+
+        // custom headers input
         if (isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope'))) {
             $custom = (array) $rule['arg1'];
             if (count($custom) == 1 && isset($this->headers[strtolower($custom[0])])) {
@@ -1381,13 +1473,28 @@
             $select_op->add(rcube::Q($this->gettext('valuenotequals')), 'value-ne');
         }
 
-        // target input (TODO: lists)
+        // (current)date part select
+        if (in_array('date', $this->exts) || in_array('currentdate', $this->exts)) {
+            $date_parts = array('date', 'iso8601', 'std11', 'julian', 'time',
+                'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday', 'zone');
+            $select_dp = new html_select(array('name' => "_rule_date_part[]", 'id' => 'rule_date_part'.$id,
+                'style' => $rule['test'] == 'currentdate' || $rule['test'] == 'date' ? '' : 'display:none',
+                'class' => 'datepart_selector',
+            ));
 
+            foreach ($date_parts as $part) {
+                $select_dp->add(rcube::Q($this->gettext($part)), $part);
+            }
+
+            $tout .= $select_dp->show($rule['test'] == 'currentdate' || $rule['test'] == 'date' ? $rule['part'] : '');
+        }
+
+        // target(s) input
         if (in_array($rule['test'], array('header', 'address', 'envelope'))) {
             $test   = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is');
             $target = $rule['arg2'];
         }
-        else if ($rule['test'] == 'body') {
+        else if (in_array($rule['test'], array('body', 'date', 'currentdate'))) {
             $test   = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is');
             $target = $rule['arg'];
         }
@@ -1449,11 +1556,9 @@
         }
 
         $need_mod = $rule['test'] != 'size' && $rule['test'] != 'body';
-        $mout = '<div id="rule_mod' .$id. '" class="adv" style="display:' . ($need_mod ? 'block' : 'none') .'">';
-        $mout .= ' <span>';
-        $mout .= rcube::Q($this->gettext('modifier')) . ' ';
+        $mout = '<div id="rule_mod' .$id. '" class="adv"' . (!$need_mod ? ' style="display:none"' : '') . '>';
+        $mout .= ' <span class="label">' . rcube::Q($this->gettext('modifier')) . ' </span>';
         $mout .= $select_mod->show($rule['test']);
-        $mout .= '</span>';
         $mout .= ' <span id="rule_mod_type' . $id . '"';
         $mout .= ' style="display:' . (in_array($rule['test'], array('address', 'envelope')) ? 'inline' : 'none') .'">';
         $mout .= rcube::Q($this->gettext('modtype')) . ' ';
@@ -1468,15 +1573,13 @@
         $select_mod->add(rcube::Q($this->gettext('undecoded')), 'raw');
         $select_mod->add(rcube::Q($this->gettext('contenttype')), 'content');
 
-        $mout .= '<div id="rule_trans' .$id. '" class="adv" style="display:' . ($rule['test'] == 'body' ? 'block' : 'none') .'">';
-        $mout .= ' <span>';
-        $mout .= rcube::Q($this->gettext('modifier')) . ' ';
+        $mout .= '<div id="rule_trans' .$id. '" class="adv"' . ($rule['test'] != 'body' ? ' style="display:none"' : '') . '>';
+        $mout .= '<span class="label">' . rcube::Q($this->gettext('modifier')) . '</span>';
         $mout .= $select_mod->show($rule['part']);
         $mout .= '<input type="text" name="_rule_trans_type[]" id="rule_trans_type'.$id
             . '" value="'.(is_array($rule['content']) ? implode(',', $rule['content']) : $rule['content'])
-            .'" size="20" style="display:' . ($rule['part'] == 'content' ? 'inline' : 'none') .'"'
+            .'" size="20"' . ($rule['part'] != 'content' ? ' style="display:none"' : '')
             . $this->error_class($id, 'test', 'part', 'rule_trans_type') .' />';
-        $mout .= '</span>';
         $mout .= '</div>';
 
         // Advanced modifiers (body transformations)
@@ -1488,12 +1591,35 @@
             $select_comp->add(rcube::Q($this->gettext('asciinumeric')), 'i;ascii-numeric');
         }
 
-        $mout .= '<div id="rule_comp' .$id. '" class="adv" style="display:' . ($rule['test'] != 'size' ? 'block' : 'none') .'">';
-        $mout .= ' <span>';
-        $mout .= rcube::Q($this->gettext('comparator')) . ' ';
+        // Comparators
+        $mout .= '<div id="rule_comp' .$id. '" class="adv"' . ($rule['test'] == 'size' ? ' style="display:none"' : '') . '>';
+        $mout .= '<span class="label">' . rcube::Q($this->gettext('comparator')) . '</span>';
         $mout .= $select_comp->show($rule['comparator']);
-        $mout .= '</span>';
         $mout .= '</div>';
+
+        // Date header
+        if (in_array('date', $this->exts)) {
+            $mout .= '<div id="rule_date_header_div' .$id. '" class="adv"'. ($rule['test'] != 'date' ? ' style="display:none"' : '') .'>';
+            $mout .= '<span class="label">' . rcube::Q($this->gettext('dateheader')) . '</span>';
+            $mout .= '<input type="text" name="_rule_date_header[]" id="rule_date_header'.$id
+                . '" value="'. Q($rule['test'] == 'date' ? $rule['header'] : '')
+                . '" size="15"' . $this->error_class($id, 'test', 'dateheader', 'rule_date_header') .' />';
+            $mout .= '</div>';
+        }
+
+        // Index
+        if (in_array('index', $this->exts)) {
+            $need_index = in_array($rule['test'], array('header', ', address', 'date'));
+            $mout .= '<div id="rule_index_div' .$id. '" class="adv"'. (!$need_index ? ' style="display:none"' : '') .'>';
+            $mout .= '<span class="label">' . rcube::Q($this->gettext('index')) . '</span>';
+            $mout .= '<input type="text" name="_rule_index[]" id="rule_index'.$id
+                . '" value="'. ($rule['index'] ? intval($rule['index']) : '')
+                . '" size="3"' . $this->error_class($id, 'test', 'index', 'rule_index') .' />';
+            $mout .= '&nbsp;<input type="checkbox" name="_rule_index_last[]" id="rule_index_last'.$id
+                . '" value="1"' . (!empty($rule['last']) ? ' checked="checked"' : '') . ' />'
+                . '<label for="rule_index_last'.$id.'">'.rcube::Q($this->gettext('indexlast')).'</label>';
+            $mout .= '</div>';
+        }
 
         // Build output table
         $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : '';
@@ -1792,6 +1918,45 @@
     }
 
     /**
+     * Validate input for date part elements
+     */
+    private function validate_date_part($type, $value)
+    {
+        // we do simple validation of date/part format
+        switch ($type) {
+            case 'date': // yyyy-mm-dd
+                return preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $value);
+            case 'iso8601':
+                return preg_match('/^[0-9: .,ZWT+-]+$/', $value);
+            case 'std11':
+                return preg_match('/^((Sun|Mon|Tue|Wed|Thu|Fri|Sat),\s+)?[0-9]{1,2}\s+'
+                    . '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+[0-9]{2,4}\s+'
+                    . '[0-9]{2}:[0-9]{2}(:[0-9]{2})?\s+([+-]*[0-9]{4}|[A-Z]{1,3})$', $value);
+            case 'julian':
+                return preg_match('/^[0-9]+$/', $value);
+            case 'time': // hh:mm:ss
+                return preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $value);
+            case 'year':
+                return preg_match('/^[0-9]{4}$/', $value);
+            case 'month':
+                return preg_match('/^[0-9]{2}$/', $value) && $value > 0 && $value < 13;
+            case 'day':
+                return preg_match('/^[0-9]{2}$/', $value) && $value > 0 && $value < 32;
+            case 'hour':
+                return preg_match('/^[0-9]{2}$/', $value) && $value < 24;
+            case 'minute':
+                return preg_match('/^[0-9]{2}$/', $value) && $value < 60;
+            case 'second':
+                // According to RFC5260, seconds can be from 00 to 60
+                return preg_match('/^[0-9]{2}$/', $value) && $value < 61;
+            case 'weekday':
+                return preg_match('/^[0-9]$/', $value) && $value < 7;
+            case 'zone':
+                return preg_match('/^[+-][0-9]{4}$/', $value);
+        }
+    }
+
+    /**
      * Converts mailbox name from/to UTF7-IMAP from/to internal Sieve encoding
      * with delimiter replacement.
      *
diff --git a/plugins/managesieve/skins/classic/managesieve.css b/plugins/managesieve/skins/classic/managesieve.css
index 1f112b9..59d88cb 100644
--- a/plugins/managesieve/skins/classic/managesieve.css
+++ b/plugins/managesieve/skins/classic/managesieve.css
@@ -212,6 +212,13 @@
   padding-top: 3px;
 }
 
+td.rowtargets div.adv span.label
+{
+  display: inline-block;
+  padding-right: 10px;
+  min-width: 65px;
+}
+
 html.mozilla #filter-form select
 {
   padding-top: 3px;
@@ -319,6 +326,7 @@
 #filter-form textarea
 {
   font-size: 11px;
+  vertical-align: middle;
 }
 
 /* smart multi-row input field */
diff --git a/plugins/managesieve/skins/larry/managesieve.css b/plugins/managesieve/skins/larry/managesieve.css
index a618502..2144fe1 100644
--- a/plugins/managesieve/skins/larry/managesieve.css
+++ b/plugins/managesieve/skins/larry/managesieve.css
@@ -43,7 +43,7 @@
 #filtersetslist
 {
   width: 100%;
-  table-layout: fixed; 
+  table-layout: fixed;
 }
 
 #filterslist tbody td,
@@ -184,6 +184,14 @@
 td.rowtargets div.adv
 {
   padding-top: 3px;
+  font-size: 10px;
+}
+
+td.rowtargets div.adv span.label
+{
+  display: inline-block;
+  padding-right: 5px;
+  min-width: 70px;
 }
 
 input.disabled, input.disabled:hover
@@ -297,6 +305,7 @@
 {
   font-size: 11px;
   padding: 1px;
+  vertical-align: middle;
 }
 
 html.mozilla #filter-form select

--
Gitblit v1.9.1