Merge branch 'feature/provide-meaningful-form-error-messages-8415'

resolves #8415
This commit is contained in:
Johannes Meyer 2015-02-12 09:11:49 +01:00
commit 6a3d1c665b
11 changed files with 182 additions and 32 deletions

View File

@ -141,7 +141,7 @@ class AuthenticationController extends ActionController
}
}
} catch (Exception $e) {
$this->view->errorInfo = $e->getMessage();
$this->view->form->addError($e->getMessage());
}
$this->view->requiresExternalAuth = $triedOnlyExternalAuth && !$auth->isAuthenticated();

View File

@ -40,13 +40,22 @@ class ExternalBackendForm extends Form
array(
'pattern' => '/^[^\\[\\]:]+$/',
'messages' => array(
'regexNotMatch' => 'The backend name cannot contain \'[\', \']\' or \':\'.'
'regexNotMatch' => $this->translate(
'The backend name cannot contain \'[\', \']\' or \':\'.'
)
)
)
)
)
)
);
$callbackValidator = new Zend_Validate_Callback(function ($value) {
return @preg_match($value, '') !== false;
});
$callbackValidator->setMessage(
$this->translate('"%value%" is not a valid regular expression'),
Zend_Validate_Callback::INVALID_VALUE
);
$this->addElement(
'text',
'strip_username_regexp',
@ -56,11 +65,7 @@ class ExternalBackendForm extends Form
'The regular expression to use to strip specific parts off from usernames.'
. ' Leave empty if you do not want to strip off anything'
),
'validators' => array(
new Zend_Validate_Callback(function ($value) {
return @preg_match($value, '') !== false;
})
)
'validators' => array($callbackValidator)
)
);
$this->addElement(

View File

@ -5,7 +5,6 @@ namespace Icinga\Forms\Config\General;
use Icinga\Application\Logger;
use Icinga\Web\Form;
use Icinga\Web\Form\Validator\WritablePathValidator;
class LoggingConfigForm extends Form
{
@ -75,7 +74,9 @@ class LoggingConfigForm extends Form
array(
'pattern' => '/^[^\W]+$/',
'messages' => array(
'regexNotMatch' => 'The application prefix cannot contain any whitespaces.'
'regexNotMatch' => $this->translate(
'The application prefix must not contain whitespace.'
)
)
)
)
@ -107,7 +108,7 @@ class LoggingConfigForm extends Form
'label' => $this->translate('File path'),
'description' => $this->translate('The full path to the log file to write messages to.'),
'value' => '/var/log/icingaweb2/icingaweb2.log',
'validators' => array(new WritablePathValidator())
'validators' => array('WritablePathValidator')
)
);
}

View File

@ -4,7 +4,6 @@
namespace Icinga\Forms\Config\Resource;
use Icinga\Web\Form;
use Icinga\Web\Form\Validator\ReadablePathValidator;
/**
* Form class for adding/modifying file resources
@ -40,7 +39,7 @@ class FileResourceForm extends Form
'required' => true,
'label' => $this->translate('Filepath'),
'description' => $this->translate('The filename to fetch information from'),
'validators' => array(new ReadablePathValidator())
'validators' => array('ReadablePathValidator')
)
);
$this->addElement(

View File

@ -53,4 +53,30 @@ class String
return $string;
}
/**
* Find and return all similar strings in $possibilites matching $string with the given minimum $similarity
*
* @param string $string
* @param array $possibilities
* @param float $similarity
*
* @return array
*/
public static function findSimilar($string, array $possibilities, $similarity = 0.33)
{
if (empty($string)) {
return array();
}
$matches = array();
foreach ($possibilities as $possibility) {
$distance = levenshtein($string, $possibility);
if ($distance / strlen($string) <= $similarity) {
$matches[] = $possibility;
}
}
return $matches;
}
}

View File

@ -12,6 +12,7 @@ use Icinga\Application\Icinga;
use Icinga\Authentication\Manager;
use Icinga\Security\SecurityException;
use Icinga\Util\Translator;
use Icinga\Web\Form\ErrorLabeller;
use Icinga\Web\Form\Decorator\NoScriptApply;
use Icinga\Web\Form\Element\CsrfCounterMeasure;
@ -520,6 +521,15 @@ class Form extends Zend_Form
}
$el = parent::createElement($type, $name, $options);
$el->setTranslator(new ErrorLabeller(array('element' => $el)));
$el->addPrefixPaths(array(
array(
'prefix' => 'Icinga\\Web\\Form\\Validator\\',
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Validator'),
'type' => $el::VALIDATE
)
));
if (($description = $el->getDescription()) !== null && ($label = $el->getDecorator('label')) !== false) {
$label->setOptions(array(

View File

@ -0,0 +1,65 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Form;
use BadMethodCallException;
use Zend_Translate_Adapter;
use Zend_Validate_NotEmpty;
use Icinga\Web\Form\Validator\DateTimeValidator;
use Icinga\Web\Form\Validator\ReadablePathValidator;
use Icinga\Web\Form\Validator\WritablePathValidator;
class ErrorLabeller extends Zend_Translate_Adapter
{
protected $messages;
public function __construct($options = array())
{
if (! isset($options['element'])) {
throw new BadMethodCallException('Option "element" is missing');
}
$this->messages = $this->createMessages($options['element']);
}
public function isTranslated($messageId, $original = false, $locale = null)
{
return array_key_exists($messageId, $this->messages);
}
public function translate($messageId, $locale = null)
{
if (array_key_exists($messageId, $this->messages)) {
return $this->messages[$messageId];
}
return $messageId;
}
protected function createMessages($element)
{
$label = $element->getLabel();
return array(
Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label),
WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label),
WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label),
ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label),
DateTimeValidator::INVALID_DATETIME_FORMAT => sprintf(
t('%s not in the expected format: %%value%%'),
$label
)
);
}
protected function _loadTranslationData($data, $locale, array $options = array())
{
// nonsense, required as being abstract otherwise...
}
public function toString()
{
return 'ErrorLabeller'; // nonsense, required as being abstract otherwise...
}
}

View File

@ -5,6 +5,7 @@ namespace Icinga\Web\Form\Validator;
use DateTime;
use Zend_Validate_Abstract;
use Icinga\Util\DateTimeFactory;
/**
* Validator for date-and-time input controls
@ -13,6 +14,21 @@ use Zend_Validate_Abstract;
*/
class DateTimeValidator extends Zend_Validate_Abstract
{
const INVALID_DATETIME_TYPE = 'invalidDateTimeType';
const INVALID_DATETIME_FORMAT = 'invalidDateTimeFormat';
/**
* The messages to write on differen error states
*
* @var array
*
* @see Zend_Validate_Abstract::$_messageTemplates
*/
protected $_messageTemplates = array(
self::INVALID_DATETIME_TYPE => 'Invalid type given. Instance of DateTime or date/time string expected',
self::INVALID_DATETIME_FORMAT => 'Date/time string not in the expected format: %value%'
);
protected $local;
/**
@ -38,14 +54,14 @@ class DateTimeValidator extends Zend_Validate_Abstract
public function isValid($value, $context = null)
{
if (! $value instanceof DateTime && ! is_string($value)) {
$this->_error(t('Invalid type given. Instance of DateTime or date/time string expected'));
$this->_error(self::INVALID_DATETIME_TYPE);
return false;
}
if (is_string($value)) {
$format = $this->local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339;
$dateTime = DateTime::createFromFormat($format, $value);
if ($dateTime === false || $dateTime->format($format) !== $value) {
$this->_error(sprintf(t('Date/time string not in the expected format %s'), $format));
$this->_error(self::INVALID_DATETIME_FORMAT, DateTimeFactory::create()->format($format));
return false;
}
}

View File

@ -0,0 +1,28 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Form\Validator;
use Zend_Validate_InArray;
use Icinga\Util\String;
class InArray extends Zend_Validate_InArray
{
protected function _error($messageKey, $value = null)
{
if ($messageKey === static::NOT_IN_ARRAY) {
$matches = String::findSimilar($this->_value, $this->_haystack);
if (empty($matches)) {
$this->_messages[$messageKey] = sprintf(t('"%s" is not in the list of allowed values.'), $this->_value);
} else {
$this->_messages[$messageKey] = sprintf(
t('"%s" is not in the list of allowed values. Did you mean one of the following?: %s'),
$this->_value,
implode(', ', $matches)
);
}
} else {
parent::_error($messageKey, $value);
}
}
}

View File

@ -13,6 +13,9 @@ use Zend_Validate_Abstract;
*/
class ReadablePathValidator extends Zend_Validate_Abstract
{
const NOT_READABLE = 'notReadable';
const DOES_NOT_EXIST = 'doesNotExist';
/**
* The messages to write on different error states
*
@ -20,18 +23,10 @@ class ReadablePathValidator extends Zend_Validate_Abstract
*
* @see Zend_Validate_Abstract::$_messageTemplates
*/
protected $_messageTemplates;
/**
* Initialize this validator
*/
public function __construct()
{
$this->_messageTemplates = array(
'NOT_READABLE' => t('Path is not readable'),
'DOES_NOT_EXIST' => t('Path does not exist')
);
}
protected $_messageTemplates = array(
self::NOT_READABLE => 'Path is not readable',
self::DOES_NOT_EXIST => 'Path does not exist'
);
/**
* Check whether the given value is a readable filepath
@ -44,12 +39,13 @@ class ReadablePathValidator extends Zend_Validate_Abstract
public function isValid($value, $context = null)
{
if (false === file_exists($value)) {
$this->_error('DOES_NOT_EXIST');
$this->_error(self::DOES_NOT_EXIST);
return false;
}
if (false === is_readable($value)) {
$this->_error('NOT_READABLE');
$this->_error(self::NOT_READABLE);
return false;
}
return true;

View File

@ -10,6 +10,9 @@ use Zend_Validate_Abstract;
*/
class WritablePathValidator extends Zend_Validate_Abstract
{
const NOT_WRITABLE = 'notWritable';
const DOES_NOT_EXIST = 'doesNotExist';
/**
* The messages to write on differen error states
*
@ -18,8 +21,8 @@ class WritablePathValidator extends Zend_Validate_Abstract
* @see Zend_Validate_Abstract::$_messageTemplates
*/
protected $_messageTemplates = array(
'NOT_WRITABLE' => 'Path is not writable',
'DOES_NOT_EXIST' => 'Path does not exist'
self::NOT_WRITABLE => 'Path is not writable',
self::DOES_NOT_EXIST => 'Path does not exist'
);
/**
@ -53,7 +56,7 @@ class WritablePathValidator extends Zend_Validate_Abstract
$this->_setValue($value);
if ($this->requireExistence && !file_exists($value)) {
$this->_error('DOES_NOT_EXIST');
$this->_error(self::DOES_NOT_EXIST);
return false;
}
@ -62,7 +65,8 @@ class WritablePathValidator extends Zend_Validate_Abstract
) {
return true;
}
$this->_error('NOT_WRITABLE');
$this->_error(self::NOT_WRITABLE);
return false;
}
}