diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php
index 50c4805ba8..c242a13c37 100644
--- a/api/src/Etemplate.php
+++ b/api/src/Etemplate.php
@@ -344,7 +344,7 @@ class Etemplate extends Etemplate\Widget\Template
{
error_log(__METHOD__."(,".array2string($_content).') validation_errors='.array2string(self::$validation_errors));
self::$response->generic('et2_validation_error', self::$validation_errors);
- exit;
+ return;
}
// tell request call to remove request, if it is not modified eg. by call to exec in callback
diff --git a/api/src/Etemplate/Widget.php b/api/src/Etemplate/Widget.php
index c19d6eca16..0ab3b25454 100644
--- a/api/src/Etemplate/Widget.php
+++ b/api/src/Etemplate/Widget.php
@@ -889,7 +889,7 @@ class Widget
public static function set_validation_error($name,$error,$cname=null)
{
// not yet used: if (is_null($cname)) $cname = self::$name_vars;
- error_log(__METHOD__."('$name','$error','$cname') ".function_backtrace());
+ //error_log(__METHOD__."('$name','$error','$cname') ".function_backtrace());
if ($cname) $name = self::form_name($cname,$name);
diff --git a/api/src/Etemplate/Widget/Textbox.php b/api/src/Etemplate/Widget/Textbox.php
index bf5c135071..e182d5d039 100644
--- a/api/src/Etemplate/Widget/Textbox.php
+++ b/api/src/Etemplate/Widget/Textbox.php
@@ -120,7 +120,7 @@ class Textbox extends Etemplate\Widget
{
if (!isset($this->attrs['validator']))
{
- switch($this->type)
+ switch($this->attrs['type'])
{
case 'int':
case 'integer':
@@ -158,7 +158,7 @@ class Textbox extends Etemplate\Widget
}
if ($this->attrs['validator'] && !preg_match($this->attrs['validator'],$value))
{
- switch($this->type)
+ switch($this->attrs['type'])
{
case 'integer':
self::set_validation_error($form_name,lang("'%1' is not a valid integer !!!",$value),'');
@@ -171,21 +171,21 @@ class Textbox extends Etemplate\Widget
break;
}
}
- elseif ($this->type == 'integer' || $this->type == 'float') // cast int and float and check range
+ elseif ($this->attrs['type'] == 'integer' || $this->attrs['type'] == 'float') // cast int and float and check range
{
if ((string)$value !== '' || $this->attrs['needed']) // empty values are Ok if needed is not set
{
- $value = $this->type == 'integer' ? (int) $value : (float) str_replace(',','.',$value); // allow for german (and maybe other) format
+ $value = $this->attrs['type'] == 'integer' ? (int) $value : (float) str_replace(',','.',$value); // allow for german (and maybe other) format
if (!empty($this->attrs['min']) && $value < $this->attrs['min'])
{
self::set_validation_error($form_name,lang("Value has to be at least '%1' !!!",$this->attrs['min']),'');
- $value = $this->type == 'integer' ? (int) $this->attrs['min'] : (float) $this->attrs['min'];
+ $value = $this->attrs['type'] == 'integer' ? (int) $this->attrs['min'] : (float) $this->attrs['min'];
}
if (!empty($this->attrs['max']) && $value > $this->attrs['max'])
{
self::set_validation_error($form_name,lang("Value has to be at maximum '%1' !!!",$this->attrs['max']),'');
- $value = $this->type == 'integer' ? (int) $this->attrs['max'] : (float) $this->attrs['max'];
+ $value = $this->attrs['type'] == 'integer' ? (int) $this->attrs['max'] : (float) $this->attrs['max'];
}
}
}
diff --git a/api/src/Etemplate/Widget/test/FloatTest.php b/api/src/Etemplate/Widget/test/FloatTest.php
new file mode 100644
index 0000000000..0095e36c87
--- /dev/null
+++ b/api/src/Etemplate/Widget/test/FloatTest.php
@@ -0,0 +1,197 @@
+read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello'
+ );
+
+ $this->validateRoundTrip($etemplate, $content, array('widget' => $value),
+ $error ? array() : array('widget' => $expected),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ /**
+ * Data provider for float tests
+ */
+ public function floatProvider()
+ {
+ return array(
+ // User value, Expected Error
+ array('', '', false),
+ array(1, 1, false),
+ array(0, 0, false),
+ array(-1, -1, false),
+ array(1.5, 1.5, false),
+ array('1,5', 1.5, false), // Comma as separator is handled
+ array('one', '', true)
+ );
+ }
+
+ /**
+ * Test for float minimum attribute
+ *
+ * @param String|numeric $value
+ * @param float $min Minimum allowed value
+ * @param boolean $error
+ *
+ * @dataProvider minProvider
+ */
+ public function testMin($value, $min, $error)
+ {
+ // Instanciate the template
+ $etemplate = new Etemplate();
+ $etemplate->read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Only lowercase
+ $etemplate->getElementById('widget')->attrs['min'] = $min;
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = array('widget' => $value);
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content,
+ $error ? array() : array('widget' => $value),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ public function minProvider()
+ {
+ return Array(
+ // User value, Min, Error
+ array('', 0, FALSE),
+ array(1.0, 0, FALSE),
+ array(0.0, 0, FALSE),
+ array(-1.0, 0, TRUE),
+ array(1.5, 0, FALSE),
+ array(1, 10, FALSE),
+ array(10, 10, FALSE),
+ array(1.5, 1.5, FALSE),
+ array(2, 1.5, TRUE),
+ );
+ }
+
+ /**
+ * Test for float maximum attribute
+ *
+ * @param String|numeric $value
+ * @param float $max Maximum allowed value
+ * @param boolean $error
+ *
+ * @dataProvider maxProvider
+ */
+ public function testMax($value, $max, $error)
+ {
+ // Instanciate the template
+ $etemplate = new Etemplate();
+ $etemplate->read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Only lowercase
+ $etemplate->getElementById('widget')->attrs['max'] = $max;
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = array('widget' => $value);
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content,
+ $error ? array() : array('widget' => $value),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ public function maxProvider()
+ {
+ return Array(
+ // User value, Max, Error
+ array('', 0, FALSE),
+ array(1.0, 0, TRUE),
+ array(0, 0, FALSE),
+ array(-1.0, 0, FALSE),
+ array(1.5, 2, FALSE),
+ array(1, 10, FALSE),
+ array(10, 10, FALSE),
+ array(2.5, 2, TRUE),
+ array(1.5, 2.5, FALSE),
+ array(3, 2.5, FALSE),
+ );
+ }
+}
diff --git a/api/src/Etemplate/Widget/test/IntegerTest.php b/api/src/Etemplate/Widget/test/IntegerTest.php
new file mode 100644
index 0000000000..83d2ee6400
--- /dev/null
+++ b/api/src/Etemplate/Widget/test/IntegerTest.php
@@ -0,0 +1,192 @@
+read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+
+ $this->validateRoundTrip($etemplate, $content, array('widget' => $value),
+ $error ? array() : array('widget' => $value),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ /**
+ * Data provider for integer tests
+ */
+ public function integerProvider()
+ {
+ return array(
+ // User value, Error
+ array('', false),
+ array(1, false),
+ array(0, false),
+ array(-1, false),
+ array(1.5, true),
+ array('one', true)
+ );
+ }
+
+ /**
+ * Test for integer minimum attribute
+ *
+ * @param String|numeric $value
+ * @param int $min Minimum allowed value
+ * @param boolean $error
+ *
+ * @dataProvider minProvider
+ */
+ public function testMin($value, $min, $error)
+ {
+ // Instanciate the template
+ $etemplate = new Etemplate();
+ $etemplate->read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Only lowercase
+ $etemplate->getElementById('widget')->attrs['min'] = $min;
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = array('widget' => $value);
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content,
+ $error ? array() : array('widget' => $value),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ public function minProvider()
+ {
+ return Array(
+ // User value, Min, Error
+ array('', 0, FALSE),
+ array(1, 0, FALSE),
+ array(0, 0, FALSE),
+ array(-1, 0, TRUE),
+ array(1.5, 0, TRUE), // Errors because it's not an int
+ array(1, 10, TRUE),
+ array(10, 10, FALSE),
+ );
+ }
+
+ /**
+ * Test for integer maximum attribute
+ *
+ * @param String|numeric $value
+ * @param int $max Maximum allowed value
+ * @param boolean $error
+ *
+ * @dataProvider maxProvider
+ */
+ public function testMax($value, $max, $error)
+ {
+ // Instanciate the template
+ $etemplate = new Etemplate();
+ $etemplate->read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Only lowercase
+ $etemplate->getElementById('widget')->attrs['max'] = $max;
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = array('widget' => $value);
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content,
+ $error ? array() : array('widget' => $value),
+ $error ? array('widget' => $error) : array()
+ );
+ }
+
+ public function maxProvider()
+ {
+ return Array(
+ // User value, Max, Error
+ array('', 0, FALSE),
+ array(1, 0, TRUE),
+ array(0, 0, FALSE),
+ array(-1, 0, FALSE),
+ array(1.5, 2, TRUE), // Errors because it's not an int
+ array(1, 10, FALSE),
+ array(10, 10, FALSE),
+ );
+ }
+}
diff --git a/api/src/Etemplate/Widget/test/TextboxTest.php b/api/src/Etemplate/Widget/test/TextboxTest.php
index eea83987a7..08d538a19f 100644
--- a/api/src/Etemplate/Widget/test/TextboxTest.php
+++ b/api/src/Etemplate/Widget/test/TextboxTest.php
@@ -17,11 +17,6 @@ require_once realpath(__DIR__.'/../../test/WidgetBaseTest.php');
use EGroupware\Api\Etemplate;
-/**
- * Description of TextboxTest
- *
- * @author nathan
- */
class TextboxTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
{
@@ -29,6 +24,7 @@ class TextboxTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
/**
* Test the widget's basic functionallity - we put data in, it comes back
+ * unchanged.
*/
public function testBasic()
{
@@ -117,4 +113,60 @@ class TextboxTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
$this->assertEquals(array(), $content);
}
+
+ /**
+ * Test regex validation
+ *
+ * @dataProvider regexProvider
+ */
+ public function testRegex($value, $error)
+ {
+ // Instanciate the template
+ $etemplate = new Etemplate();
+ $etemplate->read(static::TEST_TEMPLATE, 'test');
+
+ // Content - doesn't really matter, we're changing it
+ $content = array(
+ 'widget' => 'Hello',
+ 'widget_readonly' => 'World'
+ );
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Only lowercase
+ $etemplate->getElementById('widget')->attrs['validator'] = '/[a-z]*$/';
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = array('widget' => $value);
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content, array('widget' => $value), $error ? array('widget' => $error) : array());
+ }
+
+ public function regexProvider()
+ {
+ return array(
+ // Value Errors
+ array('', FALSE),
+ array('Hello', TRUE),
+ array('hello', FALSE),
+ array(1234, TRUE),
+ array('hi1234',TRUE)
+ );
+ }
}
diff --git a/api/src/Etemplate/test/WidgetBaseTest.php b/api/src/Etemplate/test/WidgetBaseTest.php
index 5ff2e558ae..a6730a1e5e 100644
--- a/api/src/Etemplate/test/WidgetBaseTest.php
+++ b/api/src/Etemplate/test/WidgetBaseTest.php
@@ -145,4 +145,66 @@ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest {
});
return $response;
}
+
+ /**
+ * Exec the template with the provided content, change the values according to
+ * $set_values, then validate against $expected_values
+ *
+ * @param \EGroupware\Api\Etemplate $etemplate
+ * @param array $content
+ * @param array $set_values
+ * @param array $expected_values
+ * @param array $validation_errors
+ */
+ protected function validateRoundTrip(\EGroupware\Api\Etemplate $etemplate, Array $content, Array $set_values, Array $expected_values = null, Array $validation_errors = array())
+ {
+ if(is_null($expected_values))
+ {
+ $expected_values = $set_values;
+ }
+ $result = $this->mockedExec($etemplate, $content, array(), array(), array());
+
+ // Check for the load
+ $data = array();
+ foreach($result as $command)
+ {
+ if($command['type'] == 'et2_load')
+ {
+ $data = $command['data'];
+ break;
+ }
+ }
+
+ // 'Edit' the data client side
+ $data['data']['content'] = $set_values;
+
+ // Let it validate
+ Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false);
+
+ $content = static::$mocked_exec_result;
+ static::$mocked_exec_result = array();
+
+ return $this->validateTest($content, $expected_values, $validation_errors);
+ }
+
+ protected function validateTest($content, $expected_values, $validation_errors)
+ {
+ // Make validation errors accessible
+ $ref = new \ReflectionProperty('\\EGroupware\\Api\\Etemplate\\Widget', 'validation_errors');
+ $ref->setAccessible(true);
+ $errors = $ref->getValue();
+
+ // Test values
+ foreach($expected_values as $widget_id => $value)
+ {
+ $this->assertEquals($value, $content[$widget_id], 'Widget "' . $widget_id . '" did not get expected value');
+ }
+
+ // Check validation errors
+ foreach($validation_errors as $widget_id => $errored)
+ {
+ $this->assertTrue(array_key_exists($widget_id, $validation_errors), "Widget $widget_id caused a validation error");
+ }
+ $ref->setValue(array());
+ }
}
diff --git a/api/templates/test/float_test.xet b/api/templates/test/float_test.xet
new file mode 100644
index 0000000000..f28b0936bd
--- /dev/null
+++ b/api/templates/test/float_test.xet
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/templates/test/integer_test.xet b/api/templates/test/integer_test.xet
new file mode 100644
index 0000000000..feaa37e4a2
--- /dev/null
+++ b/api/templates/test/integer_test.xet
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file