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 0000000..7d0dbad Binary files /dev/null and b/captcha/poh.ttf differ 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 = ' - ';