From c214b75d609922edb3f6a79bde650620dbb48b70 Mon Sep 17 00:00:00 2001 From: pzinovkin Date: Mon, 26 Apr 2010 19:56:01 +0000 Subject: [PATCH] Validator, captcha, form, #16 git-svn-id: svn+ssh://code.netmonsters.ru/svn/majestic/branches/evo@141 4cb57b5f-5bbd-dd11-951b-001d605cbbc5 --- captcha/Captcha.php | 79 ++++++++++ captcha/CaptchaImageAction.php | 21 +++ captcha/CaptchaValidator.php | 32 ++++ captcha/poh.ttf | Bin 0 -> 81964 bytes exception/ErrorHandler.php | 3 + form/Form.php | 119 ++++++++++++++ form/FormField.php | 173 +++++++++++++++++++++ form/FormViewHelper.php | 44 ++++++ validator/EmailValidator.php | 17 ++ validator/NotEmptyValidator.php | 32 ++++ validator/RegexValidator.php | 41 +++++ validator/Validator.php | 65 ++++++++ validator/iValidator.php | 16 ++ view/PHPView.php | 2 +- ...lperBreadcrumb.php => BreadcrumbViewHelper.php} | 2 +- .../{ViewHelperGet.php => GetViewHelper.php} | 2 +- .../{ViewHelperHead.php => HeadViewHelper.php} | 2 +- .../{ViewHelperMsg.php => MsgViewHelper.php} | 17 +- .../{ViewHelperTitle.php => TitleViewHelper.php} | 2 +- 19 files changed, 660 insertions(+), 9 deletions(-) create mode 100644 captcha/Captcha.php create mode 100644 captcha/CaptchaImageAction.php create mode 100644 captcha/CaptchaValidator.php create mode 100644 captcha/poh.ttf create mode 100644 form/Form.php create mode 100644 form/FormField.php create mode 100644 form/FormViewHelper.php create mode 100644 validator/EmailValidator.php create mode 100644 validator/NotEmptyValidator.php create mode 100644 validator/RegexValidator.php create mode 100644 validator/Validator.php create mode 100644 validator/iValidator.php rename view/helpers/{ViewHelperBreadcrumb.php => BreadcrumbViewHelper.php} (96%) rename view/helpers/{ViewHelperGet.php => GetViewHelper.php} (97%) rename view/helpers/{ViewHelperHead.php => HeadViewHelper.php} (93%) rename view/helpers/{ViewHelperMsg.php => MsgViewHelper.php} (62%) rename view/helpers/{ViewHelperTitle.php => TitleViewHelper.php} (94%) diff --git a/captcha/Captcha.php b/captcha/Captcha.php new file mode 100644 index 0000000..b42e0db --- /dev/null +++ b/captcha/Captcha.php @@ -0,0 +1,79 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage captcha + * @since 2010-04-24 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class Captcha +{ + + protected $font_size = 38; + protected $width = 200; + protected $height = 70; + + + public function getImage($token) + { + $font = dirname(__FILE__) . '/poh.ttf'; + + $image = imagecreatetruecolor($this->width, $this->height); + // белый фон + $background = imagecolorallocate($image, 255, 255, 255); + imagefilledrectangle($image, 0, 0, $this->width, $this->height, $background); + + // основная магия тут + if (Session::get('_ctoken') == $token && Session::get('_ccode')) { + + $code = Session::get('_ccode'); + + $color = imagecolorallocate($image, 81, 81, 81); + + for ($j = 0; $j < 5; $j++) { + imageellipse($image, rand(-$this->width, $this->width * 2), + rand(-$this->height, $this->height * 2), + $this->width, $this->width, $color); + } + + $letters = preg_split("//u", strtoupper($code)); + $length = count($letters); + $step = floor($this->width / $length); + $i = 2; + + foreach ($letters as $key => $letter) { + imagettftext($image, $this->font_size, 0, + rand($i + 2, $i + 4), ($this->font_size + $this->height) / 2, + $color, $font, $letter); + $i = $i + $step ; + } + } + + ob_start(); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Content-Type: image/jpg"); + imagejpeg($image, '', 100); + imagedestroy($image); + return ob_get_clean(); + } + + public function getToken() + { + $token = md5(microtime() . uniqid()); + $code = strtoupper(substr(str_ireplace(array('0', 'O'), '', md5(time() . 'captcha' . rand(0, 1000))), 1, 5)); + Session::set('_ctoken', $token); + Session::set('_ccode', $code); + return $token; + } + + public function checkCode($token, $code) + { + if (Session::get('_ctoken') == $token && Session::get('_ccode') == strtoupper($code)){ + return true; + } + return false; + } +} \ No newline at end of file diff --git a/captcha/CaptchaImageAction.php b/captcha/CaptchaImageAction.php new file mode 100644 index 0000000..d0d6109 --- /dev/null +++ b/captcha/CaptchaImageAction.php @@ -0,0 +1,21 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage captcha + * @since 2010-04-24 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class CaptchaImageAction +{ + + public function __construct() + { + $captcha = new Captcha(); + echo $captcha->getImage(Env::Get('ctoken')); + exit(); + } +} \ No newline at end of file diff --git a/captcha/CaptchaValidator.php b/captcha/CaptchaValidator.php new file mode 100644 index 0000000..4bb9f48 --- /dev/null +++ b/captcha/CaptchaValidator.php @@ -0,0 +1,32 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-26 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class CaptchaValidator extends Validator +{ + + const WRONG_CODE = 'is_empty'; + + protected $templates = array(self::WRONG_CODE => 'Entered code wrong'); + + public function isValid($value, $context = null) + { + if (!$context || !isset($context['ctoken']) || !isset($context['ccode'])) { + $this->error(); + return false; + } + $captcha = new Captcha(); + if ($captcha->checkCode($context['ctoken'], $context['ccode'])) { + return true; + } + $this->error(); + return false; + } +} \ No newline at end of file diff --git a/captcha/poh.ttf b/captcha/poh.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7d0dbad36cd1b42d23ac68dfa00f0bad05d91d63 GIT binary patch literal 81964 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 B-v9sr literal 0 HcmV?d00001 diff --git a/exception/ErrorHandler.php b/exception/ErrorHandler.php index 69dd8ad..9eeb43b 100644 --- a/exception/ErrorHandler.php +++ b/exception/ErrorHandler.php @@ -83,6 +83,7 @@ class ErrorHandler $get = self::wrapArray(Env::Get(), 'GET'); $post = self::wrapArray(Env::Post(), 'POST'); + $session = self::wrapArray(Session::get(), 'SESSION'); $files = self::wrapArray(Env::Files(), 'FILES'); $cookies = self::wrapArray(Env::Cookie(), 'COOKIE'); $server = self::wrapArray(Env::Server(), 'SERVER'); @@ -174,6 +175,8 @@ class ErrorHandler {$get}

POST

{$post} +

SESSION

+ {$session}

FILES

{$files} diff --git a/form/Form.php b/form/Form.php new file mode 100644 index 0000000..9d63da6 --- /dev/null +++ b/form/Form.php @@ -0,0 +1,119 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage form + * @since 2010-04-24 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +abstract class Form +{ + + const SUCCESS = 'success'; + const ERROR = 'error'; + + protected $fields = array(); + protected $messages = array(self::SUCCESS => 'Form data valid', + self::ERROR => 'Form data invalid'); + + protected $valid = true; + + public function __construct() + { + $this->init(); + } + + /** + * @param string $name + * @return FormField + */ + protected function addField($name, $message = false) + { + $this->fields[$name] = new FormField($message); + return $this->fields[$name]; + } + + public function isValid($data) + { + if (!is_array($data)) { + throw new Exception(__CLASS__ . '::' . __METHOD__ . ' expects an array'); + } + + foreach ($this->fields as $field_name => $field) { + if (isset($data[$field_name])) { + $this->valid &= $field->isValid($data[$field_name], $data); + } else { + $this->valid &= $field->isValid(null, $data); + } + } + if (!$this->valid) { + $this->fillHelperData(); + } + return $this->valid; + } + + public function getMessages() + { + $messages = array(); + foreach ($this->fields as $name => $field) { + if ($mess = $field->getMessage()) { + $messages[$name] = $mess; + } + } + return $messages; + } + + public function getValues() + { + $values = array(); + foreach ($this->fields as $key => $field) { + if (!$field->isIgnored()) { + $values[$key] = $field->getValue(); + } + } + return $values; + } + + public function getSourceValues() + { + $values = array(); + foreach ($this->fields as $key => $field) { + $values[$key] = $field->getSourceValue(); + } + return $values; + } + + public function getMessageType() + { + return ($this->valid) ? self::SUCCESS : self::ERROR; + } + + public function getMessage() + { + return $this->messages[$this->getMessageType()]; + } + + public function setSuccessMessage($message) + { + $this->messages[self::SUCCESS] = (string) $message; + return $this; + } + + public function setErrorMessage($message) + { + $this->messages[self::ERROR] = (string) $message; + return $this; + } + + protected function fillHelperData() + { + $data['messages'] = $this->getMessages(); + $data['values'] = $this->getSourceValues(); + Session::set(get_class($this), $data); + } + + abstract protected function init(); +} \ No newline at end of file diff --git a/form/FormField.php b/form/FormField.php new file mode 100644 index 0000000..baac180 --- /dev/null +++ b/form/FormField.php @@ -0,0 +1,173 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage form + * @since 2010-04-25 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class FormField +{ + + protected $validators = array(); + protected $filters = array(); + + /** + * Used instead message of validator if defined. + * + * @var string + */ + protected $default_message = false; + protected $message = false; + protected $value; + + /* Flags */ + protected $required = true; + protected $ignored = false; + + + public function __construct($default_message = false) + { + $this->default_message = $default_message; + } + + public function setRequired($flag) + { + $this->required = (bool) $flag; + return $this; + } + + public function isRequired() + { + return $this->required; + } + + public function setIgnored($flag) + { + $this->ignored = (bool) $flag; + return $this; + } + + public function isIgnored() + { + return $this->ignored; + } + + public function addValidators($validators) + { + foreach ($validators as $validator) { + $this->addValidator($validator); + } + return $this; + } + + public function addValidator($validator) + { + if ($validator instanceof iValidator) { + $name = get_class($validator); + } elseif (is_string($validator)) { + $name = $validator . 'Validator'; + $validator = new $name(); + } else { + throw new Exception('Invalid validator provided to addValidator; must be string or iValidator'); + } + $this->validators[$name] = $validator; + return $this; + } + + public function addFilters($filters) + { + foreach ($filters as $filter) { + $this->addFilter($filter); + } + return $this; + } + + public function addFilter($filter) + { + if ($filter instanceof iFilter) { + $name = get_class($filter); + } elseif (is_string($filter)) { + $name = $filter . 'Filter'; + $filter = new $name(); + } else { + throw new Exception('Invalid filter provided to addFilter; must be string or iFilter'); + } + $this->filters[$name] = $filter; + return $this; + } + + public function getValue() + { + $value = $this->value; + if (is_array($value)) { + array_walk_recursive($value, array($this, 'filterValue')); + } else { + $this->filterValue($value, $value); + } + return $value; + } + + /** + * $value & $key for array_walk_recursive + * + * @param mixed $value + * @param mixed $key + */ + protected function filterValue(&$value, &$key) + { + foreach ($this->filters as $filter) { + $value = $filter->filter($value); + } + } + + public function getSourceValue() + { + return $this->value; + } + + public function isValid($value, $context = null) + { + $this->value = $value; + // filtered value here + $value = $this->getValue(); + + $valid = true; + + if ((($value === '') || ($value === null)) && !$this->isRequired()) { + return $valid; + } + + foreach ($this->validators as $validator) { + if (is_array($value)) { + foreach ($value as $val) { + if (!$validator->isValid($val, $context)) { + $valid = false; + if (!$this->default_message) { + throw new Exception('Define default message for array fields'); + } + $this->message = $this->default_message; + } + } + if ($valid) { + continue; + } + } elseif ($validator->isValid($value, $context)) { + continue; + } + + $valid = false; + $this->message = ($this->default_message) ? $this->default_message : $validator->getMessage(); + break; + } + return $valid; + } + + public function getMessage() + { + return $this->message; + } +} \ No newline at end of file diff --git a/form/FormViewHelper.php b/form/FormViewHelper.php new file mode 100644 index 0000000..48aa07d --- /dev/null +++ b/form/FormViewHelper.php @@ -0,0 +1,44 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage Form + * @since 2010-04-25 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class FormViewHelper extends ViewHelper +{ + + protected $data = null; + + public function form($form = null) + { + if ($this->data === null) { + if ($form == null) { + throw new Exception('Form name required for helper init'); + } + $this->data = Session::get($form, array()); + Session::del($form); + } + return $this; + } + + public function value($field) + { + if (isset($this->data['values'][$field])) { + return $this->view->escape($this->data['values'][$field]); + } + return ''; + } + + public function message($field) + { + if (isset($this->data['messages'][$field])) { + return '' . $this->view->escape($this->data['messages'][$field]) . ''; + } + return ''; + } +} \ No newline at end of file diff --git a/validator/EmailValidator.php b/validator/EmailValidator.php new file mode 100644 index 0000000..fc83b6c --- /dev/null +++ b/validator/EmailValidator.php @@ -0,0 +1,17 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-26 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class EmailValidator extends RegexValidator +{ + protected $regex = '/^([a-z0-9._-]{2,23})\@([a-z0-9-]{2,22}\.)+\w{2,5}$/i'; + + public function __construct(){} +} \ No newline at end of file diff --git a/validator/NotEmptyValidator.php b/validator/NotEmptyValidator.php new file mode 100644 index 0000000..a9e62b6 --- /dev/null +++ b/validator/NotEmptyValidator.php @@ -0,0 +1,32 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-26 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class NotEmptyValidator extends Validator +{ + + const IS_EMPTY = 'is_empty'; + + protected $templates = array(self::IS_EMPTY => 'Value is required and can\'t be empty'); + + public function isValid($value, $context = null) + { + $this->setValue($value); + + if (is_string($value) && $value === '') { + $this->error(); + return false; + } elseif (!is_string($value) && empty($value)) { + $this->error(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/validator/RegexValidator.php b/validator/RegexValidator.php new file mode 100644 index 0000000..96a1951 --- /dev/null +++ b/validator/RegexValidator.php @@ -0,0 +1,41 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-26 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +class RegexValidator extends Validator +{ + + const NOT_MATCH = 'regex_not_match'; + + protected $vars = array('regex'); + protected $templates = array(self::NOT_MATCH => '"%value%" does not match against regex "%regex%"'); + + protected $regex; + + public function __construct($regex) + { + $this->regex = $regex; + } + + public function isValid($value, $context = null) + { + $this->setValue($value); + + $status = preg_match($this->regex, $value); + if ($status === false) { + throw new Exception('Internal error matching regex "' . $this->regex . ' against value "' . $value . '"'); + } + if (!$status) { + $this->error(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/validator/Validator.php b/validator/Validator.php new file mode 100644 index 0000000..d201b3d --- /dev/null +++ b/validator/Validator.php @@ -0,0 +1,65 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-24 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +abstract class Validator implements iValidator +{ + + protected $value; + protected $message; + protected $vars = array(); + protected $templates = array(); + + + public function getMessage() + { + return $this->message; + } + + protected function setValue($value) + { + $this->value = (string) $value; + $this->message = null; + } + + public function setMessage($key, $message) + { + $this->templates[$key] = $message; + } + + protected function error($template = null, $value = null) + { + if ($template === null) { + $template = current(array_keys($this->templates)); + } + if ($value === null) { + $value = $this->value; + } + $this->message = $this->createMessage($template, $value); + } + + protected function createMessage($template, $value) + { + if (!isset($this->templates[$template])) { + throw new Exception('Message template "' . $template . '" unknown.'); + } + + $message = $this->templates[$template]; + if (strpos($message, '%') !== false) { + $message = str_replace('%value%', (string) $value, $message); + foreach ($this->vars as $property) { + if (property_exists($this, $property)) { + $message = str_replace("%$property%", (string) $this->$property, $message); + } + } + } + return $message; + } +} \ No newline at end of file diff --git a/validator/iValidator.php b/validator/iValidator.php new file mode 100644 index 0000000..66bae7c --- /dev/null +++ b/validator/iValidator.php @@ -0,0 +1,16 @@ + + * @link http://netmonsters.ru + * @package Majestic + * @subpackage validator + * @since 2010-04-25 + * @version SVN: $Id$ + * @filesource $URL$ + */ + +interface iValidator +{ + public function isValid($value, $context = null); + public function getMessage(); +} \ No newline at end of file diff --git a/view/PHPView.php b/view/PHPView.php index 2d53292..823f5c0 100644 --- a/view/PHPView.php +++ b/view/PHPView.php @@ -119,7 +119,7 @@ class PHPView implements iView protected function getHelper($name) { if (!isset($this->helpers[$name])) { - $class = 'ViewHelper' . ucfirst($name); + $class = ucfirst($name) . 'ViewHelper'; if (!class_exists($class)) { throw new GeneralException('View helper "' . $class . '" not found.'); } diff --git a/view/helpers/ViewHelperBreadcrumb.php b/view/helpers/BreadcrumbViewHelper.php similarity index 96% rename from view/helpers/ViewHelperBreadcrumb.php rename to view/helpers/BreadcrumbViewHelper.php index 96aeb6b..04df5be 100644 --- a/view/helpers/ViewHelperBreadcrumb.php +++ b/view/helpers/BreadcrumbViewHelper.php @@ -9,7 +9,7 @@ * @filesource $URL$ */ -class ViewHelperBreadcrumb extends ViewHelper +class BreadcrumbViewHelper extends ViewHelper { protected $separator = ' > '; diff --git a/view/helpers/ViewHelperGet.php b/view/helpers/GetViewHelper.php similarity index 97% rename from view/helpers/ViewHelperGet.php rename to view/helpers/GetViewHelper.php index ece3e1d..d28e325 100644 --- a/view/helpers/ViewHelperGet.php +++ b/view/helpers/GetViewHelper.php @@ -9,7 +9,7 @@ * @filesource $URL$ */ -class ViewHelperGet extends ViewHelper +class GetViewHelper extends ViewHelper { protected $get; diff --git a/view/helpers/ViewHelperHead.php b/view/helpers/HeadViewHelper.php similarity index 93% rename from view/helpers/ViewHelperHead.php rename to view/helpers/HeadViewHelper.php index c97bfa0..ffbb444 100644 --- a/view/helpers/ViewHelperHead.php +++ b/view/helpers/HeadViewHelper.php @@ -9,7 +9,7 @@ * @filesource $URL$ */ -class ViewHelperHead extends ViewHelper +class HeadViewHelper extends ViewHelper { public function head($string = false) diff --git a/view/helpers/ViewHelperMsg.php b/view/helpers/MsgViewHelper.php similarity index 62% rename from view/helpers/ViewHelperMsg.php rename to view/helpers/MsgViewHelper.php index d6317f0..4c6c84e 100644 --- a/view/helpers/ViewHelperMsg.php +++ b/view/helpers/MsgViewHelper.php @@ -9,24 +9,33 @@ * @filesource $URL$ */ -class ViewHelperMsg extends ViewHelper +class MsgViewHelper extends ViewHelper { + const SUCCESS = 'success'; + const ERROR = 'error'; + protected $get; - public function msg() + public function msg($msg = null, $type = null) { + if ($msg && $type) { + if (!in_array($type, array(self::SUCCESS, self::ERROR))) { + throw new Exception('Unknown message type: "' . $type . '"'); + } + Session::set(__CLASS__, array('message' => $msg, 'type' => $type)); + } return $this; } public function success($msg) { - Session::set(__CLASS__, array('message' => $msg, 'type' => 'success')); + Session::set(__CLASS__, array('message' => $msg, 'type' => self::SUCCESS)); } public function error($msg) { - Session::set(__CLASS__, array('message' => $msg, 'type' => 'error')); + Session::set(__CLASS__, array('message' => $msg, 'type' => self::ERROR)); } public function __toString() diff --git a/view/helpers/ViewHelperTitle.php b/view/helpers/TitleViewHelper.php similarity index 94% rename from view/helpers/ViewHelperTitle.php rename to view/helpers/TitleViewHelper.php index 4b988fe..812230d 100644 --- a/view/helpers/ViewHelperTitle.php +++ b/view/helpers/TitleViewHelper.php @@ -9,7 +9,7 @@ * @filesource $URL$ */ -class ViewHelperTitle extends ViewHelper +class TitleViewHelper extends ViewHelper { protected $separator = ' - ';