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}
COOKIES
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 = ' - ';