<?php //netteloader=Nette\Framework


/**
 * Nette Framework (version 2.0-dev released on 2011-08-13, http://nette.org)
 *
 * Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
 *
 * For the full copyright and license information, please view
 * the file license.txt that was distributed with this source code.
 */

if (!defined('PHP_VERSION_ID')) {
	$tmp = explode('.', PHP_VERSION);
	define('PHP_VERSION_ID', ($tmp[0] * 10000 + $tmp[1] * 100 + $tmp[2]));
}

if (PHP_VERSION_ID < 50200) {
	throw new Exception('Nette Framework requires PHP 5.2.0 or newer.');
}

//error_reporting(E_ALL | E_STRICT);
//@set_magic_quotes_runtime(FALSE);
//iconv_set_encoding('internal_encoding', 'UTF-8');
//extension_loaded('mbstring') && mb_internal_encoding('UTF-8');
//@header('X-Powered-By: Nette Framework');
//@header('Content-Type: text/html; charset=utf-8');

class NCFix
{
	static $vars = array();

	static function uses($args)
	{
		self::$vars[] = $args;
		return count(self::$vars)-1;
	}
}

define('NETTE', TRUE);
define('NETTE_DIR', dirname(__FILE__));
define('NETTE_VERSION_ID', 20000);
define('NETTE_PACKAGE', 'PHP 5.2 prefixed');

interface IBarPanel
{

	function getTab();

	function getPanel();

}

interface IPresenter
{

	function run(NPresenterRequest $request);

}

interface IPresenterFactory
{

	function getPresenterClass(& $name);

	function createPresenter($name);

}

interface IPresenterResponse
{

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse);

}

interface IRouter
{

	const ONE_WAY = 1;

	const SECURED = 2;

	function match(IHttpRequest $httpRequest);

	function constructUrl(NPresenterRequest $appRequest, NUrl $refUrl);

}

interface IFreezable
{

	function freeze();

	function isFrozen();

}

interface IComponent
{

	const NAME_SEPARATOR = '-';

	function getName();

	function getParent();

	function setParent(IComponentContainer $parent = NULL, $name = NULL);

}

interface IComponentContainer extends IComponent
{

	function addComponent(IComponent $component, $name);

	function removeComponent(IComponent $component);

	function getComponent($name);

	function getComponents($deep = FALSE, $filterType = NULL);

}

interface ISignalReceiver
{

	function signalReceived($signal);

}

interface IStatePersistent
{

	function loadState(array $params);

	function saveState(array & $params);

}

interface IPartiallyRenderable extends IRenderable
{

}

interface IRenderable
{

	function invalidateControl();

	function isControlInvalid();

}

interface ICacheStorage
{

	function read($key);

	function write($key, $data, array $dependencies);

	function remove($key);

	function clean(array $conds);

}

interface ICacheJournal
{

	function write($key, array $dependencies);

	function clean(array $conditions);

}

interface IConfigAdapter
{

	static function load($file);

	static function save($config, $file);

}

interface ISupplementalDriver
{

	function delimite($name);

	function formatDateTime(DateTime $value);

	function formatLike($value, $pos);

	function applyLimit(&$sql, $limit, $offset);

	function normalizeRow($row, $statement);

}

interface IDiContainer
{

	function addService($name, $service);

	function getService($name);

	function removeService($name);

	function hasService($name);

}

interface IServiceBuilder
{

	function createService(IDiContainer $container);

}

interface IFormControl
{

	function loadHttpData();

	function setValue($value);

	function getValue();

	function getRules();

	function getErrors();

	function isDisabled();

	function translate($s, $count = NULL);

}

interface ISubmitterControl extends IFormControl
{

	function isSubmittedBy();

	function getValidationScope();

}

interface IFormRenderer
{

	function render(NForm $form);

}

interface IHttpRequest
{

	const
		GET = 'GET',
		POST = 'POST',
		HEAD = 'HEAD',
		PUT = 'PUT',
		DELETE = 'DELETE';

	function getUrl();

	function getQuery($key = NULL, $default = NULL);

	function getPost($key = NULL, $default = NULL);

	function getFile($key);

	function getFiles();

	function getCookie($key, $default = NULL);

	function getCookies();

	function getMethod();

	function isMethod($method);

	function getHeader($header, $default = NULL);

	function getHeaders();

	function isSecured();

	function isAjax();

	function getRemoteAddress();

	function getRemoteHost();

}

interface IHttpResponse
{

	const PERMANENT = 2116333333;

	const BROWSER = 0;

	const
		S200_OK = 200,
		S204_NO_CONTENT = 204,
		S300_MULTIPLE_CHOICES = 300,
		S301_MOVED_PERMANENTLY = 301,
		S302_FOUND = 302,
		S303_SEE_OTHER = 303,
		S303_POST_GET = 303,
		S304_NOT_MODIFIED = 304,
		S307_TEMPORARY_REDIRECT= 307,
		S400_BAD_REQUEST = 400,
		S401_UNAUTHORIZED = 401,
		S403_FORBIDDEN = 403,
		S404_NOT_FOUND = 404,
		S405_METHOD_NOT_ALLOWED = 405,
		S410_GONE = 410,
		S500_INTERNAL_SERVER_ERROR = 500,
		S501_NOT_IMPLEMENTED = 501,
		S503_SERVICE_UNAVAILABLE = 503;

	function setCode($code);

	function getCode();

	function setHeader($name, $value);

	function addHeader($name, $value);

	function setContentType($type, $charset = NULL);

	function redirect($url, $code = self::S302_FOUND);

	function setExpiration($seconds);

	function isSent();

	function getHeaders();

	function setCookie($name, $value, $expire, $path = NULL, $domain = NULL, $secure = NULL, $httpOnly = NULL);

	function deleteCookie($name, $path = NULL, $domain = NULL, $secure = NULL);

}

interface ISessionStorage
{

	function open($savePath, $sessionName);

	function close();

	function read($id);

	function write($id, $data);

	function remove($id);

	function clean($maxlifetime);

}

interface IUser
{

	function login();

	function logout($clearIdentity = FALSE);

	function isLoggedIn();

	function getIdentity();

	function setAuthenticator(IAuthenticator $handler);

	function getAuthenticator();

	function setNamespace($namespace);

	function getNamespace();

	function getRoles();

	function isInRole($role);

	function isAllowed();

	function setAuthorizator(IAuthorizator $handler);

	function getAuthorizator();

}

interface IMacro
{

	function initialize();

	function finalize();

	function nodeOpened(NMacroNode $node);

	function nodeClosed(NMacroNode $node);

}

interface ITranslator
{

	function translate($message, $count = NULL);

}

interface IMailer
{

	function send(NMail $mail);

}

interface IAnnotation
{

	function __construct(array $values);

}

interface IAuthenticator
{

	const USERNAME = 0,
		PASSWORD = 1;

	const IDENTITY_NOT_FOUND = 1,
		INVALID_CREDENTIAL = 2,
		FAILURE = 3,
		NOT_APPROVED = 4;

	function authenticate(array $credentials);

}

interface IAuthorizator
{

	const ALL = NULL;

	const ALLOW = TRUE;

	const DENY = FALSE;

	function isAllowed($role= self::ALL, $resource= self::ALL, $privilege= self::ALL);

}

interface IIdentity
{

	function getId();

	function getRoles();

}

interface IResource
{

	function getResourceId();

}

interface IRole
{

	function getRoleId();

}

interface ITemplate
{

	function render();

}

interface IFileTemplate extends ITemplate
{

	function setFile($file);

	function getFile();

}

class ArgumentOutOfRangeException extends InvalidArgumentException
{
}

class InvalidStateException extends RuntimeException
{
	function __construct($message = '', $code = 0, Exception $previous = NULL)
	{
		if (PHP_VERSION_ID < 50300) {
			$this->previous = $previous;
			parent::__construct($message, $code);
		} else {
			parent::__construct($message, $code, $previous);
		}
	}
}

class NotImplementedException extends LogicException
{
}

class NotSupportedException extends LogicException
{
}

class DeprecatedException extends NotSupportedException
{
}

class MemberAccessException extends LogicException
{
}

class IOException extends RuntimeException
{
}

class FileNotFoundException extends IOException
{
}

class DirectoryNotFoundException extends IOException
{
}

class NStaticClassException extends LogicException
{
}

class FatalErrorException extends Exception
{
	private $severity;

	function __construct($message, $code, $severity, $file, $line, $context)
	{
		parent::__construct($message, $code);
		$this->severity = $severity;
		$this->file = $file;
		$this->line = $line;
		$this->context = $context;
	}

	function getSeverity()
	{
		return $this->severity;
	}

}

abstract class NObject
{

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	static function __callStatic($name, $args)
	{
		return NObjectMixin::callStatic(__CLASS__, $name, $args);
	}

	static function extensionMethod($name, $callback = NULL)
	{
		if (strpos($name, '::') === FALSE) {
			$class = __CLASS__;
		} else {
			list($class, $name) = explode('::', $name);
		}
		$class = new NClassReflection($class);
		if ($callback === NULL) {
			return $class->getExtensionMethod($name);
		} else {
			$class->setExtensionMethod($name, $callback);
		}
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

final class NLimitedScope
{
	private static $vars;

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function evaluate()
	{
		if (func_num_args() > 1) {
			self::$vars = func_get_arg(1);
			extract(self::$vars);
		}
		$res = eval('?>' . func_get_arg(0));
		if ($res === FALSE && ($error = error_get_last()) && $error['type'] === E_PARSE) {
			throw new FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL);
		}
		return $res;
	}

	static function load()
	{
		if (func_num_args() > 1) {
			self::$vars = func_get_arg(1);
			extract(self::$vars);
		}
		return  include func_get_arg(0);
	}

}

abstract class NAutoLoader extends NObject
{

	static private $loaders = array();

	public static $count = 0;

	final static function load($type)
	{
		foreach (func_get_args() as $type) {
			if (!class_exists($type)) {
				throw new InvalidStateException("Unable to load class or interface '$type'.");
			}
		}
	}

	final static function getLoaders()
	{
		return array_values(self::$loaders);
	}

	function register()
	{
		if (!function_exists('spl_autoload_register')) {
			throw new NotSupportedException('spl_autoload does not exist in this PHP installation.');
		}

		spl_autoload_register(array($this, 'tryLoad'));
		self::$loaders[spl_object_hash($this)] = $this;
	}

	function unregister()
	{
		unset(self::$loaders[spl_object_hash($this)]);
		return spl_autoload_unregister(array($this, 'tryLoad'));
	}

	abstract function tryLoad($type);

}

final class NDebugHelpers
{

	static function editorLink($file, $line)
	{
		if (NDebugger::$editor && is_file($file)) {
			$dir = dirname(strtr($file, '/', DIRECTORY_SEPARATOR));
			$base = isset($_SERVER['SCRIPT_FILENAME']) ? dirname(dirname(strtr($_SERVER['SCRIPT_FILENAME'], '/', DIRECTORY_SEPARATOR))) : dirname($dir);
			if (substr($dir, 0, strlen($base)) === $base) {
				$dir = '...' . substr($dir, strlen($base));
			}
			return NHtml::el('a')
				->href(strtr(NDebugger::$editor, array('%file' => rawurlencode($file), '%line' => $line)))
				->title("$file:$line")
				->setHtml(htmlSpecialChars(rtrim($dir, DIRECTORY_SEPARATOR)) . DIRECTORY_SEPARATOR . '<b>' . htmlSpecialChars(basename($file)) . '</b>');
		} else {
			return NHtml::el('span')->setText($file);
		}
	}

	static function htmlDump(&$var, $level = 0)
	{
		static $tableUtf, $tableBin, $reBinary = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';
		if ($tableUtf === NULL) {
			foreach (range("\x00", "\xFF") as $ch) {
				if (ord($ch) < 32 && strpos("\r\n\t", $ch) === FALSE) {
					$tableUtf[$ch] = $tableBin[$ch] = '\\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
				} elseif (ord($ch) < 127) {
					$tableUtf[$ch] = $tableBin[$ch] = $ch;
				} else {
					$tableUtf[$ch] = $ch; $tableBin[$ch] = '\\x' . dechex(ord($ch));
				}
			}
			$tableBin["\\"] = '\\\\';
			$tableBin["\r"] = '\\r';
			$tableBin["\n"] = '\\n';
			$tableBin["\t"] = '\\t';
			$tableUtf['\\x'] = $tableBin['\\x'] = '\\\\x';
		}

		if (is_bool($var)) {
			return '<span class="php-bool">' . ($var ? 'TRUE' : 'FALSE') . "</span>\n";

		} elseif ($var === NULL) {
			return "<span class=\"php-null\">NULL</span>\n";

		} elseif (is_int($var)) {
			return "<span class=\"php-int\">$var</span>\n";

		} elseif (is_float($var)) {
			$var = var_export($var, TRUE);
			if (strpos($var, '.') === FALSE) {
				$var .= '.0';
			}
			return "<span class=\"php-float\">$var</span>\n";

		} elseif (is_string($var)) {
			if (NDebugger::$maxLen && strlen($var) > NDebugger::$maxLen) {
				$s = htmlSpecialChars(substr($var, 0, NDebugger::$maxLen), ENT_NOQUOTES) . ' ... ';
			} else {
				$s = htmlSpecialChars($var, ENT_NOQUOTES);
			}
			$s = strtr($s, preg_match($reBinary, $s) || preg_last_error() ? $tableBin : $tableUtf);
			$len = strlen($var);
			return "<span class=\"php-string\">\"$s\"</span>" . ($len > 1 ? " ($len)" : "") . "\n";

		} elseif (is_array($var)) {
			$s = '<span class="php-array">array</span>(' . count($var) . ") ";
			$space = str_repeat($space1 = '   ', $level);
			$brackets = range(0, count($var) - 1) === array_keys($var) ? "[]" : "{}";

			static $marker;
			if ($marker === NULL) {
				$marker = uniqid("\x00", TRUE);
			}
			if (empty($var)) {

			} elseif (isset($var[$marker])) {
				$brackets = $var[$marker];
				$s .= "$brackets[0] *RECURSION* $brackets[1]";

			} elseif ($level < NDebugger::$maxDepth || !NDebugger::$maxDepth) {
				$s .= "<code>$brackets[0]\n";
				$var[$marker] = $brackets;
				foreach ($var as $k => &$v) {
					if ($k === $marker) {
						continue;
					}
					$k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf);
					$k = htmlSpecialChars(preg_match('#^\w+$#', $k) ? $k : "\"$k\"");
					$s .= "$space$space1<span class=\"php-key\">$k</span> => " . self::htmlDump($v, $level + 1);
				}
				unset($var[$marker]);
				$s .= "$space$brackets[1]</code>";

			} else {
				$s .= "$brackets[0] ... $brackets[1]";
			}
			return $s . "\n";

		} elseif (is_object($var)) {
			if ($var instanceof Closure) {
				$rc = new ReflectionFunction($var);
				$arr = array();
				foreach ($rc->getParameters() as $param) {
					$arr[] = '$' . $param->getName();
				}
				$arr = array('file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'parameters' => implode(', ', $arr));
			} else {
				$arr = (array) $var;
			}
			$s = '<span class="php-object">' . get_class($var) . "</span>(" . count($arr) . ") ";
			$space = str_repeat($space1 = '   ', $level);

			static $list = array();
			if (empty($arr)) {

			} elseif (in_array($var, $list, TRUE)) {
				$s .= "{ *RECURSION* }";

			} elseif ($level < NDebugger::$maxDepth || !NDebugger::$maxDepth || $var instanceof Closure) {
				$s .= "<code>{\n";
				$list[] = $var;
				foreach ($arr as $k => &$v) {
					$m = '';
					if ($k[0] === "\x00") {
						$m = ' <span class="php-visibility">' . ($k[1] === '*' ? 'protected' : 'private') . '</span>';
						$k = substr($k, strrpos($k, "\x00") + 1);
					}
					$k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf);
					$k = htmlSpecialChars(preg_match('#^\w+$#', $k) ? $k : "\"$k\"");
					$s .= "$space$space1<span class=\"php-key\">$k</span>$m => " . self::htmlDump($v, $level + 1);
				}
				array_pop($list);
				$s .= "$space}</code>";

			} else {
				$s .= "{ ... }";
			}
			return $s . "\n";

		} elseif (is_resource($var)) {
			return '<span class="php-resource">' . htmlSpecialChars(get_resource_type($var)) . " resource</span>\n";

		} else {
			return "<span>unknown type</span>\n";
		}
	}

	static function clickableDump($dump)
	{
		return '<pre class="nette-dump">' . preg_replace_callback(
			'#^( *)((?>[^(]{1,200}))\((\d+)\) <code>#m',
			create_function('$m', '
				return "$m[1]<a href=\'#\' rel=\'next\'>$m[2]($m[3]) "
					. (trim($m[1]) || $m[3] < 7
					? \'<abbr>&#x25bc;</abbr> </a><code>\'
					: \'<abbr>&#x25ba;</abbr> </a><code class="nette-collapsed">\');
			'),
			self::htmlDump($dump)
		) . '</pre>';
	}

}

class NHtml extends NObject implements ArrayAccess, Countable, IteratorAggregate
{

	private $name;

	private $isEmpty;

	public $attrs = array();

	protected $children = array();

	public static $xhtml = TRUE;

	public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1,'embed'=>1,'keygen'=>1,
		'source'=>1,'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'command'=>1);

	static function el($name = NULL, $attrs = NULL)
	{
		$el = new self;
		$parts = explode(' ', $name, 2);
		$el->setName($parts[0]);

		if (is_array($attrs)) {
			$el->attrs = $attrs;

		} elseif ($attrs !== NULL) {
			$el->setText($attrs);
		}

		if (isset($parts[1])) {
			foreach (NStrings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\s))?#i') as $m) {
				$el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE;
			}
		}

		return $el;
	}

	final function setName($name, $isEmpty = NULL)
	{
		if ($name !== NULL && !is_string($name)) {
			throw new InvalidArgumentException("Name must be string or NULL, " . gettype($name) ." given.");
		}

		$this->name = $name;
		$this->isEmpty = $isEmpty === NULL ? isset(self::$emptyElements[$name]) : (bool) $isEmpty;
		return $this;
	}

	final function getName()
	{
		return $this->name;
	}

	final function isEmpty()
	{
		return $this->isEmpty;
	}

	function addAttributes(array $attrs)
	{
		$this->attrs = $attrs + $this->attrs;
		return $this;
	}

	final function __set($name, $value)
	{
		$this->attrs[$name] = $value;
	}

	final function &__get($name)
	{
		return $this->attrs[$name];
	}

	final function __unset($name)
	{
		unset($this->attrs[$name]);
	}

	final function __call($m, $args)
	{
		$p = substr($m, 0, 3);
		if ($p === 'get' || $p === 'set' || $p === 'add') {
			$m = substr($m, 3);
			$m[0] = $m[0] | "\x20";
			if ($p === 'get') {
				return isset($this->attrs[$m]) ? $this->attrs[$m] : NULL;

			} elseif ($p === 'add') {
				$args[] = TRUE;
			}
		}

		if (count($args) === 0) {

		} elseif (count($args) === 1) {
			$this->attrs[$m] = $args[0];

		} elseif ((string) $args[0] === '') {
			$tmp = & $this->attrs[$m];

		} elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) {
			$this->attrs[$m][$args[0]] = $args[1];

		} else {
			$this->attrs[$m] = array($this->attrs[$m], $args[0] => $args[1]);
		}

		return $this;
	}

	final function href($path, $query = NULL)
	{
		if ($query) {
			$query = http_build_query($query, NULL, '&');
			if ($query !== '') {
				$path .= '?' . $query;
			}
		}
		$this->attrs['href'] = $path;
		return $this;
	}

	final function setHtml($html)
	{
		if ($html === NULL) {
			$html = '';

		} elseif (is_array($html)) {
			throw new InvalidArgumentException("Textual content must be a scalar, " . gettype($html) ." given.");

		} else {
			$html = (string) $html;
		}

		$this->removeChildren();
		$this->children[] = $html;
		return $this;
	}

	final function getHtml()
	{
		$s = '';
		foreach ($this->children as $child) {
			if (is_object($child)) {
				$s .= $child->render();
			} else {
				$s .= $child;
			}
		}
		return $s;
	}

	final function setText($text)
	{
		if (!is_array($text)) {
			$text = htmlspecialchars((string) $text, ENT_NOQUOTES);
		}
		return $this->setHtml($text);
	}

	final function getText()
	{
		return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
	}

	final function add($child)
	{
		return $this->insert(NULL, $child);
	}

	final function create($name, $attrs = NULL)
	{
		$this->insert(NULL, $child = self::el($name, $attrs));
		return $child;
	}

	function insert($index, $child, $replace = FALSE)
	{
		if ($child instanceof NHtml || is_scalar($child)) {
			if ($index === NULL) {
				$this->children[] = $child;

			} else {
				array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
			}

		} else {
			throw new InvalidArgumentException("Child node must be scalar or Html object, " . (is_object($child) ? get_class($child) : gettype($child)) ." given.");
		}

		return $this;
	}

	final function offsetSet($index, $child)
	{
		$this->insert($index, $child, TRUE);
	}

	final function offsetGet($index)
	{
		return $this->children[$index];
	}

	final function offsetExists($index)
	{
		return isset($this->children[$index]);
	}

	function offsetUnset($index)
	{
		if (isset($this->children[$index])) {
			array_splice($this->children, (int) $index, 1);
		}
	}

	final function count()
	{
		return count($this->children);
	}

	function removeChildren()
	{
		$this->children = array();
	}

	final function getIterator($deep = FALSE)
	{
		if ($deep) {
			$deep = $deep > 0 ? RecursiveIteratorIterator::SELF_FIRST : RecursiveIteratorIterator::CHILD_FIRST;
			return new RecursiveIteratorIterator(new NGenericRecursiveIterator(new ArrayIterator($this->children)), $deep);

		} else {
			return new NGenericRecursiveIterator(new ArrayIterator($this->children));
		}
	}

	final function getChildren()
	{
		return $this->children;
	}

	final function render($indent = NULL)
	{
		$s = $this->startTag();

		if (!$this->isEmpty) {

			if ($indent !== NULL) {
				$indent++;
			}
			foreach ($this->children as $child) {
				if (is_object($child)) {
					$s .= $child->render($indent);
				} else {
					$s .= $child;
				}
			}

			$s .= $this->endTag();
		}

		if ($indent !== NULL) {
			return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
		}
		return $s;
	}

	final function __toString()
	{
		return $this->render();
	}

	final function startTag()
	{
		if ($this->name) {
			return '<' . $this->name . $this->attributes() . (self::$xhtml && $this->isEmpty ? ' />' : '>');

		} else {
			return '';
		}
	}

	final function endTag()
	{
		return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
	}

	final function attributes()
	{
		if (!is_array($this->attrs)) {
			return '';
		}

		$s = '';
		foreach ($this->attrs as $key => $value) {
			if ($value === NULL || $value === FALSE) {
				continue;

			} elseif ($value === TRUE) {
				if (self::$xhtml) {
					$s .= ' ' . $key . '="' . $key . '"';
				} else {
					$s .= ' ' . $key;
				}
				continue;

			} elseif (is_array($value)) {
				if ($key === 'data') {
					foreach ($value as $k => $v) {
						if ($v !== NULL && $v !== FALSE) {
							$s .= ' data-' . $k . '="' . htmlspecialchars((string) $v) . '"';
						}
					}
					continue;
				}

				$tmp = NULL;
				foreach ($value as $k => $v) {
					if ($v != NULL) {

						$tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
					}
				}
				if ($tmp === NULL) {
					continue;
				}

				$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);

			} else {
				$value = (string) $value;
			}

			$s .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
		}

		$s = str_replace('@', '&#64;', $s);
		return $s;
	}

	function __clone()
	{
		foreach ($this->children as $key => $value) {
			if (is_object($value)) {
				$this->children[$key] = clone $value;
			}
		}
	}

}

final class NDebugger
{

	public static $productionMode;

	public static $consoleMode;

	public static $time;

	private static $ajaxDetected;

	public static $source;

	public static $editor = 'editor://open/?file=%file&line=%line';

	public static $maxDepth = 3;

	public static $maxLen = 150;

	public static $showLocation = FALSE;

	const DEVELOPMENT = FALSE,
		PRODUCTION = TRUE,
		DETECT = NULL;

	public static $blueScreen;

	public static $strictMode = FALSE;

	public static $scream = FALSE;

	public static $onFatalError = array();

	private static $enabled = FALSE;

	private static $lastError = FALSE;

	public static $logger;

	public static $fireLogger;

	public static $logDirectory;

	public static $email;

	public static $mailer;

	public static $emailSnooze;

	public static $bar;

	private static $errorPanel;

	private static $dumpPanel;

	const DEBUG = 'debug',
		INFO = 'info',
		WARNING = 'warning',
		ERROR = 'error',
		CRITICAL = 'critical';

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function _init()
	{
		self::$time = microtime(TRUE);
		self::$consoleMode = PHP_SAPI === 'cli';
		self::$productionMode = self::DETECT;
		if (self::$consoleMode) {
			self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
		} else {
			self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
			if (isset($_SERVER['REQUEST_URI'])) {
				self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
					. (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
					. $_SERVER['REQUEST_URI'];
			}
		}

		self::$logger = new NLogger;
		self::$logDirectory = & self::$logger->directory;
		self::$email = & self::$logger->email;
		self::$mailer = & self::$logger->mailer;
		self::$emailSnooze = & NLogger::$emailSnooze;

		self::$fireLogger = new NFireLogger;

		self::$blueScreen = new NDebugBlueScreen;
		self::$blueScreen->addPanel(create_function('$e', '
			if ($e instanceof NTemplateException) {
				return array(
					\'tab\' => \'Template\',
					\'panel\' => \'<p><b>File:</b> \' . NDebugHelpers::editorLink($e->sourceFile, $e->sourceLine)
					. \'&nbsp; <b>Line:</b> \' . ($e->sourceLine ? $e->sourceLine : \'n/a\') . \'</p>\'
					. ($e->sourceLine ? \'<pre>\' . NDebugBlueScreen::highlightFile($e->sourceFile, $e->sourceLine) . \'</pre>\' : \'\')
				);
			}
		'));

		self::$bar = new NDebugBar;
		self::$bar->addPanel(new NDefaultBarPanel('time'));
		self::$bar->addPanel(new NDefaultBarPanel('memory'));
		self::$bar->addPanel(self::$errorPanel = new NDefaultBarPanel('errors'));
		self::$bar->addPanel(self::$dumpPanel = new NDefaultBarPanel('dumps'));
	}

	static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
	{
		error_reporting(E_ALL | E_STRICT);

		if (is_bool($mode)) {
			self::$productionMode = $mode;

		} elseif (is_string($mode)) {
			$mode = preg_split('#[,\s]+#', "$mode 127.0.0.1 ::1");
		}

		if (is_array($mode)) {
			self::$productionMode = !isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], $mode, TRUE);
		}

		if (self::$productionMode === self::DETECT) {
			if (isset($_SERVER['SERVER_ADDR']) || isset($_SERVER['LOCAL_ADDR'])) {
				$addrs = array();
				if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
					$addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
				}
				if (isset($_SERVER['REMOTE_ADDR'])) {
					$addrs[] = $_SERVER['REMOTE_ADDR'];
				}
				$addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
				self::$productionMode = FALSE;
				foreach ($addrs as $addr) {
					$oct = explode('.', $addr);
					if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
						&& ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
					) {
						self::$productionMode = TRUE;
						break;
					}
				}

			} else {
				self::$productionMode = !self::$consoleMode;
			}
		}

		if (is_string($logDirectory)) {
			self::$logDirectory = realpath($logDirectory);
			if (self::$logDirectory === FALSE) {
				throw new DirectoryNotFoundException("Directory '$logDirectory' is not found.");
			}
		} elseif ($logDirectory === FALSE) {
			self::$logDirectory = FALSE;

		} elseif (self::$logDirectory === NULL) {
			self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
		}
		if (self::$logDirectory) {
			ini_set('error_log', self::$logDirectory . '/php_error.log');
		}

		if (function_exists('ini_set')) {
			ini_set('display_errors', !self::$productionMode);
			ini_set('html_errors', FALSE);
			ini_set('log_errors', FALSE);

		} elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) {
			throw new NotSupportedException('Function ini_set() must be enabled.');
		}

		if ($email) {
			if (!is_string($email)) {
				throw new InvalidArgumentException('Email address must be a string.');
			}
			self::$email = $email;
		}

		if (!defined('E_DEPRECATED')) {
			define('E_DEPRECATED', 8192);
		}

		if (!defined('E_USER_DEPRECATED')) {
			define('E_USER_DEPRECATED', 16384);
		}

		if (!self::$enabled) {
			register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
			set_exception_handler(array(__CLASS__, '_exceptionHandler'));
			set_error_handler(array(__CLASS__, '_errorHandler'));
			self::$enabled = TRUE;
		}
	}

	static function isEnabled()
	{
		return self::$enabled;
	}

	static function log($message, $priority = self::INFO)
	{
		if (self::$logDirectory === FALSE) {
			return;

		} elseif (!self::$logDirectory) {
			throw new InvalidStateException('Logging directory is not specified in NDebugger::$logDirectory.');
		}

		if ($message instanceof Exception) {
			$exception = $message;
			$message = "PHP Fatal error: "
				. ($message instanceof FatalErrorException
					? $exception->getMessage()
					: "Uncaught exception " . get_class($exception) . " with message '" . $exception->getMessage() . "'")
				. " in " . $exception->getFile() . ":" . $exception->getLine();

			$hash = md5($exception . (method_exists($exception, 'getPrevious') ? $exception->getPrevious() : (isset($exception->previous) ? $exception->previous : '')));
			$exceptionFilename = "exception " . @date('Y-m-d H-i-s') . " $hash.html";
			foreach (new DirectoryIterator(self::$logDirectory) as $entry) {
				if (strpos($entry, $hash)) {
					$exceptionFilename = NULL; break;
				}
			}
		}

		self::$logger->log(array(
			@date('[Y-m-d H-i-s]'),
			$message,
			self::$source ? ' @  ' . self::$source : NULL,
			!empty($exceptionFilename) ? ' @@  ' . $exceptionFilename : NULL
		), $priority);

		if (!empty($exceptionFilename) && $logHandle = @fopen(self::$logDirectory . '/'. $exceptionFilename, 'w')) {
			ob_start();
			ob_start(create_function('$buffer', 'extract(NCFix::$vars['.NCFix::uses(array('logHandle'=>$logHandle)).'], EXTR_REFS); fwrite($logHandle, $buffer); '), 1);
			self::$blueScreen->render($exception);
			ob_end_flush();
			ob_end_clean();
			fclose($logHandle);
		}
	}

	static function _shutdownHandler()
	{
		if (!self::$enabled) {
			return;
		}

		static $types = array(
			E_ERROR => 1,
			E_CORE_ERROR => 1,
			E_COMPILE_ERROR => 1,
			E_PARSE => 1,
		);
		$error = error_get_last();
		if (isset($types[$error['type']])) {
			self::_exceptionHandler(new FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
		}

		if (self::$bar && !self::$productionMode && self::isHtmlMode()) {
			self::$bar->render();
		}
	}

	static function _exceptionHandler(Exception $exception)
	{
		if (!headers_sent()) {
			header('HTTP/1.1 500 Internal Server Error');
		}

		try {
			if (self::$productionMode) {
				try {
					self::log($exception, self::ERROR);
				} catch (Exception $e) {
					echo 'FATAL ERROR: unable to log error';
				}

				if (self::$consoleMode) {
					echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";

				} elseif (self::isHtmlMode()) {

?>
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name=robots content=noindex><meta name=generator content="Nette Framework">
<style>body{color:#333;background:white;width:500px;margin:100px auto}h1{font:bold 47px/1.5 sans-serif;margin:.6em 0}p{font:21px/1.5 Georgia,serif;margin:1.5em 0}small{font-size:70%;color:gray}</style>

<title>Server Error</title>

<h1>Server Error</h1>

<p>We're sorry! The server encountered an internal error and was unable to complete your request. Please try again later.</p>

<p><small>error 500</small></p>
<?php
				}

			} else {
				if (self::$consoleMode) {
					echo "$exception\n";

				} elseif (self::isHtmlMode()) {
					self::$blueScreen->render($exception);
					if (self::$bar) {
						self::$bar->render();
					}

				} elseif (!self::fireLog($exception, self::ERROR)) {
					self::log($exception);
				}
			}

			foreach (self::$onFatalError as $handler) {
				call_user_func($handler, $exception);
			}

		} catch (Exception $e) {
			if (self::$productionMode) {
				echo self::isHtmlMode() ? '<meta name=robots content=noindex>FATAL ERROR' : 'FATAL ERROR';
			} else {
				echo "FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
					"\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
			}
		}

		self::$enabled = FALSE;
		exit(255);
	}

	static function _errorHandler($severity, $message, $file, $line, $context)
	{
		if (self::$scream) {
			error_reporting(E_ALL | E_STRICT);
		}

		if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) {
			self::$lastError = new ErrorException($message, 0, $severity, $file, $line);
			return NULL;
		}

		if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
			throw new FatalErrorException($message, 0, $severity, $file, $line, $context);

		} elseif (($severity & error_reporting()) !== $severity) {
			return FALSE;

		} elseif (self::$strictMode && !self::$productionMode) {
			self::_exceptionHandler(new FatalErrorException($message, 0, $severity, $file, $line, $context));
		}

		static $types = array(
			E_WARNING => 'Warning',
			E_COMPILE_WARNING => 'Warning',
			E_USER_WARNING => 'Warning',
			E_NOTICE => 'Notice',
			E_USER_NOTICE => 'Notice',
			E_STRICT => 'Strict standards',
			E_DEPRECATED => 'Deprecated',
			E_USER_DEPRECATED => 'Deprecated',
		);

		$message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
		$count = & self::$errorPanel->data["$message|$file|$line"];

		if ($count++) {
			return NULL;

		} elseif (self::$productionMode) {
			self::log("$message in $file:$line", self::ERROR);
			return NULL;

		} else {
			$ok = self::fireLog(new ErrorException($message, 0, $severity, $file, $line), self::WARNING);
			return !self::isHtmlMode() || (!self::$bar && !$ok) ? FALSE : NULL;
		}

		return FALSE;
	}

	static function toStringException(Exception $exception)
	{
		if (self::$enabled) {
			self::_exceptionHandler($exception);
		} else {
			trigger_error($exception->getMessage(), E_USER_ERROR);
		}
	}

	static function tryError()
	{
		if (!self::$enabled && self::$lastError === FALSE) {
			set_error_handler(array(__CLASS__, '_errorHandler'));
		}
		self::$lastError = NULL;
	}

	static function catchError(& $error)
	{
		if (!self::$enabled && self::$lastError !== FALSE) {
			restore_error_handler();
		}
		$error = self::$lastError;
		self::$lastError = FALSE;
		return (bool) $error;
	}

	static function dump($var, $return = FALSE)
	{
		if (!$return && self::$productionMode) {
			return $var;
		}

		$output = "<pre class=\"nette-dump\">" . NDebugHelpers::htmlDump($var) . "</pre>\n";

		if (!$return) {
			$trace = debug_backtrace();
			$i = !isset($trace[1]['class']) && isset($trace[1]['function']) && $trace[1]['function'] === 'dump' ? 1 : 0;
			if (isset($trace[$i]['file'], $trace[$i]['line']) && is_file($trace[$i]['file'])) {
				$lines = file($trace[$i]['file']);
				preg_match('#dump\((.*)\)#', $lines[$trace[$i]['line'] - 1], $m);
				$output = substr_replace(
					$output,
					' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$trace[$i]['file']} on line {$trace[$i]['line']}") . '"',
					4, 0);

				if (self::$showLocation) {
					$output = substr_replace(
						$output,
						' <small>in ' . NDebugHelpers::editorLink($trace[$i]['file'], $trace[$i]['line']) . ":{$trace[$i]['line']}</small>",
						-8, 0);
				}
			}
		}

		if (self::$consoleMode) {
			$output = htmlspecialchars_decode(strip_tags($output), ENT_NOQUOTES);
		}

		if ($return) {
			return $output;

		} else {
			echo $output;
			return $var;
		}
	}

	static function timer($name = NULL)
	{
		static $time = array();
		$now = microtime(TRUE);
		$delta = isset($time[$name]) ? $now - $time[$name] : 0;
		$time[$name] = $now;
		return $delta;
	}

	static function barDump($var, $title = NULL)
	{
		if (!self::$productionMode) {
			$dump = array();
			foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
				$dump[$key] = NDebugHelpers::clickableDump($val);
			}
			self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
		}
		return $var;
	}

	static function fireLog($message)
	{
		if (!self::$productionMode) {
			return self::$fireLogger->log($message);
		}
	}

	private static function isHtmlMode()
	{
		return !self::$ajaxDetected && !self::$consoleMode
			&& !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
	}

	static function addPanel(IBarPanel $panel, $id = NULL)
	{
		self::$bar->addPanel($panel, $id);
	}

}

class NLogger extends NObject
{
	const DEBUG = 'debug',
		INFO = 'info',
		WARNING = 'warning',
		ERROR = 'error',
		CRITICAL = 'critical';

	public static $emailSnooze = 172800;

	public $mailer = array(__CLASS__, 'defaultMailer');

	public $directory;

	public $email;

	function log($message, $priority = self::INFO)
	{
		if (!is_dir($this->directory)) {
			throw new DirectoryNotFoundException("Directory '$this->directory' is not found or is not directory.");
		}

		if (is_array($message)) {
			$message = implode(' ', $message);
		}
		$res = error_log(trim($message) . PHP_EOL, 3, $this->directory . '/' . strtolower($priority) . '.log');

		if (($priority === self::ERROR || $priority === self::CRITICAL) && $this->email && $this->mailer
			&& @filemtime($this->directory . '/email-sent') + self::$emailSnooze < time()
			&& @file_put_contents($this->directory . '/email-sent', 'sent')
		) {
			call_user_func($this->mailer, $message, $this->email);
		}
		return $res;
	}

	private static function defaultMailer($message, $email)
	{
		$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] :
				(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '');

		$parts = str_replace(
			array("\r\n", "\n"),
			array("\n", PHP_EOL),
			array(
				'headers' => "From: noreply@$host\nX-Mailer: Nette Framework\n",
				'subject' => "PHP: An error occurred on the server $host",
				'body' => "[" . @date('Y-m-d H:i:s') . "] $message",
			)
		);

		mail($email, $parts['subject'], $parts['body'], $parts['headers']);
	}

}

class NFireLogger extends NObject
{
	const DEBUG = 'debug',
		INFO = 'info',
		WARNING = 'warning',
		ERROR = 'error',
		CRITICAL = 'critical';

	private static $payload = array('logs' => array());

	static function log($message, $priority = self::DEBUG)
	{
		if (!isset($_SERVER['HTTP_X_FIRELOGGER']) || headers_sent()) {
			return FALSE;
		}

		$item = array(
			'name' => 'PHP',
			'level' => $priority,
			'order' => count(self::$payload['logs']),
			'time' => str_pad(number_format((microtime(TRUE) - NDebugger::$time) * 1000, 1, '.', ' '), 8, '0', STR_PAD_LEFT) . ' ms',
			'template' => '',
			'message' => '',
			'style' => 'background:#767ab6',
		);

		$args = func_get_args();
		if (isset($args[0]) && is_string($args[0])) {
			$item['template'] = array_shift($args);
		}

		if (isset($args[0]) && $args[0] instanceof Exception) {
			$e = array_shift($args);
			$trace = $e->getTrace();
			if (isset($trace[0]['class']) && $trace[0]['class'] === 'NDebugger'
				&& ($trace[0]['function'] === '_shutdownHandler' || $trace[0]['function'] === '_errorHandler')
			) {
				unset($trace[0]);
			}

			$file = str_replace(dirname(dirname(dirname($e->getFile()))), "\xE2\x80\xA6", $e->getFile());
			$item['template'] = ($e instanceof ErrorException ? '' : get_class($e) . ': ')
				. $e->getMessage() . ($e->getCode() ? ' #' . $e->getCode() : '') . ' in ' . $file . ':' . $e->getLine();
			$item['pathname'] = $e->getFile();
			$item['lineno'] = $e->getLine();

		} else {
			$trace = debug_backtrace();
			if (isset($trace[1]['class']) && $trace[1]['class'] === 'NDebugger'
				&& ($trace[1]['function'] === 'fireLog')
			) {
				unset($trace[0]);
			}

			foreach ($trace as $frame) {
				if (isset($frame['file']) && is_file($frame['file'])) {
					$item['pathname'] = $frame['file'];
					$item['lineno'] = $frame['line'];
					break;
				}
			}
		}

		$item['exc_info'] = array('', '', array());
		$item['exc_frames'] = array();

		foreach ($trace as $frame) {
			$frame += array('file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL);
			$item['exc_info'][2][] = array($frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']);
			$item['exc_frames'][] = $frame['args'];
		}

		if (isset($args[0]) && in_array($args[0], array(self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL), TRUE)) {
			$item['level'] = array_shift($args);
		}

		$item['args'] = $args;

		self::$payload['logs'][] = self::jsonDump($item, -1);
		foreach (str_split(base64_encode(@json_encode(self::$payload)), 4990) as $k => $v) {
			header("FireLogger-de11e-$k:$v");
		}
		return TRUE;
	}

	private static function jsonDump(&$var, $level = 0)
	{
		if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) {
			return $var;

		} elseif (is_string($var)) {
			if (NDebugger::$maxLen && strlen($var) > NDebugger::$maxLen) {
				$var = substr($var, 0, NDebugger::$maxLen) . " \xE2\x80\xA6 ";
			}
			return NStrings::fixEncoding($var);

		} elseif (is_array($var)) {
			static $marker;
			if ($marker === NULL) {
				$marker = uniqid("\x00", TRUE);
			}
			if (isset($var[$marker])) {
				return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";

			} elseif ($level < NDebugger::$maxDepth || !NDebugger::$maxDepth) {
				$var[$marker] = TRUE;
				$res = array();
				foreach ($var as $k => &$v) {
					if ($k !== $marker) {
						$res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
					}
			}
				unset($var[$marker]);
				return $res;

			} else {
				return " \xE2\x80\xA6 ";
		}

		} elseif (is_object($var)) {
			$arr = (array) $var;
			static $list = array();
			if (in_array($var, $list, TRUE)) {
				return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";

			} elseif ($level < NDebugger::$maxDepth || !NDebugger::$maxDepth) {
				$list[] = $var;
				$res = array("\x00" => '(object) ' . get_class($var));
				foreach ($arr as $k => &$v) {
					if ($k[0] === "\x00") {
						$k = substr($k, strrpos($k, "\x00") + 1);
					}
					$res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
				}
				array_pop($list);
				return $res;

			} else {
				return " \xE2\x80\xA6 ";
			}

		} elseif (is_resource($var)) {
			return "resource " . get_resource_type($var);

		} else {
			return "unknown type";
		}
	}

}

class NDebugBlueScreen extends NObject
{

	private $panels = array();

	function addPanel($panel, $id = NULL)
	{
		if ($id === NULL) {
			$this->panels[] = $panel;
		} else {
			$this->panels[$id] = $panel;
		}
	}

	function render(Exception $exception)
	{
		$panels = $this->panels;

static $errorTypes = array(
	E_ERROR => 'Fatal Error',
	E_USER_ERROR => 'User Error',
	E_RECOVERABLE_ERROR => 'Recoverable Error',
	E_CORE_ERROR => 'Core Error',
	E_COMPILE_ERROR => 'Compile Error',
	E_PARSE => 'Parse Error',
	E_WARNING => 'Warning',
	E_CORE_WARNING => 'Core Warning',
	E_COMPILE_WARNING => 'Compile Warning',
	E_USER_WARNING => 'User Warning',
	E_NOTICE => 'Notice',
	E_USER_NOTICE => 'User Notice',
	E_STRICT => 'Strict',
	E_DEPRECATED => 'Deprecated',
	E_USER_DEPRECATED => 'User Deprecated',
);

$title = ($exception instanceof FatalErrorException && isset($errorTypes[$exception->getSeverity()])) ? $errorTypes[$exception->getSeverity()] : get_class($exception);

$expandPath = NETTE_DIR . DIRECTORY_SEPARATOR;
$counter = 0;

?><!-- "' --></script></style></pre></xmp></table>
<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<meta name="robots" content="noindex,noarchive">
	<meta name="generator" content="Nette Framework">

	<title><?php echo htmlspecialchars($title) ?></title><!-- <?php
		$ex = $exception; echo $ex->getMessage(), ($ex->getCode() ? ' #' . $ex->getCode() : '');
		while ((method_exists($ex, 'getPrevious') && $ex = $ex->getPrevious()) || (isset($ex->previous) && $ex = $ex->previous)) echo '; caused by ', get_class($ex), ' ', $ex->getMessage(), ($ex->getCode() ? ' #' . $ex->getCode() : '');
	?> -->

	<style type="text/css" class="nette">html{overflow-y:scroll}body{margin:0 0 2em;padding:0}#netteBluescreen{font:9pt/1.5 Verdana,sans-serif;background:white;color:#333;position:absolute;left:0;top:0;width:100%;z-index:23178;text-align:left}#netteBluescreen *{font:inherit;color:inherit;background:transparent;border:none;margin:0;padding:0;text-align:inherit;text-indent:0}#netteBluescreen b{font-weight:bold}#netteBluescreen i{font-style:italic}#netteBluescreen a{text-decoration:none;color:#328ADC;padding:2px 4px;margin:-2px -4px}#netteBluescreen a:hover,#netteBluescreen a:active,#netteBluescreen a:focus{color:#085AA3}#netteBluescreen a abbr{font-family:sans-serif;color:#BBB}#netteBluescreenIcon{position:absolute;right:.5em;top:.5em;z-index:23179;text-decoration:none;background:#CD1818;padding:3px}#netteBluescreenError{background:#CD1818;color:white;font:13pt/1.5 Verdana,sans-serif!important;display:block}#netteBluescreenError #netteBsSearch{color:#CD1818;font-size:.7em}#netteBluescreenError:hover #netteBsSearch{color:#ED8383}#netteBluescreen h1{font-size:18pt;font-weight:normal;text-shadow:1px 1px 0 rgba(0,0,0,.4);margin:.7em 0}#netteBluescreen h2{font:14pt/1.5 sans-serif!important;color:#888;margin:.6em 0}#netteBluescreen h3{font:bold 10pt/1.5 Verdana,sans-serif!important;margin:1em 0;padding:0}#netteBluescreen p,#netteBluescreen pre{margin:.8em 0}#netteBluescreen pre,#netteBluescreen code,#netteBluescreen table{font:9pt/1.5 Consolas,monospace!important}#netteBluescreen pre,#netteBluescreen table{background:#FDF5CE;padding:.4em .7em;border:1px dotted silver;overflow:auto}#netteBluescreen table pre{padding:0;margin:0;border:none}#netteBluescreen pre .php-array,#netteBluescreen pre .php-object{color:#C22}#netteBluescreen pre .php-string{color:#080}#netteBluescreen pre .php-int,#netteBluescreen pre .php-float,#netteBluescreen pre .php-null,#netteBluescreen pre .php-bool{color:#328ADC}#netteBluescreen pre .php-visibility{font-size:85%;color:#998}#netteBluescreen pre.nette-dump a{color:#333}#netteBluescreen div.panel{padding:1px 25px}#netteBluescreen div.inner{background:#F4F3F1;padding:.1em 1em 1em;border-radius:8px;-moz-border-radius:8px;-webkit-border-radius:8px}#netteBluescreen table{border-collapse:collapse;width:100%}#netteBluescreen .outer{overflow:auto}#netteBluescreen td,#netteBluescreen th{vertical-align:top;text-align:left;padding:2px 6px;border:1px solid #e6dfbf}#netteBluescreen th{font-weight:bold}#netteBluescreen tr>:first-child{width:20%}#netteBluescreen tr:nth-child(2n),#netteBluescreen tr:nth-child(2n) pre{background-color:#F7F0CB}#netteBluescreen ol{margin:1em 0;padding-left:2.5em}#netteBluescreen ul{font:7pt/1.5 Verdana,sans-serif!important;padding:2em 4em;margin:1em 0 0;color:#777;background:#F6F5F3 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAAAjCAMAAADbuxbOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADBQTFRF/fz24d7Y7Onj5uLd9vPu3drUzMvG09LN39zW8e7o2NbQ3NnT29jS0M7J1tXQAAAApvmsFgAAABB0Uk5T////////////////////AOAjXRkAAAKlSURBVHja7FbbsqQgDAwENEgc//9vN+SCWDtbtXPmZR/Wc6o02mlC58LA9ckFAOszvMV8xNgyUjyXhojfMVKvRL0ZHavxXYy5JrmchMdzou8YlTClxajtK8ZGGpWRoBr1+gFjKfHkJPbizabLgzE3pH7Iu4K980xgFvlrVzMZoVBWhtvouCDdcTDmTgMCJdVxJ9MKO6XxnliM7hxi5lbj2ZVM4l8DqYyKoNLYcfqBB1/LpHYxEcfVG6ZpMDgyFUVWY/Q1sSYPpIdSAKWqLWL0XqWiMWc4hpH0OQOMOAgdycY4N9Sb7wWANQs3rsDSdLAYiuxi5siVfOhBWIrtH0G3kNaF/8Q4kCPE1kMucG/ZMUBUCOgiKJkPuWWTLGVgLGpwns1DraUayCtoBqERyaYtVsm85NActRooezvSLO/sKZP/nq8n4+xcyjNsRu8zW6KWpdb7wjiQd4WrtFZYFiKHENSmWp6xshh96c2RQ+c7Lt+qbijyEjHWUJ/pZsy8MGIUuzNiPySK2Gqoh6ZTRF6ko6q3nVTkaA//itIrDpW6l3SLo8juOmqMXkYknu5FdQxWbhCfKHEGDhxxyTVaXJF3ZjSl3jMksjSOOKmne9pI+mcG5QvaUJhI9HpkmRo2NpCrDJvsktRhRE2MM6F2n7dt4OaMUq8bCctk0+PoMRzL+1l5PZ2eyM/Owr86gf8z/tOM53lom5+nVcFuB+eJVzlXwAYy9TZ9s537tfqcsJWbEU4nBngZo6FfO9T9CdhfBtmk2dLiAy8uS4zwOpMx2HqYbTC+amNeAYTpsP4SIgvWfUBWXxn3CMHW3ffd7k3+YIkx7w0t/CVGvcPejoeOlzOWzeGbawOHqXQGUTMZRcfj4XPCgW9y/fuvVn8zD9P1QHzv80uAAQA0i3Jer7Jr7gAAAABJRU5ErkJggg==') 99% 10px no-repeat;border-top:1px solid #DDD}#netteBluescreen .highlight{background:#CD1818;color:white;font-weight:bold;font-style:normal;display:block;padding:0 .4em;margin:0 -.4em}#netteBluescreen .line{color:#9F9C7F;font-weight:normal;font-style:normal}#netteBluescreen a[href^=editor\:]{color:inherit;border-bottom:1px dotted #C1D2E1}</style>
</head>

<body>
<div id="netteBluescreen">
	<a id="netteBluescreenIcon" href="#" rel="next"><abbr>&#x25bc;</abbr></a

	><div>
		<div id="netteBluescreenError" class="panel">
			<h1><?php echo htmlspecialchars($title), ($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>

			<p><?php echo htmlspecialchars($exception->getMessage()) ?> <a href="http://www.google.cz/search?sourceid=nette&amp;q=<?php echo urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>" id="netteBsSearch">search&#x25ba;</a></p>
		</div>

		<?php $ex = $exception; $level = 0; ?>
		<?php do { ?>

			<?php if ($level++): ?>
			<div class="panel">
			<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Caused by <abbr><?php echo ($collapsed = $level > 2) ? '&#x25ba;' : '&#x25bc;' ?></abbr></a></h2>

			<div id="netteBsPnl<?php echo $counter ?>" class="<?php echo $collapsed ? 'nette-collapsed ' : '' ?>inner">
				<div class="panel">
					<h1><?php echo htmlspecialchars(get_class($ex)), ($ex->getCode() ? ' #' . $ex->getCode() : '') ?></h1>

					<p><b><?php echo htmlspecialchars($ex->getMessage()) ?></b></p>
				</div>
			<?php endif ?>

			<?php foreach ($panels as $panel): ?>
			<?php $panel = call_user_func($panel, $ex); if (empty($panel['tab']) || empty($panel['panel'])) continue; ?>
			<div class="panel">
				<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>"><?php echo htmlSpecialChars($panel['tab']) ?> <abbr>&#x25bc;</abbr></a></h2>

				<div id="netteBsPnl<?php echo $counter ?>" class="inner">
				<?php echo $panel['panel'] ?>
			</div></div>
			<?php endforeach ?>

			<?php $stack = $ex->getTrace(); $expanded = NULL ?>
			<?php if (strpos($ex->getFile(), $expandPath) === 0) {
				foreach ($stack as $key => $row) {
					if (isset($row['file']) && strpos($row['file'], $expandPath) !== 0) { $expanded = $key; break; }
				}
			} ?>

			<div class="panel">
			<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Source file <abbr><?php echo ($collapsed = $expanded !== NULL) ? '&#x25ba;' : '&#x25bc;' ?></abbr></a></h2>

			<div id="netteBsPnl<?php echo $counter ?>" class="<?php echo $collapsed ? 'nette-collapsed ' : '' ?>inner">
				<p><b>File:</b> <?php echo NDebugHelpers::editorLink($ex->getFile(), $ex->getLine()) ?> &nbsp; <b>Line:</b> <?php echo $ex->getLine() ?></p>
				<?php if (is_file($ex->getFile())): ?><pre><?php echo self::highlightFile($ex->getFile(), $ex->getLine(), 15, isset($ex->context) ? $ex->context : NULL) ?></pre><?php endif ?>
			</div></div>

			<?php if (isset($stack[0]['class']) && $stack[0]['class'] === 'NDebugger' && ($stack[0]['function'] === '_shutdownHandler' || $stack[0]['function'] === '_errorHandler')) unset($stack[0]) ?>
			<?php if ($stack): ?>
			<div class="panel">
				<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Call stack <abbr>&#x25bc;</abbr></a></h2>

				<div id="netteBsPnl<?php echo $counter ?>" class="inner">
				<ol>
					<?php foreach ($stack as $key => $row): ?>
					<li><p>

					<?php if (isset($row['file']) && is_file($row['file'])): ?>
						<?php echo NDebugHelpers::editorLink($row['file'], $row['line']), ':', $row['line'] ?>
					<?php else: ?>
						<i>inner-code</i><?php if (isset($row['line'])) echo ':', $row['line'] ?>
					<?php endif ?>

					<?php if (isset($row['file']) && is_file($row['file'])): ?><a href="#" rel="netteBsSrc<?php echo "$level-$key" ?>">source <abbr>&#x25ba;</abbr></a>&nbsp; <?php endif ?>

					<?php if (isset($row['class'])) echo $row['class'] . $row['type'] ?>
					<?php echo $row['function'] ?>

					(<?php if (!empty($row['args'])): ?><a href="#" rel="netteBsArgs<?php echo "$level-$key" ?>">arguments <abbr>&#x25ba;</abbr></a><?php endif ?>)
					</p>

					<?php if (!empty($row['args'])): ?>
						<div class="nette-collapsed outer" id="netteBsArgs<?php echo "$level-$key" ?>">
						<table>
						<?php
						try {
							$r = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
							$params = $r->getParameters();
						} catch (Exception $e) {
							$params = array();
						}
						foreach ($row['args'] as $k => $v) {
							echo '<tr><th>', (isset($params[$k]) ? '$' . $params[$k]->name : "#$k"), '</th><td>';
							echo NDebugHelpers::clickableDump($v);
							echo "</td></tr>\n";
						}
						?>
						</table>
						</div>
					<?php endif ?>

					<?php if (isset($row['file']) && is_file($row['file'])): ?>
						<pre <?php if ($expanded !== $key) echo 'class="nette-collapsed"'; ?> id="netteBsSrc<?php echo "$level-$key" ?>"><?php echo self::highlightFile($row['file'], $row['line']) ?></pre>
					<?php endif ?>

					</li>
					<?php endforeach ?>
				</ol>
			</div></div>
			<?php endif ?>

			<?php if (isset($ex->context) && is_array($ex->context)):?>
			<div class="panel">
			<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Variables <abbr>&#x25ba;</abbr></a></h2>

			<div id="netteBsPnl<?php echo $counter ?>" class="nette-collapsed inner">
			<div class="outer">
			<table>
			<?php
			foreach ($ex->context as $k => $v) {
					echo '<tr><th>$', htmlspecialchars($k), '</th><td>', NDebugHelpers::clickableDump($v), "</td></tr>\n";
			}
			?>
			</table>
			</div>
			</div></div>
			<?php endif ?>

		<?php } while ((method_exists($ex, 'getPrevious') && $ex = $ex->getPrevious()) || (isset($ex->previous) && $ex = $ex->previous)); ?>
		<?php while (--$level) echo '</div></div>' ?>

		<?php foreach ($panels as $panel): ?>
		<?php $panel = call_user_func($panel, NULL); if (empty($panel['tab']) || empty($panel['panel'])) continue; ?>
		<div class="panel">
			<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>"><?php echo htmlSpecialChars($panel['tab']) ?> <abbr>&#x25ba;</abbr></a></h2>

			<div id="netteBsPnl<?php echo $counter ?>" class="nette-collapsed inner">
			<?php echo $panel['panel'] ?>
		</div></div>
		<?php endforeach ?>

		<div class="panel">
		<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Environment <abbr>&#x25ba;</abbr></a></h2>

		<div id="netteBsPnl<?php echo $counter ?>" class="nette-collapsed inner">
			<?php
			$list = get_defined_constants(TRUE);
			if (!empty($list['user'])):?>
			<h3><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Constants <abbr>&#x25bc;</abbr></a></h3>
			<div id="netteBsPnl<?php echo $counter ?>" class="outer">
			<table>
			<?php
			foreach ($list['user'] as $k => $v) {
				echo '<tr><th>', htmlspecialchars($k), '</th>';
				echo '<td>', NDebugHelpers::clickableDump($v), "</td></tr>\n";
			}
			?>
			</table>
			</div>
			<?php endif ?>

			<h3><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Included files <abbr>&#x25ba;</abbr></a> (<?php echo count(get_included_files()) ?>)</h3>
			<div id="netteBsPnl<?php echo $counter ?>" class="outer nette-collapsed">
			<table>
			<?php
			foreach (get_included_files() as $v) {
				echo '<tr><td>', htmlspecialchars($v), "</td></tr>\n";
			}
			?>
			</table>
			</div>

			<h3><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">Configuration options <abbr>&#x25ba;</abbr></a></h3>
			<div id="netteBsPnl<?php echo $counter ?>" class="outer nette-collapsed">
			<?php ob_start(); @phpinfo(INFO_CONFIGURATION | INFO_MODULES); echo preg_replace('#^.+<body>|</body>.+$#s', '', ob_get_clean()) ?>
			</div>

			<h3><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">$_SERVER <abbr>&#x25bc;</abbr></a></h3>
			<div id="netteBsPnl<?php echo $counter ?>" class="outer">
			<table>
			<?php
			foreach ($_SERVER as $k => $v) echo '<tr><th>', htmlspecialchars($k), '</th><td>', NDebugHelpers::clickableDump($v), "</td></tr>\n";
			?>
			</table>
			</div>
		</div></div>

		<div class="panel">
		<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">HTTP request <abbr>&#x25ba;</abbr></a></h2>

		<div id="netteBsPnl<?php echo $counter ?>" class="nette-collapsed inner">
			<?php if (function_exists('apache_request_headers')): ?>
			<h3>Headers</h3>
			<div class="outer">
			<table>
			<?php
			foreach (apache_request_headers() as $k => $v) echo '<tr><th>', htmlspecialchars($k), '</th><td>', htmlspecialchars($v), "</td></tr>\n";
			?>
			</table>
			</div>
			<?php endif ?>

			<?php foreach (array('_GET', '_POST', '_COOKIE') as $name): ?>
			<h3>$<?php echo $name ?></h3>
			<?php if (empty($GLOBALS[$name])):?>
			<p><i>empty</i></p>
			<?php else: ?>
			<div class="outer">
			<table>
			<?php
			foreach ($GLOBALS[$name] as $k => $v) echo '<tr><th>', htmlspecialchars($k), '</th><td>', NDebugHelpers::clickableDump($v), "</td></tr>\n";
			?>
			</table>
			</div>
			<?php endif ?>
			<?php endforeach ?>
		</div></div>

		<div class="panel">
		<h2><a href="#" rel="netteBsPnl<?php echo ++$counter ?>">HTTP response <abbr>&#x25ba;</abbr></a></h2>

		<div id="netteBsPnl<?php echo $counter ?>" class="nette-collapsed inner">
			<h3>Headers</h3>
			<?php if (headers_list()): ?>
			<pre><?php
			foreach (headers_list() as $s) echo htmlspecialchars($s), '<br>';
			?></pre>
			<?php else: ?>
			<p><i>no headers</i></p>
			<?php endif ?>
		</div></div>

		<ul>
			<li>Report generated at <?php echo @date('Y/m/d H:i:s', NDebugger::$time) ?></li>
			<?php if (preg_match('#^https?://#', NDebugger::$source)): ?>
				<li><a href="<?php echo htmlSpecialChars(NDebugger::$source) ?>"><?php echo htmlSpecialChars(NDebugger::$source) ?></a></li>
			<?php elseif (NDebugger::$source): ?>
				<li><?php echo htmlSpecialChars(NDebugger::$source) ?></li>
			<?php endif ?>
			<li>PHP <?php echo htmlSpecialChars(PHP_VERSION) ?></li>
			<?php if (isset($_SERVER['SERVER_SOFTWARE'])): ?><li><?php echo htmlSpecialChars($_SERVER['SERVER_SOFTWARE']) ?></li><?php endif ?>
			<?php if (class_exists('NFramework')): ?><li><?php echo htmlSpecialChars('Nette Framework ' . NFramework::VERSION) ?> <i>(revision <?php echo htmlSpecialChars(NFramework::REVISION) ?>)</i></li><?php endif ?>
		</ul>
	</div>
</div>

<script type="text/javascript">/*<![CDATA[*/var bs=document.getElementById("netteBluescreen");document.body.appendChild(bs);document.onkeyup=function(b){b=b||window.event;b.keyCode==27&&!b.shiftKey&&!b.altKey&&!b.ctrlKey&&!b.metaKey&&bs.onclick({target:document.getElementById("netteBluescreenIcon")})};
for(var i=0,styles=document.styleSheets;i<styles.length;i++)if((styles[i].owningElement||styles[i].ownerNode).className!=="nette"){styles[i].oldDisabled=styles[i].disabled;styles[i].disabled=true}else styles[i].addRule?styles[i].addRule(".nette-collapsed","display: none"):styles[i].insertRule(".nette-collapsed { display: none }",0);
bs.onclick=function(b){b=b||window.event;for(var a=b.target||b.srcElement;a&&a.tagName&&a.tagName.toLowerCase()!=="a";)a=a.parentNode;if(!a||!a.rel)return true;for(var d=a.getElementsByTagName("abbr")[0],c=a.rel==="next"?a.nextSibling:document.getElementById(a.rel);c.nodeType!==1;)c=c.nextSibling;b=c.currentStyle?c.currentStyle.display=="none":getComputedStyle(c,null).display=="none";try{d.innerHTML=String.fromCharCode(b?9660:9658)}catch(e){}c.style.display=b?c.tagName.toLowerCase()==="code"?"inline":
"block":"none";if(a.id==="netteBluescreenIcon"){a=0;for(d=document.styleSheets;a<d.length;a++)if((d[a].owningElement||d[a].ownerNode).className!=="nette")d[a].disabled=b?true:d[a].oldDisabled}return false};/*]]>*/</script>
</body>
</html>
<?php
	}

	static function highlightFile($file, $line, $count = 15, $vars = array())
	{
		if (function_exists('ini_set')) {
			ini_set('highlight.comment', '#998; font-style: italic');
			ini_set('highlight.default', '#000');
			ini_set('highlight.html', '#06B');
			ini_set('highlight.keyword', '#D24; font-weight: bold');
			ini_set('highlight.string', '#080');
		}

		$start = max(1, $line - floor($count / 2));

		$source = @file_get_contents($file);
		if (!$source) {
			return;
		}
		$source = explode("\n", highlight_string($source, TRUE));
		$spans = 1;
		$out = $source[0];
		$source = explode('<br />', $source[1]);
		array_unshift($source, NULL);

		$i = $start;
		while (--$i >= 1) {
			if (preg_match('#.*(</?span[^>]*>)#', $source[$i], $m)) {
				if ($m[1] !== '</span>') {
					$spans++; $out .= $m[1];
				}
				break;
			}
		}

		$source = array_slice($source, $start, $count, TRUE);
		end($source);
		$numWidth = strlen((string) key($source));

		foreach ($source as $n => $s) {
			$spans += substr_count($s, '<span') - substr_count($s, '</span');
			$s = str_replace(array("\r", "\n"), array('', ''), $s);
			preg_match_all('#<[^>]+>#', $s, $tags);
			if ($n === $line) {
				$out .= sprintf(
					"<span class='highlight'>%{$numWidth}s:    %s\n</span>%s",
					$n,
					strip_tags($s),
					implode('', $tags[0])
				);
			} else {
				$out .= sprintf("<span class='line'>%{$numWidth}s:</span>    %s\n", $n, $s);
			}
		}
		$out .= str_repeat('</span>', $spans) . '</code>';

		$out = preg_replace_callback('#">\$(\w+)(&nbsp;)?</span>#', create_function('$m', 'extract(NCFix::$vars['.NCFix::uses(array('vars'=>$vars)).'], EXTR_REFS);
			return isset($vars[$m[1]])
				? \'" title="\' . str_replace(\'"\', \'&quot;\', strip_tags(NDebugHelpers::htmlDump($vars[$m[1]]))) . $m[0]
				: $m[0];
		'), $out);

		return $out;
	}

}

class NDebugBar extends NObject
{

	private $panels = array();

	function addPanel(IBarPanel $panel, $id = NULL)
	{
		if ($id === NULL) {
			$c = 0;
			do {
				$id = get_class($panel) . ($c++ ? "-$c" : '');
			} while (isset($this->panels[$id]));
		}
		$this->panels[$id] = $panel;
	}

	function render()
	{
		$panels = array();
		foreach ($this->panels as $id => $panel) {
			try {
				$panels[] = array(
					'id' => preg_replace('#[^a-z0-9]+#i', '-', $id),
					'tab' => $tab = (string) $panel->getTab(),
					'panel' => $tab ? (string) $panel->getPanel() : NULL,
				);
			} catch (Exception $e) {
				$panels[] = array(
					'id' => "error-$id",
					'tab' => "Error: $id",
					'panel' => nl2br(htmlSpecialChars((string) $e)),
				);
			}
		}

?>

<!-- Nette Debug Bar -->

<?php ob_start() ?>
&nbsp;

<style id="nette-debug-style" class="nette">#nette-debug{display:none;position:fixed}body#nette-debug{margin:5px 5px 0;display:block}#nette-debug *{font:inherit;color:inherit;background:transparent;margin:0;padding:0;border:none;text-align:inherit;list-style:inherit}#nette-debug .nette-fixed-coords{position:fixed;_position:absolute;right:0;bottom:0;max-width:100%}#nette-debug a{color:#125EAE;text-decoration:none}#nette-debug .nette-panel a{color:#125EAE;text-decoration:none}#nette-debug a:hover,#nette-debug a:active,#nette-debug a:focus{background-color:#125EAE;color:white}#nette-debug .nette-panel h2,#nette-debug .nette-panel h3,#nette-debug .nette-panel p{margin:.4em 0}#nette-debug .nette-panel table{border-collapse:collapse;background:#FDF5CE}#nette-debug .nette-panel tr:nth-child(2n) td{background:#F7F0CB}#nette-debug .nette-panel td,#nette-debug .nette-panel th{border:1px solid #E6DFBF;padding:2px 5px;vertical-align:top;text-align:left}#nette-debug .nette-panel th{background:#F4F3F1;color:#655E5E;font-size:90%;font-weight:bold}#nette-debug .nette-panel pre,#nette-debug .nette-panel code{font:9pt/1.5 Consolas,monospace}#nette-debug table .nette-right{text-align:right}.nette-hidden,.nette-collapsed{display:none}#nette-debug-bar{font:normal normal 12px/21px Tahoma,sans-serif;color:#333;border:1px solid #c9c9c9;background:#EDEAE0 url('data:image/png;base64,R0lGODlhAQAVALMAAOTh1/Px6eHe1fHv5e/s4vLw6Ofk2u3q4PPw6PPx6PDt5PLw5+Dd1OXi2Ojm3Orn3iH5BAAAAAAALAAAAAABABUAAAQPMISEyhpYkfOcaQAgCEwEADs=') top;position:relative;overflow:auto;min-height:21px;_float:left;min-width:50px;white-space:nowrap;z-index:23181;opacity:.9;border-radius:3px;-moz-border-radius:3px;box-shadow:1px 1px 10px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 10px rgba(0,0,0,.15);-webkit-box-shadow:1px 1px 10px rgba(0,0,0,.15)}#nette-debug-bar:hover{opacity:1}#nette-debug-bar ul{list-style:none none;margin-left:4px}#nette-debug-bar li{float:left}#nette-debug-bar img{vertical-align:middle;position:relative;top:-1px;margin-right:3px}#nette-debug-bar li a{color:#000;display:block;padding:0 4px}#nette-debug-bar li a:hover{color:black;background:#c3c1b8}#nette-debug-bar li .nette-warning{color:#D32B2B;font-weight:bold}#nette-debug-bar li>span{padding:0 4px}#nette-debug-logo{background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC0AAAAPCAYAAABwfkanAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABiFJREFUSMe1VglPlGcQ5i+1xjZNqxREtGq8ahCPWsVGvEDBA1BBRQFBDjkE5BYUzwpovRBUREBEBbl3OVaWPfj2vi82eTrvbFHamLRJ4yYTvm+u95mZZ96PoKAv+LOatXBYZ+Bx6uFy6DGnt1m0EOKwSmQzwmHTgX5B/1W+yM9GYJ02CX6/B/5ZF+w2A4x6FYGTYDVp4PdY2Tbrs5N+mnRa2Km4/wV6rhPzQQj5fDc1mJM5nd0iYdZtQWtrCxobGnDpUiledTynbuvg99mgUMhw924Trl2rR01NNSTNJE9iDpTV8innv4K2kZPLroPXbYLHZeSu2K1aeF0muJ2GvwGzmNSwU2E+svm8ZrgdBliMaha/34Vx+RAKCgpwpa4OdbW1UE/L2cc/68WtWzdRVlaG6uoqtD1/BA/pA1MIxLvtes7pc5vhoDOE/rOgbVSdf9aJWa8dBp0Kyg+jdLiTx2vQKWEyqGmcNkqg4iTC1+dzQatWkK+cJqPD7KyFaKEjvRuNjY24fLkGdXW1ePjwAeX4QHonDNI0A75+/RpqqqshH+6F2UAUMaupYXouykV0mp6SQ60coxgL8Z4aMg/4x675/V60v3jKB+Xl5WJibIC4KPEIS0qKqWv5GOh7BZ/HSIk9kA33o7y8DOfPZ6GQOipkXDZAHXKxr4ipqqpkKS6+iIrycgz2dyMnJxtVlZUsotNZWZmor79KBbvgpdjm5sfIzc1hv4L8fKJPDTfJZZc+gRYKr8sAEy2DcBRdEEk62ltx9uwZ5qNILoDU1l6mbrvx5EkzUlKSuTiR7PHjR3x4fv4FyIbeIic7G5WVFUyN+qtX+Lnt2SPcvn2LfURjhF7kE4WPDr+Bx+NEUVEhkpNPoImm5CSOl5aUIC3tLOMR59gtAY4HidGIzj14cB8ZGRkM8kJeHk6cOI4xWR8vSl5uLlJTT6O74xnT5lB8PM6cSYXVqILb5UBWZiYSExMYkE4zzjqX00QHG+h9AjPqMei0k3ywy2khMdNiq6BVCf04T6ekuBgJCUdRUVHOBQwPvkNSUiLjaGi4Q/5qFgYtHgTXRJdTT59GenoaA5gY64deq0Bc3EGuNj4+DnppEheLijhZRkY6SktLsGPHdi6irOwSFTRAgO04deokTSIFsbExuHfvLnFSx8DevelAfFwcA0lJTqZi5PDS9aci/sbE7Oe4wsICbtD27b/ye1NTI3FeSX4W2gdFALRD3A4eM44ePcKViuD79/8gnZP5Kg4+cCAW2dnnqUM2Lujw4UM4ePAA2ztfPsHIYA/sdOt43A50d7UFCjkUj+joXVBMDJDeDhcVk08cjd61C3v37uFYp8PKXX3X8xJRUTtw7FgSn3Xzxg10d7ZCqRjkM+02C7pettDNogqAFjzxuI3YHR2Nffv2coXy0V44HGZERm7kJNu2/cK8bW9rwbp1axnMnj27uUijQQOb1QyTcYZ3YMOGn/Hbzp1crAAvaDfY38O5hW3//n0ce+TIYWiUcub1xo0R2Lp1y8cYsUMWM125VhPe93Zj7do1vEPi26GfUdBFbhK8tGHrli1YsWwpgoOD0dXRQqAtXMCy8DBs3rwJoSGLsWrVclylBdoUGYlVK1dg9eqVCFsSSs8/4btvvmUwEnE0KTERISE/IiIiAsGLF2HhwgU8qbc97QgPX8qFr1mzGgu+/opzdL5o5l1aEhqC9evXYWlYKFYsD6e/YVj0w/dMGZVyBDMqeaDTRuKpkxYjIz2dOyeup6H3r2kkOuJ1H3N5Z1QUzp3LQF9vJ4xGLQYHXiM9LY0pEhsTg+PHj9HNcJu4OcL3uaQZY86LiZw8mcJTkmhBTUYJbU8fcoygobgWR4Z6iKtTPLE7d35HYkICT1dIZuY59HQ9412StBPQTMvw8Z6WaMNFxy3Gab4TeQT0M9IHwUT/G0i0MGIJ9CTiJjBIH+iQaQbC7+QnfEXiQL6xgF09TjETHCt8RbeMuil+D8RNsV1LHdQoZfR/iJJzCZuYmEE/Bd3MJNs/+0UURgFWJJ//aQ8k+CsxVTqnVytHObkQrUoG8T4/bs4u4ubbxLPwFzYNPc8HI2zijLm84l39Dx8hfwJenFezFBKKQwAAAABJRU5ErkJggg==') 0 50% no-repeat;min-width:45px;cursor:move}#nette-debug-logo span{display:none}#nette-debug-bar-bgl,#nette-debug-bar-bgx,#nette-debug-bar-bgr{position:absolute;z-index:-1;top:-7px;height:37px}#nette-debug .nette-panel{font:normal normal 12px/1.5 sans-serif;background:white;color:#333}#nette-debug h1{font:normal normal 23px/1.4 Tahoma,sans-serif;color:#575753;background:#EDEAE0;margin:-5px -5px 5px;padding:0 25px 5px 5px}#nette-debug .nette-mode-peek .nette-inner,#nette-debug .nette-mode-float .nette-inner{max-width:700px;max-height:500px;overflow:auto}#nette-debug .nette-panel .nette-icons{display:none}#nette-debug .nette-mode-peek{display:none;position:relative;z-index:23180;padding:5px;min-width:150px;min-height:50px;border:5px solid #EDEAE0;border-radius:5px;-moz-border-radius:5px}#nette-debug .nette-mode-peek h1{cursor:move}#nette-debug .nette-mode-float{position:relative;z-index:23179;padding:5px;min-width:150px;min-height:50px;border:5px solid #EDEAE0;border-radius:5px;-moz-border-radius:5px;opacity:.9;box-shadow:1px 1px 6px #666;-moz-box-shadow:1px 1px 6px rgba(0,0,0,.45);-webkit-box-shadow:1px 1px 6px #666}#nette-debug .nette-focused{z-index:23180;opacity:1}#nette-debug .nette-mode-float h1{cursor:move}#nette-debug .nette-mode-float .nette-icons{display:block;position:absolute;top:0;right:0;font-size:18px}#nette-debug .nette-icons a{color:#575753}#nette-debug .nette-icons a:hover{color:white}pre.nette-dump{color:#444;background:white}pre.nette-dump .php-array,pre.nette-dump .php-object{color:#C22}pre.nette-dump .php-string{color:#080}pre.nette-dump .php-int,pre.nette-dump .php-float{color:#37D}pre.nette-dump .php-null,pre.nette-dump .php-bool{color:black}pre.nette-dump .php-visibility{font-size:85%;color:#999}</style>

<!--[if lt IE 8]><style class="nette">#nette-debug-bar img{display:none}#nette-debug-bar li{border-left:1px solid #DCD7C8;padding:0 3px}#nette-debug-logo span{background:#edeae0;display:inline}</style><![endif]-->

<script id="nette-debug-script">/*<![CDATA[*/var Nette=Nette||{};
(function(){Nette.Class=function(a){var b=a.constructor||function(){},c,d=Object.prototype.hasOwnProperty;delete a.constructor;if(a.Extends){var f=function(){this.constructor=b};f.prototype=a.Extends.prototype;b.prototype=new f;delete a.Extends}if(a.Static){for(c in a.Static)if(d.call(a.Static,c))b[c]=a.Static[c];delete a.Static}for(c in a)if(d.call(a,c))b.prototype[c]=a[c];return b};Nette.Q=Nette.Class({Static:{factory:function(a){return new Nette.Q(a)},implement:function(a){var b,c=Nette.Q.implement,
d=Nette.Q.prototype,f=Object.prototype.hasOwnProperty;for(b in a)if(f.call(a,b)){c[b]=a[b];d[b]=function(i){return function(){return this.each(c[i],arguments)}}(b)}}},constructor:function(a){if(typeof a==="string")a=this._find(document,a);else if(!a||a.nodeType||a.length===undefined||a===window)a=[a];for(var b=0,c=a.length;b<c;b++)if(a[b])this[this.length++]=a[b]},length:0,find:function(a){return new Nette.Q(this._find(this[0],a))},_find:function(a,b){if(!a||!b)return[];else if(document.querySelectorAll)return a.querySelectorAll(b);
else if(b.charAt(0)==="#")return[document.getElementById(b.substring(1))];else{b=b.split(".");var c=a.getElementsByTagName(b[0]||"*");if(b[1]){for(var d=[],f=RegExp("(^|\\s)"+b[1]+"(\\s|$)"),i=0,k=c.length;i<k;i++)f.test(c[i].className)&&d.push(c[i]);return d}else return c}},dom:function(){return this[0]},each:function(a,b){for(var c=0,d;c<this.length;c++)if((d=a.apply(this[c],b||[]))!==undefined)return d;return this}});var h=Nette.Q.factory,e=Nette.Q.implement;e({bind:function(a,b){if(document.addEventListener&&
(a==="mouseenter"||a==="mouseleave")){var c=b;a=a==="mouseenter"?"mouseover":"mouseout";b=function(g){for(var j=g.relatedTarget;j;j=j.parentNode)if(j===this)return;c.call(this,g)}}var d=e.data.call(this);d=d.events=d.events||{};if(!d[a]){var f=this,i=d[a]=[],k=e.bind.genericHandler=function(g){if(!g.target)g.target=g.srcElement;if(!g.preventDefault)g.preventDefault=function(){g.returnValue=false};if(!g.stopPropagation)g.stopPropagation=function(){g.cancelBubble=true};g.stopImmediatePropagation=function(){this.stopPropagation();
j=i.length};for(var j=0;j<i.length;j++)i[j].call(f,g)};if(document.addEventListener)this.addEventListener(a,k,false);else document.attachEvent&&this.attachEvent("on"+a,k)}d[a].push(b)},addClass:function(a){this.className=this.className.replace(/^|\s+|$/g," ").replace(" "+a+" "," ")+" "+a},removeClass:function(a){this.className=this.className.replace(/^|\s+|$/g," ").replace(" "+a+" "," ")},hasClass:function(a){return this.className.replace(/^|\s+|$/g," ").indexOf(" "+a+" ")>-1},show:function(){var a=
e.show.display=e.show.display||{},b=this.tagName;if(!a[b]){var c=document.body.appendChild(document.createElement(b));a[b]=e.css.call(c,"display")}this.style.display=a[b]},hide:function(){this.style.display="none"},css:function(a){return this.currentStyle?this.currentStyle[a]:window.getComputedStyle?document.defaultView.getComputedStyle(this,null).getPropertyValue(a):undefined},data:function(){return this.nette?this.nette:this.nette={}},val:function(){var a;if(!this.nodeName){a=0;for(len=this.length;a<
len;a++)if(this[a].checked)return this[a].value;return null}if(this.nodeName.toLowerCase()==="select"){a=this.selectedIndex;var b=this.options;if(a<0)return null;else if(this.type==="select-one")return b[a].value;a=0;values=[];for(len=b.length;a<len;a++)b[a].selected&&values.push(b[a].value);return values}if(this.type==="checkbox")return this.checked;return this.value.replace(/^\s+|\s+$/g,"")},_trav:function(a,b,c){for(b=b.split(".");a&&!(a.nodeType===1&&(!b[0]||a.tagName.toLowerCase()===b[0])&&(!b[1]||
e.hasClass.call(a,b[1])));)a=a[c];return h(a)},closest:function(a){return e._trav(this,a,"parentNode")},prev:function(a){return e._trav(this.previousSibling,a,"previousSibling")},next:function(a){return e._trav(this.nextSibling,a,"nextSibling")},offset:function(a){for(var b=this,c=a?{left:-a.left||0,top:-a.top||0}:e.position.call(b);b=b.offsetParent;){c.left+=b.offsetLeft;c.top+=b.offsetTop}if(a)e.position.call(this,{left:-c.left,top:-c.top});else return c},position:function(a){if(a){this.nette&&
this.nette.onmove&&this.nette.onmove.call(this,a);this.style.left=(a.left||0)+"px";this.style.top=(a.top||0)+"px"}else return{left:this.offsetLeft,top:this.offsetTop,width:this.offsetWidth,height:this.offsetHeight}},draggable:function(a){var b=h(this),c=document.documentElement,d;a=a||{};h(a.handle||this).bind("mousedown",function(f){f.preventDefault();f.stopPropagation();if(e.draggable.binded)return c.onmouseup(f);var i=b[0].offsetLeft-f.clientX,k=b[0].offsetTop-f.clientY;e.draggable.binded=true;
d=false;c.onmousemove=function(g){g=g||event;if(!d){a.draggedClass&&b.addClass(a.draggedClass);a.start&&a.start(g,b);d=true}b.position({left:g.clientX+i,top:g.clientY+k});return false};c.onmouseup=function(g){if(d){a.draggedClass&&b.removeClass(a.draggedClass);if(a.stop)a.stop(g||event,b)}e.draggable.binded=c.onmousemove=c.onmouseup=null;return false}}).bind("click",function(f){if(d){f.stopImmediatePropagation();preventClick=false}})}})})();
(function(){Nette.Debug={};var h=Nette.Q.factory,e=Nette.Debug.Panel=Nette.Class({Extends:Nette.Q,Static:{PEEK:"nette-mode-peek",FLOAT:"nette-mode-float",WINDOW:"nette-mode-window",FOCUSED:"nette-focused",factory:function(a){return new e(a)},_toggle:function(a){var b=a.rel;b=b.charAt(0)==="#"?h(b):h(a)[b==="prev"?"prev":"next"](b.substring(4));if(b.css("display")==="none"){b.show();a.innerHTML=a.innerHTML.replace("►","▼")}else{b.hide();a.innerHTML=a.innerHTML.replace("▼","►")}}},constructor:function(a){Nette.Q.call(this,
"#nette-debug-panel-"+a.replace("nette-debug-panel-",""))},reposition:function(){if(this.hasClass(e.WINDOW))window.resizeBy(document.documentElement.scrollWidth-document.documentElement.clientWidth,document.documentElement.scrollHeight-document.documentElement.clientHeight);else{this.position(this.position());if(this.position().width)document.cookie=this.dom().id+"="+this.position().left+":"+this.position().top+"; path=/"}},focus:function(){if(this.hasClass(e.WINDOW))this.data().win.focus();else{clearTimeout(this.data().blurTimeout);
this.addClass(e.FOCUSED).show()}},blur:function(){this.removeClass(e.FOCUSED);if(this.hasClass(e.PEEK)){var a=this;this.data().blurTimeout=setTimeout(function(){a.hide()},50)}},toFloat:function(){this.removeClass(e.WINDOW).removeClass(e.PEEK).addClass(e.FLOAT).show().reposition();return this},toPeek:function(){this.removeClass(e.WINDOW).removeClass(e.FLOAT).addClass(e.PEEK).hide();document.cookie=this.dom().id+"=; path=/"},toWindow:function(){var a=this,b,c;c=this.offset();var d=this.dom().id;c.left+=
typeof window.screenLeft==="number"?window.screenLeft:window.screenX+10;c.top+=typeof window.screenTop==="number"?window.screenTop:window.screenY+50;if(b=window.open("",d.replace(/-/g,"_"),"left="+c.left+",top="+c.top+",width="+c.width+",height="+(c.height+15)+",resizable=yes,scrollbars=yes")){c=b.document;c.write('<!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><style>'+h("#nette-debug-style").dom().innerHTML+"</style><script>"+h("#nette-debug-script").dom().innerHTML+
'<\/script><body id="nette-debug">');c.body.innerHTML='<div class="nette-panel nette-mode-window" id="'+d+'">'+this.dom().innerHTML+"</div>";b.Nette.Debug.Panel.factory(d).initToggler().reposition();c.title=a.find("h1").dom().innerHTML;h([b]).bind("unload",function(){a.toPeek();b.close()});h(c).bind("keyup",function(f){f.keyCode===27&&!f.shiftKey&&!f.altKey&&!f.ctrlKey&&!f.metaKey&&b.close()});document.cookie=d+"=window; path=/";this.hide().removeClass(e.FLOAT).removeClass(e.PEEK).addClass(e.WINDOW).data().win=
b}},init:function(){var a=this,b;a.data().onmove=function(c){var d=document,f=window.innerHeight||d.documentElement.clientHeight||d.body.clientHeight;c.left=Math.max(Math.min(c.left,0.8*this.offsetWidth),0.2*this.offsetWidth-(window.innerWidth||d.documentElement.clientWidth||d.body.clientWidth));c.top=Math.max(Math.min(c.top,0.8*this.offsetHeight),this.offsetHeight-f)};h(window).bind("resize",function(){a.reposition()});a.draggable({handle:a.find("h1"),stop:function(){a.toFloat()}}).bind("mouseenter",
function(){a.focus()}).bind("mouseleave",function(){a.blur()});this.initToggler();a.find(".nette-icons").find("a").bind("click",function(c){this.rel==="close"?a.toPeek():a.toWindow();c.preventDefault()});if(b=document.cookie.match(RegExp(a.dom().id+"=(window|(-?[0-9]+):(-?[0-9]+))")))b[2]?a.toFloat().position({left:b[2],top:b[3]}):a.toWindow();else a.addClass(e.PEEK)},initToggler:function(){var a=this;this.bind("click",function(b){var c=h(b.target).closest("a").dom();if(c&&c.rel){e._toggle(c);b.preventDefault();
a.reposition()}});return this}});Nette.Debug.Bar=Nette.Class({Extends:Nette.Q,constructor:function(){Nette.Q.call(this,"#nette-debug-bar")},init:function(){var a=this,b;a.data().onmove=function(c){var d=document,f=window.innerHeight||d.documentElement.clientHeight||d.body.clientHeight;c.left=Math.max(Math.min(c.left,0),this.offsetWidth-(window.innerWidth||d.documentElement.clientWidth||d.body.clientWidth));c.top=Math.max(Math.min(c.top,0),this.offsetHeight-f)};h(window).bind("resize",function(){a.position(a.position())});
a.draggable({draggedClass:"nette-dragged",stop:function(){document.cookie=a.dom().id+"="+a.position().left+":"+a.position().top+"; path=/"}});a.find("a").bind("click",function(c){if(this.rel==="close"){h("#nette-debug").hide();window.opera&&h("body").show()}else if(this.rel){var d=e.factory(this.rel);if(c.shiftKey)d.toFloat().toWindow();else if(d.hasClass(e.FLOAT)){var f=h(this).offset();d.offset({left:f.left-d.position().width+f.width+4,top:f.top-d.position().height-4}).toPeek()}else d.toFloat().position({left:d.position().left-
Math.round(Math.random()*100)-20,top:d.position().top-Math.round(Math.random()*100)-20}).reposition()}c.preventDefault()}).bind("mouseenter",function(){if(!(!this.rel||this.rel==="close"||a.hasClass("nette-dragged"))){var c=e.factory(this.rel);c.focus();if(c.hasClass(e.PEEK)){var d=h(this).offset();c.offset({left:d.left-c.position().width+d.width+4,top:d.top-c.position().height-4})}}}).bind("mouseleave",function(){!this.rel||this.rel==="close"||a.hasClass("nette-dragged")||e.factory(this.rel).blur()});
if(b=document.cookie.match(RegExp(a.dom().id+"=(-?[0-9]+):(-?[0-9]+)")))a.position({left:b[1],top:b[2]});a.find("a").each(function(){!this.rel||this.rel==="close"||e.factory(this.rel).init()})}})})();/*]]>*/</script>

<?php foreach ($panels as $id => $panel): if (!$panel['panel']) continue; ?>
<div class="nette-fixed-coords">
	<div class="nette-panel" id="nette-debug-panel-<?php echo $panel['id'] ?>">
		<?php echo $panel['panel'] ?>
		<div class="nette-icons">
			<a href="#" title="open in window">&curren;</a>
			<a href="#" rel="close" title="close window">&times;</a>
		</div>
	</div>
</div>
<?php endforeach ?>

<div class="nette-fixed-coords">
	<div id="nette-debug-bar">
		<ul>
			<li id="nette-debug-logo" title="PHP <?php echo htmlSpecialChars(PHP_VERSION . " |\n"
			. (isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] . " |\n" : '')
			. (class_exists('NFramework') ? 'Nette Framework ' . NFramework::VERSION . ' (' . substr(NFramework::REVISION, 8) . ')' : '')) ?>">&nbsp;<span>Nette Framework</span></li>
			<?php foreach ($panels as $panel): if (!$panel['tab']) continue; ?>
			<li><?php if ($panel['panel']): ?><a href="#" rel="<?php echo $panel['id'] ?>"><?php echo trim($panel['tab']) ?></a><?php else: echo '<span>', trim($panel['tab']), '</span>'; endif ?></li>
			<?php endforeach ?>
			<li><a href="#" rel="close" title="close debug bar">&times;</a></li>
		</ul>
	</div>
</div>
<?php $output = ob_get_clean(); ?>

<div id="nette-debug"></div>

<script>
(function (onloadOrig) {
	window.onload = function() {
		if (typeof onloadOrig === 'function') onloadOrig();
		var debug = document.getElementById('nette-debug');
		document.body.appendChild(debug);
		debug.innerHTML = <?php echo json_encode(NStrings::fixEncoding($output)) ?>;
		for (var i = 0, scripts = debug.getElementsByTagName('script'); i < scripts.length; i++) eval(scripts[i].innerHTML);
		(new Nette.Debug.Bar).init();
		Nette.Q.factory(debug).show();
	};
})(window.onload);
</script>

<!-- /Nette Debug Bar -->
<?php
	}

}

final class NDefaultBarPanel extends NObject implements IBarPanel
{
	private $id;

	public $data;

	function __construct($id)
	{
		$this->id = $id;
	}

	function getTab()
	{
		ob_start();
		$data = $this->data;
		if ($this->id === 'time') {

?>
<span title="Execution time"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ6SURBVDjLjZO7T1NhGMY7Mji6uJgYt8bElTjof6CDg4sMSqIxJsRGB5F4TwQSIg1QKC0KWmkZEEsKtEcSxF5ohV5pKSicXqX3aqGn957z+PUEGopiGJ583/A+v3znvPkJAAjWR0VNJG0kGhKahCFhXcN3YBFfx8Kry6ym4xIzce88/fbWGY2k5WRb77UTTbWuYA9gDGg7EVmSIOF4g5T7HZKuMcSW5djWDyL0uRf0dCc8inYYxTcw9fAiCMBYB3gVj1z7gLhNTjKCqHkYP79KENC9Bq3uxrrqORzy+9D3tPAAccspVx1gWg0KbaZFbGllWFM+xrKkFQudV0CeDfJsjN4+C2nracjunoPq5VXIBrowMK4V1gG1LGyWdbZwCalsBYUyh2KFQzpXxVqkAGswD3+qBDpZwow9iYE5v26/VwfUQnnznyhvjguQYabIIpKpYD1ahI8UTT92MUSFuP5Z/9TBTgOgFrVjp3nakaG/0VmEfpX58pwzjUEquNk362s+PP8XYD/KpYTBHmRg9Wch0QX1R80dCZhYipudYQY2Auib8RmODVCa4hfUK4ngaiiLNFNFdKeCWWscXZMbWy9Unv9/gsIQU09a4pwvUeA3Uapy2C2wCKXL0DqTePLexbWPOv79E8f0UWrencZ2poxciUWZlKssB4bcHeE83NsFuMgpo2iIpMuNa1TNu4XjhggWvb+R2K3wZdLlAZl8Fd9jRb5sD+Xx0RJBx5gdom6VsMEFDyWF0WyCeSOFcDKPnRxZYTQL5Rc/nn1w4oFsBaIhC3r6FRh5erPRhYMyHdeFw4C6zkRhmijM7CnMu0AUZonCDCnRJBqSus5/ABD6Ba5CkQS8AAAAAElFTkSuQmCC"
/><?php echo number_format((microtime(TRUE) - NDebugger::$time) * 1000, 1, '.', ' ') ?> ms</span>
<?php
		} elseif ($this->id === 'memory') {

?>
<span title="The peak of allocated memory"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGvSURBVDjLpZO7alZREEbXiSdqJJDKYJNCkPBXYq12prHwBezSCpaidnY+graCYO0DpLRTQcR3EFLl8p+9525xgkRIJJApB2bN+gZmqCouU+NZzVef9isyUYeIRD0RTz482xouBBBNHi5u4JlkgUfx+evhxQ2aJRrJ/oFjUWysXeG45cUBy+aoJ90Sj0LGFY6anw2o1y/mK2ZS5pQ50+2XiBbdCvPk+mpw2OM/Bo92IJMhgiGCox+JeNEksIC11eLwvAhlzuAO37+BG9y9x3FTuiWTzhH61QFvdg5AdAZIB3Mw50AKsaRJYlGsX0tymTzf2y1TR9WwbogYY3ZhxR26gBmocrxMuhZNE435FtmSx1tP8QgiHEvj45d3jNlONouAKrjjzWaDv4CkmmNu/Pz9CzVh++Yd2rIz5tTnwdZmAzNymXT9F5AtMFeaTogJYkJfdsaaGpyO4E62pJ0yUCtKQFxo0hAT1JU2CWNOJ5vvP4AIcKeao17c2ljFE8SKEkVdWWxu42GYK9KE4c3O20pzSpyyoCx4v/6ECkCTCqccKorNxR5uSXgQnmQkw2Xf+Q+0iqQ9Ap64TwAAAABJRU5ErkJggg=="
/><?php echo function_exists('memory_get_peak_usage') ? number_format(memory_get_peak_usage() / 1000000, 2, '.', ' ') : 'n/a'; ?> MB</span>
<?php
		} elseif ($this->id === 'dumps' && $this->data) {

?>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIASURBVDjLpVPPaxNREJ6Vt01caH4oWk1T0ZKlGIo9RG+BUsEK4kEP/Q8qPXnpqRdPBf8A8Wahhx7FQ0GF9FJ6UksqwfTSBDGyB5HkkphC9tfb7jfbtyQQTx142byZ75v5ZnZWC4KALmICPy+2DkvKIX2f/POz83LxCL7nrz+WPNcll49DrhM9v7xdO9JW330DuXrrqkFSgig5iR2Cfv3t3gNxOnv5BwU+eZ5HuON5/PMPJZKJ+yKQfpW0S7TxdC6WJaWkyvff1LDaFRAeLZj05MHsiPTS6hua0PUqtwC5sHq9zv9RYWl+nu5cETcnJ1M0M5WlWq3GsX6/T+VymRzHDluZiGYAAsw0TQahV8uyyGq1qFgskm0bHIO/1+sx1rFtchJhArwEyIQ1Gg2WD2A6nWawHQJVDIWgIJfLhQowTIeE9D0mKAU8qPC0220afsWFQoH93W6X7yCDJ+DEBeBmsxnPIJVKxWQVUwry+XyUwBlKMKwA8jqdDhOVCqVAzQDVvXAXhOdGBFgymYwrGoZBmUyGjxCCdF0fSahaFdgoTHRxfTveMCXvWfkuE3Y+f40qhgT/nMitupzApdvT18bu+YeDQwY9Xl4aG9/d/URiMBhQq/dvZMeVghtT17lSZW9/rAKsvPa/r9Fc2dw+Pe0/xI6kM9mT5vtXy+Nw2kU/5zOGRpvuMIu0YAAAAABJRU5ErkJggg==" />variables
<?php
		} elseif ($this->id === 'errors' && $this->data) {

?>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIsSURBVDjLpVNLSJQBEP7+h6uu62vLVAJDW1KQTMrINQ1vPQzq1GOpa9EppGOHLh0kCEKL7JBEhVCHihAsESyJiE4FWShGRmauu7KYiv6Pma+DGoFrBQ7MzGFmPr5vmDFIYj1mr1WYfrHPovA9VVOqbC7e/1rS9ZlrAVDYHig5WB0oPtBI0TNrUiC5yhP9jeF4X8NPcWfopoY48XT39PjjXeF0vWkZqOjd7LJYrmGasHPCCJbHwhS9/F8M4s8baid764Xi0Ilfp5voorpJfn2wwx/r3l77TwZUvR+qajXVn8PnvocYfXYH6k2ioOaCpaIdf11ivDcayyiMVudsOYqFb60gARJYHG9DbqQFmSVNjaO3K2NpAeK90ZCqtgcrjkP9aUCXp0moetDFEeRXnYCKXhm+uTW0CkBFu4JlxzZkFlbASz4CQGQVBFeEwZm8geyiMuRVntzsL3oXV+YMkvjRsydC1U+lhwZsWXgHb+oWVAEzIwvzyVlk5igsi7DymmHlHsFQR50rjl+981Jy1Fw6Gu0ObTtnU+cgs28AKgDiy+Awpj5OACBAhZ/qh2HOo6i+NeA73jUAML4/qWux8mt6NjW1w599CS9xb0mSEqQBEDAtwqALUmBaG5FV3oYPnTHMjAwetlWksyByaukxQg2wQ9FlccaK/OXA3/uAEUDp3rNIDQ1ctSk6kHh1/jRFoaL4M4snEMeD73gQx4M4PsT1IZ5AfYH68tZY7zv/ApRMY9mnuVMvAAAAAElFTkSuQmCC"
/><span class="nette-warning"><?php echo array_sum($data) ?> errors</span>
<?php
		}
		return ob_get_clean();
	}

	function getPanel()
	{
		ob_start();
		$data = $this->data;
		if ($this->id === 'dumps') {

?>
<style>#nette-debug .nette-DumpPanel h2{font:11pt/1.5 sans-serif;margin:0;padding:2px 8px;background:#3484d2;color:white}#nette-debug .nette-DumpPanel table{width:100%}#nette-debug .nette-DumpPanel a{color:#333;background:transparent}#nette-debug .nette-DumpPanel a abbr{font-family:sans-serif;color:#999}#nette-debug .nette-DumpPanel pre .php-array,#nette-debug .nette-DumpPanel pre .php-object{color:#c16549}</style>

<h1>Dumped variables</h1>

<div class="nette-inner nette-DumpPanel">
<?php foreach ($data as $item): ?>
	<?php if ($item['title']):?>
	<h2><?php echo htmlspecialchars($item['title']) ?></h2>
	<?php endif ?>

	<table>
	<?php $i = 0 ?>
	<?php foreach ($item['dump'] as $key => $dump): ?>
	<tr class="<?php echo $i++ % 2 ? 'nette-alt' : '' ?>">
		<th><?php echo htmlspecialchars($key) ?></th>
		<td><?php echo $dump ?></td>
	</tr>
	<?php endforeach ?>
	</table>
<?php endforeach ?>
</div>
<?php
		} elseif ($this->id === 'errors') {

?>
<h1>Errors</h1>

<div class="nette-inner">
<table>
<?php $i = 0 ?>
<?php foreach ($data as $item => $count): list($message, $file, $line) = explode('|', $item) ?>
<tr class="<?php echo $i++ % 2 ? 'nette-alt' : '' ?>">
	<td class="nette-right"><?php echo $count ? "$count\xC3\x97" : '' ?></td>
	<td><pre><?php echo htmlspecialchars($message), ' in ', NDebugHelpers::editorLink($file, $line), ':', $line ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php
		}
		return ob_get_clean();
	}

}

final class NSafeStream
{

	const PROTOCOL = 'safe';

	private $handle;

	private $tempHandle;

	private $file;

	private $tempFile;

	private $deleteFile;

	private $writeError = FALSE;

	static function register()
	{
		return stream_wrapper_register(self::PROTOCOL, __CLASS__);
	}

	function stream_open($path, $mode, $options, &$opened_path)
	{
		$path = substr($path, strlen(self::PROTOCOL)+3);

		$flag = trim($mode, 'rwax+');
		$mode = trim($mode, 'tb');
		$use_path = (bool) (STREAM_USE_PATH & $options);

		if ($mode === 'r') {
			return $this->checkAndLock($this->tempHandle = fopen($path, 'r'.$flag, $use_path), LOCK_SH);

		} elseif ($mode === 'r+') {
			if (!$this->checkAndLock($this->handle = fopen($path, 'r'.$flag, $use_path), LOCK_EX)) {
				return FALSE;
			}

		} elseif ($mode[0] === 'x') {
			if (!$this->checkAndLock($this->handle = fopen($path, 'x'.$flag, $use_path), LOCK_EX)) {
				return FALSE;
			}
			$this->deleteFile = TRUE;

		} elseif ($mode[0] === 'w' || $mode[0] === 'a') {
			if ($this->checkAndLock($this->handle = @fopen($path, 'x'.$flag, $use_path), LOCK_EX)) {
				$this->deleteFile = TRUE;

			} elseif (!$this->checkAndLock($this->handle = fopen($path, 'a+'.$flag, $use_path), LOCK_EX)) {
				return FALSE;
			}

		} else {
			trigger_error("Unknown mode $mode", E_USER_WARNING);
			return FALSE;
		}

		$tmp = '~~' . lcg_value() . '.tmp';
		if (!$this->tempHandle = fopen($path . $tmp, (strpos($mode, '+') ? 'x+' : 'x').$flag, $use_path)) {
			$this->clean();
			return FALSE;
		}
		$this->tempFile = realpath($path . $tmp);
		$this->file = substr($this->tempFile, 0, -strlen($tmp));

		if ($mode === 'r+' || $mode[0] === 'a') {
			$stat = fstat($this->handle);
			fseek($this->handle, 0);
			if (stream_copy_to_stream($this->handle, $this->tempHandle) !== $stat['size']) {
				$this->clean();
				return FALSE;
			}

			if ($mode[0] === 'a') {
				fseek($this->tempHandle, 0, SEEK_END);
			}
		}

		return TRUE;
	}

	private function checkAndLock($handle, $lock)
	{
		if (!$handle) {
			return FALSE;

		} elseif (!flock($handle, $lock)) {
			fclose($handle);
			return FALSE;
		}

		return TRUE;
	}

	private function clean()
	{
		flock($this->handle, LOCK_UN);
		fclose($this->handle);
		if ($this->deleteFile) {
			unlink($this->file);
		}
		if ($this->tempHandle) {
			fclose($this->tempHandle);
			unlink($this->tempFile);
		}
	}

	function stream_close()
	{
		if (!$this->tempFile) {
			flock($this->tempHandle, LOCK_UN);
			fclose($this->tempHandle);
			return;
		}

		flock($this->handle, LOCK_UN);
		fclose($this->handle);
		fclose($this->tempHandle);

		if ($this->writeError || !(substr(PHP_OS, 0, 3) === 'WIN' ? unlink($this->file) : TRUE)
			|| !rename($this->tempFile, $this->file)
		) {
			unlink($this->tempFile);
			if ($this->deleteFile) {
				unlink($this->file);
			}
		}
	}

	function stream_read($length)
	{
		return fread($this->tempHandle, $length);
	}

	function stream_write($data)
	{
		$len = strlen($data);
		$res = fwrite($this->tempHandle, $data, $len);

		if ($res !== $len) {
			$this->writeError = TRUE;
		}

		return $res;
	}

	function stream_tell()
	{
		return ftell($this->tempHandle);
	}

	function stream_eof()
	{
		return feof($this->tempHandle);
	}

	function stream_seek($offset, $whence)
	{
		return fseek($this->tempHandle, $offset, $whence) === 0;
	}

	function stream_stat()
	{
		return fstat($this->tempHandle);
	}

	function url_stat($path, $flags)
	{

		$path = substr($path, strlen(self::PROTOCOL)+3);
		return ($flags & STREAM_URL_STAT_LINK) ? @lstat($path) : @stat($path);
	}

	function unlink($path)
	{
		$path = substr($path, strlen(self::PROTOCOL)+3);
		return unlink($path);
	}

}

class NApplication extends NObject
{

	public static $maxLoop = 20;

	public $catchExceptions;

	public $errorPresenter;

	public $onStartup;

	public $onShutdown;

	public $onRequest;

	public $onResponse;

	public $onError;

	public $allowedMethods = array('GET', 'POST', 'HEAD', 'PUT', 'DELETE');

	private $requests = array();

	private $presenter;

	private $context;

	function __construct(IDiContainer $context)
	{
		$this->context = $context;
	}

	function run()
	{
		$httpRequest = $this->context->httpRequest;
		$httpResponse = $this->context->httpResponse;

		if ($this->allowedMethods) {
			$method = $httpRequest->getMethod();
			if (!in_array($method, $this->allowedMethods, TRUE)) {
				$httpResponse->setCode(IHttpResponse::S501_NOT_IMPLEMENTED);
				$httpResponse->setHeader('Allow', implode(',', $this->allowedMethods));
				echo '<h1>Method ' . htmlSpecialChars($method) . ' is not implemented</h1>';
				return;
			}
		}

		$request = NULL;
		$repeatedError = FALSE;
		do {
			try {
				if (count($this->requests) > self::$maxLoop) {
					throw new NApplicationException('Too many loops detected in application life cycle.');
				}

				if (!$request) {
					$this->onStartup($this);

					$router = $this->getRouter();

					NRoutingDebugger::initialize($this, $httpRequest);

					$request = $router->match($httpRequest);
					if (!$request instanceof NPresenterRequest) {
						$request = NULL;
						throw new NBadRequestException('No route for HTTP request.');
					}

					if (strcasecmp($request->getPresenterName(), $this->errorPresenter) === 0) {
						throw new NBadRequestException('Invalid request. Presenter is not achievable.');
					}
				}

				$this->requests[] = $request;
				$this->onRequest($this, $request);

				$presenterName = $request->getPresenterName();
				try {
					$this->presenter = $this->getPresenterFactory()->createPresenter($presenterName);
				} catch (NInvalidPresenterException $e) {
					throw new NBadRequestException($e->getMessage(), 404, $e);
				}

				$this->getPresenterFactory()->getPresenterClass($presenterName);
				$request->setPresenterName($presenterName);
				$request->freeze();

				$response = $this->presenter->run($request);
				$this->onResponse($this, $response);

				if ($response instanceof NForwardResponse) {
					$request = $response->getRequest();
					continue;

				} elseif ($response instanceof IPresenterResponse) {
					$response->send($httpRequest, $httpResponse);
				}
				break;

			} catch (Exception $e) {

				$this->onError($this, $e);

				if (!$this->catchExceptions) {
					$this->onShutdown($this, $e);
					throw $e;
				}

				if ($repeatedError) {
					$e = new NApplicationException('An error occurred while executing error-presenter', 0, $e);
				}

				if (!$httpResponse->isSent()) {
					$httpResponse->setCode($e instanceof NBadRequestException ? $e->getCode() : 500);
				}

				if (!$repeatedError && $this->errorPresenter) {
					$repeatedError = TRUE;
					if ($this->presenter instanceof NPresenter) {
						try {
							$this->presenter->forward(":$this->errorPresenter:", array('exception' => $e));
						} catch (NAbortException $foo) {
							$request = $this->presenter->getLastCreatedRequest();
						}
					} else {
						$request = new NPresenterRequest(
							$this->errorPresenter,
							NPresenterRequest::FORWARD,
							array('exception' => $e)
						);
					}

				} else {
					if ($e instanceof NBadRequestException) {
						$code = $e->getCode();
					} else {
						$code = 500;
						NDebugger::log($e, NDebugger::ERROR);
					}

$messages = array(
	0 => array('Oops...', 'Your browser sent a request that this server could not understand or process.'),
	403 => array('Access Denied', 'You do not have permission to view this page. Please try contact the web site administrator if you believe you should be able to view this page.'),
	404 => array('Page Not Found', 'The page you requested could not be found. It is possible that the address is incorrect, or that the page no longer exists. Please use a search engine to find what you are looking for.'),
	405 => array('Method Not Allowed', 'The requested method is not allowed for the URL.'),
	410 => array('Page Not Found', 'The page you requested has been taken off the site. We apologize for the inconvenience.'),
	500 => array('Server Error', 'We\'re sorry! The server encountered an internal error and was unable to complete your request. Please try again later.'),
);
$message = isset($messages[$code]) ? $messages[$code] : $messages[0];

?>
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name=robots content=noindex><meta name=generator content="Nette Framework">
<style>body{color:#333;background:white;width:500px;margin:100px auto}h1{font:bold 47px/1.5 sans-serif;margin:.6em 0}p{font:21px/1.5 Georgia,serif;margin:1.5em 0}small{font-size:70%;color:gray}</style>

<title><?php echo $message[0] ?></title>

<h1><?php echo $message[0] ?></h1>

<p><?php echo $message[1] ?></p>

<?php if ($code): ?><p><small>error <?php echo $code ?></small></p><?php endif ?>
<?php
					break;
				}
			}
		} while (1);

		$this->onShutdown($this, isset($e) ? $e : NULL);
	}

	final function getRequests()
	{
		return $this->requests;
	}

	final function getPresenter()
	{
		return $this->presenter;
	}

	protected function getContext()
	{
		return $this->context;
	}

	function getRouter()
	{
		return $this->context->router;
	}

	function getPresenterFactory()
	{
		return $this->context->presenterFactory;
	}

	function storeRequest($expiration = '+ 10 minutes')
	{
		$session = $this->context->session->getSection('Nette.Application/requests');
		do {
			$key = NStrings::random(5);
		} while (isset($session[$key]));

		$session[$key] = end($this->requests);
		$session->setExpiration($expiration, $key);
		return $key;
	}

	function restoreRequest($key)
	{
		$session = $this->context->session->getSection('Nette.Application/requests');
		if (isset($session[$key])) {
			$request = clone $session[$key];
			unset($session[$key]);
			$request->setFlag(NPresenterRequest::RESTORED, TRUE);
			$this->presenter->sendResponse(new NForwardResponse($request));
		}
	}

}

class NRoutingDebugger extends NObject implements IBarPanel
{

	private $router;

	private $httpRequest;

	private $routers = array();

	private $request;

	static function initialize(NApplication $application, IHttpRequest $httpRequest)
	{
		NDebugger::$bar->addPanel(new self($application->getRouter(), $httpRequest));
		NDebugger::$blueScreen->addPanel(create_function('$e', 'extract(NCFix::$vars['.NCFix::uses(array('application'=>$application)).'], EXTR_REFS);
			if ($e === NULL) {
				return array(
					\'tab\' => \'Nette Application\',
					\'panel\' => \'<h3>Requests</h3>\' . NDebugHelpers::clickableDump($application->getRequests())
						. \'<h3>Presenter</h3>\' . NDebugHelpers::clickableDump($application->getPresenter())
				);
			}
		'));
	}

	function __construct(IRouter $router, IHttpRequest $httpRequest)
	{
		$this->router = $router;
		$this->httpRequest = $httpRequest;
	}

	function getTab()
	{
		$this->analyse($this->router);
		ob_start();

?>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJHSURBVDjLlZPNi81hFMc/z7137p1mTCFvNZfGSzLIWNjZKRvFRoqNhRCSYm8xS3+AxRRZ2JAFJWJHSQqTQkbEzYwIM+6Yid/znJfH4prLXShOnb6r8/nWOd8Tcs78bz0/f+KMu50y05nK/wy+uHDylbutqS5extvGcxaWqtoGDA8PZ3dnrs2srQc2Zko41UXLmLdyDW5OfvsUkUgbYGbU63UAQggdmvMzFmzZCgTi7CQmkZwdEaX0JwDgTnGbTCaE0G4zw80omhPI92lcEtkNkdgJCCHwJX7mZvNaB0A14SaYJlwTrpHsTkoFlV1nt2c3x5YYo1/vM9A/gKpxdfwyu/v3teCayKq4JEwT5EB2R6WgYmrs2bYbcUNNUVfEhIfFYy69uci+1fuRX84mkawFSxd/4nVWUopUVIykwlQxRTJBTIDA4Pp1jBZPuNW4wUAPmCqWIn29X1k4f5Ku8g9mpKCkakRLVEs1auVuauVuyqHMo8ejNCe+sWPVTkQKXCMmkeZUmUZjETF1tc6ooly+fgUVw9So1/tRN6YnZji46QghBFKKuAouERNhMlbAHZFE6e7pB+He8MMw+GGI4xtOMf1+lsl3TQ4NHf19BSlaO1DB9BfMHdX0O0iqSgiBbJkjm491hClJbA1LxCURgpPzXwAHhg63necAIi3XngXLcRU0fof8ETMljIyM5LGxMcbHxzvy/6fuXdWgt6+PWncv1e4euqo1ZmabvHs5+jn8yzufO7hiiZmuNpNBM13rbvVSpbrXJE7/BMkHtU9jFIC/AAAAAElFTkSuQmCC"
/><?php if (empty($this->request)): ?>no route<?php else: echo $this->request->getPresenterName() . ':' . (isset($this->request->params[NPresenter::ACTION_KEY]) ? $this->request->params[NPresenter::ACTION_KEY] : NPresenter::DEFAULT_ACTION) . (isset($this->request->params[NPresenter::SIGNAL_KEY]) ? " {$this->request->params[NPresenter::SIGNAL_KEY]}!" : ''); endif ?>
<?php
		return ob_get_clean();
	}

	function getPanel()
	{
		ob_start();

?>
<style>#nette-debug .nette-RoutingPanel table{font:9pt/1.5 Consolas,monospace}#nette-debug .nette-RoutingPanel .yes td{color:green}#nette-debug .nette-RoutingPanel .may td{color:#67F}#nette-debug .nette-RoutingPanel pre,#nette-debug .nette-RoutingPanel code{display:inline}</style>

<div class="nette-RoutingPanel">
<h1>
<?php if (empty($this->request)): ?>
	no route
<?php else: ?>
	<?php echo $this->request->getPresenterName() . ':' . (isset($this->request->params[NPresenter::ACTION_KEY]) ? $this->request->params[NPresenter::ACTION_KEY] : NPresenter::DEFAULT_ACTION) . (isset($this->request->params[NPresenter::SIGNAL_KEY]) ? " {$this->request->params[NPresenter::SIGNAL_KEY]}!" : '') ?>
<?php endif ?>
</h1>

<?php if (!empty($this->request)): ?>
	<?php $params = $this->request->getParams() ?>
	<?php if (empty($params)): ?>
		<p>No parameters.</p>

	<?php else: ?>
		<table>
		<thead>
		<tr>
			<th>Parameter</th>
			<th>Value</th>
		</tr>
		</thead>
		<tbody>
		<?php unset($params[NPresenter::ACTION_KEY], $params[NPresenter::SIGNAL_KEY]) ?>
		<?php foreach ($params as $key => $value): ?>
		<tr>
			<td><code><?php echo htmlSpecialChars($key) ?></code></td>
			<td><?php if (is_string($value)):?><code><?php echo htmlSpecialChars($value) ?></code><?php else: echo NDebugger::dump($value, TRUE); endif ?></td>
		</tr>
		<?php endforeach ?>
		</tbody>
		</table>
	<?php endif ?>
<?php endif ?>

<h2>Routers</h2>

<?php if (empty($this->routers)): ?>
	<p>No routers defined.</p>

<?php else: ?>
	<div class="nette-inner">
	<table>
	<thead>
	<tr>
		<th>Matched?</th>
		<th>Class</th>
		<th>Mask</th>
		<th>Defaults</th>
		<th>Request</th>
	</tr>
	</thead>

	<tbody>
	<?php foreach ($this->routers as $router): ?>
	<tr class="<?php echo $router['matched'] ?>">
		<td><?php echo $router['matched'] ?></td>

		<td><code title="<?php echo htmlSpecialChars($router['class']) ?>"><?php echo preg_replace('#.+\\\\#', '', htmlSpecialChars($router['class'])) ?></code></td>

		<td><code><strong><?php echo htmlSpecialChars($router['mask']) ?></strong></code></td>

		<td><code>
		<?php foreach ($router['defaults'] as $key => $value): ?>
			<?php echo htmlSpecialChars($key), "&nbsp;=&nbsp;", is_string($value) ? htmlSpecialChars($value) : str_replace("\n</pre", '</pre', NDebugger::dump($value, TRUE)) ?><br />
		<?php endforeach ?>
		</code></td>

		<td><?php if ($router['request']): ?><code>
		<?php $params = $router['request']->getParams(); ?>
		<strong><?php echo htmlSpecialChars($router['request']->getPresenterName() . ':' . (isset($params[NPresenter::ACTION_KEY]) ? $params[NPresenter::ACTION_KEY] : NPresenter::DEFAULT_ACTION)) ?></strong><br />
		<?php unset($params[NPresenter::ACTION_KEY]) ?>
		<?php foreach ($params as $key => $value): ?>
			<?php echo htmlSpecialChars($key), "&nbsp;=&nbsp;", is_string($value) ? htmlSpecialChars($value) : str_replace("\n</pre", '</pre', NDebugger::dump($value, TRUE)) ?><br />
		<?php endforeach ?>
		</code><?php endif ?></td>
	</tr>
	<?php endforeach ?>
	</tbody>
	</table>
	</div>
<?php endif ?>
</div>
<?php
		return ob_get_clean();
	}

	private function analyse($router)
	{
		if ($router instanceof NRouteList) {
			foreach ($router as $subRouter) {
				$this->analyse($subRouter);
			}
			return;
		}

		$request = $router->match($this->httpRequest);
		$matched = $request === NULL ? 'no' : 'may';
		if ($request !== NULL && empty($this->request)) {
			$this->request = $request;
			$matched = 'yes';
		}

		$this->routers[] = array(
			'matched' => $matched,
			'class' => get_class($router),
			'defaults' => $router instanceof NRoute || $router instanceof NSimpleRouter ? $router->getDefaults() : array(),
			'mask' => $router instanceof NRoute ? $router->getMask() : NULL,
			'request' => $request,
		);
	}

}

class NAbortException extends Exception
{
}

class NApplicationException extends Exception
{
	function __construct($message = '', $code = 0, Exception $previous = NULL)
	{
		if (PHP_VERSION_ID < 50300) {
			$this->previous = $previous;
			parent::__construct($message, $code);
		} else {
			parent::__construct($message, $code, $previous);
		}
	}
}

class NInvalidPresenterException extends Exception
{
}

class NBadRequestException extends Exception
{

	protected $defaultCode = 404;

	function __construct($message = '', $code = 0, Exception $previous = NULL)
	{
		if ($code < 200 || $code > 504) {
			$code = $this->defaultCode;
		}

		if (PHP_VERSION_ID < 50300) {
			$this->previous = $previous;
			parent::__construct($message, $code);
		} else {
			parent::__construct($message, $code, $previous);
		}
	}

}

class NForbiddenRequestException extends NBadRequestException
{

	protected $defaultCode = 403;

}

class MicroPresenter extends NObject implements IPresenter
{

	private $context;

	private $request;

	function run(NPresenterRequest $request)
	{
		$this->request = $request;

		$httpRequest = $this->context->httpRequest;
		if (!$httpRequest->isAjax() && ($request->isMethod('get') || $request->isMethod('head'))) {
			$refUrl = clone $httpRequest->getUrl();
			$url = $this->context->router->constructUrl($request, $refUrl->setPath($refUrl->getScriptPath()));
			if ($url !== NULL && !$httpRequest->getUrl()->isEqual($url)) {
				return new NRedirectResponse($url, IHttpResponse::S301_MOVED_PERMANENTLY);
			}
		}

		$params = $request->getParams();
		if (!isset($params['callback'])) {
			return;
		}
		$params['presenter'] = $this;
		$response = callback($params['callback'])->invokeNamedArgs($params);

		if (is_string($response)) {
			$response = array($response, array());
		}
		if (is_array($response)) {
			if ($response instanceof SplFileInfo) {
				$response = $this->createTemplate('NFileTemplate')
					->setParams($response[1])->setFile($response[0]);
			} else {
				$response = $this->createTemplate('NTemplate')
					->setParams($response[1])->setSource($response[0]);
			}
		}
		if ($response instanceof ITemplate) {
			return new NTextResponse($response);
		} else {
			return $response;
		}
	}

	function createTemplate($class = NULL, $latteFactory = NULL)
	{
		$template = $class ? new $class : new NFileTemplate;

		$template->setParams($this->request->getParams());
		$template->presenter = $this;
		$template->context = $context = $this->context;
		$url = $context->httpRequest->getUrl();
		$template->baseUrl = rtrim($url->getBaseUrl(), '/');
		$template->basePath = rtrim($url->getBasePath(), '/');

		$template->registerHelperLoader('NTemplateHelpers::loader');
		$template->setCacheStorage($context->templateCacheStorage);
		$template->onPrepareFilters[] = create_function('$template', 'extract(NCFix::$vars['.NCFix::uses(array('latteFactory'=>$latteFactory,'context'=> $context)).'], EXTR_REFS);
			$template->registerFilter($latteFactory ? $latteFactory() : new NLatteFilter);
		');
		return $template;
	}

	function redirectUrl($url, $code = IHttpResponse::S302_FOUND)
	{
		return new NRedirectResponse($url, $code);
	}

	function error($code, $message = NULL)
	{
		throw new NBadRequestException($message, $code);
	}

	function getRequest()
	{
		return $this->request;
	}

	function setContext(IDiContainer $context)
	{
		$this->context = $context;
		return $this;
	}

	final function getContext()
	{
		return $this->context;
	}

}

class NPresenterFactory implements IPresenterFactory
{

	public $caseSensitive = FALSE;

	private $baseDir;

	private $cache = array();

	private $context;

	function __construct($baseDir, IDiContainer $context)
	{
		$this->baseDir = $baseDir;
		$this->context = $context;
	}

	function createPresenter($name)
	{
		$class = $this->getPresenterClass($name);
		$presenter = new $class;
		$presenter->setContext($this->context);
		return $presenter;
	}

	function getPresenterClass(& $name)
	{
		if (isset($this->cache[$name])) {
			list($class, $name) = $this->cache[$name];
			return $class;
		}

		if (!is_string($name) || !NStrings::match($name, "#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*$#")) {
			throw new NInvalidPresenterException("Presenter name must be alphanumeric string, '$name' is invalid.");
		}

		$class = $this->formatPresenterClass($name);

		if (!class_exists($class)) {

			$file = $this->formatPresenterFile($name);
			if (is_file($file) && is_readable($file)) {
				NLimitedScope::load($file);
			}

			if (!class_exists($class)) {
				throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' was not found in '$file'.");
			}
		}

		$reflection = new NClassReflection($class);
		$class = $reflection->getName();

		if (!$reflection->implementsInterface('IPresenter')) {
			throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' is not IPresenter implementor.");
		}

		if ($reflection->isAbstract()) {
			throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' is abstract.");
		}

		$realName = $this->unformatPresenterClass($class);
		if ($name !== $realName) {
			if ($this->caseSensitive) {
				throw new NInvalidPresenterException("Cannot load presenter '$name', case mismatch. Real name is '$realName'.");
			} else {
				$this->cache[$name] = array($class, $realName);
				$name = $realName;
			}
		} else {
			$this->cache[$name] = array($class, $realName);
		}

		return $class;
	}

	function formatPresenterClass($presenter)
	{
		return strtr($presenter, ':', '_') . 'Presenter';
		return str_replace(':', 'Module\\', $presenter) . 'Presenter';
	}

	function unformatPresenterClass($class)
	{
		return strtr(substr($class, 0, -9), '_', ':');
		return str_replace('Module\\', ':', substr($class, 0, -9));
	}

	function formatPresenterFile($presenter)
	{
		$path = '/' . str_replace(':', 'Module/', $presenter);
		return $this->baseDir . substr_replace($path, '/presenters', strrpos($path, '/'), 0) . 'Presenter.php';
	}

}

abstract class NFreezableObject extends NObject implements IFreezable
{

	private $frozen = FALSE;

	function freeze()
	{
		$this->frozen = TRUE;
	}

	final function isFrozen()
	{
		return $this->frozen;
	}

	function __clone()
	{
		$this->frozen = FALSE;
	}

	protected function updating()
	{
		if ($this->frozen) {
			$class = get_class($this);
			throw new InvalidStateException("Cannot modify a frozen object $class.");
		}
	}

}

final class NPresenterRequest extends NFreezableObject
{

	const FORWARD = 'FORWARD';

	const SECURED = 'secured';

	const RESTORED = 'restored';

	private $method;

	private $flags = array();

	private $name;

	private $params;

	private $post;

	private $files;

	function __construct($name, $method, array $params, array $post = array(), array $files = array(), array $flags = array())
	{
		$this->name = $name;
		$this->method = $method;
		$this->params = $params;
		$this->post = $post;
		$this->files = $files;
		$this->flags = $flags;
	}

	function setPresenterName($name)
	{
		$this->updating();
		$this->name = $name;
		return $this;
	}

	function getPresenterName()
	{
		return $this->name;
	}

	function setParams(array $params)
	{
		$this->updating();
		$this->params = $params;
		return $this;
	}

	function getParams()
	{
		return $this->params;
	}

	function setPost(array $params)
	{
		$this->updating();
		$this->post = $params;
		return $this;
	}

	function getPost()
	{
		return $this->post;
	}

	function setFiles(array $files)
	{
		$this->updating();
		$this->files = $files;
		return $this;
	}

	function getFiles()
	{
		return $this->files;
	}

	function setMethod($method)
	{
		$this->method = $method;
		return $this;
	}

	function getMethod()
	{
		return $this->method;
	}

	function isMethod($method)
	{
		return strcasecmp($this->method, $method) === 0;
	}

	function isPost()
	{
		return strcasecmp($this->method, 'post') === 0;
	}

	function setFlag($flag, $value = TRUE)
	{
		$this->updating();
		$this->flags[$flag] = (bool) $value;
		return $this;
	}

	function hasFlag($flag)
	{
		return !empty($this->flags[$flag]);
	}

}

class NFileResponse extends NObject implements IPresenterResponse
{

	private $file;

	private $contentType;

	private $name;

	public $resuming = TRUE;

	function __construct($file, $name = NULL, $contentType = NULL)
	{
		if (!is_file($file)) {
			throw new NBadRequestException("File '$file' doesn't exist.");
		}

		$this->file = $file;
		$this->name = $name ? $name : basename($file);
		$this->contentType = $contentType ? $contentType : 'application/octet-stream';
	}

	final function getFile()
	{
		return $this->file;
	}

	final function getName()
	{
		return $this->name;
	}

	final function getContentType()
	{
		return $this->contentType;
	}

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse)
	{
		$httpResponse->setContentType($this->contentType);
		$httpResponse->setHeader('Content-Disposition', 'attachment; filename="' . $this->name . '"');

		$filesize = $length = filesize($this->file);
		$handle = fopen($this->file, 'r');

		if ($this->resuming) {
			$httpResponse->setHeader('Accept-Ranges', 'bytes');
			$range = $httpRequest->getHeader('Range');
			if ($range !== NULL) {
				$range = substr($range, 6);
				list($start, $end) = explode('-', $range);
				if ($start == NULL) {
					$start = 0;
				}
				if ($end == NULL) {
					$end = $filesize - 1;
				}

				if ($start < 0 || $end <= $start || $end > $filesize -1) {
					$httpResponse->setCode(416);
					return;
				}

				$httpResponse->setCode(206);
				$httpResponse->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $filesize);
				$length = $end - $start + 1;
				fseek($handle, $start);

			} else {
				$httpResponse->setHeader('Content-Range', 'bytes 0-' . ($filesize - 1) . '/' . $filesize);
			}
		}

		$httpResponse->setHeader('Content-Length', $length);
		while (!feof($handle)) {
			echo fread($handle, 4e6);
		}
		fclose($handle);
	}

}

class NForwardResponse extends NObject implements IPresenterResponse
{

	private $request;

	function __construct(NPresenterRequest $request)
	{
		$this->request = $request;
	}

	final function getRequest()
	{
		return $this->request;
	}

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse)
	{
	}

}

class NJsonResponse extends NObject implements IPresenterResponse
{

	private $payload;

	private $contentType;

	function __construct($payload, $contentType = NULL)
	{
		if (!is_array($payload) && !is_object($payload)) {
			throw new InvalidArgumentException("Payload must be array or object class, " . gettype($payload) . " given.");
		}
		$this->payload = $payload;
		$this->contentType = $contentType ? $contentType : 'application/json';
	}

	final function getPayload()
	{
		return $this->payload;
	}

	final function getContentType()
	{
		return $this->contentType;
	}

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse)
	{
		$httpResponse->setContentType($this->contentType);
		$httpResponse->setExpiration(FALSE);
		echo NJson::encode($this->payload);
	}

}

class NRedirectResponse extends NObject implements IPresenterResponse
{

	private $url;

	private $code;

	function __construct($url, $code = IHttpResponse::S302_FOUND)
	{
		$this->url = (string) $url;
		$this->code = (int) $code;
	}

	final function getUrl()
	{
		return $this->url;
	}

	final function getCode()
	{
		return $this->code;
	}

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse)
	{
		$httpResponse->redirect($this->url, $this->code);
	}

}

class NTextResponse extends NObject implements IPresenterResponse
{

	private $source;

	function __construct($source)
	{
		$this->source = $source;
	}

	final function getSource()
	{
		return $this->source;
	}

	function send(IHttpRequest $httpRequest, IHttpResponse $httpResponse)
	{
		if ($this->source instanceof ITemplate) {
			$this->source->render();

		} else {
			echo $this->source;
		}
	}

}

class NCliRouter extends NObject implements IRouter
{
	const PRESENTER_KEY = 'action';

	private $defaults;

	function __construct($defaults = array())
	{
		$this->defaults = $defaults;
	}

	function match(IHttpRequest $httpRequest)
	{
		if (empty($_SERVER['argv']) || !is_array($_SERVER['argv'])) {
			return NULL;
		}

		$names = array(self::PRESENTER_KEY);
		$params = $this->defaults;
		$args = $_SERVER['argv'];
		array_shift($args);
		$args[] = '--';

		foreach ($args as $arg) {
			$opt = preg_replace('#/|-+#A', '', $arg);
			if ($opt === $arg) {
				if (isset($flag) || $flag = array_shift($names)) {
					$params[$flag] = $arg;
				} else {
					$params[] = $arg;
				}
				$flag = NULL;
				continue;
			}

			if (isset($flag)) {
				$params[$flag] = TRUE;
				$flag = NULL;
			}

			if ($opt !== '') {
				$pair = explode('=', $opt, 2);
				if (isset($pair[1])) {
					$params[$pair[0]] = $pair[1];
				} else {
					$flag = $pair[0];
				}
			}
		}

		if (!isset($params[self::PRESENTER_KEY])) {
			throw new InvalidStateException('Missing presenter & action in route definition.');
		}
		$presenter = $params[self::PRESENTER_KEY];
		if ($a = strrpos($presenter, ':')) {
			$params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
			$presenter = substr($presenter, 0, $a);
		}

		return new NPresenterRequest(
			$presenter,
			'CLI',
			$params
		);
	}

	function constructUrl(NPresenterRequest $appRequest, NUrl $refUrl)
	{
		return NULL;
	}

	function getDefaults()
	{
		return $this->defaults;
	}

}

class NRoute extends NObject implements IRouter
{
	const PRESENTER_KEY = 'presenter';
	const MODULE_KEY = 'module';

	const CASE_SENSITIVE = 256;

	const HOST = 1,
		PATH = 2,
		RELATIVE = 3;

	const VALUE = 'value';
	const PATTERN = 'pattern';
	const FILTER_IN = 'filterIn';
	const FILTER_OUT = 'filterOut';
	const FILTER_TABLE = 'filterTable';

	const OPTIONAL = 0,
		PATH_OPTIONAL = 1,
		CONSTANT = 2;

	public static $defaultFlags = 0;

	public static $styles = array(
		'#' => array(
			self::PATTERN => '[^/]+',
			self::FILTER_IN => 'rawurldecode',
			self::FILTER_OUT => 'rawurlencode',
		),
		'?#' => array(
		),
		'module' => array(
			self::PATTERN => '[a-z][a-z0-9.-]*',
			self::FILTER_IN => array(__CLASS__, 'path2presenter'),
			self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
		),
		'presenter' => array(
			self::PATTERN => '[a-z][a-z0-9.-]*',
			self::FILTER_IN => array(__CLASS__, 'path2presenter'),
			self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
		),
		'action' => array(
			self::PATTERN => '[a-z][a-z0-9-]*',
			self::FILTER_IN => array(__CLASS__, 'path2action'),
			self::FILTER_OUT => array(__CLASS__, 'action2path'),
		),
		'?module' => array(
		),
		'?presenter' => array(
		),
		'?action' => array(
		),
	);

	private $mask;

	private $sequence;

	private $re;

	private $metadata = array();

	private $xlat;

	private $type;

	private $flags;

	function __construct($mask, $metadata = array(), $flags = 0)
	{
		if (is_string($metadata)) {
			$a = strrpos($metadata, ':');
			if (!$a) {
				throw new InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given.");
			}
			$metadata = array(
				self::PRESENTER_KEY => substr($metadata, 0, $a),
				'action' => $a === strlen($metadata) - 1 ? NPresenter::DEFAULT_ACTION : substr($metadata, $a + 1),
			);
		} elseif ($metadata instanceof Closure || $metadata instanceof NCallback) {
			$metadata = array(
				self::PRESENTER_KEY => 'Nette:Micro',
				'callback' => $metadata,
			);
		}

		$this->flags = $flags | self::$defaultFlags;
		$this->setMask($mask, $metadata);
	}

	function match(IHttpRequest $httpRequest)
	{

		$url = $httpRequest->getUrl();

		if ($this->type === self::HOST) {
			$path = '//' . $url->getHost() . $url->getPath();

		} elseif ($this->type === self::RELATIVE) {
			$basePath = $url->getBasePath();
			if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
				return NULL;
			}
			$path = (string) substr($url->getPath(), strlen($basePath));

		} else {
			$path = $url->getPath();
		}

		if ($path !== '') {
			$path = rtrim($path, '/') . '/';
		}

		if (!$matches = NStrings::match($path, $this->re)) {

			return NULL;
		}

		$params = array();
		foreach ($matches as $k => $v) {
			if (is_string($k) && $v !== '') {
				$params[str_replace('___', '-', $k)] = $v;
			}
		}

		foreach ($this->metadata as $name => $meta) {
			if (isset($params[$name])) {

			} elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
				$params[$name] = NULL;
			}
		}

		if ($this->xlat) {
			$params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
		} else {
			$params += $httpRequest->getQuery();
		}

		foreach ($this->metadata as $name => $meta) {
			if (isset($params[$name])) {
				if (!is_scalar($params[$name])) {

				} elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) {
					$params[$name] = $meta[self::FILTER_TABLE][$params[$name]];

				} elseif (isset($meta[self::FILTER_IN])) {
					$params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
					if ($params[$name] === NULL && !isset($meta['fixity'])) {
						return NULL;
					}
				}

			} elseif (isset($meta['fixity'])) {
				$params[$name] = $meta[self::VALUE];
			}
		}

		if (!isset($params[self::PRESENTER_KEY])) {
			throw new InvalidStateException('Missing presenter in route definition.');
		}
		if (isset($this->metadata[self::MODULE_KEY])) {
			if (!isset($params[self::MODULE_KEY])) {
				throw new InvalidStateException('Missing module in route definition.');
			}
			$presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY];
			unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]);

		} else {
			$presenter = $params[self::PRESENTER_KEY];
			unset($params[self::PRESENTER_KEY]);
		}

		return new NPresenterRequest(
			$presenter,
			$httpRequest->getMethod(),
			$params,
			$httpRequest->getPost(),
			$httpRequest->getFiles(),
			array(NPresenterRequest::SECURED => $httpRequest->isSecured())
		);
	}

	function constructUrl(NPresenterRequest $appRequest, NUrl $refUrl)
	{
		if ($this->flags & self::ONE_WAY) {
			return NULL;
		}

		$params = $appRequest->getParams();
		$metadata = $this->metadata;

		$presenter = $appRequest->getPresenterName();
		$params[self::PRESENTER_KEY] = $presenter;

		if (isset($metadata[self::MODULE_KEY])) {
			$module = $metadata[self::MODULE_KEY];
			if (isset($module['fixity']) && strncasecmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) {
				$a = strlen($module[self::VALUE]);
			} else {
				$a = strrpos($presenter, ':');
			}
			if ($a === FALSE) {
				$params[self::MODULE_KEY] = '';
			} else {
				$params[self::MODULE_KEY] = substr($presenter, 0, $a);
				$params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
			}
		}

		foreach ($metadata as $name => $meta) {
			if (!isset($params[$name])) {
				continue;
			}

			if (isset($meta['fixity'])) {
				if (is_scalar($params[$name]) ? strcasecmp($params[$name], $meta[self::VALUE]) === 0
					: $params[$name] === $meta[self::VALUE]
				) {
					unset($params[$name]);
					continue;

				} elseif ($meta['fixity'] === self::CONSTANT) {
					return NULL;
				}
			}

			if (!is_scalar($params[$name])) {

			} elseif (isset($meta['filterTable2'][$params[$name]])) {
				$params[$name] = $meta['filterTable2'][$params[$name]];

			} elseif (isset($meta[self::FILTER_OUT])) {
				$params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
			}

			if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
				return NULL;
			}
		}

		$sequence = $this->sequence;
		$brackets = array();
		$required = 0;
		$url = '';
		$i = count($sequence) - 1;
		do {
			$url = $sequence[$i] . $url;
			if ($i === 0) {
				break;
			}
			$i--;

			$name = $sequence[$i]; $i--;

			if ($name === ']') {
				$brackets[] = $url;

			} elseif ($name[0] === '[') {
				$tmp = array_pop($brackets);
				if ($required < count($brackets) + 1) {
					if ($name !== '[!') {
						$url = $tmp;
					}
				} else {
					$required = count($brackets);
				}

			} elseif ($name[0] === '?') {
				continue;

			} elseif (isset($params[$name]) && $params[$name] != '') {
				$required = count($brackets);
				$url = $params[$name] . $url;
				unset($params[$name]);

			} elseif (isset($metadata[$name]['fixity'])) {
				$url = $metadata[$name]['defOut'] . $url;

			} else {
				return NULL;
			}
		} while (TRUE);

		if ($this->xlat) {
			$params = self::renameKeys($params, $this->xlat);
		}

		$sep = ini_get('arg_separator.input');
		$query = http_build_query($params, '', $sep ? $sep[0] : '&');
		if ($query != '') {
			$url .= '?' . $query;
		}

		if ($this->type === self::RELATIVE) {
			$url = '//' . $refUrl->getAuthority() . $refUrl->getBasePath() . $url;

		} elseif ($this->type === self::PATH) {
			$url = '//' . $refUrl->getAuthority() . $url;
		}

		if (strpos($url, '//', 2) !== FALSE) {
			return NULL;
		}

		$url = ($this->flags & self::SECURED ? 'https:' : 'http:') . $url;

		return $url;
	}

	private function setMask($mask, array $metadata)
	{
		$this->mask = $mask;

		if (substr($mask, 0, 2) === '//') {
			$this->type = self::HOST;

		} elseif (substr($mask, 0, 1) === '/') {
			$this->type = self::PATH;

		} else {
			$this->type = self::RELATIVE;
		}

		foreach ($metadata as $name => $meta) {
			if (!is_array($meta)) {
				$metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT);

			} elseif (array_key_exists(self::VALUE, $meta)) {
				$metadata[$name]['fixity'] = self::CONSTANT;
			}
		}

		$parts = NStrings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/');

		$this->xlat = array();
		$i = count($parts) - 1;

		if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {

			$matches = NStrings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/');

			foreach ($matches as $match) {
				list(, $param, $name, $pattern, $class) = $match;

				if ($class !== '') {
					if (!isset(self::$styles[$class])) {
						throw new InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
					}
					$meta = self::$styles[$class];

				} elseif (isset(self::$styles['?' . $name])) {
					$meta = self::$styles['?' . $name];

				} else {
					$meta = self::$styles['?#'];
				}

				if (isset($metadata[$name])) {
					$meta = $metadata[$name] + $meta;
				}

				if (array_key_exists(self::VALUE, $meta)) {
					$meta['fixity'] = self::OPTIONAL;
				}

				unset($meta['pattern']);
				$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);

				$metadata[$name] = $meta;
				if ($param !== '') {
					$this->xlat[$name] = $param;
				}
			}
			$i -= 6;
		}

		$brackets = 0;
		$re = '';
		$sequence = array();
		$autoOptional = array(0, 0);
		do {
			array_unshift($sequence, $parts[$i]);
			$re = preg_quote($parts[$i], '#') . $re;
			if ($i === 0) {
				break;
			}
			$i--;

			$part = $parts[$i];
			if ($part === '[' || $part === ']' || $part === '[!') {
				$brackets += $part[0] === '[' ? -1 : 1;
				if ($brackets < 0) {
					throw new InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
				}
				array_unshift($sequence, $part);
				$re = ($part[0] === '[' ? '(?:' : ')?') . $re;
				$i -= 5;
				continue;
			}

			$class = $parts[$i]; $i--;
			$pattern = trim($parts[$i]); $i--;
			$default = $parts[$i]; $i--;
			$name = $parts[$i]; $i--;
			array_unshift($sequence, $name);

			if ($name[0] === '?') {
				$re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re;
				$sequence[1] = substr($name, 1) . $sequence[1];
				continue;
			}

			if (preg_match('#[^a-z0-9_-]#i', $name)) {
				throw new InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given.");
			}

			if ($class !== '') {
				if (!isset(self::$styles[$class])) {
					throw new InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
				}
				$meta = self::$styles[$class];

			} elseif (isset(self::$styles[$name])) {
				$meta = self::$styles[$name];

			} else {
				$meta = self::$styles['#'];
			}

			if (isset($metadata[$name])) {
				$meta = $metadata[$name] + $meta;
			}

			if ($pattern == '' && isset($meta[self::PATTERN])) {
				$pattern = $meta[self::PATTERN];
			}

			if ($default !== '') {
				$meta[self::VALUE] = (string) substr($default, 1);
				$meta['fixity'] = self::PATH_OPTIONAL;
			}

			$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
			if (array_key_exists(self::VALUE, $meta)) {
				if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
					$meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];

				} elseif (isset($meta[self::FILTER_OUT])) {
					$meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);

				} else {
					$meta['defOut'] = $meta[self::VALUE];
				}
			}
			$meta[self::PATTERN] = "#(?:$pattern)$#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');

			$re = '(?P<' . str_replace('-', '___', $name) . '>(?U)' . $pattern . ')' . $re;
			if ($brackets) {
				if (!isset($meta[self::VALUE])) {
					$meta[self::VALUE] = $meta['defOut'] = NULL;
				}
				$meta['fixity'] = self::PATH_OPTIONAL;

			} elseif (isset($meta['fixity'])) {
				$re = '(?:' . substr_replace($re, ')?', strlen($re) - $autoOptional[0], 0);
				array_splice($sequence, count($sequence) - $autoOptional[1], 0, array(']', ''));
				array_unshift($sequence, '[', '');
				$meta['fixity'] = self::PATH_OPTIONAL;

			} else {
				$autoOptional = array(strlen($re), count($sequence));
			}

			$metadata[$name] = $meta;
		} while (TRUE);

		if ($brackets) {
			throw new InvalidArgumentException("Missing closing ']' in mask '$mask'.");
		}

		$this->re = '#' . $re . '/?$#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
		$this->metadata = $metadata;
		$this->sequence = $sequence;
	}

	function getMask()
	{
		return $this->mask;
	}

	function getDefaults()
	{
		$defaults = array();
		foreach ($this->metadata as $name => $meta) {
			if (isset($meta['fixity'])) {
				$defaults[$name] = $meta[self::VALUE];
			}
		}
		return $defaults;
	}

	function getTargetPresenter()
	{
		if ($this->flags & self::ONE_WAY) {
			return FALSE;
		}

		$m = $this->metadata;
		$module = '';

		if (isset($m[self::MODULE_KEY])) {
			if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
				$module = $m[self::MODULE_KEY][self::VALUE] . ':';
			} else {
				return NULL;
			}
		}

		if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
			return $module . $m[self::PRESENTER_KEY][self::VALUE];
		}
		return NULL;
	}

	private static function renameKeys($arr, $xlat)
	{
		if (empty($xlat)) {
			return $arr;
		}

		$res = array();
		$occupied = array_flip($xlat);
		foreach ($arr as $k => $v) {
			if (isset($xlat[$k])) {
				$res[$xlat[$k]] = $v;

			} elseif (!isset($occupied[$k])) {
				$res[$k] = $v;
			}
		}
		return $res;
	}

	private static function action2path($s)
	{
		$s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
		$s = strtolower($s);
		$s = rawurlencode($s);
		return $s;
	}

	private static function path2action($s)
	{
		$s = strtolower($s);
		$s = preg_replace('#-(?=[a-z])#', ' ', $s);
		$s = substr(ucwords('x' . $s), 1);

		$s = str_replace(' ', '', $s);
		return $s;
	}

	private static function presenter2path($s)
	{
		$s = strtr($s, ':', '.');
		$s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
		$s = strtolower($s);
		$s = rawurlencode($s);
		return $s;
	}

	private static function path2presenter($s)
	{
		$s = strtolower($s);
		$s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
		$s = ucwords($s);
		$s = str_replace('. ', ':', $s);
		$s = str_replace('- ', '', $s);
		return $s;
	}

	static function addStyle($style, $parent = '#')
	{
		if (isset(self::$styles[$style])) {
			throw new InvalidArgumentException("Style '$style' already exists.");
		}

		if ($parent !== NULL) {
			if (!isset(self::$styles[$parent])) {
				throw new InvalidArgumentException("Parent style '$parent' doesn't exist.");
			}
			self::$styles[$style] = self::$styles[$parent];

		} else {
			self::$styles[$style] = array();
		}
	}

	static function setStyleProperty($style, $key, $value)
	{
		if (!isset(self::$styles[$style])) {
			throw new InvalidArgumentException("Style '$style' doesn't exist.");
		}
		self::$styles[$style][$key] = $value;
	}

}

class NArrayList extends NObject implements ArrayAccess, Countable, IteratorAggregate
{
	private $list = array();

	function getIterator()
	{
		return new ArrayIterator($this->list);
	}

	function count()
	{
		return count($this->list);
	}

	function offsetSet($index, $value)
	{
		if ($index === NULL) {
			$this->list[] = $value;

		} elseif ($index < 0 || $index >= count($this->list)) {
			throw new OutOfRangeException("Offset invalid or out of range");

		} else {
			$this->list[(int) $index] = $value;
		}
	}

	function offsetGet($index)
	{
		if ($index < 0 || $index >= count($this->list)) {
			throw new OutOfRangeException("Offset invalid or out of range");
		}
		return $this->list[(int) $index];
	}

	function offsetExists($index)
	{
		return $index >= 0 && $index < count($this->list);
	}

	function offsetUnset($index)
	{
		if ($index < 0 || $index >= count($this->list)) {
			throw new OutOfRangeException("Offset invalid or out of range");
		}
		array_splice($this->list, (int) $index, 1);
	}

}

class NRouteList extends NArrayList implements IRouter
{

	private $cachedRoutes;

	private $module;

	function __construct($module = NULL)
	{
		$this->module = $module ? $module . ':' : '';
	}

	function match(IHttpRequest $httpRequest)
	{
		foreach ($this as $route) {
			$appRequest = $route->match($httpRequest);
			if ($appRequest !== NULL) {
				$appRequest->setPresenterName($this->module . $appRequest->getPresenterName());
				return $appRequest;
			}
		}
		return NULL;
	}

	function constructUrl(NPresenterRequest $appRequest, NUrl $refUrl)
	{
		if ($this->cachedRoutes === NULL) {
			$routes = array();
			$routes['*'] = array();

			foreach ($this as $route) {
				$presenter = $route instanceof NRoute ? $route->getTargetPresenter() : NULL;

				if ($presenter === FALSE) {
					continue;
				}

				if (is_string($presenter)) {
					$presenter = strtolower($presenter);
					if (!isset($routes[$presenter])) {
						$routes[$presenter] = $routes['*'];
					}
					$routes[$presenter][] = $route;

				} else {
					foreach ($routes as $id => $foo) {
						$routes[$id][] = $route;
					}
				}
			}

			$this->cachedRoutes = $routes;
		}

		if ($this->module) {
			if (strncasecmp($tmp = $appRequest->getPresenterName(), $this->module, strlen($this->module)) === 0) {
				$appRequest = clone $appRequest;
				$appRequest->setPresenterName(substr($tmp, strlen($this->module)));
			} else {
				return NULL;
			}
		}

		$presenter = strtolower($appRequest->getPresenterName());
		if (!isset($this->cachedRoutes[$presenter])) {
			$presenter = '*';
		}

		foreach ($this->cachedRoutes[$presenter] as $route) {
			$url = $route->constructUrl($appRequest, $refUrl);
			if ($url !== NULL) {
				return $url;
			}
		}

		return NULL;
	}

	function offsetSet($index, $route)
	{
		if (!$route instanceof IRouter) {
			throw new InvalidArgumentException("Argument must be IRouter descendant.");
		}
		parent::offsetSet($index, $route);
	}

}

class NSimpleRouter extends NObject implements IRouter
{
	const PRESENTER_KEY = 'presenter';
	const MODULE_KEY = 'module';

	private $module = '';

	private $defaults;

	private $flags;

	function __construct($defaults = array(), $flags = 0)
	{
		if (is_string($defaults)) {
			$a = strrpos($defaults, ':');
			if (!$a) {
				throw new InvalidArgumentException("Argument must be array or string in format Presenter:action, '$defaults' given.");
			}
			$defaults = array(
				self::PRESENTER_KEY => substr($defaults, 0, $a),
				'action' => $a === strlen($defaults) - 1 ? NPresenter::DEFAULT_ACTION : substr($defaults, $a + 1),
			);
		}

		if (isset($defaults[self::MODULE_KEY])) {
			$this->module = $defaults[self::MODULE_KEY] . ':';
			unset($defaults[self::MODULE_KEY]);
		}

		$this->defaults = $defaults;
		$this->flags = $flags;
	}

	function match(IHttpRequest $httpRequest)
	{
		if ($httpRequest->getUrl()->getPathInfo() !== '') {
			return NULL;
		}

		$params = $httpRequest->getQuery();
		$params += $this->defaults;

		if (!isset($params[self::PRESENTER_KEY])) {
			throw new InvalidStateException('Missing presenter.');
		}

		$presenter = $this->module . $params[self::PRESENTER_KEY];
		unset($params[self::PRESENTER_KEY]);

		return new NPresenterRequest(
			$presenter,
			$httpRequest->getMethod(),
			$params,
			$httpRequest->getPost(),
			$httpRequest->getFiles(),
			array(NPresenterRequest::SECURED => $httpRequest->isSecured())
		);
	}

	function constructUrl(NPresenterRequest $appRequest, NUrl $refUrl)
	{
		$params = $appRequest->getParams();

		$presenter = $appRequest->getPresenterName();
		if (strncasecmp($presenter, $this->module, strlen($this->module)) === 0) {
			$params[self::PRESENTER_KEY] = substr($presenter, strlen($this->module));
		} else {
			return NULL;
		}

		foreach ($this->defaults as $key => $value) {
			if (isset($params[$key]) && $params[$key] == $value) {
				unset($params[$key]);
			}
		}

		$url = ($this->flags & self::SECURED ? 'https://' : 'http://') . $refUrl->getAuthority() . $refUrl->getPath();
		$sep = ini_get('arg_separator.input');
		$query = http_build_query($params, '', $sep ? $sep[0] : '&');
		if ($query != '') {
			$url .= '?' . $query;
		}
		return $url;
	}

	function getDefaults()
	{
		return $this->defaults;
	}

}

class NBadSignalException extends NBadRequestException
{

	protected $defaultCode = 403;

}

abstract class NComponent extends NObject implements IComponent
{

	private $parent;

	private $name;

	private $monitors = array();

	function __construct(IComponentContainer $parent = NULL, $name = NULL)
	{
		if ($parent !== NULL) {
			$parent->addComponent($this, $name);

		} elseif (is_string($name)) {
			$this->name = $name;
		}
	}

	function lookup($type, $need = TRUE)
	{
		if (!isset($this->monitors[$type])) {
			$obj = $this->parent;
			$path = self::NAME_SEPARATOR . $this->name;
			$depth = 1;
			while ($obj !== NULL) {
				if ($obj instanceof $type) {
					break;
				}
				$path = self::NAME_SEPARATOR . $obj->getName() . $path;
				$depth++;
				$obj = $obj->getParent();
				if ($obj === $this) {
					$obj = NULL;
				}
			}

			if ($obj) {
				$this->monitors[$type] = array($obj, $depth, substr($path, 1), FALSE);

			} else {
				$this->monitors[$type] = array(NULL, NULL, NULL, FALSE);
			}
		}

		if ($need && $this->monitors[$type][0] === NULL) {
			throw new InvalidStateException("Component '$this->name' is not attached to '$type'.");
		}

		return $this->monitors[$type][0];
	}

	function lookupPath($type, $need = TRUE)
	{
		$this->lookup($type, $need);
		return $this->monitors[$type][2];
	}

	function monitor($type)
	{
		if (empty($this->monitors[$type][3])) {
			if ($obj = $this->lookup($type, FALSE)) {
				$this->attached($obj);
			}
			$this->monitors[$type][3] = TRUE;
		}
	}

	function unmonitor($type)
	{
		unset($this->monitors[$type]);
	}

	protected function attached($obj)
	{
	}

	protected function detached($obj)
	{
	}

	final function getName()
	{
		return $this->name;
	}

	final function getParent()
	{
		return $this->parent;
	}

	function setParent(IComponentContainer $parent = NULL, $name = NULL)
	{
		if ($parent === NULL && $this->parent === NULL && $name !== NULL) {
			$this->name = $name;
			return $this;

		} elseif ($parent === $this->parent && $name === NULL) {
			return $this;
		}

		if ($this->parent !== NULL && $parent !== NULL) {
			throw new InvalidStateException("Component '$this->name' already has a parent.");
		}

		if ($parent === NULL) {
			$this->refreshMonitors(0);
			$this->parent = NULL;

		} else {
			$this->validateParent($parent);
			$this->parent = $parent;
			if ($name !== NULL) {
				$this->name = $name;
			}

			$tmp = array();
			$this->refreshMonitors(0, $tmp);
		}
		return $this;
	}

	protected function validateParent(IComponentContainer $parent)
	{
	}

	private function refreshMonitors($depth, & $missing = NULL, & $listeners = array())
	{
		if ($this instanceof IComponentContainer) {
			foreach ($this->getComponents() as $component) {
				if ($component instanceof NComponent) {
					$component->refreshMonitors($depth + 1, $missing, $listeners);
				}
			}
		}

		if ($missing === NULL) {
			foreach ($this->monitors as $type => $rec) {
				if (isset($rec[1]) && $rec[1] > $depth) {
					if ($rec[3]) {
						$this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
						$listeners[] = array($this, $rec[0]);
					} else {
						unset($this->monitors[$type]);
					}
				}
			}

		} else {
			foreach ($this->monitors as $type => $rec) {
				if (isset($rec[0])) {
					continue;

				} elseif (!$rec[3]) {
					unset($this->monitors[$type]);

				} elseif (isset($missing[$type])) {
					$this->monitors[$type] = array(NULL, NULL, NULL, TRUE);

				} else {
					$this->monitors[$type] = NULL;
					if ($obj = $this->lookup($type, FALSE)) {
						$listeners[] = array($this, $obj);
					} else {
						$missing[$type] = TRUE;
					}
					$this->monitors[$type][3] = TRUE;
				}
			}
		}

		if ($depth === 0) {
			$method = $missing === NULL ? 'detached' : 'attached';
			foreach ($listeners as $item) {
				$item[0]->$method($item[1]);
			}
		}
	}

	function __clone()
	{
		if ($this->parent === NULL) {
			return;

		} elseif ($this->parent instanceof NComponentContainer) {
			$this->parent = $this->parent->_isCloning();
			if ($this->parent === NULL) {
				$this->refreshMonitors(0);
			}

		} else {
			$this->parent = NULL;
			$this->refreshMonitors(0);
		}
	}

	final function __wakeup()
	{
		throw new NotImplementedException;
	}

}

class NComponentContainer extends NComponent implements IComponentContainer
{

	private $components = array();

	private $cloning;

	function addComponent(IComponent $component, $name, $insertBefore = NULL)
	{
		if ($name === NULL) {
			$name = $component->getName();
		}

		if (is_int($name)) {
			$name = (string) $name;

		} elseif (!is_string($name)) {
			throw new InvalidArgumentException("Component name must be integer or string, " . gettype($name) . " given.");

		} elseif (!preg_match('#^[a-zA-Z0-9_]+$#', $name)) {
			throw new InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
		}

		if (isset($this->components[$name])) {
			throw new InvalidStateException("Component with name '$name' already exists.");
		}

		$obj = $this;
		do {
			if ($obj === $component) {
				throw new InvalidStateException("Circular reference detected while adding component '$name'.");
			}
			$obj = $obj->getParent();
		} while ($obj !== NULL);

		$this->validateChildComponent($component);

		try {
			if (isset($this->components[$insertBefore])) {
				$tmp = array();
				foreach ($this->components as $k => $v) {
					if ($k === $insertBefore) {
						$tmp[$name] = $component;
					}
					$tmp[$k] = $v;
				}
				$this->components = $tmp;
			} else {
				$this->components[$name] = $component;
			}
			$component->setParent($this, $name);

		} catch (Exception $e) {
			unset($this->components[$name]);
			throw $e;
		}
	}

	function removeComponent(IComponent $component)
	{
		$name = $component->getName();
		if (!isset($this->components[$name]) || $this->components[$name] !== $component) {
			throw new InvalidArgumentException("Component named '$name' is not located in this container.");
		}

		unset($this->components[$name]);
		$component->setParent(NULL);
	}

	final function getComponent($name, $need = TRUE)
	{
		if (is_int($name)) {
			$name = (string) $name;

		} elseif (!is_string($name)) {
			throw new InvalidArgumentException("Component name must be integer or string, " . gettype($name) . " given.");

		} else {
			$a = strpos($name, self::NAME_SEPARATOR);
			if ($a !== FALSE) {
				$ext = (string) substr($name, $a + 1);
				$name = substr($name, 0, $a);
			}

			if ($name === '') {
				throw new InvalidArgumentException("Component or subcomponent name must not be empty string.");
			}
		}

		if (!isset($this->components[$name])) {
			$component = $this->createComponent($name);
			if ($component instanceof IComponent && $component->getParent() === NULL) {
				$this->addComponent($component, $name);
			}
		}

		if (isset($this->components[$name])) {
			if (!isset($ext)) {
				return $this->components[$name];

			} elseif ($this->components[$name] instanceof IComponentContainer) {
				return $this->components[$name]->getComponent($ext, $need);

			} elseif ($need) {
				throw new InvalidArgumentException("Component with name '$name' is not container and cannot have '$ext' component.");
			}

		} elseif ($need) {
			throw new InvalidArgumentException("Component with name '$name' does not exist.");
		}
	}

	protected function createComponent($name)
	{
		$ucname = ucfirst($name);
		$method = 'createComponent' . $ucname;
		if ($ucname !== $name && method_exists($this, $method) && $this->getReflection()->getMethod($method)->getName() === $method) {
			$component = $this->$method($name);
			if (!$component instanceof IComponent && !isset($this->components[$name])) {
				$class = get_class($this);
				throw new UnexpectedValueException("Method $class::$method() did not return or create the desired component.");
			}
			return $component;
		}
	}

	final function getComponents($deep = FALSE, $filterType = NULL)
	{
		$iterator = new NRecursiveComponentIterator($this->components);
		if ($deep) {
			$deep = $deep > 0 ? RecursiveIteratorIterator::SELF_FIRST : RecursiveIteratorIterator::CHILD_FIRST;
			$iterator = new RecursiveIteratorIterator($iterator, $deep);
		}
		if ($filterType) {
			$iterator = new NInstanceFilterIterator($iterator, $filterType);
		}
		return $iterator;
	}

	protected function validateChildComponent(IComponent $child)
	{
	}

	function __clone()
	{
		if ($this->components) {
			$oldMyself = reset($this->components)->getParent();
			$oldMyself->cloning = $this;
			foreach ($this->components as $name => $component) {
				$this->components[$name] = clone $component;
			}
			$oldMyself->cloning = NULL;
		}
		parent::__clone();
	}

	function _isCloning()
	{
		return $this->cloning;
	}

}

abstract class NPresenterComponent extends NComponentContainer implements ISignalReceiver, IStatePersistent, ArrayAccess
{

	protected $params = array();

	function __construct(IComponentContainer $parent = NULL, $name = NULL)
	{
		$this->monitor('NPresenter');
		parent::__construct($parent, $name);
	}

	function getPresenter($need = TRUE)
	{
		return $this->lookup('NPresenter', $need);
	}

	function getUniqueId()
	{
		return $this->lookupPath('NPresenter', TRUE);
	}

	protected function attached($presenter)
	{
		if ($presenter instanceof NPresenter) {
			$this->loadState($presenter->popGlobalParams($this->getUniqueId()));
		}
	}

	protected function tryCall($method, array $params)
	{
		$rc = $this->getReflection();
		if ($rc->hasMethod($method)) {
			$rm = $rc->getMethod($method);
			if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
				$this->checkRequirements($rm);
				$rm->invokeNamedArgs($this, $params);
				return TRUE;
			}
		}
		return FALSE;
	}

	function checkRequirements($element)
	{
	}

	function getReflection()
	{
		return new NPresenterComponentReflection($this);
	}

	function loadState(array $params)
	{
		foreach ($this->getReflection()->getPersistentParams() as $nm => $meta) {
			if (isset($params[$nm])) {
				if (isset($meta['def'])) {
					if (is_array($params[$nm]) && !is_array($meta['def'])) {
						$params[$nm] = $meta['def'];
					} else {
						settype($params[$nm], gettype($meta['def']));
					}
				}
				$this->$nm = & $params[$nm];
			}
		}
		$this->params = $params;
	}

	function saveState(array & $params, $reflection = NULL)
	{
		$reflection = $reflection === NULL ? $this->getReflection() : $reflection;
		foreach ($reflection->getPersistentParams() as $nm => $meta) {

			if (isset($params[$nm])) {
				$val = $params[$nm];

			} elseif (array_key_exists($nm, $params)) {
				continue;

			} elseif (!isset($meta['since']) || $this instanceof $meta['since']) {
				$val = $this->$nm;

			} else {
				continue;
			}

			if (is_object($val)) {
				$class = get_class($this);
				throw new InvalidStateException("Persistent parameter must be scalar or array, $class::\$$nm is " . gettype($val));

			} else {
				if (isset($meta['def'])) {
					settype($val, gettype($meta['def']));
					if ($val === $meta['def']) {
						$val = NULL;
					}
				} else {
					if ((string) $val === '') {
						$val = NULL;
					}
				}
				$params[$nm] = $val;
			}
		}
	}

	final function getParam($name = NULL, $default = NULL)
	{
		if (func_num_args() === 0) {
			return $this->params;

		} elseif (isset($this->params[$name])) {
			return $this->params[$name];

		} else {
			return $default;
		}
	}

	final function getParamId($name)
	{
		$uid = $this->getUniqueId();
		return $uid === '' ? $name : $uid . self::NAME_SEPARATOR . $name;
	}

	static function getPersistentParams()
	{
		$arg = func_get_arg(0);
		$rc = new NClassReflection($arg);
		$params = array();
		foreach ($rc->getProperties(ReflectionProperty::IS_PUBLIC) as $rp) {
			if (!$rp->isStatic() && $rp->hasAnnotation('persistent')) {
				$params[] = $rp->getName();
			}
		}
		return $params;
	}

	function signalReceived($signal)
	{
		if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) {
			$class = get_class($this);
			throw new NBadSignalException("There is no handler for signal '$signal' in class $class.");
		}
	}

	function formatSignalMethod($signal)
	{
		return $signal == NULL ? NULL : 'handle' . $signal;
	}

	function link($destination, $args = array())
	{
		if (!is_array($args)) {
			$args = func_get_args();
			array_shift($args);
		}

		try {
			return $this->getPresenter()->createRequest($this, $destination, $args, 'link');

		} catch (NInvalidLinkException $e) {
			return $this->getPresenter()->handleInvalidLink($e);
		}
	}

	function lazyLink($destination, $args = array())
	{
		if (!is_array($args)) {
			$args = func_get_args();
			array_shift($args);
		}

		return new NLink($this, $destination, $args);
	}

	function isLinkCurrent($destination = NULL, $args = array())
	{
		if ($destination !== NULL) {
			if (!is_array($args)) {
				$args = func_get_args();
				array_shift($args);
			}
			$this->link($destination, $args);
		}
		return $this->getPresenter()->getLastCreatedRequestFlag('current');
	}

	function redirect($code, $destination = NULL, $args = array())
	{
		if (!is_numeric($code)) {
			$args = $destination;
			$destination = $code;
			$code = NULL;
		}

		if (!is_array($args)) {
			$args = func_get_args();
			if (is_numeric(array_shift($args))) {
				array_shift($args);
			}
		}

		$presenter = $this->getPresenter();
		$presenter->redirectUrl($presenter->createRequest($this, $destination, $args, 'redirect'), $code);
	}

	final function offsetSet($name, $component)
	{
		$this->addComponent($component, $name);
	}

	final function offsetGet($name)
	{
		return $this->getComponent($name, TRUE);
	}

	final function offsetExists($name)
	{
		return $this->getComponent($name, FALSE) !== NULL;
	}

	final function offsetUnset($name)
	{
		$component = $this->getComponent($name, FALSE);
		if ($component !== NULL) {
			$this->removeComponent($component);
		}
	}

}

abstract class NControl extends NPresenterComponent implements IPartiallyRenderable
{

	private $template;

	private $invalidSnippets = array();

	public $snippetMode;

	final function getTemplate()
	{
		if ($this->template === NULL) {
			$value = $this->createTemplate();
			if (!$value instanceof ITemplate && $value !== NULL) {
				$class2 = get_class($value); $class = get_class($this);
				throw new UnexpectedValueException("Object returned by $class::createTemplate() must be instance of ITemplate, '$class2' given.");
			}
			$this->template = $value;
		}
		return $this->template;
	}

	protected function createTemplate($class = NULL)
	{
		$template = $class ? new $class : new NFileTemplate;
		$presenter = $this->getPresenter(FALSE);
		$template->onPrepareFilters[] = callback($this, 'templatePrepareFilters');
		$template->registerHelperLoader('NTemplateHelpers::loader');

		$template->control = $this;
		$template->presenter = $presenter;
		if ($presenter instanceof NPresenter) {
			$template->setCacheStorage($presenter->getContext()->templateCacheStorage);
			$template->user = $presenter->getUser();
			$template->netteHttpResponse = $presenter->getHttpResponse();
			$template->netteCacheStorage = $presenter->getContext()->cacheStorage;
			$template->baseUri = $template->baseUrl = rtrim($presenter->getHttpRequest()->getUrl()->getBaseUrl(), '/');
			$template->basePath = preg_replace('#https?://[^/]+#A', '', $template->baseUrl);

			if ($presenter->hasFlashSession()) {
				$id = $this->getParamId('flash');
				$template->flashes = $presenter->getFlashSession()->$id;
			}
		}
		if (!isset($template->flashes) || !is_array($template->flashes)) {
			$template->flashes = array();
		}

		return $template;
	}

	function templatePrepareFilters($template)
	{
		$template->registerFilter(new NLatteFilter);
	}

	function getWidget($name)
	{
		return $this->getComponent($name);
	}

	function flashMessage($message, $type = 'info')
	{
		$id = $this->getParamId('flash');
		$messages = $this->getPresenter()->getFlashSession()->$id;
		$messages[] = $flash = (object) array(
			'message' => $message,
			'type' => $type,
		);
		$this->getTemplate()->flashes = $messages;
		$this->getPresenter()->getFlashSession()->$id = $messages;
		return $flash;
	}

	function invalidateControl($snippet = NULL)
	{
		$this->invalidSnippets[$snippet] = TRUE;
	}

	function validateControl($snippet = NULL)
	{
		if ($snippet === NULL) {
			$this->invalidSnippets = array();

		} else {
			unset($this->invalidSnippets[$snippet]);
		}
	}

	function isControlInvalid($snippet = NULL)
	{
		if ($snippet === NULL) {
			if (count($this->invalidSnippets) > 0) {
				return TRUE;

			} else {
				foreach ($this->getComponents() as $component) {
					if ($component instanceof IRenderable && $component->isControlInvalid()) {

						return TRUE;
					}
				}
				return FALSE;
			}

		} else {
			return isset($this->invalidSnippets[NULL]) || isset($this->invalidSnippets[$snippet]);
		}
	}

	function getSnippetId($name = NULL)
	{

		return 'snippet-' . $this->getUniqueId() . '-' . $name;
	}

}

class NFormContainer extends NComponentContainer implements ArrayAccess
{

	public $onValidate;

	protected $currentGroup;

	protected $valid;

	function setDefaults($values, $erase = FALSE)
	{
		$form = $this->getForm(FALSE);
		if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
			$this->setValues($values, $erase);
		}
		return $this;
	}

	function setValues($values, $erase = FALSE)
	{
		if ($values instanceof Traversable) {
			$values = iterator_to_array($values);

		} elseif (!is_array($values)) {
			throw new InvalidArgumentException("First parameter must be an array, " . gettype($values) ." given.");
		}

		foreach ($this->getComponents() as $name => $control) {
			if ($control instanceof IFormControl) {
				if (array_key_exists($name, $values)) {
					$control->setValue($values[$name]);

				} elseif ($erase) {
					$control->setValue(NULL);
				}

			} elseif ($control instanceof NFormContainer) {
				if (array_key_exists($name, $values)) {
					$control->setValues($values[$name], $erase);

				} elseif ($erase) {
					$control->setValues(array(), $erase);
				}
			}
		}
		return $this;
	}

	function getValues()
	{
		$values = new NArrayHash;
		foreach ($this->getComponents() as $name => $control) {
			if ($control instanceof IFormControl && !$control->isDisabled() && !$control instanceof ISubmitterControl) {
				$values->$name = $control->getValue();

			} elseif ($control instanceof NFormContainer) {
				$values->$name = $control->getValues();
			}
		}
		return $values;
	}

	function isValid()
	{
		if ($this->valid === NULL) {
			$this->validate();
		}
		return $this->valid;
	}

	function validate()
	{
		$this->valid = TRUE;
		$this->onValidate($this);
		foreach ($this->getControls() as $control) {
			if (!$control->getRules()->validate()) {
				$this->valid = FALSE;
			}
		}
	}

	function setCurrentGroup(NFormGroup $group = NULL)
	{
		$this->currentGroup = $group;
		return $this;
	}

	function getCurrentGroup()
	{
		return $this->currentGroup;
	}

	function addComponent(IComponent $component, $name, $insertBefore = NULL)
	{
		parent::addComponent($component, $name, $insertBefore);
		if ($this->currentGroup !== NULL && $component instanceof IFormControl) {
			$this->currentGroup->add($component);
		}
	}

	function getControls()
	{
		return $this->getComponents(TRUE, 'IFormControl');
	}

	function getForm($need = TRUE)
	{
		return $this->lookup('NForm', $need);
	}

	function addText($name, $label = NULL, $cols = NULL, $maxLength = NULL)
	{
		return $this[$name] = new NTextInput($label, $cols, $maxLength);
	}

	function addPassword($name, $label = NULL, $cols = NULL, $maxLength = NULL)
	{
		$control = new NTextInput($label, $cols, $maxLength);
		$control->setType('password');
		return $this[$name] = $control;
	}

	function addTextArea($name, $label = NULL, $cols = 40, $rows = 10)
	{
		return $this[$name] = new NTextArea($label, $cols, $rows);
	}

	function addUpload($name, $label = NULL)
	{
		return $this[$name] = new NUploadControl($label);
	}

	function addHidden($name, $default = NULL)
	{
		$control = new NHiddenField;
		$control->setDefaultValue($default);
		return $this[$name] = $control;
	}

	function addCheckbox($name, $caption = NULL)
	{
		return $this[$name] = new NCheckbox($caption);
	}

	function addRadioList($name, $label = NULL, array $items = NULL)
	{
		return $this[$name] = new NRadioList($label, $items);
	}

	function addSelect($name, $label = NULL, array $items = NULL, $size = NULL)
	{
		return $this[$name] = new NSelectBox($label, $items, $size);
	}

	function addMultiSelect($name, $label = NULL, array $items = NULL, $size = NULL)
	{
		return $this[$name] = new NMultiSelectBox($label, $items, $size);
	}

	function addSubmit($name, $caption = NULL)
	{
		return $this[$name] = new NSubmitButton($caption);
	}

	function addButton($name, $caption)
	{
		return $this[$name] = new NButton($caption);
	}

	function addImage($name, $src = NULL, $alt = NULL)
	{
		return $this[$name] = new NImageButton($src, $alt);
	}

	function addContainer($name)
	{
		$control = new NFormContainer;
		$control->currentGroup = $this->currentGroup;
		return $this[$name] = $control;
	}

	final function offsetSet($name, $component)
	{
		$this->addComponent($component, $name);
	}

	final function offsetGet($name)
	{
		return $this->getComponent($name, TRUE);
	}

	final function offsetExists($name)
	{
		return $this->getComponent($name, FALSE) !== NULL;
	}

	final function offsetUnset($name)
	{
		$component = $this->getComponent($name, FALSE);
		if ($component !== NULL) {
			$this->removeComponent($component);
		}
	}

	final function __clone()
	{
		throw new NotImplementedException('Form cloning is not supported yet.');
	}

	function addFile($name, $label = NULL)
	{
		trigger_error(__METHOD__ . '() is deprecated; use addUpload() instead.', E_USER_WARNING);
		return $this->addUpload($name, $label);
	}

}

class NForm extends NFormContainer
{

	const EQUAL = ':equal',
		IS_IN = ':equal',
		FILLED = ':filled',
		VALID = ':valid';

	const PROTECTION = 'NHiddenField::validateEqual';

	const SUBMITTED = ':submitted';

	const MIN_LENGTH = ':minLength',
		MAX_LENGTH = ':maxLength',
		LENGTH = ':length',
		EMAIL = ':email',
		URL = ':url',
		REGEXP = ':regexp',
		PATTERN = ':pattern',
		INTEGER = ':integer',
		NUMERIC = ':integer',
		FLOAT = ':float',
		RANGE = ':range';

	const MAX_FILE_SIZE = ':fileSize',
		MIME_TYPE = ':mimeType',
		IMAGE = ':image';

	const GET = 'get',
		POST = 'post';

	const TRACKER_ID = '_form_';

	const PROTECTOR_ID = '_token_';

	public $onSuccess;

	public $onError;

	public $onSubmit;

	public $onInvalidSubmit;

	private $submittedBy;

	private $httpData;

	private $element;

	private $renderer;

	private $translator;

	private $groups = array();

	private $errors = array();

	function __construct($name = NULL)
	{
		$this->element = NHtml::el('form');
		$this->element->action = '';
		$this->element->method = self::POST;
		$this->element->id = 'frm-' . $name;

		$this->monitor(__CLASS__);
		if ($name !== NULL) {
			$tracker = new NHiddenField($name);
			$tracker->unmonitor(__CLASS__);
			$this[self::TRACKER_ID] = $tracker;
		}
		parent::__construct(NULL, $name);
	}

	protected function attached($obj)
	{
		if ($obj instanceof self) {
			throw new InvalidStateException('Nested forms are forbidden.');
		}
	}

	final function getForm($need = TRUE)
	{
		return $this;
	}

	function setAction($url)
	{
		$this->element->action = $url;
		return $this;
	}

	function getAction()
	{
		return $this->element->action;
	}

	function setMethod($method)
	{
		if ($this->httpData !== NULL) {
			throw new InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
		}
		$this->element->method = strtolower($method);
		return $this;
	}

	function getMethod()
	{
		return $this->element->method;
	}

	function addProtection($message = NULL, $timeout = NULL)
	{
		$session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
		$key = "key$timeout";
		if (isset($session->$key)) {
			$token = $session->$key;
		} else {
			$session->$key = $token = NStrings::random();
		}
		$session->setExpiration($timeout, $key);
		$this[self::PROTECTOR_ID] = new NHiddenField($token);
		$this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
	}

	function addGroup($caption = NULL, $setAsCurrent = TRUE)
	{
		$group = new NFormGroup;
		$group->setOption('label', $caption);
		$group->setOption('visual', TRUE);

		if ($setAsCurrent) {
			$this->setCurrentGroup($group);
		}

		if (isset($this->groups[$caption])) {
			return $this->groups[] = $group;
		} else {
			return $this->groups[$caption] = $group;
		}
	}

	function removeGroup($name)
	{
		if (is_string($name) && isset($this->groups[$name])) {
			$group = $this->groups[$name];

		} elseif ($name instanceof NFormGroup && in_array($name, $this->groups, TRUE)) {
			$group = $name;
			$name = array_search($group, $this->groups, TRUE);

		} else {
			throw new InvalidArgumentException("Group not found in form '$this->name'");
		}

		foreach ($group->getControls() as $control) {
			$this->removeComponent($control);
		}

		unset($this->groups[$name]);
	}

	function getGroups()
	{
		return $this->groups;
	}

	function getGroup($name)
	{
		return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
	}

	function setTranslator(ITranslator $translator = NULL)
	{
		$this->translator = $translator;
		return $this;
	}

	final function getTranslator()
	{
		return $this->translator;
	}

	function isAnchored()
	{
		return TRUE;
	}

	final function isSubmitted()
	{
		if ($this->submittedBy === NULL) {
			$this->getHttpData();
			$this->submittedBy = !empty($this->httpData);
		}
		return $this->submittedBy;
	}

	function setSubmittedBy(ISubmitterControl $by = NULL)
	{
		$this->submittedBy = $by === NULL ? FALSE : $by;
		return $this;
	}

	final function getHttpData()
	{
		if ($this->httpData === NULL) {
			if (!$this->isAnchored()) {
				throw new InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
			}
			$this->httpData = (array) $this->receiveHttpData();
		}
		return $this->httpData;
	}

	function fireEvents()
	{
		if (!$this->isSubmitted()) {
			return;

		} elseif ($this->submittedBy instanceof ISubmitterControl) {
			if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
				$this->submittedBy->click();
				$valid = TRUE;
			} else {
				$this->submittedBy->onInvalidClick($this->submittedBy);
			}
		}

		if (isset($valid) || $this->isValid()) {
			$this->onSuccess($this);
		} else {
			$this->onError($this);
			if ($this->onInvalidSubmit) {
				trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
				$this->onInvalidSubmit($this);
			}
		}

		if ($this->onSuccess) {
			$this->onSubmit($this);
		} elseif ($this->onSubmit) {
			trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
			if (isset($valid) || $this->isValid()) {
				$this->onSubmit($this);
			}
		}
	}

	protected function receiveHttpData()
	{
		$httpRequest = $this->getHttpRequest();
		if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
			return;
		}

		if ($httpRequest->isMethod('post')) {
			$data = NArrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
		} else {
			$data = $httpRequest->getQuery();
		}

		if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
			if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
				return;
			}
		}

		return $data;
	}

	function getValues()
	{
		$values = parent::getValues();
		unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
		return $values;
	}

	function addError($message)
	{
		$this->valid = FALSE;
		if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
			$this->errors[] = $message;
		}
	}

	function getErrors()
	{
		return $this->errors;
	}

	function hasErrors()
	{
		return (bool) $this->getErrors();
	}

	function cleanErrors()
	{
		$this->errors = array();
		$this->valid = NULL;
	}

	function getElementPrototype()
	{
		return $this->element;
	}

	function setRenderer(IFormRenderer $renderer)
	{
		$this->renderer = $renderer;
		return $this;
	}

	final function getRenderer()
	{
		if ($this->renderer === NULL) {
			$this->renderer = new NDefaultFormRenderer;
		}
		return $this->renderer;
	}

	function render()
	{
		$args = func_get_args();
		array_unshift($args, $this);
		echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
	}

	function __toString()
	{
		try {
			return $this->getRenderer()->render($this);

		} catch (Exception $e) {
			if (func_get_args() && func_get_arg(0)) {
				throw $e;
			} else {
				NDebugger::toStringException($e);
			}
		}
	}

	protected function getHttpRequest()
	{
		return NEnvironment::getHttpRequest();
	}

	protected function getSession()
	{
		return NEnvironment::getSession();
	}

}

class NAppForm extends NForm implements ISignalReceiver
{

	function __construct(IComponentContainer $parent = NULL, $name = NULL)
	{
		parent::__construct();
		$this->monitor('NPresenter');
		if ($parent !== NULL) {
			$parent->addComponent($this, $name);
		}
	}

	function getPresenter($need = TRUE)
	{
		return $this->lookup('NPresenter', $need);
	}

	protected function attached($presenter)
	{
		if ($presenter instanceof NPresenter) {
			$name = $this->lookupPath('NPresenter');

			if (!isset($this->getElementPrototype()->id)) {
				$this->getElementPrototype()->id = 'frm-' . $name;
			}

			$this->setAction(new NLink(
				$presenter,
				$name . self::NAME_SEPARATOR . 'submit!',
				array()
			));

			if ($this->isSubmitted()) {
				foreach ($this->getControls() as $control) {
					$control->loadHttpData();
				}
			}
		}
		parent::attached($presenter);
	}

	function isAnchored()
	{
		return (bool) $this->getPresenter(FALSE);
	}

	protected function receiveHttpData()
	{
		$presenter = $this->getPresenter();
		if (!$presenter->isSignalReceiver($this, 'submit')) {
			return;
		}

		$isPost = $this->getMethod() === self::POST;
		$request = $presenter->getRequest();
		if ($request->isMethod('forward') || $request->isMethod('post') !== $isPost) {
			return;
		}

		if ($isPost) {
			return NArrays::mergeTree($request->getPost(), $request->getFiles());
		} else {
			return $request->getParams();
		}
	}

	function signalReceived($signal)
	{
		if ($signal === 'submit') {
			$this->fireEvents();
		} else {
			$class = get_class($this);
			throw new NBadSignalException("Missing handler for signal '$signal' in $class.");
		}
	}

}

class NInvalidLinkException extends Exception
{
}

class NLink extends NObject
{

	private $component;

	private $destination;

	private $params;

	function __construct(NPresenterComponent $component, $destination, array $params)
	{
		$this->component = $component;
		$this->destination = $destination;
		$this->params = $params;
	}

	function getDestination()
	{
		return $this->destination;
	}

	function setParam($key, $value)
	{
		$this->params[$key] = $value;
		return $this;
	}

	function getParam($key)
	{
		return isset($this->params[$key]) ? $this->params[$key] : NULL;
	}

	function getParams()
	{
		return $this->params;
	}

	function __toString()
	{
		try {
			return $this->component->link($this->destination, $this->params);

		} catch (Exception $e) {
			NDebugger::toStringException($e);
		}
	}

}

abstract class NPresenter extends NControl implements IPresenter
{

	const INVALID_LINK_SILENT = 1,
		INVALID_LINK_WARNING = 2,
		INVALID_LINK_EXCEPTION = 3;

	const SIGNAL_KEY = 'do',
		ACTION_KEY = 'action',
		FLASH_KEY = '_fid',
		DEFAULT_ACTION = 'default';

	public static $invalidLinkMode;

	public $onShutdown;

	private $request;

	private $response;

	public $autoCanonicalize = TRUE;

	public $absoluteUrls = FALSE;

	private $globalParams;

	private $globalState;

	private $globalStateSinces;

	private $action;

	private $view;

	private $layout;

	private $payload;

	private $signalReceiver;

	private $signal;

	private $ajaxMode;

	private $startupCheck;

	private $lastCreatedRequest;

	private $lastCreatedRequestFlag;

	private $context;

	final function getRequest()
	{
		return $this->request;
	}

	final function getPresenter($need = TRUE)
	{
		return $this;
	}

	final function getUniqueId()
	{
		return '';
	}

	function run(NPresenterRequest $request)
	{
		try {

			$this->request = $request;
			$this->payload = (object) NULL;
			$this->setParent($this->getParent(), $request->getPresenterName());

			$this->initGlobalParams();
			$this->checkRequirements($this->getReflection());
			$this->startup();
			if (!$this->startupCheck) {
				$class = $this->getReflection()->getMethod('startup')->getDeclaringClass()->getName();
				throw new InvalidStateException("Method $class::startup() or its descendant doesn't call parent::startup().");
			}

			$this->tryCall($this->formatActionMethod($this->getAction()), $this->params);

			if ($this->autoCanonicalize) {
				$this->canonicalize();
			}
			if ($this->getHttpRequest()->isMethod('head')) {
				$this->terminate();
			}

			$this->processSignal();

			$this->beforeRender();

			$this->tryCall($this->formatRenderMethod($this->getView()), $this->params);
			$this->afterRender();

			$this->saveGlobalState();
			if ($this->isAjax()) {
				$this->payload->state = $this->getGlobalState();
			}

			$this->sendTemplate();

		} catch (NAbortException $e) {

			if ($this->isAjax()) try {
				$hasPayload = (array) $this->payload; unset($hasPayload['state']);
				if ($this->response instanceof NTextResponse && $this->isControlInvalid()) {
					$this->snippetMode = TRUE;
					$this->response->send($this->getHttpRequest(), $this->getHttpResponse());
					$this->sendPayload();

				} elseif (!$this->response && $hasPayload) {
					$this->sendPayload();
				}
			} catch (NAbortException $e) { }

			if ($this->hasFlashSession()) {
				$this->getFlashSession()->setExpiration($this->response instanceof NRedirectResponse ? '+ 30 seconds': '+ 3 seconds');
			}

			$this->onShutdown($this, $this->response);
			$this->shutdown($this->response);

			return $this->response;
		}
	}

	protected function startup()
	{
		$this->startupCheck = TRUE;
	}

	protected function beforeRender()
	{
	}

	protected function afterRender()
	{
	}

	protected function shutdown($response)
	{
	}

	function checkRequirements($element)
	{
		$user = (array) $element->getAnnotation('User');
		if (in_array('loggedIn', $user) && !$this->getUser()->isLoggedIn()) {
			throw new NForbiddenRequestException;
		}
	}

	function processSignal()
	{
		if ($this->signal === NULL) {
			return;
		}

		$component = $this->signalReceiver === '' ? $this : $this->getComponent($this->signalReceiver, FALSE);
		if ($component === NULL) {
			throw new NBadSignalException("The signal receiver component '$this->signalReceiver' is not found.");

		} elseif (!$component instanceof ISignalReceiver) {
			throw new NBadSignalException("The signal receiver component '$this->signalReceiver' is not ISignalReceiver implementor.");
		}

		$component->signalReceived($this->signal);
		$this->signal = NULL;
	}

	final function getSignal()
	{
		return $this->signal === NULL ? NULL : array($this->signalReceiver, $this->signal);
	}

	final function isSignalReceiver($component, $signal = NULL)
	{
		if ($component instanceof NComponent) {
			$component = $component === $this ? '' : $component->lookupPath(__CLASS__, TRUE);
		}

		if ($this->signal === NULL) {
			return FALSE;

		} elseif ($signal === TRUE) {
			return $component === ''
				|| strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;

		} elseif ($signal === NULL) {
			return $this->signalReceiver === $component;

		} else {
			return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
		}
	}

	final function getAction($fullyQualified = FALSE)
	{
		return $fullyQualified ? ':' . $this->getName() . ':' . $this->action : $this->action;
	}

	function changeAction($action)
	{
		if (NStrings::match($action, "#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#")) {
			$this->action = $action;
			$this->view = $action;

		} else {
			throw new NBadRequestException("Action name '$action' is not alphanumeric string.");
		}
	}

	final function getView()
	{
		return $this->view;
	}

	function setView($view)
	{
		$this->view = (string) $view;
		return $this;
	}

	final function getLayout()
	{
		return $this->layout;
	}

	function setLayout($layout)
	{
		$this->layout = $layout === FALSE ? FALSE : (string) $layout;
		return $this;
	}

	function sendTemplate()
	{
		$template = $this->getTemplate();
		if (!$template) {
			return;
		}

		if ($template instanceof IFileTemplate && !$template->getFile()) {
			$files = $this->formatTemplateFiles();
			foreach ($files as $file) {
				if (is_file($file)) {
					$template->setFile($file);
					break;
				}
			}

			if (!$template->getFile()) {
				$file = preg_replace('#^.*([/\\\\].{1,70})$#U', "\xE2\x80\xA6\$1", reset($files));
				$file = strtr($file, '/', DIRECTORY_SEPARATOR);
				throw new NBadRequestException("Page not found. Missing template '$file'.");
			}
		}

		if ($this->layout !== FALSE) {
			$files = $this->formatLayoutTemplateFiles();
			foreach ($files as $file) {
				if (is_file($file)) {
					$template->layout = $file;
					$template->_extends = $file;
					break;
				}
			}

			if (empty($template->layout) && $this->layout !== NULL) {
				$file = preg_replace('#^.*([/\\\\].{1,70})$#U', "\xE2\x80\xA6\$1", reset($files));
				$file = strtr($file, '/', DIRECTORY_SEPARATOR);
				throw new FileNotFoundException("Layout not found. Missing template '$file'.");
			}
		}

		$this->sendResponse(new NTextResponse($template));
	}

	function formatLayoutTemplateFiles()
	{
		$name = $this->getName();
		$presenter = substr($name, strrpos(':' . $name, ':'));
		$layout = $this->layout ? $this->layout : 'layout';
		$dir = dirname(dirname($this->getReflection()->getFileName()));
		$list = array(
			"$dir/templates/$presenter/@$layout.latte",
			"$dir/templates/$presenter.@$layout.latte",
			"$dir/templates/$presenter/@$layout.phtml",
			"$dir/templates/$presenter.@$layout.phtml",
		);
		do {
			$list[] = "$dir/templates/@$layout.latte";
			$list[] = "$dir/templates/@$layout.phtml";
			$dir = dirname($dir);
		} while ($dir && ($name = substr($name, 0, strrpos($name, ':'))));
		return $list;
	}

	function formatTemplateFiles()
	{
		$name = $this->getName();
		$presenter = substr($name, strrpos(':' . $name, ':'));
		$dir = dirname(dirname($this->getReflection()->getFileName()));
		return array(
			"$dir/templates/$presenter/$this->view.latte",
			"$dir/templates/$presenter.$this->view.latte",
			"$dir/templates/$presenter/$this->view.phtml",
			"$dir/templates/$presenter.$this->view.phtml",
		);
	}

	protected static function formatActionMethod($action)
	{
		return 'action' . $action;
	}

	protected static function formatRenderMethod($view)
	{
		return 'render' . $view;
	}

	final function getPayload()
	{
		return $this->payload;
	}

	function isAjax()
	{
		if ($this->ajaxMode === NULL) {
			$this->ajaxMode = $this->getHttpRequest()->isAjax();
		}
		return $this->ajaxMode;
	}

	function sendPayload()
	{
		$this->sendResponse(new NJsonResponse($this->payload));
	}

	function sendResponse(IPresenterResponse $response)
	{
		$this->response = $response;
		$this->terminate();
	}

	function terminate()
	{
		if (func_num_args() !== 0) {
			trigger_error(__METHOD__ . ' is not intended to send a Application\Response; use sendResponse() instead.', E_USER_WARNING);
			$this->sendResponse(func_get_arg(0));
		}
		throw new NAbortException();
	}

	function forward($destination, $args = array())
	{
		if ($destination instanceof NPresenterRequest) {
			$this->sendResponse(new NForwardResponse($destination));

		} elseif (!is_array($args)) {
			$args = func_get_args();
			array_shift($args);
		}

		$this->createRequest($this, $destination, $args, 'forward');
		$this->sendResponse(new NForwardResponse($this->lastCreatedRequest));
	}

	function redirectUrl($url, $code = NULL)
	{
		if ($this->isAjax()) {
			$this->payload->redirect = (string) $url;
			$this->sendPayload();

		} elseif (!$code) {
			$code = $this->getHttpRequest()->isMethod('post')
				? IHttpResponse::S303_POST_GET
				: IHttpResponse::S302_FOUND;
		}
		$this->sendResponse(new NRedirectResponse($url, $code));
	}

	function redirectUri($url, $code = NULL)
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::redirectUrl() instead.', E_USER_WARNING);
		$this->redirectUrl($url, $code);
	}

	function backlink()
	{
		return $this->getAction(TRUE);
	}

	function getLastCreatedRequest()
	{
		return $this->lastCreatedRequest;
	}

	function getLastCreatedRequestFlag($flag)
	{
		return !empty($this->lastCreatedRequestFlag[$flag]);
	}

	function canonicalize()
	{
		if (!$this->isAjax() && ($this->request->isMethod('get') || $this->request->isMethod('head'))) {
			$url = $this->createRequest($this, $this->action, $this->getGlobalState() + $this->request->params, 'redirectX');
			if ($url !== NULL && !$this->getHttpRequest()->getUrl()->isEqual($url)) {
				$this->sendResponse(new NRedirectResponse($url, IHttpResponse::S301_MOVED_PERMANENTLY));
			}
		}
	}

	function lastModified($lastModified, $etag = NULL, $expire = NULL)
	{
		if ($expire !== NULL) {
			$this->getHttpResponse()->setExpiration($expire);
		}

		if (!$this->getHttpContext()->isModified($lastModified, $etag)) {
			$this->terminate();
		}
	}

	final protected function createRequest($component, $destination, array $args, $mode)
	{

		static $presenterFactory, $router, $refUrl;
		if ($presenterFactory === NULL) {
			$presenterFactory = $this->getApplication()->getPresenterFactory();
			$router = $this->getApplication()->getRouter();
			$refUrl = new NUrl($this->getHttpRequest()->getUrl());
			$refUrl->setPath($this->getHttpRequest()->getUrl()->getScriptPath());
		}

		$this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;

		$a = strpos($destination, '#');
		if ($a === FALSE) {
			$fragment = '';
		} else {
			$fragment = substr($destination, $a);
			$destination = substr($destination, 0, $a);
		}

		$a = strpos($destination, '?');
		if ($a !== FALSE) {
			parse_str(substr($destination, $a + 1), $args);
			$destination = substr($destination, 0, $a);
		}

		$a = strpos($destination, '//');
		if ($a === FALSE) {
			$scheme = FALSE;
		} else {
			$scheme = substr($destination, 0, $a);
			$destination = substr($destination, $a + 2);
		}

		if (!$component instanceof NPresenter || substr($destination, -1) === '!') {
			$signal = rtrim($destination, '!');
			$a = strrpos($signal, ':');
			if ($a !== FALSE) {
				$component = $component->getComponent(strtr(substr($signal, 0, $a), ':', '-'));
				$signal = (string) substr($signal, $a + 1);
			}
			if ($signal == NULL) {
				throw new NInvalidLinkException("Signal must be non-empty string.");
			}
			$destination = 'this';
		}

		if ($destination == NULL) {
			throw new NInvalidLinkException("Destination must be non-empty string.");
		}

		$current = FALSE;
		$a = strrpos($destination, ':');
		if ($a === FALSE) {
			$action = $destination === 'this' ? $this->action : $destination;
			$presenter = $this->getName();
			$presenterClass = get_class($this);

		} else {
			$action = (string) substr($destination, $a + 1);
			if ($destination[0] === ':') {
				if ($a < 2) {
					throw new NInvalidLinkException("Missing presenter name in '$destination'.");
				}
				$presenter = substr($destination, 1, $a - 1);

			} else {
				$presenter = $this->getName();
				$b = strrpos($presenter, ':');
				if ($b === FALSE) {
					$presenter = substr($destination, 0, $a);
				} else {
					$presenter = substr($presenter, 0, $b + 1) . substr($destination, 0, $a);
				}
			}
			try {
				$presenterClass = $presenterFactory->getPresenterClass($presenter);
			} catch (NInvalidPresenterException $e) {
				throw new NInvalidLinkException($e->getMessage());
			}
		}

		if (isset($signal)) {
			$reflection = new NPresenterComponentReflection(get_class($component));
			if ($signal === 'this') {
				$signal = '';
				if (array_key_exists(0, $args)) {
					throw new NInvalidLinkException("Unable to pass parameters to 'this!' signal.");
				}

			} elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) {

				$method = $component->formatSignalMethod($signal);
				if (!$reflection->hasCallableMethod($method)) {
					throw new NInvalidLinkException("Unknown signal '$signal', missing handler {$reflection->name}::$method()");
				}
				if ($args) {
					self::argsToParams(get_class($component), $method, $args);
				}
			}

			if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
				$component->saveState($args);
			}

			if ($args && $component !== $this) {
				$prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
				foreach ($args as $key => $val) {
					unset($args[$key]);
					$args[$prefix . $key] = $val;
				}
			}
		}

		if (is_subclass_of($presenterClass, __CLASS__)) {
			if ($action === '') {
				$action = self::DEFAULT_ACTION;
			}

			$current = ($action === '*' || $action === $this->action) && $presenterClass === get_class($this);

			$reflection = new NPresenterComponentReflection($presenterClass);
			if ($args || $destination === 'this') {

				$method = call_user_func(array($presenterClass, 'formatActionMethod'), $action);
				if (!$reflection->hasCallableMethod($method)) {
					$method = call_user_func(array($presenterClass, 'formatRenderMethod'), $action);
					if (!$reflection->hasCallableMethod($method)) {
						$method = NULL;
					}
				}

				if ($method === NULL) {
					if (array_key_exists(0, $args)) {
						throw new NInvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method.");
					}

				} elseif ($destination === 'this') {
					self::argsToParams($presenterClass, $method, $args, $this->params);

				} else {
					self::argsToParams($presenterClass, $method, $args);
				}
			}

			if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
				$this->saveState($args, $reflection);
			}

			$globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass);
			if ($current && $args) {
				$tmp = $globalState + $this->params;
				foreach ($args as $key => $val) {
					if ((string) $val !== (isset($tmp[$key]) ? (string) $tmp[$key] : '')) {
						$current = FALSE;
						break;
					}
				}
			}
			$args += $globalState;
		}

		$args[self::ACTION_KEY] = $action;
		if (!empty($signal)) {
			$args[self::SIGNAL_KEY] = $component->getParamId($signal);
			$current = $current && $args[self::SIGNAL_KEY] === $this->getParam(self::SIGNAL_KEY);
		}
		if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
			$args[self::FLASH_KEY] = $this->getParam(self::FLASH_KEY);
		}

		$this->lastCreatedRequest = new NPresenterRequest(
			$presenter,
			NPresenterRequest::FORWARD,
			$args,
			array(),
			array()
		);
		$this->lastCreatedRequestFlag = array('current' => $current);

		if ($mode === 'forward') {
			return;
		}

		$url = $router->constructUrl($this->lastCreatedRequest, $refUrl);
		if ($url === NULL) {
			unset($args[self::ACTION_KEY]);
			$params = urldecode(http_build_query($args, NULL, ', '));
			throw new NInvalidLinkException("No route for $presenter:$action($params)");
		}

		if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) {
			$hostUrl = $refUrl->getHostUrl();
			if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) {
				$url = substr($url, strlen($hostUrl));
			}
		}

		return $url . $fragment;
	}

	private static function argsToParams($class, $method, & $args, $supplemental = array())
	{
		static $cache;
		$params = & $cache[strtolower($class . ':' . $method)];
		if ($params === NULL) {
			$params = NMethodReflection::from($class, $method)->getDefaultParameters();
		}
		$i = 0;
		foreach ($params as $name => $def) {
			if (array_key_exists($i, $args)) {
				$args[$name] = $args[$i];
				unset($args[$i]);
				$i++;

			} elseif (array_key_exists($name, $args)) {

			} elseif (array_key_exists($name, $supplemental)) {
				$args[$name] = $supplemental[$name];

			} else {
				continue;
			}

			if ($def === NULL) {
				if ((string) $args[$name] === '') {
					$args[$name] = NULL;
				}
			} else {
				settype($args[$name], gettype($def));
				if ($args[$name] === $def) {
					$args[$name] = NULL;
				}
			}
		}

		if (array_key_exists($i, $args)) {
			$method = NMethodReflection::from($class, $method)->getName();
			throw new NInvalidLinkException("Passed more parameters than method $class::$method() expects.");
		}
	}

	protected function handleInvalidLink($e)
	{
		if (self::$invalidLinkMode === self::INVALID_LINK_SILENT) {
			return '#';

		} elseif (self::$invalidLinkMode === self::INVALID_LINK_WARNING) {
			return 'error: ' . $e->getMessage();

		} else {
			throw $e;
		}
	}

	static function getPersistentComponents()
	{
		$arg = func_get_arg(0);
		return (array) NClassReflection::from($arg)
			->getAnnotation('persistent');
	}

	private function getGlobalState($forClass = NULL)
	{
		$sinces = & $this->globalStateSinces;

		if ($this->globalState === NULL) {
			$state = array();
			foreach ($this->globalParams as $id => $params) {
				$prefix = $id . self::NAME_SEPARATOR;
				foreach ($params as $key => $val) {
					$state[$prefix . $key] = $val;
				}
			}
			$this->saveState($state, $forClass ? new NPresenterComponentReflection($forClass) : NULL);

			if ($sinces === NULL) {
				$sinces = array();
				foreach ($this->getReflection()->getPersistentParams() as $nm => $meta) {
					$sinces[$nm] = $meta['since'];
				}
			}

			$components = $this->getReflection()->getPersistentComponents();
			$iterator = $this->getComponents(TRUE, 'IStatePersistent');

			foreach ($iterator as $name => $component) {
				if ($iterator->getDepth() === 0) {

					$since = isset($components[$name]['since']) ? $components[$name]['since'] : FALSE;
				}
				$prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
				$params = array();
				$component->saveState($params);
				foreach ($params as $key => $val) {
					$state[$prefix . $key] = $val;
					$sinces[$prefix . $key] = $since;
				}
			}

		} else {
			$state = $this->globalState;
		}

		if ($forClass !== NULL) {
			$since = NULL;
			foreach ($state as $key => $foo) {
				if (!isset($sinces[$key])) {
					$x = strpos($key, self::NAME_SEPARATOR);
					$x = $x === FALSE ? $key : substr($key, 0, $x);
					$sinces[$key] = isset($sinces[$x]) ? $sinces[$x] : FALSE;
				}
				if ($since !== $sinces[$key]) {
					$since = $sinces[$key];
					$ok = $since && (is_subclass_of($forClass, $since) || $forClass === $since);
				}
				if (!$ok) {
					unset($state[$key]);
				}
			}
		}

		return $state;
	}

	protected function saveGlobalState()
	{

		foreach ($this->globalParams as $id => $foo) {
			$this->getComponent($id, FALSE);
		}

		$this->globalParams = array();
		$this->globalState = $this->getGlobalState();
	}

	private function initGlobalParams()
	{

		$this->globalParams = array();
		$selfParams = array();

		$params = $this->request->getParams();
		if ($this->isAjax()) {
			$params = $this->request->getPost() + $params;
		}

		foreach ($params as $key => $value) {
			$a = strlen($key) > 2 ? strrpos($key, self::NAME_SEPARATOR, -2) : FALSE;
			if ($a === FALSE) {
				$selfParams[$key] = $value;
			} else {
				$this->globalParams[substr($key, 0, $a)][substr($key, $a + 1)] = $value;
			}
		}

		$this->changeAction(isset($selfParams[self::ACTION_KEY]) ? $selfParams[self::ACTION_KEY] : self::DEFAULT_ACTION);

		$this->signalReceiver = $this->getUniqueId();
		if (!empty($selfParams[self::SIGNAL_KEY])) {
			$param = $selfParams[self::SIGNAL_KEY];
			$pos = strrpos($param, '-');
			if ($pos) {
				$this->signalReceiver = substr($param, 0, $pos);
				$this->signal = substr($param, $pos + 1);
			} else {
				$this->signalReceiver = $this->getUniqueId();
				$this->signal = $param;
			}
			if ($this->signal == NULL) {
				$this->signal = NULL;
			}
		}

		$this->loadState($selfParams);
	}

	final function popGlobalParams($id)
	{
		if (isset($this->globalParams[$id])) {
			$res = $this->globalParams[$id];
			unset($this->globalParams[$id]);
			return $res;

		} else {
			return array();
		}
	}

	function hasFlashSession()
	{
		return !empty($this->params[self::FLASH_KEY])
			&& $this->getSession()->hasSection('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
	}

	function getFlashSession()
	{
		if (empty($this->params[self::FLASH_KEY])) {
			$this->params[self::FLASH_KEY] = NStrings::random(4);
		}
		return $this->getSession('Nette.Application.Flash/' . $this->params[self::FLASH_KEY]);
	}

	function setContext(IDiContainer $context)
	{
		$this->context = $context;
		return $this;
	}

	final function getContext()
	{
		return $this->context;
	}

	final function getService($name)
	{
		return $this->context->getService($name);
	}

	protected function getHttpRequest()
	{
		return $this->context->httpRequest;
	}

	protected function getHttpResponse()
	{
		return $this->context->httpResponse;
	}

	protected function getHttpContext()
	{
		return $this->context->httpContext;
	}

	function getApplication()
	{
		return $this->context->application;
	}

	function getSession($namespace = NULL)
	{
		$handler = $this->context->session;
		return $namespace === NULL ? $handler : $handler->getSection($namespace);
	}

	function getUser()
	{
		return $this->context->user;
	}

}

class NClassReflection extends ReflectionClass
{

	private static $extMethods;

	static function from($class)
	{
		return new self($class);
	}

	function __toString()
	{
		return 'Class ' . $this->getName();
	}

	function hasEventProperty($name)
	{
		if (preg_match('#^on[A-Z]#', $name) && $this->hasProperty($name)) {
			$rp = $this->getProperty($name);
			return $rp->isPublic() && !$rp->isStatic();
		}
		return FALSE;
	}

	function setExtensionMethod($name, $callback)
	{
		$l = & self::$extMethods[strtolower($name)];
		$l[strtolower($this->getName())] = callback($callback);
		$l[''] = NULL;
		return $this;
	}

	function getExtensionMethod($name)
	{
		if (self::$extMethods === NULL || $name === NULL) {
			$list = get_defined_functions();
			foreach ($list['user'] as $fce) {
				$pair = explode('_prototype_', $fce);
				if (count($pair) === 2) {
					self::$extMethods[$pair[1]][$pair[0]] = callback($fce);
					self::$extMethods[$pair[1]][''] = NULL;
				}
			}
			if ($name === NULL) {
				return NULL;
			}
		}

		$class = strtolower($this->getName());
		$l = & self::$extMethods[strtolower($name)];

		if (empty($l)) {
			return FALSE;

		} elseif (isset($l[''][$class])) {
			return $l[''][$class];
		}

		$cl = $class;
		do {
			if (isset($l[$cl])) {
				return $l[''][$class] = $l[$cl];
			}
		} while (($cl = strtolower(get_parent_class($cl))) !== '');

		foreach (class_implements($class) as $cl) {
			$cl = strtolower($cl);
			if (isset($l[$cl])) {
				return $l[''][$class] = $l[$cl];
			}
		}
		return $l[''][$class] = FALSE;
	}

	function getConstructor()
	{
		return ($ref = parent::getConstructor()) ? NMethodReflection::from($this->getName(), $ref->getName()) : NULL;
	}

	function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new NExtensionReflection($name) : NULL;
	}

	function getInterfaces()
	{
		$res = array();
		foreach (parent::getInterfaceNames() as $val) {
			$res[$val] = new self($val);
		}
		return $res;
	}

	function getMethod($name)
	{
		return new NMethodReflection($this->getName(), $name);
	}

	function getMethods($filter = -1)
	{
		foreach ($res = parent::getMethods($filter) as $key => $val) {
			$res[$key] = new NMethodReflection($this->getName(), $val->getName());
		}
		return $res;
	}

	function getParentClass()
	{
		return ($ref = parent::getParentClass()) ? new self($ref->getName()) : NULL;
	}

	function getProperties($filter = -1)
	{
		foreach ($res = parent::getProperties($filter) as $key => $val) {
			$res[$key] = new NPropertyReflection($this->getName(), $val->getName());
		}
		return $res;
	}

	function getProperty($name)
	{
		return new NPropertyReflection($this->getName(), $name);
	}

	function hasAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	function getAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	function getAnnotations()
	{
		return NAnnotationsParser::getAll($this);
	}

	function getDescription()
	{
		return $this->getAnnotation('description');
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NPresenterComponentReflection extends NClassReflection
{

	private static $ppCache = array();

	private static $pcCache = array();

	private static $mcCache = array();

	function getPersistentParams($class = NULL)
	{
		$class = $class === NULL ? $this->getName() : $class;
		$params = & self::$ppCache[$class];
		if ($params !== NULL) {
			return $params;
		}
		$params = array();
		if (is_subclass_of($class, 'NPresenterComponent')) {

			$defaults = get_class_vars($class);
			foreach (call_user_func(array($class, 'getPersistentParams'), $class) as $name => $meta) {
				if (is_string($meta)) {
					$name = $meta;
				}
				$params[$name] = array(
					'def' => $defaults[$name],
					'since' => $class,
				);
			}
			foreach ($this->getPersistentParams(get_parent_class($class)) as $name => $param) {
				if (isset($params[$name])) {
					$params[$name]['since'] = $param['since'];
					continue;
				}

				$params[$name] = $param;
			}
		}
		return $params;
	}

	function getPersistentComponents()
	{
		$class = $this->getName();
		$components = & self::$pcCache[$class];
		if ($components !== NULL) {
			return $components;
		}
		$components = array();
		if (is_subclass_of($class, 'NPresenter')) {

			foreach (call_user_func(array($class, 'getPersistentComponents'), $class) as $name => $meta) {
				if (is_string($meta)) {
					$name = $meta;
				}
				$components[$name] = array('since' => $class);
			}
			$components = self::getPersistentComponents(get_parent_class($class)) + $components;
		}
		return $components;
	}

	function hasCallableMethod($method)
	{
		$class = $this->getName();
		$cache = & self::$mcCache[strtolower($class . ':' . $method)];
		if ($cache === NULL) try {
			$cache = FALSE;
			$rm = NMethodReflection::from($class, $method);
			$cache = $this->isInstantiable() && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
		} catch (ReflectionException $e) {
		}
		return $cache;
	}

}

class NCache extends NObject implements ArrayAccess
{

	const PRIORITY = 'priority',
		EXPIRATION = 'expire',
		EXPIRE = 'expire',
		SLIDING = 'sliding',
		TAGS = 'tags',
		FILES = 'files',
		ITEMS = 'items',
		CONSTS = 'consts',
		CALLBACKS = 'callbacks',
		ALL = 'all';

	const NAMESPACE_SEPARATOR = "\x00";

	private $storage;

	private $namespace;

	private $key;

	private $data;

	function __construct(ICacheStorage $storage, $namespace = NULL)
	{
		$this->storage = $storage;
		$this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
	}

	function getStorage()
	{
		return $this->storage;
	}

	function getNamespace()
	{
		return (string) substr($this->namespace, 0, -1);
	}

	function derive($namespace)
	{
		$derived = new self($this->storage, $this->namespace . $namespace);
		return $derived;
	}

	function release()
	{
		$this->key = $this->data = NULL;
	}

	function load($key)
	{
		$key = is_scalar($key) ? (string) $key : serialize($key);
		if ($this->key === $key) {
			return $this->data;
		}
		$this->key = $key;
		$this->data = $this->storage->read($this->namespace . md5($key));
		return $this->data;
	}

	function save($key, $data, array $dp = NULL)
	{
		$this->key = is_scalar($key) ? (string) $key : serialize($key);
		$key = $this->namespace . md5($this->key);

		if (isset($dp[NCache::EXPIRATION])) {
			$dp[NCache::EXPIRATION] = NDateTime53::from($dp[NCache::EXPIRATION])->format('U') - time();
		}

		if (isset($dp[self::FILES])) {

			foreach ((array) $dp[self::FILES] as $item) {
				$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item));
			}
			unset($dp[self::FILES]);
		}

		if (isset($dp[self::ITEMS])) {
			$dp[self::ITEMS] = (array) $dp[self::ITEMS];
			foreach ($dp[self::ITEMS] as $k => $item) {
				$dp[self::ITEMS][$k] = $this->namespace . md5(is_scalar($item) ? $item : serialize($item));
			}
		}

		if (isset($dp[self::CONSTS])) {
			foreach ((array) $dp[self::CONSTS] as $item) {
				$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item));
			}
			unset($dp[self::CONSTS]);
		}

		if ($data instanceof NCallback || $data instanceof Closure) {
			NCriticalSection::enter();
			$data = $data->__invoke();
			NCriticalSection::leave();
		}

		if (is_object($data)) {
			$dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkSerializationVersion'), get_class($data),
				NClassReflection::from($data)->getAnnotation('serializationVersion'));
		}

		$this->data = $data;
		if ($data === NULL) {
			$this->storage->remove($key);
		} else {
			$this->storage->write($key, $data, (array) $dp);
		}
		return $data;
	}

	function clean(array $conds = NULL)
	{
		$this->release();
		$this->storage->clean((array) $conds);
	}

	function call($function)
	{
		$key = func_get_args();
		if ($this->load($key) === NULL) {
			array_shift($key);
			return $this->save($this->key, call_user_func_array($function, $key));
		} else {
			return $this->data;
		}
	}

	function start($key)
	{
		if ($this->offsetGet($key) === NULL) {
			return new NCachingHelper($this, $key);
		} else {
			echo $this->data;
		}
	}

	function offsetSet($key, $data)
	{
		$this->save($key, $data);
	}

	function offsetGet($key)
	{
		return $this->load($key);
	}

	function offsetExists($key)
	{
		return $this->load($key) !== NULL;
	}

	function offsetUnset($key)
	{
		$this->save($key, NULL);
	}

	static function checkCallbacks($callbacks)
	{
		foreach ($callbacks as $callback) {
			$func = array_shift($callback);
			if (!call_user_func_array($func, $callback)) {
				return FALSE;
			}
		}
		return TRUE;
	}

	private static function checkConst($const, $value)
	{
		return defined($const) && constant($const) === $value;
	}

	private static function checkFile($file, $time)
	{
		return @filemtime($file) == $time;
	}

	private static function checkSerializationVersion($class, $value)
	{
		return NClassReflection::from($class)->getAnnotation('serializationVersion') === $value;
	}

}

class NCachingHelper extends NObject
{

	public $dependencies;

	private $cache;

	private $key;

	function __construct(NCache $cache, $key)
	{
		$this->cache = $cache;
		$this->key = $key;
		ob_start();
	}

	function end(array $dp = NULL)
	{
		if ($this->cache === NULL) {
			throw new InvalidStateException('Output cache has already been saved.');
		}
		$this->cache->save($this->key, ob_get_flush(), (array) $dp + (array) $this->dependencies);
		$this->cache = NULL;
	}

}

class NDevNullStorage extends NObject implements ICacheStorage
{

	function read($key)
	{
	}

	function write($key, $data, array $dp)
	{
	}

	function remove($key)
	{
	}

	function clean(array $conds)
	{
	}

}

class NFileJournal extends NObject implements ICacheJournal
{

	const FILE = 'btfj.dat';

	const FILE_MAGIC  = 0x6274666A;

	const INDEX_MAGIC = 0x696E6465;

	const DATA_MAGIC  = 0x64617461;

	const NODE_SIZE = 4096;

	const BITROT = 12;

	const HEADER_SIZE = 4096;

	const INT32_SIZE  = 4;

	const INFO = 'i',
		TYPE = 't',
		IS_LEAF = 'il',
		PREV_NODE = 'p',
		END = 'e',
		MAX = 'm',
		INDEX_DATA = 'id',
		LAST_INDEX = 'l';

	const TAGS = 't',
		PRIORITY = 'p',
		ENTRIES = 'e';

	const DATA = 'd',
		KEY = 'k',
		DELETED = 'd';

	public static $debug = FALSE;

	private $file;

	private $handle;

	private $lastNode = 2;

	private $lastModTime = NULL;

	private $nodeCache = array();

	private $nodeChanged = array();

	private $toCommit = array();

	private $deletedLinks = array();

	private $dataNodeFreeSpace = array();

	private static $startNode = array(
		self::TAGS     => 0,
		self::PRIORITY => 1,
		self::ENTRIES  => 2,
		self::DATA     => 3,
	);

	function __construct($dir)
	{
		$this->file = $dir . '/' . self::FILE;

		if (!file_exists($this->file)) {
			$init = @fopen($this->file, 'xb');
			if (!$init) {
				clearstatcache();
				if (!file_exists($this->file)) {
					throw new InvalidStateException("Cannot create journal file $this->file.");
				}
			} else {
				$writen = fwrite($init, pack('N2', self::FILE_MAGIC, $this->lastNode));
				fclose($init);
				if ($writen !== self::INT32_SIZE * 2) {
					throw new InvalidStateException("Cannot write journal header.");
				}
			}
		}

		$this->handle = fopen($this->file, 'r+b');

		if (!$this->handle) {
			throw new InvalidStateException("Cannot open journal file '$this->file'.");
		}

		if (!flock($this->handle, LOCK_SH)) {
			throw new InvalidStateException('Cannot acquire shared lock on journal.');
		}

		$header = stream_get_contents($this->handle, 2 * self::INT32_SIZE, 0);

		flock($this->handle, LOCK_UN);

		list(, $fileMagic, $this->lastNode) = unpack('N2', $header);

		if ($fileMagic !== self::FILE_MAGIC) {
			fclose($this->handle);
			$this->handle = false;
			throw new InvalidStateException("Malformed journal file '$this->file'.");
		}
	}

	function __destruct()
	{
		if ($this->handle) {
			$this->headerCommit();
			flock($this->handle, LOCK_UN);
			fclose($this->handle);
			$this->handle = false;
		}
	}

	function write($key, array $dependencies)
	{
		$this->lock();

		$priority = !isset($dependencies[NCache::PRIORITY]) ? FALSE : (int) $dependencies[NCache::PRIORITY];
		$tags = empty($dependencies[NCache::TAGS]) ? FALSE : (array) $dependencies[NCache::TAGS];

		$exists = FALSE;
		$keyHash = crc32($key);
		list($entriesNodeId, $entriesNode) = $this->findIndexNode(self::ENTRIES, $keyHash);

		if (isset($entriesNode[$keyHash])) {
			$entries = $this->mergeIndexData($entriesNode[$keyHash]);
			foreach ($entries as $link => $foo) {
				$dataNode = $this->getNode($link >> self::BITROT);
				if ($dataNode[$link][self::KEY] === $key) {
					if ($dataNode[$link][self::TAGS] == $tags && $dataNode[$link][self::PRIORITY] === $priority) {
						if ($dataNode[$link][self::DELETED]) {
							$dataNode[$link][self::DELETED] = FALSE;
							$this->saveNode($link >> self::BITROT, $dataNode);
						}
						$exists = TRUE;
					} else {
						$toDelete = array();
						foreach ($dataNode[$link][self::TAGS] as $tag) {
							$toDelete[self::TAGS][$tag][$link] = TRUE;
						}
						if ($dataNode[$link][self::PRIORITY] !== FALSE) {
							$toDelete[self::PRIORITY][$dataNode[$link][self::PRIORITY]][$link] = TRUE;
						}
						$toDelete[self::ENTRIES][$keyHash][$link] = TRUE;
						$this->cleanFromIndex($toDelete);
						$entriesNode = $this->getNode($entriesNodeId);
						unset($dataNode[$link]);
						$this->saveNode($link >> self::BITROT, $dataNode);
					}
					break;
				}
			}
		}

		if ($exists === FALSE) {

			$requiredSize = strlen($key) + 75;
			if ($tags) {
				foreach ($tags as $tag) {
					$requiredSize += strlen($tag) + 13;
				}
			}
			$requiredSize += $priority ? 10 : 1;

			$freeDataNode = $this->findFreeDataNode($requiredSize);
			$data = $this->getNode($freeDataNode);

			if ($data === FALSE) {
				$data = array(
					self::INFO => array(
						self::LAST_INDEX => ($freeDataNode << self::BITROT),
						self::TYPE => self::DATA,
					)
				);
			}

			$dataNodeKey = ++$data[self::INFO][self::LAST_INDEX];
			$data[$dataNodeKey] = array(
				self::KEY => $key,
				self::TAGS => $tags ? $tags : array(),
				self::PRIORITY => $priority,
				self::DELETED => FALSE,
			);

			$this->saveNode($freeDataNode, $data);

			$entriesNode[$keyHash][$dataNodeKey] = 1;
			$this->saveNode($entriesNodeId, $entriesNode);

			if ($tags) {
				foreach ($tags as $tag) {
					list($nodeId, $node) = $this->findIndexNode(self::TAGS, $tag);
					$node[$tag][$dataNodeKey] = 1;
					$this->saveNode($nodeId, $node);
				}
			}

			if ($priority) {
				list($nodeId, $node) = $this->findIndexNode(self::PRIORITY, $priority);
				$node[$priority][$dataNodeKey] = 1;
				$this->saveNode($nodeId, $node);
			}
		}

		$this->commit();
		$this->unlock();
	}

	function clean(array $conditions)
	{
		$this->lock();

		if (!empty($conditions[NCache::ALL])) {
			$this->nodeCache = $this->nodeChanged = $this->dataNodeFreeSpace = array();
			$this->deleteAll();
			$this->unlock();
			return;
		}

		$toDelete = array(
			self::TAGS => array(),
			self::PRIORITY => array(),
			self::ENTRIES => array()
		);

		$entries = array();

		if (!empty($conditions[NCache::TAGS])) {
			$entries = $this->cleanTags((array) $conditions[NCache::TAGS], $toDelete);
		}

		if (isset($conditions[NCache::PRIORITY])) {
			$this->arrayAppend($entries, $this->cleanPriority((int) $conditions[NCache::PRIORITY], $toDelete));
		}

		$this->deletedLinks = array();
		$this->cleanFromIndex($toDelete);

		$this->commit();
		$this->unlock();

		return $entries;
	}

	private function cleanTags(array $tags, array &$toDelete)
	{
		$entries = array();

		foreach ($tags as $tag) {
			list($nodeId, $node) = $this->findIndexNode(self::TAGS, $tag);

			if (isset($node[$tag])) {
				$ent = $this->cleanLinks($this->mergeIndexData($node[$tag]), $toDelete);
				$this->arrayAppend($entries, $ent);
			}
		}

		return $entries;
	}

	private function cleanPriority($priority, array &$toDelete)
	{
		list($nodeId, $node) = $this->findIndexNode(self::PRIORITY, $priority);

		ksort($node);

		$allData = array();

		foreach ($node as $prior => $data) {
			if ($prior === self::INFO) {
				continue;
			} elseif ($prior > $priority) {
				break;
			}

			$this->arrayAppendKeys($allData, $this->mergeIndexData($data));
		}

		$nodeInfo = $node[self::INFO];
		while ($nodeInfo[self::PREV_NODE] !== -1) {
			$nodeId = $nodeInfo[self::PREV_NODE];
			$node = $this->getNode($nodeId);

			if ($node === FALSE) {
				if (self::$debug) {
					throw new InvalidStateException("Cannot load node number $nodeId.");
				}
				break;
			}

			$nodeInfo = $node[self::INFO];
			unset($node[self::INFO]);

			foreach ($node as $prior => $data) {
				$this->arrayAppendKeys($allData, $this->mergeIndexData($data));
			}
		}

		return $this->cleanLinks($allData, $toDelete);
	}

	private function cleanLinks(array $data, array &$toDelete)
	{
		$return = array();

		$data = array_keys($data);
		sort($data);
		$max = count($data);
		$data[] = 0;
		$i = 0;

		while ($i < $max) {
			$searchLink = $data[$i];

			if (isset($this->deletedLinks[$searchLink])) {
				++$i;
				continue;
			}

			$nodeId = $searchLink >> self::BITROT;
			$node = $this->getNode($nodeId);

			if ($node === FALSE) {
				if (self::$debug) {
					throw new InvalidStateException('Cannot load node number ' . ($nodeId) . '.');
				}
				++$i;
				continue;
			}

			do {
				$link = $data[$i];

				if (!isset($node[$link])) {
					if (self::$debug) {
						throw new InvalidStateException("Link with ID $searchLink is not in node ". ($nodeId) . '.');
					}
					continue;
				} elseif (isset($this->deletedLinks[$link])) {
					continue;
				}

				$nodeLink = &$node[$link];
				if (!$nodeLink[self::DELETED]) {
					$nodeLink[self::DELETED] = TRUE;
					$return[] = $nodeLink[self::KEY];
				} else {
					foreach ($nodeLink[self::TAGS] as $tag) {
						$toDelete[self::TAGS][$tag][$link] = TRUE;
					}
					if ($nodeLink[self::PRIORITY] !== FALSE) {
						$toDelete[self::PRIORITY][$nodeLink[self::PRIORITY]][$link] = TRUE;
					}
					$toDelete[self::ENTRIES][crc32($nodeLink[self::KEY])][$link] = TRUE;
					unset($node[$link]);
					$this->deletedLinks[$link] = TRUE;
				}
			} while (($data[++$i] >> self::BITROT) === $nodeId);

			$this->saveNode($nodeId, $node);
		}

		return $return;
	}

	private function cleanFromIndex(array $toDeleteFromIndex)
	{
		foreach ($toDeleteFromIndex as $type => $toDelete) {
			ksort($toDelete);

			while (!empty($toDelete)) {
				reset($toDelete);
				$searchKey = key($toDelete);
				list($masterNodeId, $masterNode) = $this->findIndexNode($type, $searchKey);

				if (!isset($masterNode[$searchKey])) {
					if (self::$debug) {
						throw new InvalidStateException('Bad index.');
					}
					unset($toDelete[$searchKey]);
					continue;
				}

				foreach ($toDelete as $key => $links) {
					if (isset($masterNode[$key])) {
						foreach ($links as $link => $foo) {
							if (isset($masterNode[$key][$link])) {
								unset($masterNode[$key][$link], $links[$link]);
							}
						}

						if (!empty($links) && isset($masterNode[$key][self::INDEX_DATA])) {
							$this->cleanIndexData($masterNode[$key][self::INDEX_DATA], $links, $masterNode[$key]);
						}

						if (empty($masterNode[$key])) {
							unset($masterNode[$key]);
						}
						unset($toDelete[$key]);
					} else {
						break;
					}
				}
				$this->saveNode($masterNodeId, $masterNode);
			}
		}
	}

	private function mergeIndexData(array $data)
	{
		while (isset($data[self::INDEX_DATA])) {
			$id = $data[self::INDEX_DATA];
			unset($data[self::INDEX_DATA]);
			$childNode = $this->getNode($id);

			if ($childNode === FALSE) {
				if (self::$debug) {
					throw new InvalidStateException("Cannot load node number $id.");
				}
				break;
			}

			$this->arrayAppendKeys($data, $childNode[self::INDEX_DATA]);
		}

		return $data;
	}

	private function cleanIndexData($nextNodeId, array $links, &$masterNodeLink)
	{
		$prev = -1;

		while ($nextNodeId && !empty($links)) {
			$nodeId = $nextNodeId;
			$node = $this->getNode($nodeId);

			if ($node === FALSE) {
				if (self::$debug) {
					throw new InvalidStateException("Cannot load node number $nodeId.");
				}
				break;
			}

			foreach ($links as $link => $foo) {
				if (isset($node[self::INDEX_DATA][$link])) {
					unset($node[self::INDEX_DATA][$link], $links[$link]);
				}
			}

			if (isset($node[self::INDEX_DATA][self::INDEX_DATA])) {
				$nextNodeId = $node[self::INDEX_DATA][self::INDEX_DATA];
			} else {
				$nextNodeId = FALSE;
			}

			if (empty($node[self::INDEX_DATA]) || (count($node[self::INDEX_DATA]) === 1 && $nextNodeId)) {
				if ($prev === -1) {
					if ($nextNodeId === FALSE) {
						unset($masterNodeLink[self::INDEX_DATA]);
					} else {
						$masterNodeLink[self::INDEX_DATA] = $nextNodeId;
					}
				} else {
					$prevNode = $this->getNode($prev);
					if ($prevNode === FALSE) {
						if (self::$debug) {
							throw new InvalidStateException("Cannot load node number $prev.");
						}
					} else {
						if ($nextNodeId === FALSE) {
							unset($prevNode[self::INDEX_DATA][self::INDEX_DATA]);
							if (empty($prevNode[self::INDEX_DATA])) {
								unset($prevNode[self::INDEX_DATA]);
							}
						} else {
							$prevNode[self::INDEX_DATA][self::INDEX_DATA] = $nextNodeId;
						}

						$this->saveNode($prev, $prevNode);
					}
				}
				unset($node[self::INDEX_DATA]);
			} else {
				$prev = $nodeId;
			}

			$this->saveNode($nodeId, $node);
		}
	}

	private function getNode($id)
	{

		if (isset($this->nodeCache[$id])) {
			return $this->nodeCache[$id];
		}

		$binary = stream_get_contents($this->handle, self::NODE_SIZE, self::HEADER_SIZE + self::NODE_SIZE * $id);

		if (empty($binary)) {

			return FALSE;
		}

		list(, $magic, $lenght) = unpack('N2', $binary);
		if ($magic !== self::INDEX_MAGIC && $magic !== self::DATA_MAGIC) {
			if (!empty($magic)) {
				if (self::$debug) {
					throw new InvalidStateException("Node $id has malformed header.");
				}
				$this->deleteNode($id);
			}
			return FALSE;
		}

		$data = substr($binary, 2 * self::INT32_SIZE, $lenght - 2 * self::INT32_SIZE);

		$node = @unserialize($data);
		if ($node === FALSE) {
			$this->deleteNode($id);
			if (self::$debug) {
				throw new InvalidStateException("Cannot deserialize node number $id.");
			}
			return FALSE;
		}

		return $this->nodeCache[$id] = $node;
	}

	private function saveNode($id, array $node)
	{
		if (count($node) === 1) {
			$nodeInfo = $node[self::INFO];
			if ($nodeInfo[self::TYPE] !== self::DATA) {

				if ($nodeInfo[self::END] !== -1) {
					$this->nodeCache[$id] = $node;
					$this->nodeChanged[$id] = TRUE;
					return;
				}

				if ($nodeInfo[self::MAX] === -1) {
					$max = PHP_INT_MAX;
				} else {
					$max = $nodeInfo[self::MAX];
				}

				list(, , $parentId) = $this->findIndexNode($nodeInfo[self::TYPE], $max, $id);
				if ($parentId !== -1 && $parentId !== $id) {
					$parentNode = $this->getNode($parentId);
					if ($parentNode === FALSE) {
						if (self::$debug) {
							throw new InvalidStateException("Cannot load node number $parentId.");
						}
					} else {
						if ($parentNode[self::INFO][self::END] === $id) {
							if (count($parentNode) === 1) {
								$parentNode[self::INFO][self::END] = -1;
							} else {
								end($parentNode);
								$lastKey = key($parentNode);
								$parentNode[self::INFO][self::END] = $parentNode[$lastKey];
								unset($parentNode[$lastKey]);
							}
						} else {
							unset($parentNode[$nodeInfo[self::MAX]]);
						}

						$this->saveNode($parentId, $parentNode);
					}
				}

				if ($nodeInfo[self::TYPE] === self::PRIORITY) {
					if ($nodeInfo[self::MAX] === -1) {
						if ($nodeInfo[self::PREV_NODE] !== -1) {
							$prevNode = $this->getNode($nodeInfo[self::PREV_NODE]);
							if ($prevNode === FALSE) {
								if (self::$debug) {
									throw new InvalidStateException('Cannot load node number ' . $nodeInfo[self::PREV_NODE] . '.');
								}
							} else {
								$prevNode[self::INFO][self::MAX] = -1;
								$this->saveNode($nodeInfo[self::PREV_NODE], $prevNode);
							}
						}
					} else {
						list($nextId, $nextNode) = $this->findIndexNode($nodeInfo[self::TYPE], $nodeInfo[self::MAX] + 1, NULL, $id);
						if ($nextId !== -1 && $nextId !== $id) {
							$nextNode[self::INFO][self::PREV_NODE] = $nodeInfo[self::PREV_NODE];
							$this->saveNode($nextId, $nextNode);
						}
					}
				}
			}
			$this->nodeCache[$id] = FALSE;
		} else {
			$this->nodeCache[$id] = $node;
		}
		$this->nodeChanged[$id] = TRUE;
	}

	private function commit()
	{
		do {
			foreach ($this->nodeChanged as $id => $foo) {
				if ($this->prepareNode($id, $this->nodeCache[$id])) {
					unset($this->nodeChanged[$id]);
				}
			}
		} while (!empty($this->nodeChanged));

		foreach ($this->toCommit as $node => $str) {
			$this->commitNode($node, $str);
		}
		$this->toCommit = array();
	}

	private function prepareNode($id, $node)
	{
		if ($node === FALSE) {
			if ($id < $this->lastNode) {
				$this->lastNode = $id;
			}
			unset($this->nodeCache[$id]);
			unset($this->dataNodeFreeSpace[$id]);
			$this->deleteNode($id);
			return TRUE;
		}

		$data = serialize($node);
		$dataSize = strlen($data) + 2 * self::INT32_SIZE;

		$isData = $node[self::INFO][self::TYPE] === self::DATA;
		if ($dataSize > self::NODE_SIZE) {
			if ($isData) {
				throw new InvalidStateException('Saving node is bigger than maximum node size.');
			} else {
				$this->bisectNode($id, $node);
				return FALSE;
			}
		}

		$this->toCommit[$id] = pack('N2', $isData ? self::DATA_MAGIC : self::INDEX_MAGIC, $dataSize) . $data;

		if ($this->lastNode < $id) {
			$this->lastNode = $id;
		}
		if ($isData) {
			$this->dataNodeFreeSpace[$id] = self::NODE_SIZE - $dataSize;
		}

		return TRUE;
	}

	private function commitNode($id, $str)
	{
		fseek($this->handle, self::HEADER_SIZE + self::NODE_SIZE * $id);
		$writen = fwrite($this->handle, $str);
		if ($writen === FALSE) {
			throw new InvalidStateException("Cannot write node number $id to journal.");
		}
	}

	private function findIndexNode($type, $search, $childId = NULL, $prevId = NULL)
	{
		$nodeId = self::$startNode[$type];

		$parentId = -1;
		while (TRUE) {
			$node = $this->getNode($nodeId);

			if ($node === FALSE) {
				return array(
					$nodeId,
					array(
						self::INFO => array(
							self::TYPE => $type,
							self::IS_LEAF => TRUE,
							self::PREV_NODE => -1,
							self::END => -1,
							self::MAX => -1,
						)
					),
					$parentId,
				);
			}

			if ($node[self::INFO][self::IS_LEAF] || $nodeId === $childId || $node[self::INFO][self::PREV_NODE] === $prevId) {
				return array($nodeId, $node, $parentId);
			}

			$parentId = $nodeId;

			if (isset($node[$search])) {
				$nodeId = $node[$search];
			} else {
				foreach ($node as $key => $childNode) {
					if ($key > $search and $key !== self::INFO) {
						$nodeId = $childNode;
						continue 2;
					}
				}

				$nodeId = $node[self::INFO][self::END];
			}
		}
	}

	private function findFreeNode($count = 1)
	{
		$id = $this->lastNode;
		$nodesId = array();

		do {
			if (isset($this->nodeCache[$id])) {
				++$id;
				continue;
			}

			$offset = self::HEADER_SIZE + self::NODE_SIZE * $id;

			$binary = stream_get_contents($this->handle, self::INT32_SIZE, $offset);

			if (empty($binary)) {
				$nodesId[] = $id;
			} else {
				list(, $magic) = unpack('N', $binary);
				if ($magic !== self::INDEX_MAGIC && $magic !== self::DATA_MAGIC) {
					$nodesId[] = $id;
				}
			}

			++$id;
		} while (count($nodesId) !== $count);

		if ($count === 1) {
			return $nodesId[0];
		} else {
			return $nodesId;
		}
	}

	private function findFreeDataNode($size)
	{
		foreach ($this->dataNodeFreeSpace as $id => $freeSpace) {
			if ($freeSpace > $size) {
				return $id;
			}
		}

		$id = self::$startNode[self::DATA];
		while (TRUE) {
			if (isset($this->dataNodeFreeSpace[$id]) || isset($this->nodeCache[$id])) {
				++$id;
				continue;
			}

			$offset = self::HEADER_SIZE + self::NODE_SIZE * $id;
			$binary = stream_get_contents($this->handle, 2 * self::INT32_SIZE, $offset);

			if (empty($binary)) {
				$this->dataNodeFreeSpace[$id] = self::NODE_SIZE;
				return $id;
			}

			list(, $magic, $nodeSize) = unpack('N2', $binary);
			if (empty($magic)) {
				$this->dataNodeFreeSpace[$id] = self::NODE_SIZE;
				return $id;
			} elseif ($magic === self::DATA_MAGIC) {
				$freeSpace = self::NODE_SIZE - $nodeSize;
				$this->dataNodeFreeSpace[$id] = $freeSpace;

				if ($freeSpace > $size) {
					return $id;
				}
			}

			++$id;
		}
	}

	private function bisectNode($id, array $node)
	{
		$nodeInfo = $node[self::INFO];
		unset($node[self::INFO]);

		if (count($node) === 1) {
			$key = key($node);

			$dataId = $this->findFreeDataNode(self::NODE_SIZE);
			$this->saveNode($dataId, array(
				self::INDEX_DATA => $node[$key],
				self::INFO => array(
					self::TYPE => self::DATA,
					self::LAST_INDEX => ($dataId << self::BITROT),
			)));

			unset($node[$key]);
			$node[$key][self::INDEX_DATA] = $dataId;
			$node[self::INFO] = $nodeInfo;

			$this->saveNode($id, $node);
			return;
		}

		ksort($node);
		$halfCount = ceil(count($node) / 2);

		list($first, $second) = array_chunk($node, $halfCount, TRUE);

		end($first);
		$halfKey = key($first);

		if ($id <= 2) {
			list($firstId, $secondId) = $this->findFreeNode(2);

			$first[self::INFO] = array(
				self::TYPE => $nodeInfo[self::TYPE],
				self::IS_LEAF => $nodeInfo[self::IS_LEAF],
				self::PREV_NODE => -1,
				self::END => -1,
				self::MAX => $halfKey,
			);
			$this->saveNode($firstId, $first);

			$second[self::INFO] = array(
				self::TYPE => $nodeInfo[self::TYPE],
				self::IS_LEAF => $nodeInfo[self::IS_LEAF],
				self::PREV_NODE => $firstId,
				self::END => $nodeInfo[self::END],
				self::MAX => -1,
			);
			$this->saveNode($secondId, $second);

			$parentNode = array(
				self::INFO => array(
					self::TYPE => $nodeInfo[self::TYPE],
					self::IS_LEAF => FALSE,
					self::PREV_NODE => -1,
					self::END => $secondId,
					self::MAX => -1,
				),
				$halfKey => $firstId,
			);
			$this->saveNode($id, $parentNode);
		} else {
			$firstId = $this->findFreeNode();

			$first[self::INFO] = array(
				self::TYPE => $nodeInfo[self::TYPE],
				self::IS_LEAF => $nodeInfo[self::IS_LEAF],
				self::PREV_NODE => $nodeInfo[self::PREV_NODE],
				self::END => -1,
				self::MAX => $halfKey,
			);
			$this->saveNode($firstId, $first);

			$second[self::INFO] = array(
				self::TYPE => $nodeInfo[self::TYPE],
				self::IS_LEAF => $nodeInfo[self::IS_LEAF],
				self::PREV_NODE => $firstId,
				self::END => $nodeInfo[self::END],
				self::MAX => $nodeInfo[self::MAX],
			);
			$this->saveNode($id, $second);

			list(,, $parent) = $this->findIndexNode($nodeInfo[self::TYPE], $halfKey);
			$parentNode = $this->getNode($parent);
			if ($parentNode === FALSE) {
				if (self::$debug) {
					throw new InvalidStateException("Cannot load node number $parent.");
				}
			} else {
				$parentNode[$halfKey] = $firstId;
				ksort($parentNode);
				$this->saveNode($parent, $parentNode);
			}
		}
	}

	private function headerCommit()
	{
		fseek($this->handle, self::INT32_SIZE);
		@fwrite($this->handle, pack('N', $this->lastNode));
	}

	private function deleteNode($id)
	{
		fseek($this->handle, 0, SEEK_END);
		$end = ftell($this->handle);

		if ($end <= (self::HEADER_SIZE + self::NODE_SIZE * ($id + 1))) {
			$packedNull = pack('N', 0);

			do {
				$binary = stream_get_contents($this->handle, self::INT32_SIZE, (self::HEADER_SIZE + self::NODE_SIZE * --$id));
			} while (empty($binary) || $binary === $packedNull);

			if (!ftruncate($this->handle, self::HEADER_SIZE + self::NODE_SIZE * ($id + 1))) {
				throw new InvalidStateException('Cannot truncate journal file.');
			}
		} else {
			fseek($this->handle, self::HEADER_SIZE + self::NODE_SIZE * $id);
			$writen = fwrite($this->handle, pack('N', 0));
			if ($writen !== self::INT32_SIZE) {
				throw new InvalidStateException("Cannot delete node number $id from journal.");
			}
		}
	}

	private function deleteAll()
	{
		if (!ftruncate($this->handle, self::HEADER_SIZE)) {
			throw new InvalidStateException('Cannot truncate journal file.');
		}
	}

	private function lock()
	{
		if (!$this->handle) {
			throw new InvalidStateException('File journal file is not opened');
		}

		if (!flock($this->handle, LOCK_EX)) {
			throw new InvalidStateException('Cannot acquire exclusive lock on journal.');
		}

		if ($this->lastModTime !== NULL) {
			clearstatcache();
			if ($this->lastModTime < @filemtime($this->file)) {
				$this->nodeCache = $this->dataNodeFreeSpace = array();
			}
		}
	}

	private function unlock()
	{
		if ($this->handle) {
			fflush($this->handle);
			flock($this->handle, LOCK_UN);
			clearstatcache();
			$this->lastModTime = @filemtime($this->file);
		}
	}

	private function arrayAppend(array &$array, array $append)
	{
		foreach ($append as $value) {
			$array[] = $value;
		}
	}

	private function arrayAppendKeys(array &$array, array $append)
	{
		foreach ($append as $key => $value) {
			$array[$key] = $value;
		}
	}

}

class NFileStorage extends NObject implements ICacheStorage
{

	const META_HEADER_LEN = 28,

		META_TIME = 'time',
		META_SERIALIZED = 'serialized',
		META_EXPIRE = 'expire',
		META_DELTA = 'delta',
		META_ITEMS = 'di',
		META_CALLBACKS = 'callbacks';

	const FILE = 'file',
		HANDLE = 'handle';

	public static $gcProbability = 0.001;

	public static $useDirectories = TRUE;

	private $dir;

	private $useDirs;

	private $journal;

	function __construct($dir, ICacheJournal $journal = NULL)
	{
		$this->dir = realpath($dir);
		if ($this->dir === FALSE) {
			throw new DirectoryNotFoundException("Directory '$dir' not found.");
		}

		$this->useDirs = (bool) self::$useDirectories;
		$this->journal = $journal;

		if (mt_rand() / mt_getrandmax() < self::$gcProbability) {
			$this->clean(array());
		}
	}

	function read($key)
	{
		$meta = $this->readMetaAndLock($this->getCacheFile($key), LOCK_SH);
		if ($meta && $this->verify($meta)) {
			return $this->readData($meta);

		} else {
			return NULL;
		}
	}

	private function verify($meta)
	{
		do {
			if (!empty($meta[self::META_DELTA])) {

				if (filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < time()) {
					break;
				}
				touch($meta[self::FILE]);

			} elseif (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < time()) {
				break;
			}

			if (!empty($meta[self::META_CALLBACKS]) && !NCache::checkCallbacks($meta[self::META_CALLBACKS])) {
				break;
			}

			if (!empty($meta[self::META_ITEMS])) {
				foreach ($meta[self::META_ITEMS] as $depFile => $time) {
					$m = $this->readMetaAndLock($depFile, LOCK_SH);
					if ($m[self::META_TIME] !== $time || ($m && !$this->verify($m))) {
						break 2;
					}
				}
			}

			return TRUE;
		} while (FALSE);

		$this->delete($meta[self::FILE], $meta[self::HANDLE]);
		return FALSE;
	}

	function write($key, $data, array $dp)
	{
		$meta = array(
			self::META_TIME => microtime(),
		);

		if (isset($dp[NCache::EXPIRATION])) {
			if (empty($dp[NCache::SLIDING])) {
				$meta[self::META_EXPIRE] = $dp[NCache::EXPIRATION] + time();
			} else {
				$meta[self::META_DELTA] = (int) $dp[NCache::EXPIRATION];
			}
		}

		if (isset($dp[NCache::ITEMS])) {
			foreach ((array) $dp[NCache::ITEMS] as $item) {
				$depFile = $this->getCacheFile($item);
				$m = $this->readMetaAndLock($depFile, LOCK_SH);
				$meta[self::META_ITEMS][$depFile] = $m[self::META_TIME];
				unset($m);
			}
		}

		if (isset($dp[NCache::CALLBACKS])) {
			$meta[self::META_CALLBACKS] = $dp[NCache::CALLBACKS];
		}

		$cacheFile = $this->getCacheFile($key);
		if ($this->useDirs && !is_dir($dir = dirname($cacheFile))) {
			umask(0000);
			if (!mkdir($dir, 0777)) {
				return;
			}
		}
		$handle = @fopen($cacheFile, 'r+b');
		if (!$handle) {
			$handle = fopen($cacheFile, 'wb');
			if (!$handle) {
				return;
			}
		}

		if (isset($dp[NCache::TAGS]) || isset($dp[NCache::PRIORITY])) {
			if (!$this->journal) {
				throw new InvalidStateException('CacheJournal has not been provided.');
			}
			$this->journal->write($cacheFile, $dp);
		}

		flock($handle, LOCK_EX);
		ftruncate($handle, 0);

		if (!is_string($data)) {
			$data = serialize($data);
			$meta[self::META_SERIALIZED] = TRUE;
		}

		$head = serialize($meta) . '?>';
		$head = '<?php //netteCache[01]' . str_pad((string) strlen($head), 6, '0', STR_PAD_LEFT) . $head;
		$headLen = strlen($head);
		$dataLen = strlen($data);

		do {
			if (fwrite($handle, str_repeat("\x00", $headLen), $headLen) !== $headLen) {
				break;
			}

			if (fwrite($handle, $data, $dataLen) !== $dataLen) {
				break;
			}

			fseek($handle, 0);
			if (fwrite($handle, $head, $headLen) !== $headLen) {
				break;
			}

			flock($handle, LOCK_UN);
			fclose($handle);
			return TRUE;
		} while (FALSE);

		$this->delete($cacheFile, $handle);
	}

	function remove($key)
	{
		$this->delete($this->getCacheFile($key));
	}

	function clean(array $conds)
	{
		$all = !empty($conds[NCache::ALL]);
		$collector = empty($conds);

		if ($all || $collector) {
			$now = time();
			foreach (NFinder::find('*')->from($this->dir)->childFirst() as $entry) {
				$path = (string) $entry;
				if ($entry->isDir()) {
					@rmdir($path);
					continue;
				}
				if ($all) {
					$this->delete($path);

				} else {
					$meta = $this->readMetaAndLock($path, LOCK_SH);
					if (!$meta) {
						continue;
					}

					if ((!empty($meta[self::META_DELTA]) && filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < $now)
						|| (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < $now)
					) {
						$this->delete($path, $meta[self::HANDLE]);
						continue;
					}

					flock($meta[self::HANDLE], LOCK_UN);
					fclose($meta[self::HANDLE]);
				}
			}

			if ($this->journal) {
				$this->journal->clean($conds);
			}
			return;
		}

		if ($this->journal) {
			foreach ($this->journal->clean($conds) as $file) {
				$this->delete($file);
			}
		}
	}

	protected function readMetaAndLock($file, $lock)
	{
		$handle = @fopen($file, 'r+b');
		if (!$handle) {
			return NULL;
		}

		flock($handle, $lock);

		$head = stream_get_contents($handle, self::META_HEADER_LEN);
		if ($head && strlen($head) === self::META_HEADER_LEN) {
			$size = (int) substr($head, -6);
			$meta = stream_get_contents($handle, $size, self::META_HEADER_LEN);
			$meta = @unserialize($meta);
			if (is_array($meta)) {
				fseek($handle, $size + self::META_HEADER_LEN);
				$meta[self::FILE] = $file;
				$meta[self::HANDLE] = $handle;
				return $meta;
			}
		}

		flock($handle, LOCK_UN);
		fclose($handle);
		return NULL;
	}

	protected function readData($meta)
	{
		$data = stream_get_contents($meta[self::HANDLE]);
		flock($meta[self::HANDLE], LOCK_UN);
		fclose($meta[self::HANDLE]);

		if (empty($meta[self::META_SERIALIZED])) {
			return $data;
		} else {
			return @unserialize($data);
		}
	}

	protected function getCacheFile($key)
	{
		$file = urlencode($key);
		if ($this->useDirs && $a = strrpos($file, '%00')) {
			$file = substr_replace($file, '/_', $a, 3);
		}
		return $this->dir . '/_' . $file;
	}

	private static function delete($file, $handle = NULL)
	{
		if (@unlink($file)) {
			if ($handle) {
				flock($handle, LOCK_UN);
				fclose($handle);
			}
			return;
		}

		if (!$handle) {
			$handle = @fopen($file, 'r+');
		}
		if ($handle) {
			flock($handle, LOCK_EX);
			ftruncate($handle, 0);
			flock($handle, LOCK_UN);
			fclose($handle);
			@unlink($file);
		}
	}

}

class NMemcachedStorage extends NObject implements ICacheStorage
{

	const META_CALLBACKS = 'callbacks',
		META_DATA = 'data',
		META_DELTA = 'delta';

	private $memcache;

	private $prefix;

	private $journal;

	static function isAvailable()
	{
		return extension_loaded('memcache');
	}

	function __construct($host = 'localhost', $port = 11211, $prefix = '', ICacheJournal $journal = NULL)
	{
		if (!self::isAvailable()) {
			throw new NotSupportedException("PHP extension 'memcache' is not loaded.");
		}

		$this->prefix = $prefix;
		$this->journal = $journal;
		$this->memcache = new Memcache;
		NDebugger::tryError();
		$this->memcache->connect($host, $port);
		if (NDebugger::catchError($e)) {
			throw new InvalidStateException('Memcache::connect(): ' . $e->getMessage(), 0, $e);
		}
	}

	function read($key)
	{
		$key = $this->prefix . $key;
		$meta = $this->memcache->get($key);
		if (!$meta) {
			return NULL;
		}

		if (!empty($meta[self::META_CALLBACKS]) && !NCache::checkCallbacks($meta[self::META_CALLBACKS])) {
			$this->memcache->delete($key, 0);
			return NULL;
		}

		if (!empty($meta[self::META_DELTA])) {
			$this->memcache->replace($key, $meta, 0, $meta[self::META_DELTA] + time());
		}

		return $meta[self::META_DATA];
	}

	function write($key, $data, array $dp)
	{
		if (isset($dp[NCache::ITEMS])) {
			throw new NotSupportedException('Dependent items are not supported by MemcachedStorage.');
		}

		$key = $this->prefix . $key;
		$meta = array(
			self::META_DATA => $data,
		);

		$expire = 0;
		if (isset($dp[NCache::EXPIRATION])) {
			$expire = (int) $dp[NCache::EXPIRATION];
			if (!empty($dp[NCache::SLIDING])) {
				$meta[self::META_DELTA] = $expire;
			}
		}

		if (isset($dp[NCache::CALLBACKS])) {
			$meta[self::META_CALLBACKS] = $dp[NCache::CALLBACKS];
		}

		if (isset($dp[NCache::TAGS]) || isset($dp[NCache::PRIORITY])) {
			if (!$this->journal) {
				throw new InvalidStateException('CacheJournal has not been provided.');
			}
			$this->journal->write($key, $dp);
		}

		$this->memcache->set($key, $meta, 0, $expire);
	}

	function remove($key)
	{
		$this->memcache->delete($this->prefix . $key, 0);
	}

	function clean(array $conds)
	{
		if (!empty($conds[NCache::ALL])) {
			$this->memcache->flush();

		} elseif ($this->journal) {
			foreach ($this->journal->clean($conds) as $entry) {
				$this->memcache->delete($entry, 0);
			}
		}
	}

}

class NMemoryStorage extends NObject implements ICacheStorage
{

	private $data = array();

	function read($key)
	{
		return isset($this->data[$key]) ? $this->data[$key] : NULL;
	}

	function write($key, $data, array $dp)
	{
		$this->data[$key] = $data;
	}

	function remove($key)
	{
		unset($this->data[$key]);
	}

	function clean(array $conds)
	{
		if (!empty($conds[NCache::ALL])) {
			$this->data = array();
		}
	}

}

class NPhpFileStorage extends NFileStorage
{

	public $hint;

	protected function readData($meta)
	{
		return array(
			'file' => $meta[self::FILE],
			'handle' => $meta[self::HANDLE],
		);
	}

	protected function getCacheFile($key)
	{
		return parent::getCacheFile(substr_replace(
			$key,
			trim(strtr($this->hint, '\\/@', '.._'), '.') . '-',
			strpos($key, NCache::NAMESPACE_SEPARATOR) + 1,
			0
		)) . '.php';
	}

}

class NArrayHash extends stdClass implements ArrayAccess, Countable, IteratorAggregate
{

	static function from($arr, $recursive = TRUE)
	{
		$obj = new self;
		foreach ($arr as $key => $value) {
			if ($recursive && is_array($value)) {
				$obj->$key = self::from($value, TRUE);
			} else {
				$obj->$key = $value;
			}
		}
		return $obj;
	}

	function getIterator()
	{
		return new RecursiveArrayIterator($this);
	}

	function count()
	{
		return count((array) $this);
	}

	function offsetSet($key, $value)
	{
		if (!is_scalar($key)) {
			throw new InvalidArgumentException("Key must be either a string or an integer, " . gettype($key) ." given.");
		}
		$this->$key = $value;
	}

	function offsetGet($key)
	{
		return $this->$key;
	}

	function offsetExists($key)
	{
		return isset($this->$key);
	}

	function offsetUnset($key)
	{
		unset($this->$key);
	}

}

final class NCallback extends NObject
{

	private $cb;

	function __construct($t, $m = NULL)
	{
		if ($m === NULL) {
			if (is_string($t)) {
				$t = explode('::', $t, 2);
				$this->cb = isset($t[1]) ? $t : $t[0];
			} elseif (is_object($t)) {
				$this->cb = $t instanceof Closure ? $t : array($t, '__invoke');
			} else {
				$this->cb = $t;
			}

		} else {
			$this->cb = array($t, $m);
		}

		if (is_string($this->cb) && $a = strrpos($this->cb, '\\')) {
			$this->cb = substr($this->cb, $a + 1);

		} elseif (is_array($this->cb) && is_string($this->cb[0]) && $a = strrpos($this->cb[0], '\\')) {
			$this->cb[0] = substr($this->cb[0], $a + 1);
		}

		if (!is_callable($this->cb, TRUE)) {
			throw new InvalidArgumentException("Invalid callback.");
		}
	}

	function __invoke()
	{
		if (!is_callable($this->cb)) {
			throw new InvalidStateException("Callback '$this' is not callable.");
		}
		$args = func_get_args();
		return call_user_func_array($this->cb, $args);
	}

	function invoke()
	{
		if (!is_callable($this->cb)) {
			throw new InvalidStateException("Callback '$this' is not callable.");
		}
		$args = func_get_args();
		return call_user_func_array($this->cb, $args);
	}

	function invokeArgs(array $args)
	{
		if (!is_callable($this->cb)) {
			throw new InvalidStateException("Callback '$this' is not callable.");
		}
		return call_user_func_array($this->cb, $args);
	}

	function invokeNamedArgs(array $args)
	{
		$ref = $this->toReflection();
		if (is_array($this->cb)) {
			return $ref->invokeNamedArgs(is_object($this->cb[0]) ? $this->cb[0] : NULL, $args);
		} else {
			return $ref->invokeNamedArgs($args);
		}
	}

	function isCallable()
	{
		return is_callable($this->cb);
	}

	function getNative()
	{
		return $this->cb;
	}

	function toReflection()
	{
		if (is_array($this->cb)) {
			return new NMethodReflection($this->cb[0], $this->cb[1]);
		} else {
			return new NFunctionReflection($this->cb);
		}
	}

	function isStatic()
	{
		return is_array($this->cb) ? is_string($this->cb[0]) : is_string($this->cb);
	}

	function __toString()
	{
		if ($this->cb instanceof Closure) {
			return '{closure}';
		} elseif (is_string($this->cb) && $this->cb[0] === "\0") {
			return '{lambda}';
		} else {
			is_callable($this->cb, TRUE, $textual);
			return $textual;
		}
	}

}

class NConfigurator extends NObject
{
	public static $instance;

	public $defaultConfigFile = '%appDir%/config.neon';

	private $container;

	function __construct($containerClass = 'NDiContainer')
	{
		self::$instance = $this;
		$this->container = new $containerClass;
		$this->container->addService('container', $this->container);

		foreach (get_class_methods($this) as $name) {
			if (substr($name, 0, 13) === 'createService' ) {
				$this->container->addService(strtolower($name[13]) . substr($name, 14), array(__CLASS__, $name));
			}
		}

		defined('WWW_DIR') && $this->container->params['wwwDir'] = realpath(WWW_DIR);
		defined('APP_DIR') && $this->container->params['appDir'] = realpath(APP_DIR);
		defined('LIBS_DIR') && $this->container->params['libsDir'] = realpath(LIBS_DIR);
		defined('TEMP_DIR') && $this->container->params['tempDir'] = realpath(TEMP_DIR);
		$this->container->params['productionMode'] = self::detectProductionMode();
		$this->container->params['consoleMode'] = PHP_SAPI === 'cli';
	}

	function getContainer()
	{
		return $this->container;
	}

	function loadConfig($file, $section = NULL)
	{
		if ($file === NULL) {
			$file = $this->defaultConfigFile;
		}
		$container = $this->container;
		$file = $container->expand($file);
		if (!is_file($file)) {
			$file = preg_replace('#\.neon$#', '.ini', $file);
		}
		if ($section === NULL) {
			if (PHP_SAPI === 'cli') {
				$section = NEnvironment::CONSOLE;
			} else {
				$section = $container->params['productionMode'] ? NEnvironment::PRODUCTION : NEnvironment::DEVELOPMENT;
			}
		}

		$cache = new NCache($container->templateCacheStorage, 'Nette.Configurator');
		$cacheKey = array((array) $container->params, $file, $section);
		$cached = $cache->load($cacheKey);
		if ($cached) {
			 require $cached['file'];
			fclose($cached['handle']);
			return $this->container;
		}

		$config = NConfig::fromFile($file, $section);
		$code = "<?php\n// source file $file\n\n";

		foreach (array('service', 'variable') as $item) {
			if (isset($config[$item])) {
				trigger_error(basename($file) . ": Section '$item' is deprecated; use plural form '{$item}s' instead.", E_USER_WARNING);
				$config[$item . 's'] = $config[$item];
				unset($config[$item]);
			}
		}

		if (isset($config['services'])) {
			foreach ($config['services'] as $key => & $def) {
				if (preg_match('#^Nette\\\\.*\\\\I?([a-zA-Z]+)$#', strtr($key, '-', '\\'), $m)) {
					$m[1][0] = strtolower($m[1][0]);
					trigger_error(basename($file) . ": service name '$key' has been renamed to '$m[1]'", E_USER_WARNING);
					$key = $m[1];
				}

				if (is_array($def)) {
					if (method_exists(__CLASS__, "createService$key") && !isset($def['factory']) && !isset($def['class'])) {
						$def['factory'] = array(__CLASS__, "createService$key");
					}

					if (isset($def['option'])) {
						$def['arguments'][] = $def['option'];
					}

					if (!empty($def['run'])) {
						$def['tags'] = array('run');
					}
				}
			}
			$builder = new NContainerBuilder;
			$code .= '$builder = new '.get_class($builder).'; $builder->addDefinitions($container, '.var_export($config['services'], TRUE).');';
			unset($config['services']);
		}

		if (!isset($config['variables'])) {
			$config['variables'] = array();
		}
		foreach ($config as $key => $value) {
			if (!in_array($key, array('variables', 'services', 'php', 'const', 'mode'))) {
				$config['variables'][$key] = $value;
			}
		}

		$variables = $config['variables'];
		array_walk_recursive($config, create_function('&$val', 'extract(NCFix::$vars['.NCFix::uses(array('variables'=>$variables)).'], EXTR_REFS);
			$val = NConfigurator::preExpand($val, $variables);
		'));

		foreach ($config['variables'] as $key => $value) {
			$code .= $this->generateCode('$container->params[?] = ?', $key, $value);
		}

		if (isset($config['php'])) {
			foreach ($config['php'] as $key => $value) {
				if (is_array($value)) {
					foreach ($value as $k => $v) {
						$code .= $this->configurePhp("$key.$k", $v);
					}
				} else {
					$code .= $this->configurePhp($key, $value);
				}
			}
		}

		if (isset($config['const'])) {
			foreach ($config['const'] as $key => $value) {
				$code .= $this->generateCode('define', $key, $value);
			}
		}

		if (isset($config['mode'])) {
			foreach ($config['mode'] as $mode => $state) {
				trigger_error(basename($file) . ": Section 'mode' is deprecated; use '{$mode}Mode' in section 'variables' instead.", E_USER_WARNING);
				$code .= $this->generateCode('$container->params[?] = ?', $mode . 'Mode', (bool) $state);
			}
		}

		$code .= self::preloadEnvironment($container);

		$code .= 'foreach ($container->getServiceNamesByTag("run") as $name => $foo) { $container->getService($name); }' . "\n";

		$cache->save($cacheKey, $code, array(
			NCache::FILES => $file,
		));

		NLimitedScope::evaluate($code, array('container' => $container));
		return $this->container;
	}

	static function detectProductionMode()
	{
		$addrs = array();
		if (PHP_SAPI === 'cli') {
			$addrs[] = getHostByName(php_uname('n'));
		}
		else {
			if (!isset($_SERVER['SERVER_ADDR']) && !isset($_SERVER['LOCAL_ADDR'])) {
				return TRUE;
			}
			if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
				$addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
			}
			if (isset($_SERVER['REMOTE_ADDR'])) {
				$addrs[] = $_SERVER['REMOTE_ADDR'];
			}
			$addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
		}
		foreach ($addrs as $addr) {
			$oct = explode('.', $addr);

			if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
				&& ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
			) {
				return TRUE;
			}
		}
		return FALSE;
	}

	function configurePhp($name, $value)
	{
		if (!is_scalar($value)) {
			throw new InvalidStateException("Configuration value for directive '$name' is not scalar.");
		}

		switch ($name) {
		case 'include_path':
			return $this->generateCode('set_include_path', str_replace(';', PATH_SEPARATOR, $value));
		case 'ignore_user_abort':
			return $this->generateCode('ignore_user_abort', $value);
		case 'max_execution_time':
			return $this->generateCode('set_time_limit', $value);
		case 'date.timezone':
			return $this->generateCode('date_default_timezone_set', $value);
		}

		if (function_exists('ini_set')) {
			return $this->generateCode('ini_set', $name, $value);
		} elseif (ini_get($name) != $value && !NFramework::$iAmUsingBadHost) {
			throw new NotSupportedException('Required function ini_set() is disabled.');
		}
	}

	private static function generateCode($statement)
	{
		$args = func_get_args();
		unset($args[0]);
		foreach ($args as &$arg) {
			$arg = var_export($arg, TRUE);
			$arg = preg_replace("#(?<!\\\)'%([\w-]+)%'#", '\$container->params[\'$1\']', $arg);
			$arg = preg_replace("#(?<!\\\)'(?:[^'\\\]|\\\.)*%(?:[^'\\\]|\\\.)*'#", '\$container->expand($0)', $arg);
		}
		if (strpos($statement, '?') === FALSE) {
			return $statement .= '(' . implode(', ', $args) . ");\n\n";
		}
		$a = strpos($statement, '?');
		$i = 1;
		while ($a !== FALSE) {
			$statement = substr_replace($statement, $args[$i], $a, 1);
			$a = strpos($statement, '?', $a + strlen($args[$i]));
			$i++;
		}
		return $statement . ";\n\n";
	}

	static function preExpand($s, array $params, $check = array())
	{
		if (!is_string($s)) {
			return $s;
		}

		$parts = preg_split('#%([\w.-]*)%#i', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
		$res = '';
		foreach ($parts as $n => $part) {
			if ($n % 2 === 0) {
				$res .= str_replace('%', '%%', $part);

			} elseif ($part === '') {
				$res .= '%%';

			} elseif (isset($check[$part])) {
				throw new InvalidArgumentException('Circular reference detected for variables: ' . implode(', ', array_keys($check)) . '.');

			} else {
				try {
					$val = NArrays::get($params, explode('.', $part));
				} catch (InvalidArgumentException $e) {
					$res .= "%$part%";
					continue;
				}
				$val = self::preExpand($val, $params, $check + array($part => 1));
				if (strlen($part) + 2 === strlen($s)) {
					if (is_array($val)) {
						array_walk_recursive($val, create_function('&$val', 'extract(NCFix::$vars['.NCFix::uses(array('params'=>$params,'check'=> $check,'part'=> $part)).'], EXTR_REFS);
							$val = NConfigurator::preExpand($val, $params, $check + array($part => 1));
						'));
					}
					return $val;
				}
				if (!is_scalar($val)) {
					throw new InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$s'.");
				}
				$res .= $val;
			}
		}
		return $res;
	}

	static function createServiceApplication(NDiContainer $container, array $options = NULL)
	{
		$context = new NDiContainer;
		$context->addService('httpRequest', $container->httpRequest);
		$context->addService('httpResponse', $container->httpResponse);
		$context->addService('session', $container->session);
		$context->addService('presenterFactory', $container->presenterFactory);
		$context->addService('router', $container->router);

		NPresenter::$invalidLinkMode = $container->params['productionMode']
			? NPresenter::INVALID_LINK_SILENT
			: NPresenter::INVALID_LINK_WARNING;

		$class = isset($options['class']) ? $options['class'] : 'NApplication';
		$application = new $class($context);
		$application->catchExceptions = $container->params['productionMode'];
		if ($container->session->exists()) {
		$application->onStartup[] = create_function('', 'extract(NCFix::$vars['.NCFix::uses(array('container'=>$container)).'], EXTR_REFS);
				$container->session->start(); // opens already started session
			');
		}
		return $application;
	}

	static function createServicePresenterFactory(NDiContainer $container)
	{
		return new NPresenterFactory(
			isset($container->params['appDir']) ? $container->params['appDir'] : NULL,
			$container
		);
	}

	static function createServiceRouter(NDiContainer $container)
	{
		return new NRouteList;
	}

	static function createServiceHttpRequest()
	{
		$factory = new NHttpRequestFactory;
		$factory->setEncoding('UTF-8');
		return $factory->createHttpRequest();
	}

	static function createServiceHttpResponse()
	{
		return new NHttpResponse;
	}

	static function createServiceHttpContext(NDiContainer $container)
	{
		return new NHttpContext($container->httpRequest, $container->httpResponse);
	}

	static function createServiceSession(NDiContainer $container, array $options = NULL)
	{
		$session = new NSession($container->httpRequest, $container->httpResponse);
		$session->setOptions((array) $options);
		if (isset($options['expiration'])) {
			$session->setExpiration($options['expiration']);
		}
		return $session;
	}

	static function createServiceUser(NDiContainer $container)
	{
		$context = new NDiContainer;

		$context->addService('authenticator', create_function('', 'extract(NCFix::$vars['.NCFix::uses(array('container'=>$container)).'], EXTR_REFS);
			return $container->authenticator;
		'));
		$context->addService('authorizator', create_function('', 'extract(NCFix::$vars['.NCFix::uses(array('container'=>$container)).'], EXTR_REFS);
			return $container->authorizator;
		'));
		$context->addService('session', $container->session);
		return new NUser($context);
	}

	static function createServiceCacheStorage(NDiContainer $container)
	{
		if (!isset($container->params['tempDir'])) {
			throw new InvalidStateException("Service cacheStorage requires that parameter 'tempDir' contains path to temporary directory.");
		}
		$dir = $container->expand('%tempDir%/cache');
		umask(0000);
		@mkdir($dir, 0777);
		return new NFileStorage($dir, $container->cacheJournal);
	}

	static function createServiceTemplateCacheStorage(NDiContainer $container)
	{
		if (!isset($container->params['tempDir'])) {
			throw new InvalidStateException("Service templateCacheStorage requires that parameter 'tempDir' contains path to temporary directory.");
		}
		$dir = $container->expand('%tempDir%/cache');
		umask(0000);
		@mkdir($dir, 0777);
		return new NPhpFileStorage($dir);
	}

	static function createServiceCacheJournal(NDiContainer $container)
	{
		return new NFileJournal($container->params['tempDir']);
	}

	static function createServiceMailer(NDiContainer $container, array $options = NULL)
	{
		if (empty($options['smtp'])) {
			return new NSendmailMailer;
		} else {
			return new NSmtpMailer($options);
		}
	}

	static function createServiceRobotLoader(NDiContainer $container, array $options = NULL)
	{
		$loader = new NRobotLoader;
		$loader->autoRebuild = isset($options['autoRebuild']) ? $options['autoRebuild'] : !$container->params['productionMode'];
		$loader->setCacheStorage($container->cacheStorage);
		if (isset($options['directory'])) {
			$loader->addDirectory($options['directory']);
		} else {
			foreach (array('appDir', 'libsDir') as $var) {
				if (isset($container->params[$var])) {
					$loader->addDirectory($container->params[$var]);
				}
			}
		}
		$loader->register();
		return $loader;
	}

	static function preloadEnvironment(NDiContainer $container)
	{
		$code = '';
		$dir = $container->expand('%tempDir%/cache');
		umask(0000);
		@mkdir($dir, 0777);

		$uniq = uniqid('_', TRUE);
		umask(0000);
		if (!@mkdir("$dir/$uniq", 0777)) {
			throw new InvalidStateException("Unable to write to directory '$dir'. Make this directory writable.");
		}

		$useDirs = @file_put_contents("$dir/$uniq/_", '') !== FALSE;
		@unlink("$dir/$uniq/_");
		@rmdir("$dir/$uniq");

		$code .= self::generateCode('NFileStorage::$useDirectories = ?', $useDirs);
		return $code;
	}

}

class NDateTime53 extends DateTime
{

	const MINUTE = 60;

	const HOUR = 3600;

	const DAY = 86400;

	const WEEK = 604800;

	const MONTH = 2629800;

	const YEAR = 31557600;

	static function from($time)
	{
		if ($time instanceof DateTime) {
			return clone $time;

		} elseif (is_numeric($time)) {
			if ($time <= self::YEAR) {
				$time += time();
			}
			return new self(date('Y-m-d H:i:s', $time));

		} else {
			return new self($time);
		}
	}

	static function __set_state($state)
	{
		return new self($state['date'], new DateTimeZone($state['timezone']));
	}

	function __sleep()
	{
		$this->fix = array($this->format('Y-m-d H:i:s'), $this->getTimezone()->getName());
		return array('fix');
	}

	function __wakeup()
	{
		$this->__construct($this->fix[0], new DateTimeZone($this->fix[1]));
		unset($this->fix);
	}

	function getTimestamp()
	{
		return (int) $this->format('U');
	}

	function setTimestamp($timestamp)
	{
		return $this->__construct(
			gmdate('Y-m-d H:i:s', $timestamp + $this->getOffset()),
			new DateTimeZone($this->getTimezone()->getName())
		);
	}

}

final class NEnvironment
{

	const DEVELOPMENT = 'development',
		PRODUCTION = 'production',
		CONSOLE = 'console';

	private static $configurator;

	private static $context;

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function setConfigurator(NConfigurator $configurator)
	{
		self::$configurator = $configurator;
	}

	static function getConfigurator()
	{
		if (self::$configurator === NULL) {
			self::$configurator = ($tmp= NConfigurator::$instance) ? $tmp : new NConfigurator;
		}
		return self::$configurator;
	}

	static function isConsole()
	{
		return self::getContext()->params['consoleMode'];
	}

	static function isProduction()
	{
		return self::getContext()->params['productionMode'];
	}

	static function setProductionMode($value = TRUE)
	{
		self::getContext()->params['productionMode'] = (bool) $value;
	}

	static function setVariable($name, $value, $expand = TRUE)
	{
		if ($expand && is_string($value)) {
			$value = self::getContext()->expand($value);
		}
		self::getContext()->params[$name] = $value;
	}

	static function getVariable($name, $default = NULL)
	{
		if (isset(self::getContext()->params[$name])) {
			return self::getContext()->params[$name];
		} elseif (func_num_args() > 1) {
			return $default;
		} else {
			throw new InvalidStateException("Unknown environment variable '$name'.");
		}
	}

	static function getVariables()
	{
		return self::getContext()->params;
	}

	static function expand($s)
	{
		return self::getContext()->expand($s);
	}

	static function setContext(IDiContainer $context)
	{
		self::$context = $context;
	}

	static function getContext()
	{
		if (self::$context === NULL) {
			self::$context = self::getConfigurator()->getContainer();
		}
		return self::$context;
	}

	static function getService($name)
	{
		return self::getContext()->getService($name);
	}

	static function __callStatic($name, $args)
	{
		if (!$args && strncasecmp($name, 'get', 3) === 0) {
			return self::getContext()->getService(lcfirst(substr($name, 3)));
		} else {
			throw new MemberAccessException("Call to undefined static method NEnvironment::$name().");
		}
	}

	static function getHttpRequest()
	{
		return self::getContext()->httpRequest;
	}

	static function getHttpContext()
	{
		return self::getContext()->httpContext;
	}

	static function getHttpResponse()
	{
		return self::getContext()->httpResponse;
	}

	static function getApplication()
	{
		return self::getContext()->application;
	}

	static function getUser()
	{
		return self::getContext()->user;
	}

	static function getRobotLoader()
	{
		return self::getContext()->robotLoader;
	}

	static function getCache($namespace = '')
	{
		return new NCache(self::getContext()->cacheStorage, $namespace);
	}

	static function getSession($namespace = NULL)
	{
		return $namespace === NULL
			? self::getContext()->session
			: self::getContext()->session->getSection($namespace);
	}

	static function loadConfig($file = NULL, $section = NULL)
	{
		self::getConfigurator()->loadConfig($file, $section);
		return self::getConfig();
	}

	static function getConfig($key = NULL, $default = NULL)
	{
		$params = NArrayHash::from(self::getContext()->params);
		if (func_num_args()) {
			return isset($params[$key]) ? $params[$key] : $default;
		} else {
			return $params;
		}
	}

}

final class NFramework
{

	const NAME = 'Nette Framework',
		VERSION = '2.0-beta',
		REVISION = 'eee17d5 released on 2011-08-13';

	public static $iAmUsingBadHost = FALSE;

	final function __construct()
	{
		throw new NStaticClassException;
	}

}

class NImage extends NObject
{

	const ENLARGE = 1;

	const STRETCH = 2;

	const FIT = 0;

	const FILL = 4;

	const JPEG = IMAGETYPE_JPEG,
		PNG = IMAGETYPE_PNG,
		GIF = IMAGETYPE_GIF;

	const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";

	private $image;

	static function rgb($red, $green, $blue, $transparency = 0)
	{
		return array(
			'red' => max(0, min(255, (int) $red)),
			'green' => max(0, min(255, (int) $green)),
			'blue' => max(0, min(255, (int) $blue)),
			'alpha' => max(0, min(127, (int) $transparency)),
		);
	}

	static function fromFile($file, & $format = NULL)
	{
		if (!extension_loaded('gd')) {
			throw new NotSupportedException("PHP extension GD is not loaded.");
		}

		$info = @getimagesize($file);

		switch ($format = $info[2]) {
		case self::JPEG:
			return new self(imagecreatefromjpeg($file));

		case self::PNG:
			return new self(imagecreatefrompng($file));

		case self::GIF:
			return new self(imagecreatefromgif($file));

		default:
			throw new NUnknownImageFileException("Unknown image type or file '$file' not found.");
		}
	}

	static function getFormatFromString($s)
	{
		$types = array('image/jpeg' => self::JPEG, 'image/gif' => self::GIF, 'image/png' => self::PNG);
		$type = NMimeTypeDetector::fromString($s);
		return isset($types[$type]) ? $types[$type] : NULL;
	}

	static function fromString($s, & $format = NULL)
	{
		if (!extension_loaded('gd')) {
			throw new NotSupportedException("PHP extension GD is not loaded.");
		}

		$format = self::getFormatFromString($s);

		return new self(imagecreatefromstring($s));
	}

	static function fromBlank($width, $height, $color = NULL)
	{
		if (!extension_loaded('gd')) {
			throw new NotSupportedException("PHP extension GD is not loaded.");
		}

		$width = (int) $width;
		$height = (int) $height;
		if ($width < 1 || $height < 1) {
			throw new InvalidArgumentException('Image width and height must be greater than zero.');
		}

		$image = imagecreatetruecolor($width, $height);
		if (is_array($color)) {
			$color += array('alpha' => 0);
			$color = imagecolorallocatealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
			imagealphablending($image, FALSE);
			imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
			imagealphablending($image, TRUE);
		}
		return new self($image);
	}

	function __construct($image)
	{
		$this->setImageResource($image);
		imagesavealpha($image, TRUE);
	}

	function getWidth()
	{
		return imagesx($this->image);
	}

	function getHeight()
	{
		return imagesy($this->image);
	}

	protected function setImageResource($image)
	{
		if (!is_resource($image) || get_resource_type($image) !== 'gd') {
			throw new InvalidArgumentException('Image is not valid.');
		}
		$this->image = $image;
		return $this;
	}

	function getImageResource()
	{
		return $this->image;
	}

	function resize($width, $height, $flags = self::FIT)
	{
		list($newWidth, $newHeight) = self::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);

		if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) {
			$newImage = self::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
			imagecopyresampled(
				$newImage, $this->getImageResource(),
				0, 0, 0, 0,
				$newWidth, $newHeight, $this->getWidth(), $this->getHeight()
			);
			$this->image = $newImage;
		}

		if ($width < 0 || $height < 0) {
			$newImage = self::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
			imagecopyresampled(
				$newImage, $this->getImageResource(),
				0, 0, $width < 0 ? $newWidth - 1 : 0, $height < 0 ? $newHeight - 1 : 0,
				$newWidth, $newHeight, $width < 0 ? -$newWidth : $newWidth, $height < 0 ? -$newHeight : $newHeight
			);
			$this->image = $newImage;
		}
		return $this;
	}

	static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT)
	{
		if (substr($newWidth, -1) === '%') {
			$newWidth = round($srcWidth / 100 * abs($newWidth));
			$flags |= self::ENLARGE;
			$percents = TRUE;
		} else {
			$newWidth = (int) abs($newWidth);
		}

		if (substr($newHeight, -1) === '%') {
			$newHeight = round($srcHeight / 100 * abs($newHeight));
			$flags |= empty($percents) ? self::ENLARGE : self::STRETCH;
		} else {
			$newHeight = (int) abs($newHeight);
		}

		if ($flags & self::STRETCH) {
			if (empty($newWidth) || empty($newHeight)) {
				throw new InvalidArgumentException('For stretching must be both width and height specified.');
			}

			if (($flags & self::ENLARGE) === 0) {
				$newWidth = round($srcWidth * min(1, $newWidth / $srcWidth));
				$newHeight = round($srcHeight * min(1, $newHeight / $srcHeight));
			}

		} else {
			if (empty($newWidth) && empty($newHeight)) {
				throw new InvalidArgumentException('At least width or height must be specified.');
			}

			$scale = array();
			if ($newWidth > 0) {
				$scale[] = $newWidth / $srcWidth;
			}

			if ($newHeight > 0) {
				$scale[] = $newHeight / $srcHeight;
			}

			if ($flags & self::FILL) {
				$scale = array(max($scale));
			}

			if (($flags & self::ENLARGE) === 0) {
				$scale[] = 1;
			}

			$scale = min($scale);
			$newWidth = round($srcWidth * $scale);
			$newHeight = round($srcHeight * $scale);
		}

		return array(max((int) $newWidth, 1), max((int) $newHeight, 1));
	}

	function crop($left, $top, $width, $height)
	{
		list($left, $top, $width, $height) = self::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
		$newImage = self::fromBlank($width, $height, self::RGB(0, 0, 0, 127))->getImageResource();
		imagecopy($newImage, $this->getImageResource(), 0, 0, $left, $top, $width, $height);
		$this->image = $newImage;
		return $this;
	}

	static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight)
	{
		if (substr($newWidth, -1) === '%') {
			$newWidth = round($srcWidth / 100 * $newWidth);
		}
		if (substr($newHeight, -1) === '%') {
			$newHeight = round($srcHeight / 100 * $newHeight);
		}
		if (substr($left, -1) === '%') {
			$left = round(($srcWidth - $newWidth) / 100 * $left);
		}
		if (substr($top, -1) === '%') {
			$top = round(($srcHeight - $newHeight) / 100 * $top);
		}
		if ($left < 0) {
			$newWidth += $left; $left = 0;
		}
		if ($top < 0) {
			$newHeight += $top; $top = 0;
		}
		$newWidth = min((int) $newWidth, $srcWidth - $left);
		$newHeight = min((int) $newHeight, $srcHeight - $top);
		return array($left, $top, $newWidth, $newHeight);
	}

	function sharpen()
	{
		imageconvolution($this->getImageResource(), array(
			array( -1, -1, -1 ),
			array( -1, 24, -1 ),
			array( -1, -1, -1 ),
		), 16, 0);
		return $this;
	}

	function place(NImage $image, $left = 0, $top = 0, $opacity = 100)
	{
		$opacity = max(0, min(100, (int) $opacity));

		if (substr($left, -1) === '%') {
			$left = round(($this->getWidth() - $image->getWidth()) / 100 * $left);
		}

		if (substr($top, -1) === '%') {
			$top = round(($this->getHeight() - $image->getHeight()) / 100 * $top);
		}

		if ($opacity === 100) {
			imagecopy(
				$this->getImageResource(), $image->getImageResource(),
				$left, $top, 0, 0, $image->getWidth(), $image->getHeight()
			);

		} elseif ($opacity <> 0) {
			imagecopymerge(
				$this->getImageResource(), $image->getImageResource(),
				$left, $top, 0, 0, $image->getWidth(), $image->getHeight(),
				$opacity
			);
		}
		return $this;
	}

	function save($file = NULL, $quality = NULL, $type = NULL)
	{
		if ($type === NULL) {
			switch (strtolower(pathinfo($file, PATHINFO_EXTENSION))) {
			case 'jpg':
			case 'jpeg':
				$type = self::JPEG;
				break;
			case 'png':
				$type = self::PNG;
				break;
			case 'gif':
				$type = self::GIF;
			}
		}

		switch ($type) {
		case self::JPEG:
			$quality = $quality === NULL ? 85 : max(0, min(100, (int) $quality));
			return imagejpeg($this->getImageResource(), $file, $quality);

		case self::PNG:
			$quality = $quality === NULL ? 9 : max(0, min(9, (int) $quality));
			return imagepng($this->getImageResource(), $file, $quality);

		case self::GIF:
			return $file === NULL ? imagegif($this->getImageResource()) : imagegif($this->getImageResource(), $file);

		default:
			throw new InvalidArgumentException("Unsupported image type.");
		}
	}

	function toString($type = self::JPEG, $quality = NULL)
	{
		ob_start();
		$this->save(NULL, $quality, $type);
		return ob_get_clean();
	}

	function __toString()
	{
		try {
			return $this->toString();

		} catch (Exception $e) {
			NDebugger::toStringException($e);
		}
	}

	function send($type = self::JPEG, $quality = NULL)
	{
		if ($type !== self::GIF && $type !== self::PNG && $type !== self::JPEG) {
			throw new InvalidArgumentException("Unsupported image type.");
		}
		header('Content-Type: ' . image_type_to_mime_type($type));
		return $this->save(NULL, $quality, $type);
	}

	function __call($name, $args)
	{
		$function = 'image' . $name;
		if (function_exists($function)) {
			foreach ($args as $key => $value) {
				if ($value instanceof self) {
					$args[$key] = $value->getImageResource();

				} elseif (is_array($value) && isset($value['red'])) {
					$args[$key] = imagecolorallocatealpha(
						$this->getImageResource(),
						$value['red'], $value['green'], $value['blue'], $value['alpha']
					);
				}
			}
			array_unshift($args, $this->getImageResource());

			$res = call_user_func_array($function, $args);
			return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
		}

		return parent::__call($name, $args);
	}

}

class NUnknownImageFileException extends Exception
{
}

final class NObjectMixin
{

	private static $methods;

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function call($_this, $name, $args)
	{
		$class = new NClassReflection($_this);

		if ($name === '') {
			throw new MemberAccessException("Call to class '$class->name' method without name.");
		}

		if ($class->hasEventProperty($name)) {
			if (is_array($list = $_this->$name) || $list instanceof Traversable) {
				foreach ($list as $handler) {
					callback($handler)->invokeArgs($args);
				}
			}
			return NULL;
		}

		if ($cb = $class->getExtensionMethod($name)) {
			array_unshift($args, $_this);
			return $cb->invokeArgs($args);
		}

		throw new MemberAccessException("Call to undefined method $class->name::$name().");
	}

	static function callStatic($class, $name, $args)
	{
		throw new MemberAccessException("Call to undefined static method $class::$name().");
	}

	static function & get($_this, $name)
	{
		$class = get_class($_this);

		if ($name === '') {
			throw new MemberAccessException("Cannot read a class '$class' property without name.");
		}

		if (!isset(self::$methods[$class])) {

			self::$methods[$class] = array_flip(get_class_methods($class));
		}

		$name[0] = $name[0] & "\xDF";
		$m = 'get' . $name;
		if (isset(self::$methods[$class][$m])) {

			$val = $_this->$m();
			return $val;
		}

		$m = 'is' . $name;
		if (isset(self::$methods[$class][$m])) {
			$val = $_this->$m();
			return $val;
		}

		$type = isset(self::$methods[$class]['set' . $name]) ? 'a write-only' : 'an undeclared';
		$name = func_get_arg(1);
		throw new MemberAccessException("Cannot read $type property $class::\$$name.");
	}

	static function set($_this, $name, $value)
	{
		$class = get_class($_this);

		if ($name === '') {
			throw new MemberAccessException("Cannot write to a class '$class' property without name.");
		}

		if (!isset(self::$methods[$class])) {
			self::$methods[$class] = array_flip(get_class_methods($class));
		}

		$name[0] = $name[0] & "\xDF";

		$m = 'set' . $name;
		if (isset(self::$methods[$class][$m])) {
			$_this->$m($value);
			return;
		}

		$type = isset(self::$methods[$class]['get' . $name]) || isset(self::$methods[$class]['is' . $name])
			? 'a read-only' : 'an undeclared';
		$name = func_get_arg(1);
		throw new MemberAccessException("Cannot write to $type property $class::\$$name.");
	}

	static function remove($_this, $name)
	{
		$class = get_class($_this);
		throw new MemberAccessException("Cannot unset the property $class::\$$name.");
	}

	static function has($_this, $name)
	{
		if ($name === '') {
			return FALSE;
		}

		$class = get_class($_this);
		if (!isset(self::$methods[$class])) {
			self::$methods[$class] = array_flip(get_class_methods($class));
		}

		$name[0] = $name[0] & "\xDF";
		return isset(self::$methods[$class]['get' . $name]) || isset(self::$methods[$class]['is' . $name]);
	}

}

class NRecursiveComponentIterator extends RecursiveArrayIterator implements Countable
{

	function hasChildren()
	{
		return $this->current() instanceof IComponentContainer;
	}

	function getChildren()
	{
		return $this->current()->getComponents();
	}

	function count()
	{
		return iterator_count($this);
	}

}

class NConfig
{

	private static $extensions = array(
		'ini' => 'NConfigAdapterIni',
		'neon' => 'NConfigAdapterNeon',
	);

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function registerExtension($extension, $class)
	{
		if (!class_exists($class)) {
			throw new InvalidArgumentException("Class '$class' was not found.");
		}

		if (!NClassReflection::from($class)->implementsInterface('IConfigAdapter')) {
			throw new InvalidArgumentException("Configuration adapter '$class' is not IConfigAdapter implementor.");
		}

		self::$extensions[strtolower($extension)] = $class;
	}

	static function fromFile($file, $section = NULL)
	{
		$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
		if (!isset(self::$extensions[$extension])) {
			throw new InvalidArgumentException("Unknown file extension '$file'.");
		}

		$data = call_user_func(array(self::$extensions[$extension], 'load'), $file, $section);
		if ($section) {
			if (!isset($data[$section]) || !is_array($data[$section])) {
				throw new InvalidStateException("There is not section [$section] in file '$file'.");
			}
			$data = $data[$section];
		}
		return $data;
	}

	static function save($config, $file)
	{
		$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
		if (!isset(self::$extensions[$extension])) {
			throw new InvalidArgumentException("Unknown file extension '$file'.");
		}
		return call_user_func(array(self::$extensions[$extension], 'save'), $config, $file);
	}

}

final class NConfigAdapterIni implements IConfigAdapter
{

	public static $keySeparator = '.';

	public static $sectionSeparator = ' < ';

	public static $rawSection = '!';

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function load($file)
	{
		if (!is_file($file) || !is_readable($file)) {
			throw new FileNotFoundException("File '$file' is missing or is not readable.");
		}

		NDebugger::tryError();
		$ini = parse_ini_file($file, TRUE);
		if (NDebugger::catchError($e)) {
			throw new InvalidStateException('parse_ini_file(): ' . $e->getMessage(), 0, $e);
		}

		$separator = trim(self::$sectionSeparator);
		$data = array();
		foreach ($ini as $secName => $secData) {

			if (is_array($secData)) {
				if (substr($secName, -1) === self::$rawSection) {
					$secName = substr($secName, 0, -1);

				} elseif (self::$keySeparator) {

					$tmp = array();
					foreach ($secData as $key => $val) {
						$cursor = & $tmp;
						foreach (explode(self::$keySeparator, $key) as $part) {
							if (!isset($cursor[$part]) || is_array($cursor[$part])) {
								$cursor = & $cursor[$part];
							} else {
								throw new InvalidStateException("Invalid key '$key' in section [$secName] in file '$file'.");
							}
						}
						$cursor = $val;
					}
					$secData = $tmp;
				}

				$parts = $separator ? explode($separator, strtr($secName, ':', $separator)) : array($secName);
				if (count($parts) > 1) {
					$parent = trim($parts[1]);
					if (!isset($data[$parent]) || !is_array($data[$parent])) {
						throw new InvalidStateException("Missing parent section [$parent] in file '$file'.");
					}
					$secData = array_reverse(NArrays::mergeTree(array_reverse($secData, TRUE), array_reverse($data[$parent], TRUE)), TRUE);
					$secName = trim($parts[0]);
					if ($secName === '') {
						throw new InvalidStateException("Invalid empty section name in file '$file'.");
					}
				}
			}

			if (self::$keySeparator) {
				$cursor = & $data;
				foreach (explode(self::$keySeparator, $secName) as $part) {
					if (!isset($cursor[$part]) || is_array($cursor[$part])) {
						$cursor = & $cursor[$part];
					} else {
						throw new InvalidStateException("Invalid section [$secName] in file '$file'.");
					}
				}
			} else {
				$cursor = & $data[$secName];
			}

			if (is_array($secData) && is_array($cursor)) {
				$secData = NArrays::mergeTree($secData, $cursor);
			}

			$cursor = $secData;
		}

		return $data;
	}

	static function save($config, $file)
	{
		$output = array();
		$output[] = '; generated by Nette';
		$output[] = '';

		foreach ($config as $secName => $secData) {
			if (!(is_array($secData) || $secData instanceof Traversable)) {
				throw new InvalidStateException("Invalid section '$secName'.");
			}

			$output[] = "[$secName]";
			self::build($secData, $output, '');
			$output[] = '';
		}

		if (!file_put_contents($file, implode(PHP_EOL, $output))) {
			throw new IOException("Cannot write file '$file'.");
		}
	}

	private static function build($input, & $output, $prefix)
	{
		foreach ($input as $key => $val) {
			if (is_array($val) || $val instanceof Traversable) {
				self::build($val, $output, $prefix . $key . self::$keySeparator);

			} elseif (is_bool($val)) {
				$output[] = "$prefix$key = " . ($val ? 'true' : 'false');

			} elseif (is_numeric($val)) {
				$output[] = "$prefix$key = $val";

			} elseif (is_string($val)) {
				$output[] = "$prefix$key = \"$val\"";

			} else {
				throw new InvalidArgumentException("The '$prefix$key' item must be scalar or array, " . gettype($val) ." given.");
			}
		}
	}

}

final class NConfigAdapterNeon implements IConfigAdapter
{

	public static $sectionSeparator = ' < ';

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function load($file)
	{
		if (!is_file($file) || !is_readable($file)) {
			throw new FileNotFoundException("File '$file' is missing or is not readable.");
		}

		$neon = (array) NNeon::decode(file_get_contents($file));

		$separator = trim(self::$sectionSeparator);
		$data = array();
		foreach ($neon as $secName => $secData) {
			if ($secData === NULL) {
				$secData = array();
			}

			if (is_array($secData)) {

				$parts = $separator ? explode($separator, $secName) : array($secName);
				if (count($parts) > 1) {
					$parent = trim($parts[1]);
					if (!isset($data[$parent]) || !is_array($data[$parent])) {
						throw new InvalidStateException("Missing parent section '$parent' in file '$file'.");
					}
					$secData = array_reverse(NArrays::mergeTree(array_reverse($secData, TRUE), array_reverse($data[$parent], TRUE)), TRUE);
					$secName = trim($parts[0]);
					if ($secName === '') {
						throw new InvalidStateException("Invalid empty section name in file '$file'.");
					}
				}
			}

			$data[$secName] = $secData;
		}

		return $data;
	}

	static function save($config, $file)
	{
		if (!file_put_contents($file, "# generated by Nette\n\n" . NNeon::encode($config, NNeon::BLOCK))) {
			throw new IOException("Cannot write file '$file'.");
		}
	}

}

if (class_exists('PDO')){ class NConnection extends PDO
{

	private $dsn;

	private $driver;

	private $preprocessor;

	public $databaseReflection;

	private $cache;

	public $substitutions = array();

	public $onQuery;

	function __construct($dsn, $username = NULL, $password  = NULL, array $options = NULL, NDatabaseReflection $databaseReflection = NULL)
	{
		parent::__construct($this->dsn = $dsn, $username, $password, $options);
		$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('NStatement', array($this)));

		$class = 'N' . $this->getAttribute(PDO::ATTR_DRIVER_NAME) . 'Driver';
		if (class_exists($class)) {
			$this->driver = new $class($this, (array) $options);
		}

		$this->preprocessor = new NSqlPreprocessor($this);
		$this->databaseReflection = ($tmp= $databaseReflection) ? $tmp : new NDatabaseReflection;

		NDatabasePanel::initialize($this);
	}

	function getSupplementalDriver()
	{
		return $this->driver;
	}

	function setCacheStorage(ICacheStorage $storage = NULL)
	{
		$this->cache = $storage ? new NCache($storage, "Nette.Database/$this->dsn") : NULL;
	}

	function getCache()
	{
		return $this->cache;
	}

	function query($statement)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args);
	}

	function exec($statement)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args)->rowCount();
	}

	function queryArgs($statement, $params)
	{
		foreach ($params as $value) {
			if (is_array($value) || is_object($value)) {
				$need = TRUE; break;
			}
		}
		if (isset($need) || strpos($statement, ':') !== FALSE && $this->preprocessor !== NULL) {
			list($statement, $params) = $this->preprocessor->process($statement, $params);
		}

		return $this->prepare($statement)->execute($params);
	}

	function fetch($args)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args)->fetch();
	}

	function fetchColumn($args)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args)->fetchColumn();
	}

	function fetchPairs($args)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args)->fetchPairs();
	}

	function fetchAll($args)
	{
		$args = func_get_args();
		return $this->queryArgs(array_shift($args), $args)->fetchAll();
	}

	function table($table)
	{
		return new NTableSelection($table, $this);
	}

	function loadFile($file)
	{
		@set_time_limit(0);

		$handle = @fopen($file, 'r');
		if (!$handle) {
			throw new FileNotFoundException("Cannot open file '$file'.");
		}

		$count = 0;
		$sql = '';
		while (!feof($handle)) {
			$s = fgets($handle);
			$sql .= $s;
			if (substr(rtrim($s), -1) === ';') {
				parent::exec($sql);
				$sql = '';
				$count++;
			}
		}
		fclose($handle);
		return $count;
	}

	static function highlightSql($sql)
	{
		static $keywords1 = 'SELECT|UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
		static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|TRUE|FALSE';

		$sql = " $sql ";
		$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);

		$sql = preg_replace('#[ \t]{2,}#', " ", $sql);

		$sql = wordwrap($sql, 100);
		$sql = preg_replace("#([ \t]*\r?\n){2,}#", "\n", $sql);

		$sql = htmlSpecialChars($sql);
		$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", create_function('$matches', '
			if (!empty($matches[1])) // comment
				return \'<em style="color:gray">\' . $matches[1] . \'</em>\';

			if (!empty($matches[2])) // error
				return \'<strong style="color:red">\' . $matches[2] . \'</strong>\';

			if (!empty($matches[3])) // most important keywords
				return \'<strong style="color:blue">\' . $matches[3] . \'</strong>\';

			if (!empty($matches[4])) // other keywords
				return \'<strong style="color:green">\' . $matches[4] . \'</strong>\';
		'), $sql);

		return '<pre class="dump">' . trim($sql) . "</pre>\n";
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}
 }

class NDatabasePanel extends NObject implements IBarPanel
{

	static public $maxLength = 1000;

	public $totalTime = 0;

	public $queries = array();

	public $name;

	public $explain = TRUE;

	public $disabled = FALSE;

	static function initialize(NConnection $connection)
	{
		$panel = new self;
		NDebugger::$blueScreen->addPanel(array($panel, 'renderException'), __CLASS__);
		if (!NDebugger::$productionMode) {
			$connection->onQuery[] = callback($panel, 'logQuery');
			NDebugger::$bar->addPanel($panel);
		}
	}

	function logQuery(NStatement $result, array $params = NULL)
	{
		if ($this->disabled) {
			return;
		}
		$source = NULL;
		foreach (debug_backtrace(FALSE) as $row) {
			if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], NETTE_DIR . DIRECTORY_SEPARATOR) !== 0) {
				$source = array($row['file'], (int) $row['line']);
				break;
			}
		}
		$this->totalTime += $result->time;
		$this->queries[] = array($result->queryString, $params, $result->time, $result->rowCount(), $result->getConnection(), $source);
	}

	function renderException($e)
	{
		if ($e instanceof PDOException && isset($e->queryString)) {
			return array(
				'tab' => 'SQL',
				'panel' => NConnection::highlightSql($e->queryString),
			);
		}
	}

	function getTab()
	{
		return '<span title="Nette\\Database ' . htmlSpecialChars($this->name) . '">'
			. '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEYSURBVBgZBcHPio5hGAfg6/2+R980k6wmJgsJ5U/ZOAqbSc2GnXOwUg7BESgLUeIQ1GSjLFnMwsKGGg1qxJRmPM97/1zXFAAAAEADdlfZzr26miup2svnelq7d2aYgt3rebl585wN6+K3I1/9fJe7O/uIePP2SypJkiRJ0vMhr55FLCA3zgIAOK9uQ4MS361ZOSX+OrTvkgINSjS/HIvhjxNNFGgQsbSmabohKDNoUGLohsls6BaiQIMSs2FYmnXdUsygQYmumy3Nhi6igwalDEOJEjPKP7CA2aFNK8Bkyy3fdNCg7r9/fW3jgpVJbDmy5+PB2IYp4MXFelQ7izPrhkPHB+P5/PjhD5gCgCenx+VR/dODEwD+A3T7nqbxwf1HAAAAAElFTkSuQmCC" />'
			. count($this->queries) . ' queries'
			. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '')
			. '</span>';
	}

	function getPanel()
	{
		$this->disabled = TRUE;
		$s = '';
		$h = 'htmlSpecialChars';
		foreach ($this->queries as $i => $query) {
			list($sql, $params, $time, $rows, $connection, $source) = $query;

			$explain = NULL;
			if ($this->explain && preg_match('#\s*SELECT\s#iA', $sql)) {
				try {
					$explain = $connection->queryArgs('EXPLAIN ' . $sql, $params)->fetchAll();
				} catch (PDOException $e) {}
			}

			$s .= '<tr><td>' . sprintf('%0.3f', $time * 1000);
			if ($explain) {
				static $counter;
				$counter++;
				$s .= "<br /><a href='#' class='nette-toggler' rel='#nette-DbConnectionPanel-row-$counter'>explain&nbsp;&#x25ba;</a>";
			}

			$s .= '</td><td class="nette-DbConnectionPanel-sql">' . NConnection::highlightSql(NStrings::truncate($sql, self::$maxLength));
			if ($explain) {
				$s .= "<table id='nette-DbConnectionPanel-row-$counter' class='nette-collapsed'><tr>";
				foreach ($explain[0] as $col => $foo) {
					$s .= "<th>{$h($col)}</th>";
				}
				$s .= "</tr>";
				foreach ($explain as $row) {
					$s .= "<tr>";
					foreach ($row as $col) {
						$s .= "<td>{$h($col)}</td>";
					}
					$s .= "</tr>";
				}
				$s .= "</table>";
			}
			if ($source) {
				$s .= NDebugHelpers::editorLink($source[0], $source[1])->class('nette-DbConnectionPanel-source');
			}

			$s .= '</td><td>';
			foreach ($params as $param) {
				$s .= NDebugger::dump($param, TRUE);
			}

			$s .= '</td><td>' . $rows . '</td></tr>';
		}

		return empty($this->queries) ? '' :
			'<style> #nette-debug td.nette-DbConnectionPanel-sql { background: white !important }
			#nette-debug .nette-DbConnectionPanel-source { color: #BBB !important }
			#nette-debug nette-DbConnectionPanel tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
			<h1>Queries: ' . count($this->queries) . ($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . '</h1>
			<div class="nette-inner nette-DbConnectionPanel">
			<table>
				<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Params</th><th>Rows</th></tr>' . $s . '
			</table>
			</div>';
	}

}

class NMsSqlDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => TRUE);

	private $connection;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
	}

	function delimite($name)
	{

		return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	function formatLike($value, $pos)
	{
		$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	function applyLimit(&$sql, $limit, $offset)
	{

		if ($limit >= 0) {
			$sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ') t';
		}

		if ($offset) {
			throw new NotImplementedException('Offset is not implemented.');
		}
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NMySqlDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => TRUE);

	private $connection;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
		$charset = isset($options['charset']) ? $options['charset'] : 'utf8';
		if ($charset) {
			$connection->exec("SET NAMES '$charset'");
		}
		if (isset($options['sqlmode'])) {
			$connection->exec("SET sql_mode='$options[sqlmode]'");
		}
		$connection->exec("SET time_zone='" . date('P') . "'");
	}

	function delimite($name)
	{

		return '`' . str_replace('`', '``', $name) . '`';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	function formatLike($value, $pos)
	{
		$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	function applyLimit(&$sql, $limit, $offset)
	{
		if ($limit >= 0 || $offset > 0) {

			$sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
				. ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
		}
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NOciDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => TRUE);

	private $connection;

	private $fmtDateTime;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
		$this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
	}

	function delimite($name)
	{

		return '"' . str_replace('"', '""', $name) . '"';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format($this->fmtDateTime);
	}

	function formatLike($value, $pos)
	{
		throw new NotImplementedException;
	}

	function applyLimit(&$sql, $limit, $offset)
	{
		if ($offset > 0) {

			$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
				. ($limit >= 0 ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
				. ') WHERE "__rnum" > '. (int) $offset;

		} elseif ($limit >= 0) {
			$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . (int) $limit;
		}
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NOdbcDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => TRUE);

	private $connection;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
	}

	function delimite($name)
	{
		return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format("#m/d/Y H:i:s#");
	}

	function formatLike($value, $pos)
	{
		$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	function applyLimit(&$sql, $limit, $offset)
	{

		if ($limit >= 0) {
			$sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ')';
		}

		if ($offset) {
			throw new InvalidArgumentException('Offset is not implemented in driver odbc.');
		}
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NPgSqlDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => TRUE);

	private $connection;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
	}

	function delimite($name)
	{

		return '"' . str_replace('"', '""', $name) . '"';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	function formatLike($value, $pos)
	{
		throw new NotImplementedException;
	}

	function applyLimit(&$sql, $limit, $offset)
	{
		if ($limit >= 0)
			$sql .= ' LIMIT ' . (int) $limit;

		if ($offset > 0)
			$sql .= ' OFFSET ' . (int) $offset;
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NSqliteDriver extends NObject implements ISupplementalDriver
{

	public $supports = array('meta' => FALSE);

	private $connection;

	private $fmtDateTime;

	function __construct(NConnection $connection, array $options)
	{
		$this->connection = $connection;
		$this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
	}

	function delimite($name)
	{
		return '[' . strtr($name, '[]', '  ') . ']';
	}

	function formatDateTime(DateTime $value)
	{
		return $value->format($this->fmtDateTime);
	}

	function formatLike($value, $pos)
	{
		$value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\');
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
	}

	function applyLimit(&$sql, $limit, $offset)
	{
		if ($limit >= 0 || $offset > 0) {
			$sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
		}
	}

	function normalizeRow($row, $statement)
	{
		return $row;
	}

}

class NSqlite2Driver extends NSqliteDriver
{

	function formatLike($value, $pos)
	{
		throw new NotSupportedException;
	}

	function normalizeRow($row, $statement)
	{
		if (!is_object($row)) {
			$iterator = $row;
		} elseif ($row instanceof Traversable) {
			$iterator = iterator_to_array($row);
		} else {
			$iterator = (array) $row;
		}
		foreach ($iterator as $key => $value) {
			unset($row[$key]);
			if ($key[0] === '[' || $key[0] === '"') {
				$key = substr($key, 1, -1);
			}
			$row[$key] = $value;
		}
		return $row;
	}

}

class NDatabaseReflection extends NObject
{
	const FIELD_TEXT = 'string',
		FIELD_BINARY = 'bin',
		FIELD_BOOL = 'bool',
		FIELD_INTEGER = 'int',
		FIELD_FLOAT = 'float',
		FIELD_DATETIME = 'datetime';

	private $primary;

	private $foreign;

	private $table;

	function __construct($primary = 'id', $foreign = '%s_id', $table = '%s')
	{
		$this->primary = $primary;
		$this->foreign = $foreign;
		$this->table = $table;
	}

	function getPrimary($table)
	{
		return sprintf($this->primary, $table);
	}

	function getReferencingColumn($name, $table)
	{
		return $this->getReferencedColumn($table, $name);
	}

	function getReferencedColumn($name, $table)
	{
		if ($this->table !== '%s' && preg_match('(^' . str_replace('%s', '(.*)', preg_quote($this->table)) . '$)', $name, $match)) {
			$name = $match[1];
		}
		return sprintf($this->foreign, $name, $table);
	}

	function getReferencedTable($name, $table)
	{
		return sprintf($this->table, $name, $table);
	}

	static function detectType($type)
	{
		static $types, $patterns = array(
			'BYTEA|BLOB|BIN' => self::FIELD_BINARY,
			'TEXT|CHAR' => self::FIELD_TEXT,
			'YEAR|BYTE|COUNTER|SERIAL|INT|LONG' => self::FIELD_INTEGER,
			'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => self::FIELD_FLOAT,
			'TIME|DATE' => self::FIELD_DATETIME,
			'BOOL|BIT' => self::FIELD_BOOL,
		);

		if (!isset($types[$type])) {
			$types[$type] = 'string';
			foreach ($patterns as $s => $val) {
				if (preg_match("#$s#i", $type)) {
					return $types[$type] = $val;
				}
			}
		}
		return $types[$type];
	}

}

class NRow extends NArrayHash
{

	function __construct($statement)
	{
		$statement->normalizeRow($this);
	}

	function offsetGet($key)
	{
		if (is_int($key)) {
			$arr = array_values((array) $this);
			return $arr[$key];
		}
		return $this->$key;
	}

}

class NSqlLiteral
{

	public $value = '';

	function __construct($value)
	{
		$this->value = (string) $value;
	}

}

class NSqlPreprocessor extends NObject
{

	private $connection;

	private $driver;

	private $params;

	private $remaining;

	private $counter;

	private $arrayMode;

	function __construct(NConnection $connection)
	{
		$this->connection = $connection;
		$this->driver = $connection->getSupplementalDriver();
	}

	function process($sql, $params)
	{
		$this->params = $params;
		$this->counter = 0;
		$this->remaining = array();

		$cmd = strtoupper(substr(ltrim($sql), 0, 6));
		$this->arrayMode = $cmd === 'INSERT' || $cmd === 'REPLAC' ? 'values' : 'assoc';

		$sql = NStrings::replace($sql, '~\'.*?\'|".*?"|:[a-zA-Z0-9_]+:|\?~s', array($this, 'callback'));

		while ($this->counter < count($params)) {
			$sql .= ' ' . $this->formatValue($params[$this->counter++]);
		}

		return array($sql, $this->remaining);
	}

	function callback($m)
	{
		$m = $m[0];
		if ($m[0] === "'" || $m[0] === '"') {
			return $m;

		} elseif ($m[0] === '?') {
			return $this->formatValue($this->params[$this->counter++]);

		} elseif ($m[0] === ':') {
			$s = substr($m, 1, -1);
			return isset($this->connection->substitutions[$s]) ? $this->connection->substitutions[$s] : $m;
		}
	}

	private function formatValue($value)
	{
		if (is_string($value)) {
			if (strlen($value) > 20) {
				$this->remaining[] = $value;
				return '?';

			} else {
				return $this->connection->quote($value);
			}

		} elseif (is_int($value)) {
			return (string) $value;

		} elseif (is_float($value)) {
			return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');

		} elseif (is_bool($value)) {
			$this->remaining[] = $value;
			return '?';

		} elseif ($value === NULL) {
			return 'NULL';

		} elseif (is_array($value) || $value instanceof Traversable) {
			$vx = $kx = array();

			if (isset($value[0])) {
				foreach ($value as $v) {
					$vx[] = $this->formatValue($v);
				}
				return implode(', ', $vx);

			} elseif ($this->arrayMode === 'values') {
				$this->arrayMode = 'multi';
				foreach ($value as $k => $v) {
					$kx[] = $this->driver->delimite($k);
					$vx[] = $this->formatValue($v);
				}
				return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';

			} elseif ($this->arrayMode === 'assoc') {
				foreach ($value as $k => $v) {
					$vx[] = $this->driver->delimite($k) . '=' . $this->formatValue($v);
				}
				return implode(', ', $vx);

			} elseif ($this->arrayMode === 'multi') {
				foreach ($value as $k => $v) {
					$vx[] = $this->formatValue($v);
				}
				return ', (' . implode(', ', $vx) . ')';
			}

		} elseif ($value instanceof DateTime) {
			return $this->driver->formatDateTime($value);

		} elseif ($value instanceof NSqlLiteral) {
			return $value->value;

		} else {
			$this->remaining[] = $value;
			return '?';
		}
	}

}

if (class_exists('PDO')){ class NStatement extends PDOStatement
{

	private $connection;

	public $time;

	private $types;

	protected function __construct(NConnection $connection)
	{
		$this->connection = $connection;
		$this->setFetchMode(PDO::FETCH_CLASS, 'NRow', array($this));
	}

	function getConnection()
	{
		return $this->connection;
	}

	function execute($params = array())
	{
		static $types = array('boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT,
			'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL);

		foreach ($params as $key => $value) {
			$type = gettype($value);
			$this->bindValue(is_int($key) ? $key + 1 : $key, $value, isset($types[$type]) ? $types[$type] : PDO::PARAM_STR);
		}

		$time = microtime(TRUE);
		try {
			parent::execute();
		} catch (PDOException $e) {
			$e->queryString = $this->queryString;
			throw $e;
		}
		$this->time = microtime(TRUE) - $time;
		$this->connection->__call('onQuery', array($this, $params));

		return $this;
	}

	function fetchPairs()
	{
		return $this->fetchAll(PDO::FETCH_KEY_PAIR);
	}

	function normalizeRow($row)
	{
		if ($this->types === NULL) {
			$this->types = array();
			if ($this->connection->getSupplementalDriver()->supports['meta']) {
				$col = 0;
				foreach ($row as $key => $foo) {
					$type = $this->getColumnMeta($col++);
					if (isset($type['native_type'])) {
						$this->types[$key] = NDatabaseReflection::detectType($type['native_type']);
					}
				}
			}
		}

		foreach ($this->types as $key => $type) {
			$value = $row[$key];
			if ($value === NULL || $value === FALSE || $type === NDatabaseReflection::FIELD_TEXT) {

			} elseif ($type === NDatabaseReflection::FIELD_INTEGER) {
				$row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;

			} elseif ($type === NDatabaseReflection::FIELD_FLOAT) {
				$row[$key] = (string) ($tmp = (float) $value) === $value ? $tmp : $value;

			} elseif ($type === NDatabaseReflection::FIELD_BOOL) {
				$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
			}
		}

		return $this->connection->getSupplementalDriver()->normalizeRow($row, $this);
	}

	function dump()
	{
		echo "\n<table class=\"dump\">\n<caption>" . htmlSpecialChars($this->queryString) . "</caption>\n";
		if (!$this->columnCount()) {
			echo "\t<tr>\n\t\t<th>Affected rows:</th>\n\t\t<td>", $this->rowCount(), "</td>\n\t</tr>\n</table>\n";
			return;
		}
		$i = 0;
		foreach ($this as $row) {
			if ($i === 0) {
				echo "<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
				foreach ($row as $col => $foo) {
					echo "\t\t<th>" . htmlSpecialChars($col) . "</th>\n";
				}
				echo "\t</tr>\n</thead>\n<tbody>\n";
			}
			echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
			foreach ($row as $col) {

				echo "\t\t<td>", htmlSpecialChars($col), "</td>\n";
			}
			echo "\t</tr>\n";
			$i++;
		}

		if ($i === 0) {
			echo "\t<tr>\n\t\t<td><em>empty result set</em></td>\n\t</tr>\n</table>\n";
		} else {
			echo "</tbody>\n</table>\n";
		}
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}
 }

class NTableRow extends NObject implements IteratorAggregate, ArrayAccess
{

	protected $table;

	protected $data;

	private $modified = array();

	function __construct(array $data, NTableSelection $table)
	{
		$this->data = $data;
		$this->table = $table;
	}

	function __toString()
	{
		return (string) $this[$this->table->primary];
	}

	function toArray()
	{
		$this->access(NULL);
		return $this->data;
	}

	function ref($name)
	{
		$referenced = $this->table->getReferencedTable($name, $column);
		if (isset($referenced[$this[$column]])) {
			$res = $referenced[$this[$column]];
			return $res;
		}
	}

	function related($table)
	{
		$referencing = $this->table->getReferencingTable($table);
		$referencing->active = $this[$this->table->primary];
		return $referencing;
	}

	function update($data = NULL)
	{
		if ($data === NULL) {
			$data = $this->modified;
		}
		return $this->table->connection->table($this->table->name)
			->where($this->table->primary, $this[$this->table->primary])
			->update($data);
	}

	function delete()
	{
		return $this->table->connection->table($this->table->name)
			->where($this->table->primary, $this[$this->table->primary])
			->delete();
	}

	function getIterator()
	{
		$this->access(NULL);
		return new ArrayIterator($this->data);
	}

	function offsetSet($key, $value)
	{
		$this->__set($key, $value);
	}

	function offsetGet($key)
	{
		return $this->__get($key);
	}

	function offsetExists($key)
	{
		return $this->__isset($key);
	}

	function offsetUnset($key)
	{
		$this->__unset($key);
	}

	function __set($key, $value)
	{
		$this->data[$key] = $value;
		$this->modified[$key] = $value;
	}

	function &__get($key)
	{
		if (array_key_exists($key, $this->data)) {
			$this->access($key);
			return $this->data[$key];
		}

		$column = $this->table->connection->databaseReflection->getReferencedColumn($key, $this->table->name);
		if (array_key_exists($column, $this->data)) {
			$value = $this->data[$column];
			$referenced = $this->table->getReferencedTable($key);
			$ret = isset($referenced[$value]) ? $referenced[$value] : NULL;
			return $ret;
		}

		$this->access($key);
		if (array_key_exists($key, $this->data)) {
			return $this->data[$key];

		} else {
			$this->access($key, TRUE);

			$this->access($column);
			if (array_key_exists($column, $this->data)) {
				$value = $this->data[$column];
				$referenced = $this->table->getReferencedTable($key);
				$ret = isset($referenced[$value]) ? $referenced[$value] : NULL;

			} else {
				$this->access($column, TRUE);
				trigger_error("Unknown column $key", E_USER_WARNING);
				$ret = NULL;
			}
			return $ret;
		}
	}

	function __isset($key)
	{
		$this->access($key);
		$return = array_key_exists($key, $this->data);
		if (!$return) {
			$this->access($key, TRUE);
		}
		return $return;
	}

	function __unset($key)
	{
		unset($this->data[$key]);
		unset($this->modified[$key]);
	}

	function access($key, $delete = FALSE)
	{
		if ($this->table->connection->getCache() && !isset($this->modified[$key]) && $this->table->access($key, $delete)) {
			$id = (isset($this->data[$this->table->primary]) ? $this->data[$this->table->primary] : $this->data);
			$this->data = $this->table[$id]->data;
		}
	}

}

class NTableSelection extends NObject implements Iterator, ArrayAccess, Countable
{

	public $connection;

	public $name;

	public $primary;

	protected $rows;

	protected $data;

	protected $select = array();

	protected $where = array();

	protected $conditions = array();

	protected $parameters = array();

	protected $order = array();

	protected $limit = NULL;

	protected $offset = NULL;

	protected $group = '';

	protected $having = '';

	protected $referenced = array();

	protected $referencing = array();

	protected $aggregation = array();

	protected $accessed;

	protected $prevAccessed;

	protected $keys = array();

	protected $delimitedName;

	protected $delimitedPrimary;

	function __construct($table, NConnection $connection)
	{
		$this->name = $table;
		$this->connection = $connection;
		$this->primary = $this->getPrimary($table);
		$this->delimitedName = $connection->getSupplementalDriver()->delimite($this->name);
		$this->delimitedPrimary = $connection->getSupplementalDriver()->delimite($this->primary);
	}

	function __destruct()
	{
		$cache = $this->connection->getCache();
		if ($cache && !$this->select && $this->rows !== NULL) {
			$accessed = $this->accessed;
			if (is_array($accessed)) {
				$accessed = array_filter($accessed);
			}
			$cache->save(array(__CLASS__, $this->name, $this->conditions), $accessed);
		}
		$this->rows = NULL;
	}

	function get($key)
	{

		$clone = clone $this;
		$clone->where($this->delimitedPrimary, $key);
		return $clone->fetch();
	}

	function select($columns)
	{
		$this->__destruct();
		$this->select[] = $this->tryDelimite($columns);
		return $this;
	}

	function find($key)
	{
		return $this->where($this->delimitedPrimary, $key);
	}

	function where($condition, $parameters = array())
	{
		if (is_array($condition)) {
			foreach ($condition as $key => $val) {
				$this->where($key, $val);
			}
			return $this;
		}

		$this->__destruct();

		$this->conditions[] = $condition;
		$condition = $this->tryDelimite($condition);

		$args = func_num_args();
		if ($args !== 2 || strpbrk($condition, '?:')) {
			if ($args !== 2 || !is_array($parameters)) {
				$parameters = func_get_args();
				array_shift($parameters);
			}
			$this->parameters = array_merge($this->parameters, $parameters);

		} elseif ($parameters === NULL) {
			$condition .= ' IS NULL';

		} elseif ($parameters instanceof NTableSelection) {
			$clone = clone $parameters;
			if (!$clone->select) {
				$clone->select = array($this->getPrimary($clone->name));
			}
			if ($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') {
				$condition .= " IN ($clone)";
			} else {
				$in = array();
				foreach ($clone as $row) {
					$this->parameters[] = array_values(iterator_to_array($row));
					$in[] = (count($row) === 1 ? '?' : '(?)');
				}
				$condition .= ' IN (' . ($in ? implode(', ', $in) : 'NULL') . ')';
			}

		} elseif (!is_array($parameters)) {
			$condition .= ' = ?';
			$this->parameters[] = $parameters;

		} else {
			if ($parameters) {
				$condition .= " IN (?)";
				$this->parameters[] = $parameters;
			} else {
				$condition .= " IN (NULL)";
			}
		}

		$this->where[] = $condition;
		return $this;
	}

	function order($columns)
	{
		$this->rows = NULL;
		$this->order[] = $this->tryDelimite($columns);
		return $this;
	}

	function limit($limit, $offset = NULL)
	{
		$this->rows = NULL;
		$this->limit = $limit;
		$this->offset = $offset;
		return $this;
	}

	function page($page, $itemsPerPage)
	{
		$this->rows = NULL;
		$this->limit = $itemsPerPage;
		$this->offset = ($page - 1) * $itemsPerPage;
		return $this;
	}

	function group($columns, $having = '')
	{
		$this->__destruct();
		$this->group = $columns;
		$this->having = $having;
		return $this;
	}

	function aggregation($function)
	{
		$join = $this->createJoins(implode(',', $this->conditions), TRUE) + $this->createJoins($function);
		$query = "SELECT $function FROM $this->delimitedName" . implode($join);
		if ($this->where) {
			$query .= ' WHERE (' . implode(') AND (', $this->where) . ')';
		}
		foreach ($this->query($query)->fetch() as $val) {
			return $val;
		}
	}

	function count($column = '')
	{
		if (!$column) {
			$this->execute();
			return count($this->data);
		}
		return $this->aggregation("COUNT({$this->tryDelimite($column)})");
	}

	function min($column)
	{
		return $this->aggregation("MIN({$this->tryDelimite($column)})");
	}

	function max($column)
	{
		return $this->aggregation("MAX({$this->tryDelimite($column)})");
	}

	function sum($column)
	{
		return $this->aggregation("SUM({$this->tryDelimite($column)})");
	}

	function getSql()
	{
		$join = $this->createJoins(implode(',', $this->conditions), TRUE)
			+ $this->createJoins(implode(',', $this->select) . ",$this->group,$this->having," . implode(',', $this->order));

		$cache = $this->connection->getCache();
		if ($this->rows === NULL && $cache && !is_string($this->prevAccessed)) {
			$this->accessed = $this->prevAccessed = $cache->load(array(__CLASS__, $this->name, $this->conditions));
		}

		$prefix = $join ? "$this->delimitedName." : '';
		if ($this->select) {
			$cols = implode(', ', $this->select);

		} elseif ($this->prevAccessed) {
			$cols = $prefix . implode(', ' . $prefix, array_map(array($this->connection->getSupplementalDriver(), 'delimite'), array_keys($this->prevAccessed)));

		} else {
			$cols = $prefix . '*';
		}

		return "SELECT{$this->topString()} $cols FROM $this->delimitedName" . implode($join) . $this->whereString();
	}

	protected function createJoins($val, $inner = FALSE)
	{
		$supplementalDriver = $this->connection->getSupplementalDriver();
		$joins = array();
		preg_match_all('~\\b([a-z][\\w.]*)\\.([a-z]\\w*)(\\s+IS\\b|\\s*<=>)?~i', $val, $matches, PREG_SET_ORDER);
		foreach ($matches as $match) {
			if ($match[1] !== $this->name) {
				foreach (explode('.', $match[1]) as $name) {
					$table = $this->connection->databaseReflection->getReferencedTable($name, $this->name);
					$column = $this->connection->databaseReflection->getReferencedColumn($name, $this->name);
					$primary = $this->getPrimary($table);
					$joins[$name] = ' ' . (!isset($joins[$name]) && $inner && !isset($match[3]) ? 'INNER' : 'LEFT')
						. ' JOIN ' . $supplementalDriver->delimite($table)
						. ($table !== $name ? ' AS ' . $supplementalDriver->delimite($name) : '')
						. " ON $this->delimitedName." . $supplementalDriver->delimite($column)
						. ' = ' . $supplementalDriver->delimite($name) . '.' . $supplementalDriver->delimite($primary);
				}
			}
		}
		return $joins;
	}

	protected function execute()
	{
		if ($this->rows !== NULL) {
			return;
		}

		try {
			$result = $this->query($this->getSql());

		} catch (PDOException $exception) {
			if (!$this->select && $this->prevAccessed) {
				$this->prevAccessed = '';
				$this->accessed = array();
				$result = $this->query($this->getSql());
			} else {
				throw $exception;
			}
		}

		$this->rows = array();
		$result->setFetchMode(PDO::FETCH_ASSOC);
		foreach ($result as $key => $row) {
			$row = $result->normalizeRow($row);
			$this->rows[isset($row[$this->primary]) ? $row[$this->primary] : $key] = new NTableRow($row, $this);
		}
		$this->data = $this->rows;

		if (isset($row[$this->primary]) && !is_string($this->accessed)) {
			$this->accessed[$this->primary] = TRUE;
		}
	}

	protected function whereString()
	{
		$return = '';
		$driver = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
		$where = $this->where;
		if ($this->limit !== NULL && $driver === 'oci') {
			$where[] = ($this->offset ? "rownum > $this->offset AND " : '') . 'rownum <= ' . ($this->limit + $this->offset);
		}
		if ($where) {
			$return .= ' WHERE (' . implode(') AND (', $where) . ')';
		}
		if ($this->group) {
			$return .= ' GROUP BY '. $this->tryDelimite($this->group);
		}
		if ($this->having) {
			$return .= ' HAVING '. $this->tryDelimite($this->having);
		}
		if ($this->order) {
			$return .= ' ORDER BY ' . implode(', ', $this->order);
		}
		if ($this->limit !== NULL && $driver !== 'oci' && $driver !== 'dblib') {
			$return .= " LIMIT $this->limit";
			if ($this->offset !== NULL) {
				$return .= " OFFSET $this->offset";
			}
		}
		return $return;
	}

	protected function topString()
	{
		if ($this->limit !== NULL && $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') {
			return " TOP ($this->limit)";
		}
		return '';
	}

	protected function tryDelimite($s)
	{
		return preg_match('#^[a-z_][a-z0-9_.]*$#i', $s)
			? implode('.', array_map(array($this->connection->getSupplementalDriver(), 'delimite'), explode('.', $s)))
			: $s;
	}

	protected function query($query)
	{
		return $this->connection->queryArgs($query, $this->parameters);
	}

	function access($key, $delete = FALSE)
	{
		if ($delete) {
			if (is_array($this->accessed)) {
				$this->accessed[$key] = FALSE;
			}
			return FALSE;
		}

		if ($key === NULL) {
			$this->accessed = '';

		} elseif (!is_string($this->accessed)) {
			$this->accessed[$key] = TRUE;
		}

		if (!$this->select && $this->prevAccessed && ($key === NULL || !isset($this->prevAccessed[$key]))) {
			$this->prevAccessed = '';
			$this->rows = NULL;
			return TRUE;
		}
		return FALSE;
	}

	function insert($data)
	{
		if ($data instanceof NTableSelection) {
			$data = $data->getSql();

		} elseif ($data instanceof Traversable) {
			$data = iterator_to_array($data);
		}

		$return = $this->connection->query("INSERT INTO $this->delimitedName", $data);

		$this->rows = NULL;
		if (!is_array($data)) {
			return $return->rowCount();
		}

		if (!isset($data[$this->primary]) && ($id = $this->connection->lastInsertId())) {
			$data[$this->primary] = $id;
		}
		return new NTableRow($data, $this);
	}

	function update($data)
	{
		if ($data instanceof Traversable) {
			$data = iterator_to_array($data);

		} elseif (!is_array($data)) {
			throw new InvalidArgumentException;
		}

		if (!$data) {
			return 0;
		}

		return $this->connection->queryArgs(
			'UPDATE' . $this->topString() . " $this->delimitedName SET ?" . $this->whereString(),
			array_merge(array($data), $this->parameters)
		)->rowCount();
	}

	function delete()
	{
		return $this->query(
			'DELETE' . $this->topString() . " FROM $this->delimitedName" . $this->whereString()
		)->rowCount();
	}

	function getReferencedTable($name, & $column = NULL)
	{
		$column = $this->connection->databaseReflection->getReferencedColumn($name, $this->name);
		$referenced = & $this->referenced[$name];
		if ($referenced === NULL) {
			$keys = array();
			foreach ($this->rows as $row) {
				if ($row[$column] !== NULL) {
					$keys[$row[$column]] = NULL;
				}
			}
			if ($keys) {
				$table = $this->connection->databaseReflection->getReferencedTable($name, $this->name);
				$referenced = new NTableSelection($table, $this->connection);
				$referenced->where($table . '.' . $this->getPrimary($table), array_keys($keys));
			} else {
				$referenced = array();
			}
		}
		return $referenced;
	}

	function getReferencingTable($table)
	{
		$column = $this->connection->databaseReflection->getReferencingColumn($table, $this->name);
		$referencing = new NGroupedTableSelection($table, $this, $column);
		$referencing->where("$table.$column", array_keys((array) $this->rows));
		return $referencing;
	}

	private function getPrimary($table)
	{
		return $this->connection->databaseReflection->getPrimary($table);
	}

	function rewind()
	{
		$this->execute();
		$this->keys = array_keys($this->data);
		reset($this->keys);
	}

	function current()
	{
		return $this->data[current($this->keys)];
	}

	function key()
	{
		return current($this->keys);
	}

	function next()
	{
		next($this->keys);
	}

	function valid()
	{
		return current($this->keys) !== FALSE;
	}

	function offsetSet($key, $value)
	{
		$this->execute();
		$this->data[$key] = $value;
	}

	function offsetGet($key)
	{
		$this->execute();
		return $this->data[$key];
	}

	function offsetExists($key)
	{
		$this->execute();
		return isset($this->data[$key]);
	}

	function offsetUnset($key)
	{
		$this->execute();
		unset($this->data[$key]);
	}

	function fetch()
	{
		$this->execute();
		$return = current($this->data);
		next($this->data);
		return $return;
	}

	function fetchPairs($key, $value = '')
	{
		$return = array();

		foreach ($this as $row) {
			$return[$row[$key]] = ($value !== '' ? $row[$value] : $row);
		}
		return $return;
	}

}

class NGroupedTableSelection extends NTableSelection
{

	private $refTable;

	private $column;

	private $delimitedColumn;

	public $active;

	function __construct($name, NTableSelection $refTable, $column)
	{
		parent::__construct($name, $refTable->connection);
		$this->refTable = $refTable;
		$this->through($column);
	}

	function through($column)
	{
		$this->column = $column;
		$this->delimitedColumn = $this->refTable->connection->getSupplementalDriver()->delimite($this->column);
		return $this;
	}

	function select($columns)
	{
		if (!$this->select) {
			$this->select[] = "$this->delimitedName.$this->delimitedColumn";
		}
		return parent::select($columns);
	}

	function order($columns)
	{
		if (!$this->order) {
			$this->order[] = "$this->delimitedName.$this->delimitedColumn"
				. (preg_match('~\\bDESC$~i', $columns) ? ' DESC' : '');
		}
		return parent::order($columns);
	}

	function aggregation($function)
	{
		$join = $this->createJoins(implode(',', $this->conditions), TRUE) + $this->createJoins($function);
		$column = ($join ? "$this->delimitedName." : '') . $this->delimitedColumn;
		$query = "SELECT $function, $column FROM $this->delimitedName" . implode($join);
		if ($this->where) {
			$query .= ' WHERE (' . implode(') AND (', $this->where) . ')';
		}
		$query .= " GROUP BY $column";
		$aggregation = & $this->refTable->aggregation[$query];
		if ($aggregation === NULL) {
			$aggregation = array();
			foreach ($this->query($query, $this->parameters) as $row) {
				$aggregation[$row[$this->column]] = $row;
			}
		}

		if (isset($aggregation[$this->active])) {
			foreach ($aggregation[$this->active] as $val) {
				return $val;
			}
		}
	}

	function count($column = '')
	{
		$return = parent::count($column);
		return isset($return) ? $return : 0;
	}

	function insert($data)
	{
		if ($data instanceof Traversable && !$data instanceof NTableSelection) {
			$data = iterator_to_array($data);
		}
		if (is_array($data)) {
			$data[$this->column] = $this->active;
		}
		return parent::insert($data);
	}

	function update($data)
	{
		$where = $this->where;
		$this->where[0] = "$this->delimitedColumn = " . $this->connection->quote($this->active);
		$return = parent::update($data);
		$this->where = $where;
		return $return;
	}

	function delete()
	{
		$where = $this->where;
		$this->where[0] = "$this->delimitedColumn = " . $this->connection->quote($this->active);
		$return = parent::delete();
		$this->where = $where;
		return $return;
	}

	protected function execute()
	{
		if ($this->rows !== NULL) {
			return;
		}

		$referencing = & $this->refTable->referencing[$this->getSql()];
		if ($referencing === NULL) {
			$limit = $this->limit;
			$rows = count($this->refTable->rows);
			if ($this->limit && $rows > 1) {
				$this->limit = NULL;
			}
			parent::execute();
			$this->limit = $limit;
			$referencing = array();
			$offset = array();
			foreach ($this->rows as $key => $row) {
				$ref = & $referencing[$row[$this->column]];
				$skip = & $offset[$row[$this->column]];
				if ($limit === NULL || $rows <= 1 || (count($ref) < $limit && $skip >= $this->offset)) {
					$ref[$key] = $row;
				} else {
					unset($this->rows[$key]);
				}
				$skip++;
				unset($ref, $skip);
			}
		}

		$this->data = & $referencing[$this->active];
		if ($this->data === NULL) {
			$this->data = array();
		}
	}

}

class NDiContainer extends NFreezableObject implements IDiContainer
{
	const TAG_TYPEHINT = 'typeHint';

	public $params = array();

	private $registry = array();

	private $factories = array();

	private $tags = array();

	private $creating;

	function addService($name, $service, $tags = NULL)
	{
		$this->updating();
		if (!is_string($name) || $name === '') {
			throw new InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
		}

		if (isset($this->registry[$name]) || method_exists($this, "createService$name")) {
			throw new InvalidStateException("Service '$name' has already been registered.");
		}

		if (is_string($tags)) {
			$tags = array(self::TAG_TYPEHINT => array($tags));
		} elseif (is_array($tags)) {
			foreach ($tags as $id => $attrs) {
				if (is_int($id) && is_string($attrs)) {
					$tags[$attrs] = array();
					unset($tags[$id]);
				} elseif (!is_array($attrs)) {
					$tags[$id] = (array) $attrs;
				}
			}
		}

		if (is_string($service) && strpos($service, ':') === FALSE&& $service[0] !== "\0") {
			if (!isset($tags[self::TAG_TYPEHINT][0])) {
				$tags[self::TAG_TYPEHINT][0] = $service;
			}
			$service = new NServiceBuilder($service);
		}

		if ($service instanceof IServiceBuilder) {
			$factory = array($service, 'createService');

		} elseif (is_object($service) && !$service instanceof Closure && !$service instanceof NCallback) {
			$this->registry[$name] = $service;
			$this->tags[$name] = $tags;
			return $this;

		} else {
			$factory = $service;
		}

		$this->factories[$name] = array(callback($factory));
		$this->tags[$name] = $tags;
		$this->registry[$name] = & $this->factories[$name][1];
		return $service;
	}

	function removeService($name)
	{
		$this->updating();
		unset($this->registry[$name], $this->factories[$name]);
	}

	function getService($name)
	{
		if (isset($this->registry[$name])) {
			return $this->registry[$name];

		} elseif (isset($this->creating[$name])) {
			throw new InvalidStateException("Circular reference detected for services: "
				. implode(', ', array_keys($this->creating)) . ".");
		}

		if (isset($this->factories[$name])) {
			list($factory) = $this->factories[$name];
			if (!$factory->isCallable()) {
				throw new InvalidStateException("Unable to create service '$name', factory '$factory' is not callable.");
			}

			$this->creating[$name] = TRUE;
			try {
				$service = $factory->invoke($this);
			} catch (Exception $e) {}

		} elseif (method_exists($this, $factory = 'createService' . ucfirst($name))) {
			$this->creating[$name] = TRUE;
			try {
				$service = $this->$factory();
			} catch (Exception $e) {}

		} else {
			throw new NMissingServiceException("Service '$name' not found.");
		}

		unset($this->creating[$name]);

		if (isset($e)) {
			throw $e;

		} elseif (!is_object($service)) {
			throw new UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not object.");

		} elseif (isset($this->tags[$name][self::TAG_TYPEHINT][0]) && !$service instanceof $this->tags[$name][self::TAG_TYPEHINT][0]) {
			throw new UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not '{$this->tags[$name][self::TAG_TYPEHINT][0]}' type.");
		}

		unset($this->factories[$name]);
		return $this->registry[$name] = $service;
	}

	function getServiceByType($type)
	{
		foreach ($this->registry as $name => $service) {
			if (isset($this->tags[$name][self::TAG_TYPEHINT][0]) ? !strcasecmp($this->tags[$name][self::TAG_TYPEHINT][0], $type) : $service instanceof $type) {
				$found[] = $name;
			}
		}
		if (!isset($found)) {
			throw new NMissingServiceException("Service matching '$type' type not found.");

		} elseif (count($found) > 1) {
			throw new NAmbiguousServiceException("Found more than one service ('" . implode("', '", $found) . "') matching '$type' type.");
		}
		return $this->getService($found[0]);
	}

	function getServiceNamesByTag($tag)
	{
		$found = array();
		foreach ($this->registry as $name => $service) {
			if (isset($this->tags[$name][$tag])) {
				$found[$name] = $this->tags[$name][$tag];
			}
		}
		return $found;
	}

	function hasService($name)
	{
		return isset($this->registry[$name])
			|| isset($this->factories[$name])
			|| method_exists($this, "createService$name");
	}

	function checkServiceType($name, $type)
	{
		return isset($this->tags[$name][self::TAG_TYPEHINT][0])
			? !strcasecmp($this->tags[$name][self::TAG_TYPEHINT][0], $type)
			: (isset($this->registry[$name]) && $this->registry[$name] instanceof $type);
	}

	function expand($s)
	{
		return is_string($s) ? NStrings::expand($s, $this->params) : $s;
	}

	function &__get($name)
	{
		if (!isset($this->registry[$name])) {
			$this->getService($name);
		}
		return $this->registry[$name];
	}

	function __set($name, $service)
	{
		$this->updating();
		if (!is_string($name) || $name === '') {
			throw new InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");

		} elseif (isset($this->registry[$name]) || method_exists($this, "createService$name")) {
			throw new InvalidStateException("Service '$name' has already been registered.");

		} elseif (!is_object($service)) {
			throw new InvalidArgumentException("Service must be a object, " . gettype($service) . " given.");
		}
		$this->registry[$name] = $service;
	}

	function __isset($name)
	{
		return $this->hasService($name);
	}

	function __unset($name)
	{
		$this->removeService($name);
	}

}

class NContainerBuilder extends NObject
{

	function addDefinitions(IDiContainer $container, array $definitions)
	{
		foreach ($definitions as $name => $definition) {
			if (is_scalar($definition)) {
				if (substr($definition, 0, 1) === '@') {
					$definition = array('alias' => substr($definition, 1));
				} else {
					$definition = array('class' => $definition);
				}
			}

			$arguments = isset($definition['arguments']) ? $definition['arguments'] : array();
			$expander = create_function('&$val', 'extract(NCFix::$vars['.NCFix::uses(array('container'=>$container)).'], EXTR_REFS);
				if (substr($val, 0, 1) === \'@\') {
					$val = $container->getService(substr($val, 1));
				} elseif (is_string($val)) {
					$val = NStrings::expand($val, $container->params);
				}
			');

			if (isset($definition['class']) || isset($definition['factory'])) {
				if (isset($definition['class'])) {
					$class = $definition['class'];
				} else {
					$class = NULL;
					array_unshift($arguments, $definition['factory']);
				}
				$methods = isset($definition['methods']) ? $definition['methods'] : array();
				$factory = create_function('$container', 'extract(NCFix::$vars['.NCFix::uses(array('class'=>$class,'arguments'=> $arguments,'methods'=> $methods,'expander'=> $expander)).'], EXTR_REFS);
					array_walk_recursive($arguments, $expander);
					if ($class) {
						$class = NStrings::expand($class, $container->params);
						if ($arguments) {
							$service = NClassReflection::from($class)->newInstanceArgs($arguments);
						} else {
							$service = new $class;
						}
					} else {
						$factory = $arguments[0]; $arguments[0] = $container;
						$service = call_user_func_array($factory, $arguments);
					}

					array_walk_recursive($methods, $expander);
					foreach ($methods as $method) {
						call_user_func_array(array($service, $method[0]), isset($method[1]) ? $method[1] : array());
					}

					return $service;
				');

			} elseif (isset($definition['alias'])) {
				$factory = create_function('$container', 'extract(NCFix::$vars['.NCFix::uses(array('definition'=>$definition)).'], EXTR_REFS);
					return $container->getService($definition[\'alias\']);
				');
			} else {
				throw new InvalidStateException("The definition of service '$name' is missing factory method.");
			}

			if (isset($definition['tags'])) {
				$tags = (array) $definition['tags'];
				array_walk_recursive($tags, $expander);
			} else {
				$tags = NULL;
			}
			$container->addService($name, $factory, $tags);
		}
	}

	function generateCode(array $definitions)
	{
		$code = '';
		foreach ($definitions as $name => $definition) {
			$name = $this->varExport($name);
			if (is_scalar($definition)) {
				if (substr($definition, 0, 1) === '@') {
					$definition = array('alias' => substr($definition, 1));
				} else {
					$factory = $this->varExport($definition);
					$code .= "\$container->addService($name, $factory);\n\n";
					continue;
				}
			}

			if (isset($definition['class']) || isset($definition['factory'])) {
				$arguments = $this->argsExport(isset($definition['arguments']) ? $definition['arguments'] : array());
				$factory = "function(\$container) {\n\t";
				$factory .= isset($definition['class'])
					? '$class = ' . $this->argsExport(array($definition['class'])) . '; $service = new $class(' . $arguments . ");\n"
					: "\$service = call_user_func(\n\t\t" . $this->argsExport(array($definition['factory']))
						. ",\n\t\t\$container" . ($arguments ? ",\n\t\t$arguments" : '') . "\n\t);\n";

				if (isset($definition['methods'])) {
					foreach ($definition['methods'] as $method) {
						$args = isset($method[1]) ? $this->argsExport($method[1]) : '';
						$factory .= "\t\$service->$method[0]($args);\n";
					}
				}
				$factory .= "\treturn \$service;\n}";

			} elseif (isset($definition['alias'])) {
				$factory = $this->varExport($definition['alias']);
				$factory = "function(\$container) {\n\treturn \$container->getService($factory);\n}";
			} else {
				throw new InvalidStateException("The definition of service '$name' is missing factory method.");
			}

			$tags = isset($definition['tags']) ? $this->argsExport(array($definition['tags'])) : 'NULL';
			$code .= "\$container->addService($name, $factory, $tags);\n\n";
		}
		return $code;
	}

	private function argsExport($args)
	{
		$args = implode(', ', array_map(array($this, 'varExport'), $args));
		$args = preg_replace("#(?<!\\\)'@(\w+)'#", '\$container->getService(\'$1\')', $args);
		$args = preg_replace("#(?<!\\\)'%([\w-]+)%'#", '\$container->params[\'$1\']', $args);
		$args = preg_replace("#(?<!\\\)'(?:[^'\\\]|\\\.)*%(?:[^'\\\]|\\\.)*'#", 'NStrings::expand($0, \$container->params)', $args);
		return $args;
	}

	private function varExport($arg)
	{
		return preg_replace('#\n *#', ' ', var_export($arg, TRUE));
	}

}

class NMissingServiceException extends InvalidStateException
{
}

class NAmbiguousServiceException extends InvalidStateException
{
}

class NServiceBuilder extends NObject implements IServiceBuilder
{

	private $class;

	function __construct($class)
	{
		$this->class = $class;
	}

	function getClass()
	{
		return $this->class;
	}

	function createService(IDiContainer $container)
	{
		if (!class_exists($this->class)) {
			throw new InvalidStateException("Cannot instantiate service, class '$this->class' not found.");
		}
		return new $this->class;
	}

}

class NFormGroup extends NObject
{

	protected $controls;

	private $options = array();

	function __construct()
	{
		$this->controls = new SplObjectStorage;
	}

	function add()
	{
		foreach (func_get_args() as $num => $item) {
			if ($item instanceof IFormControl) {
				$this->controls->attach($item);

			} elseif ($item instanceof Traversable || is_array($item)) {
				foreach ($item as $control) {
					$this->controls->attach($control);
				}

			} else {
				throw new InvalidArgumentException("Only IFormControl items are allowed, the #$num parameter is invalid.");
			}
		}
		return $this;
	}

	function getControls()
	{
		return iterator_to_array($this->controls);
	}

	function setOption($key, $value)
	{
		if ($value === NULL) {
			unset($this->options[$key]);

		} else {
			$this->options[$key] = $value;
		}
		return $this;
	}

	final function getOption($key, $default = NULL)
	{
		return isset($this->options[$key]) ? $this->options[$key] : $default;
	}

	final function getOptions()
	{
		return $this->options;
	}

}

abstract class NFormControl extends NComponent implements IFormControl
{

	public static $idMask = 'frm%s-%s';

	public $caption;

	protected $value;

	protected $control;

	protected $label;

	private $errors = array();

	private $disabled = FALSE;

	private $htmlId;

	private $htmlName;

	private $rules;

	private $translator = TRUE;

	private $options = array();

	function __construct($caption = NULL)
	{
		$this->monitor('NForm');
		parent::__construct();
		$this->control = NHtml::el('input');
		$this->label = NHtml::el('label');
		$this->caption = $caption;
		$this->rules = new NRules($this);
	}

	protected function attached($form)
	{
		if (!$this->disabled && $form instanceof NForm && $form->isAnchored() && $form->isSubmitted()) {
			$this->htmlName = NULL;
			$this->loadHttpData();
		}
	}

	function getForm($need = TRUE)
	{
		return $this->lookup('NForm', $need);
	}

	function getHtmlName()
	{
		if ($this->htmlName === NULL) {
			$name = str_replace(self::NAME_SEPARATOR, '][', $this->lookupPath('NForm'), $count);
			if ($count) {
				$name = substr_replace($name, '', strpos($name, ']'), 1) . ']';
			}
			if (is_numeric($name) || in_array($name, array('attributes','children','elements','focus','length','reset','style','submit','onsubmit'))) {
				$name .= '_';
			}
			$this->htmlName = $name;
		}
		return $this->htmlName;
	}

	function setHtmlId($id)
	{
		$this->htmlId = $id;
		return $this;
	}

	function getHtmlId()
	{
		if ($this->htmlId === FALSE) {
			return NULL;

		} elseif ($this->htmlId === NULL) {
			$this->htmlId = sprintf(self::$idMask, $this->getForm()->getName(), $this->lookupPath('NForm'));
		}
		return $this->htmlId;
	}

	function setAttribute($name, $value = TRUE)
	{
		$this->control->$name = $value;
		return $this;
	}

	function setOption($key, $value)
	{
		if ($value === NULL) {
			unset($this->options[$key]);

		} else {
			$this->options[$key] = $value;
		}
		return $this;
	}

	final function getOption($key, $default = NULL)
	{
		return isset($this->options[$key]) ? $this->options[$key] : $default;
	}

	final function getOptions()
	{
		return $this->options;
	}

	function setTranslator(ITranslator $translator = NULL)
	{
		$this->translator = $translator;
		return $this;
	}

	final function getTranslator()
	{
		if ($this->translator === TRUE) {
			return $this->getForm(FALSE) ? $this->getForm()->getTranslator() : NULL;
		}
		return $this->translator;
	}

	function translate($s, $count = NULL)
	{
		$translator = $this->getTranslator();
		return $translator === NULL || $s == NULL ? $s : $translator->translate($s, $count);
	}

	function setValue($value)
	{
		$this->value = $value;
		return $this;
	}

	function getValue()
	{
		return $this->value;
	}

	function isFilled()
	{
		return (string) $this->getValue() !== '';
	}

	function setDefaultValue($value)
	{
		$form = $this->getForm(FALSE);
		if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
			$this->setValue($value);
		}
		return $this;
	}

	function loadHttpData()
	{
		$path = explode('[', strtr(str_replace(array('[]', ']'), '', $this->getHtmlName()), '.', '_'));
		$this->setValue(NArrays::get($this->getForm()->getHttpData(), $path, NULL));
	}

	function setDisabled($value = TRUE)
	{
		$this->disabled = (bool) $value;
		return $this;
	}

	function isDisabled()
	{
		return $this->disabled;
	}

	function getControl()
	{
		$this->setOption('rendered', TRUE);

		$control = clone $this->control;
		$control->name = $this->getHtmlName();
		$control->disabled = $this->disabled;
		$control->id = $this->getHtmlId();
		$control->required = $this->isRequired();

		$rules = self::exportRules($this->rules);
		$rules = substr(json_encode($rules), 1, -1);
		$rules = preg_replace('#"([a-z0-9]+)":#i', '$1:', $rules);
		$rules = preg_replace('#(?<!\\\\)"([^\\\\\',]*)"#i', "'$1'", $rules);
		$control->data('nette-rules', $rules ? $rules : NULL);

		return $control;
	}

	function getLabel($caption = NULL)
	{
		$label = clone $this->label;
		$label->for = $this->getHtmlId();
		if ($caption !== NULL) {
			$label->setText($this->translate($caption));

		} elseif ($this->caption instanceof NHtml) {
			$label->add($this->caption);

		} else {
			$label->setText($this->translate($this->caption));
		}
		return $label;
	}

	final function getControlPrototype()
	{
		return $this->control;
	}

	final function getLabelPrototype()
	{
		return $this->label;
	}

	function addRule($operation, $message = NULL, $arg = NULL)
	{
		$this->rules->addRule($operation, $message, $arg);
		return $this;
	}

	function addCondition($operation, $value = NULL)
	{
		return $this->rules->addCondition($operation, $value);
	}

	function addConditionOn(IFormControl $control, $operation, $value = NULL)
	{
		return $this->rules->addConditionOn($control, $operation, $value);
	}

	final function getRules()
	{
		return $this->rules;
	}

	final function setRequired($message = NULL)
	{
		return $this->addRule(NForm::FILLED, $message);
	}

	final function isRequired()
	{
		foreach ($this->rules as $rule) {
			if ($rule->type === NRule::VALIDATOR && !$rule->isNegative && $rule->operation === NForm::FILLED) {
				return TRUE;
			}
		}
		return FALSE;
	}

	private static function exportRules($rules)
	{
		$payload = array();
		foreach ($rules as $rule) {
			if (!is_string($op = $rule->operation)) {
				$op = callback($op);
				if (!$op->isStatic()) {
					continue;
				}
			}
			if ($rule->type === NRule::VALIDATOR) {
				$item = array('op' => ($rule->isNegative ? '~' : '') . $op, 'msg' => $rules->formatMessage($rule, FALSE));

			} elseif ($rule->type === NRule::CONDITION) {
				$item = array(
					'op' => ($rule->isNegative ? '~' : '') . $op,
					'rules' => self::exportRules($rule->subRules),
					'control' => $rule->control->getHtmlName()
				);
				if ($rule->subRules->getToggles()) {
					$item['toggle'] = $rule->subRules->getToggles();
				}
			}

			if (is_array($rule->arg)) {
				foreach ($rule->arg as $key => $value) {
					$item['arg'][$key] = $value instanceof IFormControl ? (object) array('control' => $value->getHtmlName()) : $value;
				}
			} elseif ($rule->arg !== NULL) {
				$item['arg'] = $rule->arg instanceof IFormControl ? (object) array('control' => $rule->arg->getHtmlName()) : $rule->arg;
			}

			$payload[] = $item;
		}
		return $payload;
	}

	static function validateEqual(IFormControl $control, $arg)
	{
		$value = $control->getValue();
		foreach ((is_array($value) ? $value : array($value)) as $val) {
			foreach ((is_array($arg) ? $arg : array($arg)) as $item) {
				if ((string) $val === (string) ($item instanceof IFormControl ? $item->value : $item)) {
					return TRUE;
				}
			}
		}
		return FALSE;
	}

	static function validateFilled(IFormControl $control)
	{
		return $control->isFilled();
	}

	static function validateValid(IFormControl $control)
	{
		return $control->rules->validate(TRUE);
	}

	function addError($message)
	{
		if (!in_array($message, $this->errors, TRUE)) {
			$this->errors[] = $message;
		}
		$this->getForm()->addError($message);
	}

	function getErrors()
	{
		return $this->errors;
	}

	function hasErrors()
	{
		return (bool) $this->errors;
	}

	function cleanErrors()
	{
		$this->errors = array();
	}

}

class NButton extends NFormControl
{

	function __construct($caption = NULL)
	{
		parent::__construct($caption);
		$this->control->type = 'button';
	}

	function getLabel($caption = NULL)
	{
		return NULL;
	}

	function getControl($caption = NULL)
	{
		$control = parent::getControl();
		$control->value = $this->translate($caption === NULL ? $this->caption : $caption);
		return $control;
	}

}

class NCheckbox extends NFormControl
{

	function __construct($label = NULL)
	{
		parent::__construct($label);
		$this->control->type = 'checkbox';
		$this->value = FALSE;
	}

	function setValue($value)
	{
		$this->value = is_scalar($value) ? (bool) $value : FALSE;
		return $this;
	}

	function getControl()
	{
		return parent::getControl()->checked($this->value);
	}

}

class NHiddenField extends NFormControl
{

	private $forcedValue;

	function __construct($forcedValue = NULL)
	{
		parent::__construct();
		$this->control->type = 'hidden';
		$this->value = (string) $forcedValue;
		$this->forcedValue = $forcedValue;
	}

	function getLabel($caption = NULL)
	{
		return NULL;
	}

	function setValue($value)
	{
		$this->value = is_scalar($value) ? (string) $value : '';
		return $this;
	}

	function getControl()
	{
		return parent::getControl()
			->value($this->forcedValue === NULL ? $this->value : $this->forcedValue)
			->data('nette-rules', NULL);
	}

}

class NSubmitButton extends NButton implements ISubmitterControl
{

	public $onClick;

	public $onInvalidClick;

	private $validationScope = TRUE;

	function __construct($caption = NULL)
	{
		parent::__construct($caption);
		$this->control->type = 'submit';
	}

	function setValue($value)
	{
		if ($this->value = is_scalar($value) && (bool) $value) {
			$this->getForm()->setSubmittedBy($this);
		}
		return $this;
	}

	function isSubmittedBy()
	{
		return $this->getForm()->isSubmitted() === $this;
	}

	function setValidationScope($scope)
	{

		$this->validationScope = (bool) $scope;
		$this->control->formnovalidate = !$this->validationScope;
		return $this;
	}

	final function getValidationScope()
	{
		return $this->validationScope;
	}

	function click()
	{
		$this->onClick($this);
	}

	static function validateSubmitted(ISubmitterControl $control)
	{
		return $control->isSubmittedBy();
	}

}

class NImageButton extends NSubmitButton
{

	function __construct($src = NULL, $alt = NULL)
	{
		parent::__construct();
		$this->control->type = 'image';
		$this->control->src = $src;
		$this->control->alt = $alt;
	}

	function getHtmlName()
	{
		$name = parent::getHtmlName();
		return strpos($name, '[') === FALSE ? $name : $name . '[]';
	}

	function loadHttpData()
	{
		$path = $this->getHtmlName();
		$path = explode('[', strtr(str_replace(']', '', strpos($path, '[') === FALSE ? $path . '.x' : substr($path, 0, -2)), '.', '_'));
		$this->setValue(NArrays::get($this->getForm()->getHttpData(), $path, NULL) !== NULL);
	}

}

class NSelectBox extends NFormControl
{

	private $items = array();

	protected $allowed = array();

	private $prompt = FALSE;

	private $useKeys = TRUE;

	function __construct($label = NULL, array $items = NULL, $size = NULL)
	{
		parent::__construct($label);
		$this->control->setName('select');
		$this->control->size = $size > 1 ? (int) $size : NULL;
		if ($items !== NULL) {
			$this->setItems($items);
		}
	}

	function getValue()
	{
		$allowed = $this->allowed;
		if ($this->prompt) {
			$allowed = array_slice($allowed, 1, count($allowed), TRUE);
		}

		return is_scalar($this->value) && isset($allowed[$this->value]) ? $this->value : NULL;
	}

	function getRawValue()
	{
		return is_scalar($this->value) ? $this->value : NULL;
	}

	function isFilled()
	{
		$value = $this->getValue();
		return is_array($value) ? count($value) > 0 : $value !== NULL;
	}

	function setPrompt($prompt)
	{
		if (is_bool($prompt)) {
			$this->prompt = $prompt;
		} else {
			$this->prompt = TRUE;
			if ($prompt !== NULL) {
				$this->items = array('' => $prompt) + $this->items;
				$this->allowed = array('' => '') + $this->allowed;
			}
		}
		return $this;
	}

	function skipFirst($v = NULL)
	{
		trigger_error(__METHOD__ . '() is deprecated; use setPrompt() instead.', E_USER_WARNING);
		return $this->setPrompt($v);
	}

	final function getPrompt()
	{
		return $this->prompt;
	}

	final function areKeysUsed()
	{
		return $this->useKeys;
	}

	function setItems(array $items, $useKeys = TRUE)
	{
		$this->items = $items;
		$this->allowed = array();
		$this->useKeys = (bool) $useKeys;

		foreach ($items as $key => $value) {
			if (!is_array($value)) {
				$value = array($key => $value);
			}

			foreach ($value as $key2 => $value2) {
				if (!$this->useKeys) {
					if (!is_scalar($value2)) {
						throw new InvalidArgumentException("All items must be scalar.");
					}
					$key2 = $value2;
				}

				if (isset($this->allowed[$key2])) {
					throw new InvalidArgumentException("Items contain duplication for key '$key2'.");
				}

				$this->allowed[$key2] = $value2;
			}
		}
		return $this;
	}

	final function getItems()
	{
		return $this->items;
	}

	function getSelectedItem()
	{
		if (!$this->useKeys) {
			return $this->getValue();

		} else {
			$value = $this->getValue();
			return $value === NULL ? NULL : $this->allowed[$value];
		}
	}

	function getControl()
	{
		$control = parent::getControl();
		if ($this->prompt) {
			reset($this->items);
			$control->data('nette-empty-value', $this->useKeys ? key($this->items) : current($this->items));
		}
		$selected = $this->getValue();
		$selected = is_array($selected) ? array_flip($selected) : array($selected => TRUE);
		$option = NHtml::el('option');

		foreach ($this->items as $key => $value) {
			if (!is_array($value)) {
				$value = array($key => $value);
				$dest = $control;

			} else {
				$dest = $control->create('optgroup')->label($this->translate($key));
			}

			foreach ($value as $key2 => $value2) {
				if ($value2 instanceof NHtml) {
					$dest->add((string) $value2->selected(isset($selected[$key2])));

				} else {
					$key2 = $this->useKeys ? $key2 : $value2;
					$value2 = $this->translate((string) $value2);
					$dest->add((string) $option->value($key2 === $value2 ? NULL : $key2)
						->selected(isset($selected[$key2]))
						->setText($value2));
				}
			}
		}
		return $control;
	}

}

class NMultiSelectBox extends NSelectBox
{

	function getValue()
	{
		$allowed = array_keys($this->allowed);
		if ($this->getPrompt()) {
			unset($allowed[0]);
		}
		return array_intersect($this->getRawValue(), $allowed);
	}

	function getRawValue()
	{
		if (is_scalar($this->value)) {
			$value = array($this->value);

		} elseif (!is_array($this->value)) {
			$value = array();

		} else {
			$value = $this->value;
		}

		$res = array();
		foreach ($value as $val) {
			if (is_scalar($val)) {
				$res[] = $val;
			}
		}
		return $res;
	}

	function getSelectedItem()
	{
		if (!$this->areKeysUsed()) {
			return $this->getValue();

		} else {
			$res = array();
			foreach ($this->getValue() as $value) {
				$res[$value] = $this->allowed[$value];
			}
			return $res;
		}
	}

	function getHtmlName()
	{
		return parent::getHtmlName() . '[]';
	}

	function getControl()
	{
		$control = parent::getControl();
		$control->multiple = TRUE;
		return $control;
	}

}

class NRadioList extends NFormControl
{

	protected $separator;

	protected $container;

	protected $items = array();

	function __construct($label = NULL, array $items = NULL)
	{
		parent::__construct($label);
		$this->control->type = 'radio';
		$this->container = NHtml::el();
		$this->separator = NHtml::el('br');
		if ($items !== NULL) {
			$this->setItems($items);
		}
	}

	function getValue($raw = FALSE)
	{
		return is_scalar($this->value) && ($raw || isset($this->items[$this->value])) ? $this->value : NULL;
	}

	function isFilled()
	{
		return $this->getValue() !== NULL;
	}

	function setItems(array $items)
	{
		$this->items = $items;
		return $this;
	}

	final function getItems()
	{
		return $this->items;
	}

	final function getSeparatorPrototype()
	{
		return $this->separator;
	}

	final function getContainerPrototype()
	{
		return $this->container;
	}

	function getControl($key = NULL)
	{
		if ($key === NULL) {
			$container = clone $this->container;
			$separator = (string) $this->separator;

		} elseif (!isset($this->items[$key])) {
			return NULL;
		}

		$control = parent::getControl();
		$id = $control->id;
		$counter = -1;
		$value = $this->value === NULL ? NULL : (string) $this->getValue();
		$label = NHtml::el('label');

		foreach ($this->items as $k => $val) {
			$counter++;
			if ($key !== NULL && (string) $key !== (string) $k) {
				continue;
			}

			$control->id = $label->for = $id . '-' . $counter;
			$control->checked = (string) $k === $value;
			$control->value = $k;

			if ($val instanceof NHtml) {
				$label->setHtml($val);
			} else {
				$label->setText($this->translate((string) $val));
			}

			if ($key !== NULL) {
				return NHtml::el()->add($control)->add($label);
			}

			$container->add((string) $control . (string) $label . $separator);
			$control->data('nette-rules', NULL);

		}

		return $container;
	}

	function getLabel($caption = NULL)
	{
		$label = parent::getLabel($caption);
		$label->for = NULL;
		return $label;
	}

}

abstract class NTextBase extends NFormControl
{

	protected $emptyValue = '';

	protected $filters = array();

	function setValue($value)
	{
		$this->value = is_scalar($value) ? (string) $value : '';
		return $this;
	}

	function getValue()
	{
		$value = $this->value;
		foreach ($this->filters as $filter) {
			$value = (string) $filter->invoke($value);
		}
		return $value === $this->translate($this->emptyValue) ? '' : $value;
	}

	function setEmptyValue($value)
	{
		$this->emptyValue = (string) $value;
		return $this;
	}

	final function getEmptyValue()
	{
		return $this->emptyValue;
	}

	function addFilter($filter)
	{
		$this->filters[] = callback($filter);
		return $this;
	}

	function getControl()
	{
		$control = parent::getControl();
		foreach ($this->getRules() as $rule) {
			if ($rule->type === NRule::VALIDATOR && !$rule->isNegative
				&& ($rule->operation === NForm::LENGTH || $rule->operation === NForm::MAX_LENGTH)
			) {
				$control->maxlength = is_array($rule->arg) ? $rule->arg[1] : $rule->arg;
			}
		}
		if ($this->emptyValue !== '') {
			$control->data('nette-empty-value', $this->translate($this->emptyValue));
		}
		return $control;
	}

	function addRule($operation, $message = NULL, $arg = NULL)
	{
		if ($operation === NForm::FLOAT) {
			$this->addFilter(callback(__CLASS__, 'filterFloat'));
		}
		return parent::addRule($operation, $message, $arg);
	}

	static function validateMinLength(NTextBase $control, $length)
	{
		return NStrings::length($control->getValue()) >= $length;
	}

	static function validateMaxLength(NTextBase $control, $length)
	{
		return NStrings::length($control->getValue()) <= $length;
	}

	static function validateLength(NTextBase $control, $range)
	{
		if (!is_array($range)) {
			$range = array($range, $range);
		}
		$len = NStrings::length($control->getValue());
		return ($range[0] === NULL || $len >= $range[0]) && ($range[1] === NULL || $len <= $range[1]);
	}

	static function validateEmail(NTextBase $control)
	{
		$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]";
		$localPart = "(?:\"(?:[ !\\x23-\\x5B\\x5D-\\x7E]*|\\\\[ -~])+\"|$atom+(?:\\.$atom+)*)";
		$chars = "a-z0-9\x80-\xFF";
		$domain = "[$chars](?:[-$chars]{0,61}[$chars])";
		return (bool) NStrings::match($control->getValue(), "(^$localPart@(?:$domain?\\.)+[-$chars]{2,19}\\z)i");
	}

	static function validateUrl(NTextBase $control)
	{
		$chars = "a-z0-9\x80-\xFF";
		return (bool) NStrings::match(
			$control->getValue(),
			"#^(?:https?://|)(?:[$chars](?:[-$chars]{0,61}[$chars])?\\.)+[-$chars]{2,19}(/\S*)?$#i"
		);
	}

	static function validateRegexp(NTextBase $control, $regexp)
	{
		return (bool) NStrings::match($control->getValue(), $regexp);
	}

	static function validatePattern(NTextBase $control, $pattern)
	{
		return (bool) NStrings::match($control->getValue(), "\x01^($pattern)$\x01u");
	}

	static function validateInteger(NTextBase $control)
	{
		return (bool) NStrings::match($control->getValue(), '/^-?[0-9]+$/');
	}

	static function validateFloat(NTextBase $control)
	{
		return (bool) NStrings::match($control->getValue(), '/^-?[0-9]*[.,]?[0-9]+$/');
	}

	static function validateRange(NTextBase $control, $range)
	{
		return ($range[0] === NULL || $control->getValue() >= $range[0])
			&& ($range[1] === NULL || $control->getValue() <= $range[1]);
	}

	static function filterFloat($s)
	{
		return str_replace(array(' ', ','), array('', '.'), $s);
	}

}

class NTextArea extends NTextBase
{

	function __construct($label = NULL, $cols = NULL, $rows = NULL)
	{
		parent::__construct($label);
		$this->control->setName('textarea');
		$this->control->cols = $cols;
		$this->control->rows = $rows;
		$this->value = '';
	}

	function getControl()
	{
		$control = parent::getControl();
		$control->setText($this->getValue() === '' ? $this->translate($this->emptyValue) : $this->value);
		return $control;
	}

}

class NTextInput extends NTextBase
{

	function __construct($label = NULL, $cols = NULL, $maxLength = NULL)
	{
		parent::__construct($label);
		$this->control->type = 'text';
		$this->control->size = $cols;
		$this->control->maxlength = $maxLength;
		$this->filters[] = callback($this, 'sanitize');
		$this->value = '';
	}

	function sanitize($value)
	{
		if ($this->control->maxlength && NStrings::length($value) > $this->control->maxlength) {
			$value = iconv_substr($value, 0, $this->control->maxlength, 'UTF-8');
		}
		return NStrings::trim(strtr($value, "\r\n", '  '));
	}

	function setType($type)
	{
		$this->control->type = $type;
		return $this;
	}

	function setPasswordMode($mode = TRUE)
	{
		$this->control->type = $mode ? 'password' : 'text';
		return $this;
	}

	function getControl()
	{
		$control = parent::getControl();
		foreach ($this->getRules() as $rule) {
			if ($rule->isNegative || $rule->type !== NRule::VALIDATOR) {

			} elseif ($rule->operation === NForm::RANGE && $control->type !== 'text') {
				list($control->min, $control->max) = $rule->arg;

			} elseif ($rule->operation === NForm::PATTERN) {
				$control->pattern = $rule->arg;
			}
		}
		if ($control->type !== 'password') {
			$control->value = $this->getValue() === '' ? $this->translate($this->emptyValue) : $this->value;
		}
		return $control;
	}

}

class NUploadControl extends NFormControl
{

	function __construct($label = NULL)
	{
		parent::__construct($label);
		$this->control->type = 'file';
	}

	protected function attached($form)
	{
		if ($form instanceof NForm) {
			if ($form->getMethod() !== NForm::POST) {
				throw new InvalidStateException('File upload requires method POST.');
			}
			$form->getElementPrototype()->enctype = 'multipart/form-data';
		}
		parent::attached($form);
	}

	function setValue($value)
	{
		if (is_array($value)) {
			$this->value = new NHttpUploadedFile($value);

		} elseif ($value instanceof NHttpUploadedFile) {
			$this->value = $value;

		} else {
			$this->value = new NHttpUploadedFile(NULL);
		}
		return $this;
	}

	function isFilled()
	{
		return $this->value instanceof NHttpUploadedFile && $this->value->isOK();
	}

	static function validateFileSize(NUploadControl $control, $limit)
	{
		$file = $control->getValue();
		return $file instanceof NHttpUploadedFile && $file->getSize() <= $limit;
	}

	static function validateMimeType(NUploadControl $control, $mimeType)
	{
		$file = $control->getValue();
		if ($file instanceof NHttpUploadedFile) {
			$type = strtolower($file->getContentType());
			$mimeTypes = is_array($mimeType) ? $mimeType : explode(',', $mimeType);
			if (in_array($type, $mimeTypes, TRUE)) {
				return TRUE;
			}
			if (in_array(preg_replace('#/.*#', '/*', $type), $mimeTypes, TRUE)) {
				return TRUE;
			}
		}
		return FALSE;
	}

	static function validateImage(NUploadControl $control)
	{
		$file = $control->getValue();
		return $file instanceof NHttpUploadedFile && $file->isImage();
	}

}

class NDefaultFormRenderer extends NObject implements IFormRenderer
{

	public $wrappers = array(
		'form' => array(
			'container' => NULL,
			'errors' => TRUE,
		),

		'error' => array(
			'container' => 'ul class=error',
			'item' => 'li',
		),

		'group' => array(
			'container' => 'fieldset',
			'label' => 'legend',
			'description' => 'p',
		),

		'controls' => array(
			'container' => 'table',
		),

		'pair' => array(
			'container' => 'tr',
			'.required' => 'required',
			'.optional' => NULL,
			'.odd' => NULL,
		),

		'control' => array(
			'container' => 'td',
			'.odd' => NULL,

			'errors' => FALSE,
			'description' => 'small',
			'requiredsuffix' => '',

			'.required' => 'required',
			'.text' => 'text',
			'.password' => 'text',
			'.file' => 'text',
			'.submit' => 'button',
			'.image' => 'imagebutton',
			'.button' => 'button',
		),

		'label' => array(
			'container' => 'th',
			'suffix' => NULL,
			'requiredsuffix' => '',
		),

		'hidden' => array(
			'container' => 'div',
		),
	);

	protected $form;

	protected $counter;

	function render(NForm $form, $mode = NULL)
	{
		if ($this->form !== $form) {
			$this->form = $form;
			$this->init();
		}

		$s = '';
		if (!$mode || $mode === 'begin') {
			$s .= $this->renderBegin();
		}
		if ((!$mode && $this->getValue('form errors')) || $mode === 'errors') {
			$s .= $this->renderErrors();
		}
		if (!$mode || $mode === 'body') {
			$s .= $this->renderBody();
		}
		if (!$mode || $mode === 'end') {
			$s .= $this->renderEnd();
		}
		return $s;
	}

	function setClientScript()
	{
		trigger_error(__METHOD__ . '() is deprecated; use unobstructive JavaScript instead.', E_USER_WARNING);
		return $this;
	}

	protected function init()
	{

		$wrapper = & $this->wrappers['control'];
		foreach ($this->form->getControls() as $control) {
			if ($control->isRequired() && isset($wrapper['.required'])) {
				$control->getLabelPrototype()->class($wrapper['.required'], TRUE);
			}

			$el = $control->getControlPrototype();
			if ($el->getName() === 'input' && isset($wrapper['.' . $el->type])) {
				$el->class($wrapper['.' . $el->type], TRUE);
			}
		}
	}

	function renderBegin()
	{
		$this->counter = 0;

		foreach ($this->form->getControls() as $control) {
			$control->setOption('rendered', FALSE);
		}

		if (strcasecmp($this->form->getMethod(), 'get') === 0) {
			$el = clone $this->form->getElementPrototype();
			$url = explode('?', (string) $el->action, 2);
			$el->action = $url[0];
			$s = '';
			if (isset($url[1])) {
				foreach (preg_split('#[;&]#', $url[1]) as $param) {
					$parts = explode('=', $param, 2);
					$name = urldecode($parts[0]);
					if (!isset($this->form[$name])) {
						$s .= NHtml::el('input', array('type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])));
					}
				}
				$s = "\n\t" . $this->getWrapper('hidden container')->setHtml($s);
			}
			return $el->startTag() . $s;

		} else {
			return $this->form->getElementPrototype()->startTag();
		}
	}

	function renderEnd()
	{
		$s = '';
		foreach ($this->form->getControls() as $control) {
			if ($control instanceof NHiddenField && !$control->getOption('rendered')) {
				$s .= (string) $control->getControl();
			}
		}
		if (iterator_count($this->form->getComponents(TRUE, 'NTextInput')) < 2) {
			$s .= '<!--[if IE]><input type=IEbug disabled style="display:none"><![endif]-->';
		}
		if ($s) {
			$s = $this->getWrapper('hidden container')->setHtml($s) . "\n";
		}

		return $s . $this->form->getElementPrototype()->endTag() . "\n";
	}

	function renderErrors(IFormControl $control = NULL)
	{
		$errors = $control === NULL ? $this->form->getErrors() : $control->getErrors();
		if (count($errors)) {
			$ul = $this->getWrapper('error container');
			$li = $this->getWrapper('error item');

			foreach ($errors as $error) {
				$item = clone $li;
				if ($error instanceof NHtml) {
					$item->add($error);
				} else {
					$item->setText($error);
				}
				$ul->add($item);
			}
			return "\n" . $ul->render(0);
		}
	}

	function renderBody()
	{
		$s = $remains = '';

		$defaultContainer = $this->getWrapper('group container');
		$translator = $this->form->getTranslator();

		foreach ($this->form->getGroups() as $group) {
			if (!$group->getControls() || !$group->getOption('visual')) {
				continue;
			}

			$container = $group->getOption('container', $defaultContainer);
			$container = $container instanceof NHtml ? clone $container : NHtml::el($container);

			$s .= "\n" . $container->startTag();

			$text = $group->getOption('label');
			if ($text instanceof NHtml) {
				$s .= $text;

			} elseif (is_string($text)) {
				if ($translator !== NULL) {
					$text = $translator->translate($text);
				}
				$s .= "\n" . $this->getWrapper('group label')->setText($text) . "\n";
			}

			$text = $group->getOption('description');
			if ($text instanceof NHtml) {
				$s .= $text;

			} elseif (is_string($text)) {
				if ($translator !== NULL) {
					$text = $translator->translate($text);
				}
				$s .= $this->getWrapper('group description')->setText($text) . "\n";
			}

			$s .= $this->renderControls($group);

			$remains = $container->endTag() . "\n" . $remains;
			if (!$group->getOption('embedNext')) {
				$s .= $remains;
				$remains = '';
			}
		}

		$s .= $remains . $this->renderControls($this->form);

		$container = $this->getWrapper('form container');
		$container->setHtml($s);
		return $container->render(0);
	}

	function renderControls($parent)
	{
		if (!($parent instanceof NFormContainer || $parent instanceof NFormGroup)) {
			throw new InvalidArgumentException("Argument must be FormContainer or FormGroup instance.");
		}

		$container = $this->getWrapper('controls container');

		$buttons = NULL;
		foreach ($parent->getControls() as $control) {
			if ($control->getOption('rendered') || $control instanceof NHiddenField || $control->getForm(FALSE) !== $this->form) {

			} elseif ($control instanceof NButton) {
				$buttons[] = $control;

			} else {
				if ($buttons) {
					$container->add($this->renderPairMulti($buttons));
					$buttons = NULL;
				}
				$container->add($this->renderPair($control));
			}
		}

		if ($buttons) {
			$container->add($this->renderPairMulti($buttons));
		}

		$s = '';
		if (count($container)) {
			$s .= "\n" . $container . "\n";
		}

		return $s;
	}

	function renderPair(IFormControl $control)
	{
		$pair = $this->getWrapper('pair container');
		$pair->add($this->renderLabel($control));
		$pair->add($this->renderControl($control));
		$pair->class($this->getValue($control->isRequired() ? 'pair .required' : 'pair .optional'), TRUE);
		$pair->class($control->getOption('class'), TRUE);
		if (++$this->counter % 2) {
			$pair->class($this->getValue('pair .odd'), TRUE);
		}
		$pair->id = $control->getOption('id');
		return $pair->render(0);
	}

	function renderPairMulti(array $controls)
	{
		$s = array();
		foreach ($controls as $control) {
			if (!$control instanceof IFormControl) {
				throw new InvalidArgumentException("Argument must be array of IFormControl instances.");
			}
			$s[] = (string) $control->getControl();
		}
		$pair = $this->getWrapper('pair container');
		$pair->add($this->renderLabel($control));
		$pair->add($this->getWrapper('control container')->setHtml(implode(" ", $s)));
		return $pair->render(0);
	}

	function renderLabel(IFormControl $control)
	{
		$head = $this->getWrapper('label container');

		if ($control instanceof NCheckbox || $control instanceof NButton) {
			return $head->setHtml(($head->getName() === 'td' || $head->getName() === 'th') ? '&nbsp;' : '');

		} else {
			$label = $control->getLabel();
			$suffix = $this->getValue('label suffix') . ($control->isRequired() ? $this->getValue('label requiredsuffix') : '');
			if ($label instanceof NHtml) {
				$label->setHtml($label->getHtml() . $suffix);
				$suffix = '';
			}
			return $head->setHtml((string) $label . $suffix);
		}
	}

	function renderControl(IFormControl $control)
	{
		$body = $this->getWrapper('control container');
		if ($this->counter % 2) {
			$body->class($this->getValue('control .odd'), TRUE);
		}

		$description = $control->getOption('description');
		if ($description instanceof NHtml) {
			$description = ' ' . $control->getOption('description');

		} elseif (is_string($description)) {
			$description = ' ' . $this->getWrapper('control description')->setText($control->translate($description));

		} else {
			$description = '';
		}

		if ($control->isRequired()) {
			$description = $this->getValue('control requiredsuffix') . $description;
		}

		if ($this->getValue('control errors')) {
			$description .= $this->renderErrors($control);
		}

		if ($control instanceof NCheckbox || $control instanceof NButton) {
			return $body->setHtml((string) $control->getControl() . (string) $control->getLabel() . $description);

		} else {
			return $body->setHtml((string) $control->getControl() . $description);
		}
	}

	protected function getWrapper($name)
	{
		$data = $this->getValue($name);
		return $data instanceof NHtml ? clone $data : NHtml::el($data);
	}

	protected function getValue($name)
	{
		$name = explode(' ', $name);
		$data = & $this->wrappers[$name[0]][$name[1]];
		return $data;
	}

}

final class NRule extends NObject
{

	const CONDITION = 1;

	const VALIDATOR = 2;

	const FILTER = 3;

	public $control;

	public $operation;

	public $arg;

	public $type;

	public $isNegative = FALSE;

	public $message;

	public $subRules;

}

final class NRules extends NObject implements IteratorAggregate
{

	const VALIDATE_PREFIX = 'validate';

	public static $defaultMessages = array(
		NForm::PROTECTION => 'Security token did not match. Possible CSRF attack.',
		NForm::EQUAL => 'Please enter %s.',
		NForm::FILLED => 'Please complete mandatory field.',
		NForm::MIN_LENGTH => 'Please enter a value of at least %d characters.',
		NForm::MAX_LENGTH => 'Please enter a value no longer than %d characters.',
		NForm::LENGTH => 'Please enter a value between %d and %d characters long.',
		NForm::EMAIL => 'Please enter a valid email address.',
		NForm::URL => 'Please enter a valid URL.',
		NForm::INTEGER => 'Please enter a numeric value.',
		NForm::FLOAT => 'Please enter a numeric value.',
		NForm::RANGE => 'Please enter a value between %d and %d.',
		NForm::MAX_FILE_SIZE => 'The size of the uploaded file can be up to %d bytes.',
		NForm::IMAGE => 'The uploaded file must be image in format JPEG, GIF or PNG.',
	);

	private $rules = array();

	private $parent;

	private $toggles = array();

	private $control;

	function __construct(IFormControl $control)
	{
		$this->control = $control;
	}

	function addRule($operation, $message = NULL, $arg = NULL)
	{
		$rule = new NRule;
		$rule->control = $this->control;
		$rule->operation = $operation;
		$this->adjustOperation($rule);
		$rule->arg = $arg;
		$rule->type = NRule::VALIDATOR;
		if ($message === NULL && is_string($rule->operation) && isset(self::$defaultMessages[$rule->operation])) {
			$rule->message = self::$defaultMessages[$rule->operation];
		} else {
			$rule->message = $message;
		}
		$this->rules[] = $rule;
		return $this;
	}

	function addCondition($operation, $arg = NULL)
	{
		return $this->addConditionOn($this->control, $operation, $arg);
	}

	function addConditionOn(IFormControl $control, $operation, $arg = NULL)
	{
		$rule = new NRule;
		$rule->control = $control;
		$rule->operation = $operation;
		$this->adjustOperation($rule);
		$rule->arg = $arg;
		$rule->type = NRule::CONDITION;
		$rule->subRules = new self($this->control);
		$rule->subRules->parent = $this;

		$this->rules[] = $rule;
		return $rule->subRules;
	}

	function elseCondition()
	{
		$rule = clone end($this->parent->rules);
		$rule->isNegative = !$rule->isNegative;
		$rule->subRules = new self($this->parent->control);
		$rule->subRules->parent = $this->parent;
		$this->parent->rules[] = $rule;
		return $rule->subRules;
	}

	function endCondition()
	{
		return $this->parent;
	}

	function toggle($id, $hide = TRUE)
	{
		$this->toggles[$id] = $hide;
		return $this;
	}

	function validate($onlyCheck = FALSE)
	{
		foreach ($this->rules as $rule) {
			if ($rule->control->isDisabled()) {
				continue;
			}

			$success = ($rule->isNegative xor $this->getCallback($rule)->invoke($rule->control, $rule->arg));

			if ($rule->type === NRule::CONDITION && $success) {
				if (!$rule->subRules->validate($onlyCheck)) {
					return FALSE;
				}

			} elseif ($rule->type === NRule::VALIDATOR && !$success) {
				if (!$onlyCheck) {
					$rule->control->addError(self::formatMessage($rule, TRUE));
				}
				return FALSE;
			}
		}
		return TRUE;
	}

	final function getIterator()
	{
		return new ArrayIterator($this->rules);
	}

	final function getToggles()
	{
		return $this->toggles;
	}

	private function adjustOperation($rule)
	{
		if (is_string($rule->operation) && ord($rule->operation[0]) > 127) {
			$rule->isNegative = TRUE;
			$rule->operation = ~$rule->operation;
		}

		if (!$this->getCallback($rule)->isCallable()) {
			$operation = is_scalar($rule->operation) ? " '$rule->operation'" : '';
			throw new InvalidArgumentException("Unknown operation$operation for control '{$rule->control->name}'.");
		}
	}

	private function getCallback($rule)
	{
		$op = $rule->operation;
		if (is_string($op) && strncmp($op, ':', 1) === 0) {
			return callback(get_class($rule->control), self::VALIDATE_PREFIX . ltrim($op, ':'));
		} else {
			return callback($op);
		}
	}

	static function formatMessage($rule, $withValue)
	{
		$message = $rule->message;
		if (!isset($message)) {
			$message = self::$defaultMessages[$rule->operation];
		}
		if ($translator = $rule->control->getForm()->getTranslator()) {
			$message = $translator->translate($message, is_int($rule->arg) ? $rule->arg : NULL);
		}
		$message = vsprintf(preg_replace('#%(name|label|value)#', '%$0', $message), (array) $rule->arg);
		$message = str_replace('%name', $rule->control->getName(), $message);
		$message = str_replace('%label', $rule->control->translate($rule->control->caption), $message);
		if ($withValue && strpos($message, '%value') !== FALSE) {
			$message = str_replace('%value', $rule->control->getValue(), $message);
		}
		return $message;
	}

}

class NHttpContext extends NObject
{

	private $request;

	private $response;

	function __construct(IHttpRequest $request, IHttpResponse $response)
	{
		$this->request = $request;
		$this->response = $response;
	}

	function isModified($lastModified = NULL, $etag = NULL)
	{
		if ($lastModified) {
			$this->response->setHeader('Last-Modified', $this->response->date($lastModified));
		}
		if ($etag) {
			$this->response->setHeader('ETag', '"' . addslashes($etag) . '"');
		}

		$ifNoneMatch = $this->request->getHeader('If-None-Match');
		if ($ifNoneMatch === '*') {
			$match = TRUE;

		} elseif ($ifNoneMatch !== NULL) {
			$etag = $this->response->getHeader('ETag');

			if ($etag == NULL || strpos(' ' . strtr($ifNoneMatch, ",\t", '  '), ' ' . $etag) === FALSE) {
				return TRUE;

			} else {
				$match = TRUE;
			}
		}

		$ifModifiedSince = $this->request->getHeader('If-Modified-Since');
		if ($ifModifiedSince !== NULL) {
			$lastModified = $this->response->getHeader('Last-Modified');
			if ($lastModified != NULL && strtotime($lastModified) <= strtotime($ifModifiedSince)) {
				$match = TRUE;

			} else {
				return TRUE;
			}
		}

		if (empty($match)) {
			return TRUE;
		}

		$this->response->setCode(IHttpResponse::S304_NOT_MODIFIED);
		return FALSE;
	}

	function getRequest()
	{
		return $this->request;
	}

	function getResponse()
	{
		return $this->response;
	}

}

class NHttpUploadedFile extends NObject
{

	private $name;

	private $type;

	private $size;

	private $tmpName;

	private $error;

	function __construct($value)
	{
		foreach (array('name', 'type', 'size', 'tmp_name', 'error') as $key) {
			if (!isset($value[$key]) || !is_scalar($value[$key])) {
				$this->error = UPLOAD_ERR_NO_FILE;
				return;
			}
		}
		$this->name = $value['name'];
		$this->size = $value['size'];
		$this->tmpName = $value['tmp_name'];
		$this->error = $value['error'];
	}

	function getName()
	{
		return $this->name;
	}

	function getSanitizedName()
	{
		return trim(NStrings::webalize($this->name, '.', FALSE), '.-');
	}

	function getContentType()
	{
		if ($this->isOk() && $this->type === NULL) {
			$this->type = NMimeTypeDetector::fromFile($this->tmpName);
		}
		return $this->type;
	}

	function getSize()
	{
		return $this->size;
	}

	function getTemporaryFile()
	{
		return $this->tmpName;
	}

	function __toString()
	{
		return $this->tmpName;
	}

	function getError()
	{
		return $this->error;
	}

	function isOk()
	{
		return $this->error === UPLOAD_ERR_OK;
	}

	function move($dest)
	{
		@mkdir(dirname($dest), 0777, TRUE);
		if (substr(PHP_OS, 0, 3) === 'WIN') { @unlink($dest); }
		if (!call_user_func(is_uploaded_file($this->tmpName) ? 'move_uploaded_file' : 'rename', $this->tmpName, $dest)) {
			throw new InvalidStateException("Unable to move uploaded file '$this->tmpName' to '$dest'.");
		}
		chmod($dest, 0666);
		$this->tmpName = $dest;
		return $this;
	}

	function isImage()
	{
		return in_array($this->getContentType(), array('image/gif', 'image/png', 'image/jpeg'), TRUE);
	}

	function toImage()
	{
		return NImage::fromFile($this->tmpName);
	}

	function getImageSize()
	{
		return $this->isOk() ? @getimagesize($this->tmpName) : NULL;
	}

	function getContents()
	{

		return $this->isOk() ? file_get_contents($this->tmpName) : NULL;
	}

}

class NHttpRequest extends NObject implements IHttpRequest
{

	private $method;

	private $url;

	private $query;

	private $post;

	private $files;

	private $cookies;

	private $headers;

	private $remoteAddress;

	private $remoteHost;

	function __construct(NUrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL,
		$headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL)
	{
		$this->url = $url;
		$this->url->freeze();
		if ($query === NULL) {
			parse_str($url->query, $this->query);
		} else {
			$this->query = (array) $query;
		}
		$this->post = (array) $post;
		$this->files = (array) $files;
		$this->cookies = (array) $cookies;
		$this->headers = (array) $headers;
		$this->method = $method;
		$this->remoteAddress = $remoteAddress;
		$this->remoteHost = $remoteHost;
	}

	final function getUrl()
	{
		return $this->url;
	}

	function getUri()
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::getUrl() instead.', E_USER_WARNING);
		return $this->getUrl();
	}

	final function getQuery($key = NULL, $default = NULL)
	{
		if (func_num_args() === 0) {
			return $this->query;

		} elseif (isset($this->query[$key])) {
			return $this->query[$key];

		} else {
			return $default;
		}
	}

	final function getPost($key = NULL, $default = NULL)
	{
		if (func_num_args() === 0) {
			return $this->post;

		} elseif (isset($this->post[$key])) {
			return $this->post[$key];

		} else {
			return $default;
		}
	}

	final function getFile($key)
	{
		$args = func_get_args();
		return NArrays::get($this->files, $args, NULL);
	}

	final function getFiles()
	{
		return $this->files;
	}

	final function getCookie($key, $default = NULL)
	{
		if (func_num_args() === 0) {
			return $this->cookies;

		} elseif (isset($this->cookies[$key])) {
			return $this->cookies[$key];

		} else {
			return $default;
		}
	}

	final function getCookies()
	{
		return $this->cookies;
	}

	function getMethod()
	{
		return $this->method;
	}

	function isMethod($method)
	{
		return strcasecmp($this->method, $method) === 0;
	}

	function isPost()
	{
		return $this->isMethod('POST');
	}

	final function getHeader($header, $default = NULL)
	{
		$header = strtolower($header);
		if (isset($this->headers[$header])) {
			return $this->headers[$header];
		} else {
			return $default;
		}
	}

	function getHeaders()
	{
		return $this->headers;
	}

	final function getReferer()
	{
		return isset($this->headers['referer']) ? new NUrl($this->headers['referer']) : NULL;
	}

	function isSecured()
	{
		return $this->url->scheme === 'https';
	}

	function isAjax()
	{
		return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
	}

	function getRemoteAddress()
	{
		return $this->remoteAddress;
	}

	function getRemoteHost()
	{
		if (!$this->remoteHost) {
			$this->remoteHost = $this->remoteAddress ? getHostByAddr($this->remoteAddress) : NULL;
		}
		return $this->remoteHost;
	}

	function detectLanguage(array $langs)
	{
		$header = $this->getHeader('Accept-Language');
		if (!$header) {
			return NULL;
		}

		$s = strtolower($header);
		$s = strtr($s, '_', '-');
		rsort($langs);
		preg_match_all('#(' . implode('|', $langs) . ')(?:-[^\s,;=]+)?\s*(?:;\s*q=([0-9.]+))?#', $s, $matches);

		if (!$matches[0]) {
			return NULL;
		}

		$max = 0;
		$lang = NULL;
		foreach ($matches[1] as $key => $value) {
			$q = $matches[2][$key] === '' ? 1.0 : (float) $matches[2][$key];
			if ($q > $max) {
				$max = $q; $lang = $value;
			}
		}

		return $lang;
	}

}

class NHttpRequestFactory extends NObject
{

	const NONCHARS = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';

	public $urlFilters = array(
		'path' => array('#/{2,}#' => '/'),
		'url' => array(),
	);

	private $encoding;

	function setEncoding($encoding)
	{
		$this->encoding = $encoding;
		return $this;
	}

	function createHttpRequest()
	{

		$url = new NUrlScript;
		$url->scheme = isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http';
		$url->user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
		$url->password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';

		if (isset($_SERVER['HTTP_HOST'])) {
			$pair = explode(':', $_SERVER['HTTP_HOST']);

		} elseif (isset($_SERVER['SERVER_NAME'])) {
			$pair = explode(':', $_SERVER['SERVER_NAME']);

		} else {
			$pair = array('');
		}

		$url->host = preg_match('#^[-._a-z0-9]+$#', $pair[0]) ? $pair[0] : '';

		if (isset($pair[1])) {
			$url->port = (int) $pair[1];

		} elseif (isset($_SERVER['SERVER_PORT'])) {
			$url->port = (int) $_SERVER['SERVER_PORT'];
		}

		if (isset($_SERVER['REQUEST_URI'])) {
			$requestUrl = $_SERVER['REQUEST_URI'];

		} elseif (isset($_SERVER['ORIG_PATH_INFO'])) {
			$requestUrl = $_SERVER['ORIG_PATH_INFO'];
			if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '') {
				$requestUrl .= '?' . $_SERVER['QUERY_STRING'];
			}
		} else {
			$requestUrl = '';
		}

		$requestUrl = NStrings::replace($requestUrl, $this->urlFilters['url']);
		$tmp = explode('?', $requestUrl, 2);
		$url->path = NStrings::replace($tmp[0], $this->urlFilters['path']);
		$url->query = isset($tmp[1]) ? $tmp[1] : '';

		$url->canonicalize();
		$url->path = NStrings::fixEncoding($url->path);

		if (isset($_SERVER['DOCUMENT_ROOT'], $_SERVER['SCRIPT_FILENAME'])
			&& strncmp($_SERVER['DOCUMENT_ROOT'], $_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT'])) === 0
		) {
			$script = '/' . ltrim(strtr(substr($_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT'])), '\\', '/'), '/');
		} elseif (isset($_SERVER['SCRIPT_NAME'])) {
			$script = $_SERVER['SCRIPT_NAME'];
		} else {
			$script = '/';
		}

		if (strncasecmp($url->path . '/', $script . '/', strlen($script) + 1) === 0) {
			$url->scriptPath = substr($url->path, 0, strlen($script));

		} elseif (strncasecmp($url->path, $script, strrpos($script, '/') + 1) === 0) {
			$url->scriptPath = substr($url->path, 0, strrpos($script, '/') + 1);

		} else {
			$url->scriptPath = '/';
		}

		$useFilter = (!in_array(ini_get('filter.default'), array('', 'unsafe_raw')) || ini_get('filter.default_flags'));

		parse_str($url->query, $query);
		if (!$query) {
			$query = $useFilter ? filter_input_array(INPUT_GET, FILTER_UNSAFE_RAW) : (empty($_GET) ? array() : $_GET);
		}
		$post = $useFilter ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) : (empty($_POST) ? array() : $_POST);
		$cookies = $useFilter ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? array() : $_COOKIE);

		$gpc = (bool) @get_magic_quotes_gpc();
		$old = error_reporting(error_reporting() ^ E_NOTICE);

		if ($gpc || $this->encoding) {
			$utf = strcasecmp($this->encoding, 'UTF-8') === 0;
			$list = array(& $query, & $post, & $cookies);
			while (list($key, $val) = each($list)) {
				foreach ($val as $k => $v) {
					unset($list[$key][$k]);

					if ($gpc) {
						$k = stripslashes($k);
					}

					if ($this->encoding && is_string($k) && (preg_match(self::NONCHARS, $k) || preg_last_error())) {

					} elseif (is_array($v)) {
						$list[$key][$k] = $v;
						$list[] = & $list[$key][$k];

					} else {
						if ($gpc && !$useFilter) {
							$v = stripSlashes($v);
						}
						if ($this->encoding) {
							if ($utf) {
								$v = NStrings::fixEncoding($v);

							} else {
								if (!NStrings::checkEncoding($v)) {
									$v = iconv($this->encoding, 'UTF-8//IGNORE', $v);
								}
								$v = html_entity_decode($v, ENT_QUOTES, 'UTF-8');
							}
							$v = preg_replace(self::NONCHARS, '', $v);
						}
						$list[$key][$k] = $v;
					}
				}
			}
			unset($list, $key, $val, $k, $v);
		}

		$files = array();
		$list = array();
		if (!empty($_FILES)) {
			foreach ($_FILES as $k => $v) {
				if ($this->encoding && is_string($k) && (preg_match(self::NONCHARS, $k) || preg_last_error())) {
					continue;
				}
				$v['@'] = & $files[$k];
				$list[] = $v;
			}
		}

		while (list(, $v) = each($list)) {
			if (!isset($v['name'])) {
				continue;

			} elseif (!is_array($v['name'])) {
				if ($gpc) {
					$v['name'] = stripSlashes($v['name']);
				}
				if ($this->encoding) {
					$v['name'] = preg_replace(self::NONCHARS, '', NStrings::fixEncoding($v['name']));
				}
				$v['@'] = new NHttpUploadedFile($v);
				continue;
			}

			foreach ($v['name'] as $k => $foo) {
				if ($this->encoding && is_string($k) && (preg_match(self::NONCHARS, $k) || preg_last_error())) {
					continue;
				}
				$list[] = array(
					'name' => $v['name'][$k],
					'type' => $v['type'][$k],
					'size' => $v['size'][$k],
					'tmp_name' => $v['tmp_name'][$k],
					'error' => $v['error'][$k],
					'@' => & $v['@'][$k],
				);
			}
		}

		error_reporting($old);

		if (function_exists('apache_request_headers')) {
			$headers = array_change_key_case(apache_request_headers(), CASE_LOWER);
		} else {
			$headers = array();
			foreach ($_SERVER as $k => $v) {
				if (strncmp($k, 'HTTP_', 5) == 0) {
					$k = substr($k, 5);
				} elseif (strncmp($k, 'CONTENT_', 8)) {
					continue;
				}
				$headers[ strtr(strtolower($k), '_', '-') ] = $v;
			}
		}

		return new NHttpRequest($url, $query, $post, $files, $cookies, $headers,
			isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : NULL,
			isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL,
			isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL
		);
	}

}

final class NHttpResponse extends NObject implements IHttpResponse
{

	private static $fixIE = TRUE;

	public $cookieDomain = '';

	public $cookiePath = '/';

	public $cookieSecure = FALSE;

	public $cookieHttpOnly = TRUE;

	private $code = self::S200_OK;

	function setCode($code)
	{
		$code = (int) $code;

		static $allowed = array(
			200=>1, 201=>1, 202=>1, 203=>1, 204=>1, 205=>1, 206=>1,
			300=>1, 301=>1, 302=>1, 303=>1, 304=>1, 307=>1,
			400=>1, 401=>1, 403=>1, 404=>1, 405=>1, 406=>1, 408=>1, 410=>1, 412=>1, 415=>1, 416=>1,
			500=>1, 501=>1, 503=>1, 505=>1
		);

		if (!isset($allowed[$code])) {
			throw new InvalidArgumentException("Bad HTTP response '$code'.");

		} elseif (headers_sent($file, $line)) {
			throw new InvalidStateException("Cannot set HTTP code after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));

		} else {
			$this->code = $code;
			$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
			header($protocol . ' ' . $code, TRUE, $code);
		}
		return $this;
	}

	function getCode()
	{
		return $this->code;
	}

	function setHeader($name, $value)
	{
		if (headers_sent($file, $line)) {
			throw new InvalidStateException("Cannot send header after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
		}

		if ($value === NULL && function_exists('header_remove')) {
			header_remove($name);
		} else {
			header($name . ': ' . $value, TRUE, $this->code);
		}
		return $this;
	}

	function addHeader($name, $value)
	{
		if (headers_sent($file, $line)) {
			throw new InvalidStateException("Cannot send header after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
		}

		header($name . ': ' . $value, FALSE, $this->code);
	}

	function setContentType($type, $charset = NULL)
	{
		$this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
		return $this;
	}

	function redirect($url, $code = self::S302_FOUND)
	{
		if (isset($_SERVER['SERVER_SOFTWARE']) && preg_match('#^Microsoft-IIS/[1-5]#', $_SERVER['SERVER_SOFTWARE'])
			&& $this->getHeader('Set-Cookie') !== NULL
		) {
			$this->setHeader('Refresh', "0;url=$url");
			return;
		}

		$this->setCode($code);
		$this->setHeader('Location', $url);
		echo "<h1>Redirect</h1>\n\n<p><a href=\"" . htmlSpecialChars($url) . "\">Please click here to continue</a>.</p>";
	}

	function setExpiration($time)
	{
		if (!$time) {
			$this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate');
			$this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT');
			return $this;
		}

		$time = NDateTime53::from($time);
		$this->setHeader('Cache-Control', 'max-age=' . ($time->format('U') - time()));
		$this->setHeader('Expires', self::date($time));
		return $this;
	}

	function isSent()
	{
		return headers_sent();
	}

	function getHeader($header, $default = NULL)
	{
		$header .= ':';
		$len = strlen($header);
		foreach (headers_list() as $item) {
			if (strncasecmp($item, $header, $len) === 0) {
				return ltrim(substr($item, $len));
			}
		}
		return $default;
	}

	function getHeaders()
	{
		$headers = array();
		foreach (headers_list() as $header) {
			$a = strpos($header, ':');
			$headers[substr($header, 0, $a)] = (string) substr($header, $a + 2);
		}
		return $headers;
	}

	static function date($time = NULL)
	{
		$time = NDateTime53::from($time);
		$time->setTimezone(new DateTimeZone('GMT'));
		return $time->format('D, d M Y H:i:s \G\M\T');
	}

	function __destruct()
	{
		if (self::$fixIE && isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE
			&& in_array($this->code, array(400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505), TRUE)
			&& $this->getHeader('Content-Type', 'text/html') === 'text/html'
		) {
			echo NStrings::random(2e3, " \t\r\n");
			self::$fixIE = FALSE;
		}
	}

	function setCookie($name, $value, $time, $path = NULL, $domain = NULL, $secure = NULL, $httpOnly = NULL)
	{
		if (headers_sent($file, $line)) {
			throw new InvalidStateException("Cannot set cookie after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
		}

		setcookie(
			$name,
			$value,
			$time ? NDateTime53::from($time)->format('U') : 0,
			$path === NULL ? $this->cookiePath : (string) $path,
			$domain === NULL ? $this->cookieDomain : (string) $domain,
			$secure === NULL ? $this->cookieSecure : (bool) $secure,
			$httpOnly === NULL ? $this->cookieHttpOnly : (bool) $httpOnly
		);
		return $this;
	}

	function deleteCookie($name, $path = NULL, $domain = NULL, $secure = NULL)
	{
		if (headers_sent($file, $line)) {
			throw new InvalidStateException("Cannot delete cookie after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
		}

		setcookie(
			$name,
			FALSE,
			254400000,
			$path === NULL ? $this->cookiePath : (string) $path,
			$domain === NULL ? $this->cookieDomain : (string) $domain,
			$secure === NULL ? $this->cookieSecure : (bool) $secure,
			TRUE
		);
	}

}

class NSession extends NObject
{

	const DEFAULT_FILE_LIFETIME = 10800;

	private $regenerationNeeded;

	private static $started;

	private $options = array(

		'referer_check' => '',
		'use_cookies' => 1,
		'use_only_cookies' => 1,
		'use_trans_sid' => 0,

		'cookie_lifetime' => 0,
		'cookie_path' => '/',
		'cookie_domain' => '',
		'cookie_secure' => FALSE,
		'cookie_httponly' => TRUE,

		'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
		'cache_limiter' => NULL,
		'cache_expire' => NULL,
		'hash_function' => NULL,
		'hash_bits_per_character' => NULL,
	);

	private $request;

	private $response;

	function __construct(IHttpRequest $request, IHttpResponse $response)
	{
		$this->request = $request;
		$this->response = $response;
	}

	function start()
	{
		if (self::$started) {
			return;
		}

		$this->configure($this->options);

		if (!defined('SID')) {
			NDebugger::tryError();
			session_start();
			if (NDebugger::catchError($e)) {
				@session_write_close();
				throw new InvalidStateException('session_start(): ' . $e->getMessage(), 0, $e);
			}
		}

		self::$started = TRUE;
		if ($this->regenerationNeeded) {
			session_regenerate_id(TRUE);
			$this->regenerationNeeded = FALSE;
		}

		unset($_SESSION['__NT'], $_SESSION['__NS'], $_SESSION['__NM']);

		$nf = & $_SESSION['__NF'];
		if (empty($nf)) {
			$nf = array('C' => 0);
		} else {
			$nf['C']++;
		}

		$browserKey = $this->request->getCookie('nette-browser');
		if (!$browserKey) {
			$browserKey = NStrings::random();
		}
		$browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
		$nf['B'] = $browserKey;

		$this->sendCookie();

		if (isset($nf['META'])) {
			$now = time();

			foreach ($nf['META'] as $section => $metadata) {
				if (is_array($metadata)) {
					foreach ($metadata as $variable => $value) {
						if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T'])
							|| ($variable !== '' && is_object($nf['DATA'][$section][$variable]) && (isset($value['V']) ? $value['V'] : NULL)
								!== NClassReflection::from($nf['DATA'][$section][$variable])->getAnnotation('serializationVersion'))
						) {
							if ($variable === '') {
								unset($nf['META'][$section], $nf['DATA'][$section]);
								continue 2;
							}
							unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
						}
					}
				}
			}
		}

		register_shutdown_function(array($this, 'clean'));
	}

	function isStarted()
	{
		return (bool) self::$started;
	}

	function close()
	{
		if (self::$started) {
			$this->clean();
			session_write_close();
			self::$started = FALSE;
		}
	}

	function destroy()
	{
		if (!self::$started) {
			throw new InvalidStateException('Session is not started.');
		}

		session_destroy();
		$_SESSION = NULL;
		self::$started = FALSE;
		if (!$this->response->isSent()) {
			$params = session_get_cookie_params();
			$this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
		}
	}

	function exists()
	{
		return self::$started || $this->request->getCookie(session_name()) !== NULL;
	}

	function regenerateId()
	{
		if (self::$started) {
			if (headers_sent($file, $line)) {
				throw new InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
			}
			session_regenerate_id(TRUE);

		} else {
			$this->regenerationNeeded = TRUE;
		}
	}

	function getId()
	{
		return session_id();
	}

	function setName($name)
	{
		if (!is_string($name) || !preg_match('#[^0-9.][^.]*$#A', $name)) {
			throw new InvalidArgumentException('Session name must be a string and cannot contain dot.');
		}

		session_name($name);
		return $this->setOptions(array(
			'name' => $name,
		));
	}

	function getName()
	{
		return session_name();
	}

	function getSection($section, $class = 'NSessionSection')
	{
		return new $class($this, $section);
	}

	function getNamespace($section)
	{
		trigger_error(__METHOD__ . '() is deprecated; use getSection() instead.', E_USER_WARNING);
		return $this->getSection($section);
	}

	function hasSection($section)
	{
		if ($this->exists() && !self::$started) {
			$this->start();
		}

		return !empty($_SESSION['__NF']['DATA'][$section]);
	}

	function getIterator()
	{
		if ($this->exists() && !self::$started) {
			$this->start();
		}

		if (isset($_SESSION['__NF']['DATA'])) {
			return new ArrayIterator(array_keys($_SESSION['__NF']['DATA']));

		} else {
			return new ArrayIterator;
		}
	}

	function clean()
	{
		if (!self::$started || empty($_SESSION)) {
			return;
		}

		$nf = & $_SESSION['__NF'];
		if (isset($nf['META']) && is_array($nf['META'])) {
			foreach ($nf['META'] as $name => $foo) {
				if (empty($nf['META'][$name])) {
					unset($nf['META'][$name]);
				}
			}
		}

		if (empty($nf['META'])) {
			unset($nf['META']);
		}

		if (empty($nf['DATA'])) {
			unset($nf['DATA']);
		}

		if (empty($_SESSION)) {

		}
	}

	function setOptions(array $options)
	{
		if (self::$started) {
			$this->configure($options);
		}
		$this->options = $options + $this->options;
		if (!empty($options['auto_start'])) {
			$this->start();
		}
		return $this;
	}

	function getOptions()
	{
		return $this->options;
	}

	private function configure(array $config)
	{
		$special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);

		foreach ($config as $key => $value) {
			if (!strncmp($key, 'session.', 8)) {
				$key = substr($key, 8);
			}

			if ($value === NULL || ini_get("session.$key") == $value) {
				continue;

			} elseif (strncmp($key, 'cookie_', 7) === 0) {
				if (!isset($cookie)) {
					$cookie = session_get_cookie_params();
				}
				$cookie[substr($key, 7)] = $value;

			} else {
				if (defined('SID')) {
					throw new InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started by session.auto_start or session_start().");
				}
				if (isset($special[$key])) {
					$key = "session_$key";
					$key($value);

				} elseif (function_exists('ini_set')) {
					ini_set("session.$key", $value);

				} elseif (!NFramework::$iAmUsingBadHost) {
					throw new NotSupportedException('Required function ini_set() is disabled.');
				}
			}
		}

		if (isset($cookie)) {
			session_set_cookie_params(
				$cookie['lifetime'], $cookie['path'], $cookie['domain'],
				$cookie['secure'], $cookie['httponly']
			);
			if (self::$started) {
				$this->sendCookie();
			}
		}
	}

	function setExpiration($time)
	{
		if (empty($time)) {
			return $this->setOptions(array(
				'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
				'cookie_lifetime' => 0,
			));

		} else {
			$time = NDateTime53::from($time)->format('U') - time();
			return $this->setOptions(array(
				'gc_maxlifetime' => $time,
				'cookie_lifetime' => $time,
			));
		}
	}

	function setCookieParams($path, $domain = NULL, $secure = NULL)
	{
		return $this->setOptions(array(
			'cookie_path' => $path,
			'cookie_domain' => $domain,
			'cookie_secure' => $secure
		));
	}

	function getCookieParams()
	{
		return session_get_cookie_params();
	}

	function setSavePath($path)
	{
		return $this->setOptions(array(
			'save_path' => $path,
		));
	}

	function setStorage(ISessionStorage $storage)
	{
		if (self::$started) {
			throw new InvalidStateException("Unable to set storage when session has been started.");
		}
		session_set_save_handler(
			array($storage, 'open'), array($storage, 'close'), array($storage, 'read'),
			array($storage, 'write'), array($storage, 'remove'), array($storage, 'clean')
		);
	}

	private function sendCookie()
	{
		$cookie = $this->getCookieParams();
		$this->response->setCookie(
			session_name(), session_id(),
			$cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
			$cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']

		)->setCookie(
			'nette-browser', $_SESSION['__NF']['B'],
			NHttpResponse::BROWSER, $cookie['path'], $cookie['domain']
		);
	}

}

final class NSessionSection extends NObject implements IteratorAggregate, ArrayAccess
{

	private $session;

	private $name;

	private $data;

	private $meta = FALSE;

	public $warnOnUndefined = FALSE;

	function __construct(NSession $session, $name)
	{
		if (!is_string($name)) {
			throw new InvalidArgumentException("Session namespace must be a string, " . gettype($name) ." given.");
		}

		$this->session = $session;
		$this->name = $name;
	}

	private function start()
	{
		if ($this->meta === FALSE) {
			$this->session->start();
			$this->data = & $_SESSION['__NF']['DATA'][$this->name];
			$this->meta = & $_SESSION['__NF']['META'][$this->name];
		}
	}

	function getIterator()
	{
		$this->start();
		if (isset($this->data)) {
			return new ArrayIterator($this->data);
		} else {
			return new ArrayIterator;
		}
	}

	function __set($name, $value)
	{
		$this->start();
		$this->data[$name] = $value;
		if (is_object($value)) {
			$this->meta[$name]['V'] = NClassReflection::from($value)->getAnnotation('serializationVersion');
		}
	}

	function &__get($name)
	{
		$this->start();
		if ($this->warnOnUndefined && !array_key_exists($name, $this->data)) {
			trigger_error("The variable '$name' does not exist in session section", E_USER_NOTICE);
		}

		return $this->data[$name];
	}

	function __isset($name)
	{
		if ($this->session->exists()) {
			$this->start();
		}
		return isset($this->data[$name]);
	}

	function __unset($name)
	{
		$this->start();
		unset($this->data[$name], $this->meta[$name]);
	}

	function offsetSet($name, $value)
	{
		$this->__set($name, $value);
	}

	function offsetGet($name)
	{
		return $this->__get($name);
	}

	function offsetExists($name)
	{
		return $this->__isset($name);
	}

	function offsetUnset($name)
	{
		$this->__unset($name);
	}

	function setExpiration($time, $variables = NULL)
	{
		$this->start();
		if (empty($time)) {
			$time = NULL;
			$whenBrowserIsClosed = TRUE;
		} else {
			$time = NDateTime53::from($time)->format('U');
			$max = ini_get('session.gc_maxlifetime');
			if ($time - time() > $max + 3) {
				trigger_error("The expiration time is greater than the session expiration $max seconds", E_USER_NOTICE);
			}
			$whenBrowserIsClosed = FALSE;
		}

		if ($variables === NULL) {
			$this->meta['']['T'] = $time;
			$this->meta['']['B'] = $whenBrowserIsClosed;

		} elseif (is_array($variables)) {
			foreach ($variables as $variable) {
				$this->meta[$variable]['T'] = $time;
				$this->meta[$variable]['B'] = $whenBrowserIsClosed;
			}

		} else {
			$this->meta[$variables]['T'] = $time;
			$this->meta[$variables]['B'] = $whenBrowserIsClosed;
		}
		return $this;
	}

	function removeExpiration($variables = NULL)
	{
		$this->start();
		if ($variables === NULL) {

			unset($this->meta['']['T'], $this->meta['']['B']);

		} elseif (is_array($variables)) {

			foreach ($variables as $variable) {
				unset($this->meta[$variable]['T'], $this->meta[$variable]['B']);
			}
		} else {
			unset($this->meta[$variables]['T'], $this->meta[$variable]['B']);
		}
	}

	function remove()
	{
		$this->start();
		$this->data = NULL;
		$this->meta = NULL;
	}

}

class NUrl extends NFreezableObject
{

	public static $defaultPorts = array(
		'http' => 80,
		'https' => 443,
		'ftp' => 21,
		'news' => 119,
		'nntp' => 119,
	);

	private $scheme = '';

	private $user = '';

	private $pass = '';

	private $host = '';

	private $port = NULL;

	private $path = '';

	private $query = '';

	private $fragment = '';

	function __construct($url = NULL)
	{
		if (is_string($url)) {
			$parts = @parse_url($url);
			if ($parts === FALSE) {
				throw new InvalidArgumentException("Malformed or unsupported URI '$url'.");
			}

			foreach ($parts as $key => $val) {
				$this->$key = $val;
			}

			if (!$this->port && isset(self::$defaultPorts[$this->scheme])) {
				$this->port = self::$defaultPorts[$this->scheme];
			}

			if ($this->path === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
				$this->path = '/';
			}

		} elseif ($url instanceof self) {
			foreach ($this as $key => $val) {
				$this->$key = $url->$key;
			}
		}
	}

	function setScheme($value)
	{
		$this->updating();
		$this->scheme = (string) $value;
		return $this;
	}

	function getScheme()
	{
		return $this->scheme;
	}

	function setUser($value)
	{
		$this->updating();
		$this->user = (string) $value;
		return $this;
	}

	function getUser()
	{
		return $this->user;
	}

	function setPassword($value)
	{
		$this->updating();
		$this->pass = (string) $value;
		return $this;
	}

	function getPassword()
	{
		return $this->pass;
	}

	function setHost($value)
	{
		$this->updating();
		$this->host = (string) $value;
		return $this;
	}

	function getHost()
	{
		return $this->host;
	}

	function setPort($value)
	{
		$this->updating();
		$this->port = (int) $value;
		return $this;
	}

	function getPort()
	{
		return $this->port;
	}

	function setPath($value)
	{
		$this->updating();
		$this->path = (string) $value;
		return $this;
	}

	function getPath()
	{
		return $this->path;
	}

	function setQuery($value)
	{
		$this->updating();
		$this->query = (string) (is_array($value) ? http_build_query($value, '', '&') : $value);
		return $this;
	}

	function appendQuery($value)
	{
		$this->updating();
		$value = (string) (is_array($value) ? http_build_query($value, '', '&') : $value);
		$this->query .= ($this->query === '' || $value === '') ? $value : '&' . $value;
	}

	function getQuery()
	{
		return $this->query;
	}

	function setFragment($value)
	{
		$this->updating();
		$this->fragment = (string) $value;
		return $this;
	}

	function getFragment()
	{
		return $this->fragment;
	}

	function getAbsoluteUrl()
	{
		return $this->scheme . '://' . $this->getAuthority() . $this->path
			. ($this->query === '' ? '' : '?' . $this->query)
			. ($this->fragment === '' ? '' : '#' . $this->fragment);
	}

	function getAuthority()
	{
		$authority = $this->host;
		if ($this->port && isset(self::$defaultPorts[$this->scheme]) && $this->port !== self::$defaultPorts[$this->scheme]) {
			$authority .= ':' . $this->port;
		}

		if ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https') {
			$authority = $this->user . ($this->pass === '' ? '' : ':' . $this->pass) . '@' . $authority;
		}

		return $authority;
	}

	function getHostUrl()
	{
		return $this->scheme . '://' . $this->getAuthority();
	}

	function getBasePath()
	{
		$pos = strrpos($this->path, '/');
		return $pos === FALSE ? '' : substr($this->path, 0, $pos + 1);
	}

	function getBaseUrl()
	{
		return $this->scheme . '://' . $this->getAuthority() . $this->getBasePath();
	}

	function getRelativeUrl()
	{
		return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
	}

	function isEqual($url)
	{

		$part = self::unescape(strtok($url, '?#'), '%/');
		if (strncmp($part, '//', 2) === 0) {
			if ($part !== '//' . $this->getAuthority() . $this->path) {
				return FALSE;
			}

		} elseif (strncmp($part, '/', 1) === 0) {
			if ($part !== $this->path) {
				return FALSE;
			}

		} else {
			if ($part !== $this->scheme . '://' . $this->getAuthority() . $this->path) {
				return FALSE;
			}
		}

		$part = preg_split('#[&;]#', self::unescape(strtr((string) strtok('?#'), '+', ' '), '%&;=+'));
		sort($part);
		$query = preg_split('#[&;]#', $this->query);
		sort($query);
		return $part === $query;
	}

	function canonicalize()
	{
		$this->updating();
		$this->path = $this->path === '' ? '/' : self::unescape($this->path, '%/');
		$this->host = strtolower(rawurldecode($this->host));
		$this->query = self::unescape(strtr($this->query, '+', ' '), '%&;=+');
	}

	function __toString()
	{
		return $this->getAbsoluteUrl();
	}

	static function unescape($s, $reserved = '%;/?:@&=+$,')
	{

		preg_match_all('#(?<=%)[a-f0-9][a-f0-9]#i', $s, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
		foreach (array_reverse($matches) as $match) {
			$ch = chr(hexdec($match[0][0]));
			if (strpos($reserved, $ch) === FALSE) {
				$s = substr_replace($s, $ch, $match[0][1] - 1, 3);
			}
		}
		return $s;
	}

	function getRelativeUri()
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::getRelativeUrl() instead.', E_USER_WARNING);
		return $this->getRelativeUrl();
	}

	function getAbsoluteUri()
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::getAbsoluteUrl() instead.', E_USER_WARNING);
		return $this->getAbsoluteUrl();
	}

	function getHostUri()
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::getHostUrl() instead.', E_USER_WARNING);
		return $this->getHostUrl();
	}

	function getBaseUri()
	{
		trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::getBaseUrl() instead.', E_USER_WARNING);
		return $this->getBaseUrl();
	}

}

class NUrlScript extends NUrl
{

	private $scriptPath = '/';

	function setScriptPath($value)
	{
		$this->updating();
		$this->scriptPath = (string) $value;
		return $this;
	}

	function getScriptPath()
	{
		return $this->scriptPath;
	}

	function getBasePath()
	{
		$pos = strrpos($this->scriptPath, '/');
		return $pos === FALSE ? '' : substr($this->path, 0, $pos + 1);
	}

	function getPathInfo()
	{
		return (string) substr($this->path, strlen($this->scriptPath));
	}

}

class NUser extends NObject implements IUser
{

	const MANUAL = 1,
		INACTIVITY = 2,
		BROWSER_CLOSED = 3;

	public $guestRole = 'guest';

	public $authenticatedRole = 'authenticated';

	public $onLoggedIn;

	public $onLoggedOut;

	private $namespace = '';

	private $session;

	private $context;

	function __construct(IDiContainer $context)
	{
		$this->context = $context;
	}

	function login($username = NULL, $password = NULL)
	{
		$this->logout(TRUE);
		$credentials = func_get_args();
		$this->setIdentity($this->context->authenticator->authenticate($credentials));
		$this->setAuthenticated(TRUE);
		$this->onLoggedIn($this);
	}

	final function logout($clearIdentity = FALSE)
	{
		if ($this->isLoggedIn()) {
			$this->setAuthenticated(FALSE);
			$this->onLoggedOut($this);
		}

		if ($clearIdentity) {
			$this->setIdentity(NULL);
		}
	}

	final function isLoggedIn()
	{
		$session = $this->getSessionSection(FALSE);
		return $session && $session->authenticated;
	}

	final function getIdentity()
	{
		$session = $this->getSessionSection(FALSE);
		return $session ? $session->identity : NULL;
	}

	function getId()
	{
		$identity = $this->getIdentity();
		return $identity ? $identity->getId() : NULL;
	}

	function setAuthenticator(IAuthenticator $handler)
	{
		$this->context->removeService('authenticator');
		$this->context->authenticator = $handler;
		return $this;
	}

	final function getAuthenticator()
	{
		return $this->context->authenticator;
	}

	function setNamespace($namespace)
	{
		if ($this->namespace !== $namespace) {
			$this->namespace = (string) $namespace;
			$this->session = NULL;
		}
		return $this;
	}

	final function getNamespace()
	{
		return $this->namespace;
	}

	function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE)
	{
		$session = $this->getSessionSection(TRUE);
		if ($time) {
			$time = NDateTime53::from($time)->format('U');
			$session->expireTime = $time;
			$session->expireDelta = $time - time();

		} else {
			unset($session->expireTime, $session->expireDelta);
		}

		$session->expireIdentity = (bool) $clearIdentity;
		$session->expireBrowser = (bool) $whenBrowserIsClosed;
		$session->browserCheck = TRUE;
		$session->setExpiration(0, 'browserCheck');
		return $this;
	}

	final function getLogoutReason()
	{
		$session = $this->getSessionSection(FALSE);
		return $session ? $session->reason : NULL;
	}

	protected function getSessionSection($need)
	{
		if ($this->session !== NULL) {
			return $this->session;
		}

		if (!$need && !$this->context->session->exists()) {
			return NULL;
		}

		$this->session = $session = $this->context->session->getSection('Nette.Web.User/' . $this->namespace);

		if (!$session->identity instanceof IIdentity || !is_bool($session->authenticated)) {
			$session->remove();
		}

		if ($session->authenticated && $session->expireBrowser && !$session->browserCheck) {
			$session->reason = self::BROWSER_CLOSED;
			$session->authenticated = FALSE;
			$this->onLoggedOut($this);
			if ($session->expireIdentity) {
				unset($session->identity);
			}
		}

		if ($session->authenticated && $session->expireDelta > 0) {
			if ($session->expireTime < time()) {
				$session->reason = self::INACTIVITY;
				$session->authenticated = FALSE;
				$this->onLoggedOut($this);
				if ($session->expireIdentity) {
					unset($session->identity);
				}
			}
			$session->expireTime = time() + $session->expireDelta;
		}

		if (!$session->authenticated) {
			unset($session->expireTime, $session->expireDelta, $session->expireIdentity,
				$session->expireBrowser, $session->browserCheck, $session->authTime);
		}

		return $this->session;
	}

	protected function setAuthenticated($state)
	{
		$session = $this->getSessionSection(TRUE);
		$session->authenticated = (bool) $state;

		$this->context->session->regenerateId();

		if ($state) {
			$session->reason = NULL;
			$session->authTime = time();

		} else {
			$session->reason = self::MANUAL;
			$session->authTime = NULL;
		}
		return $this;
	}

	protected function setIdentity(IIdentity $identity = NULL)
	{
		$this->getSessionSection(TRUE)->identity = $identity;
		return $this;
	}

	function getRoles()
	{
		if (!$this->isLoggedIn()) {
			return array($this->guestRole);
		}

		$identity = $this->getIdentity();
		return $identity ? $identity->getRoles() : array($this->authenticatedRole);
	}

	final function isInRole($role)
	{
		return in_array($role, $this->getRoles(), TRUE);
	}

	function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL)
	{
		$authorizator = $this->context->authorizator;
		foreach ($this->getRoles() as $role) {
			if ($authorizator->isAllowed($role, $resource, $privilege)) {
				return TRUE;
			}
		}

		return FALSE;
	}

	function setAuthorizator(IAuthorizator $handler)
	{
		$this->context->removeService('authorizator');
		$this->context->authorizator = $handler;
		return $this;
	}

	final function getAuthorizator()
	{
		return $this->context->authorizator;
	}

	function setAuthenticationHandler($v)
	{
		trigger_error(__METHOD__ . '() is deprecated; use setAuthenticator() instead.', E_USER_WARNING);
		return $this->setAuthenticator($v);
	}

	function setAuthorizationHandler($v)
	{
		trigger_error(__METHOD__ . '() is deprecated; use setAuthorizator() instead.', E_USER_WARNING);
		return $this->setAuthorizator($v);
	}

}

class NSmartCachingIterator extends CachingIterator implements Countable
{

	private $counter = 0;

	function __construct($iterator)
	{
		if (is_array($iterator) || $iterator instanceof stdClass) {
			$iterator = new ArrayIterator($iterator);

		} elseif ($iterator instanceof Traversable) {
			if ($iterator instanceof IteratorAggregate) {
				$iterator = $iterator->getIterator();

			} elseif (!$iterator instanceof Iterator) {
				$iterator = new IteratorIterator($iterator);
			}

		} else {
			throw new InvalidArgumentException("Invalid argument passed to foreach resp. " . __CLASS__ . "; array or Traversable expected, " . (is_object($iterator) ? get_class($iterator) : gettype($iterator)) ." given.");
		}

		parent::__construct($iterator, 0);
	}

	function isFirst($width = NULL)
	{
		return $this->counter === 1 || ($width && $this->counter !== 0 && (($this->counter - 1) % $width) === 0);
	}

	function isLast($width = NULL)
	{
		return !$this->hasNext() || ($width && ($this->counter % $width) === 0);
	}

	function isEmpty()
	{
		return $this->counter === 0;
	}

	function isOdd()
	{
		return $this->counter % 2 === 1;
	}

	function isEven()
	{
		return $this->counter % 2 === 0;
	}

	function getCounter()
	{
		return $this->counter;
	}

	function count()
	{
		$inner = $this->getInnerIterator();
		if ($inner instanceof Countable) {
			return $inner->count();

		} else {
			throw new NotSupportedException('Iterator is not countable.');
		}
	}

	function next()
	{
		parent::next();
		if (parent::valid()) {
			$this->counter++;
		}
	}

	function rewind()
	{
		parent::rewind();
		$this->counter = parent::valid() ? 1 : 0;
	}

	function getNextKey()
	{
		return $this->getInnerIterator()->key();
	}

	function getNextValue()
	{
		return $this->getInnerIterator()->current();
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NCallbackFilterIterator extends FilterIterator
{

	private $callback;

	function __construct(Iterator $iterator, $callback)
	{
		parent::__construct($iterator);
		$this->callback = $callback;
	}

	function accept()
	{
		return call_user_func($this->callback, $this);
	}

}

class NInstanceFilterIterator extends FilterIterator implements Countable
{

	private $type;

	function __construct(Iterator $iterator, $type)
	{
		$this->type = $type;
		parent::__construct($iterator);
	}

	function accept()
	{
		return $this->current() instanceof $this->type;
	}

	function count()
	{
		return iterator_count($this);
	}

}

class NMapIterator extends IteratorIterator
{

	private $callback;

	function __construct(Traversable $iterator, $callback)
	{
		parent::__construct($iterator);
		$this->callback = $callback;
	}

	function current()
	{
		return call_user_func($this->callback, parent::current(), parent::key());
	}

}

class NRecursiveCallbackFilterIterator extends FilterIterator implements RecursiveIterator
{

	private $callback;

	private $childrenCallback;

	function __construct(RecursiveIterator $iterator, $callback, $childrenCallback = NULL)
	{
		parent::__construct($iterator);
		$this->callback = $callback;
		$this->childrenCallback = $childrenCallback;
	}

	function accept()
	{
		return $this->callback === NULL || call_user_func($this->callback, $this);
	}

	function hasChildren()
	{
		return $this->getInnerIterator()->hasChildren()
			&& ($this->childrenCallback === NULL || call_user_func($this->childrenCallback, $this));
	}

	function getChildren()
	{
		return new self($this->getInnerIterator()->getChildren(), $this->callback, $this->childrenCallback);
	}

}

class NGenericRecursiveIterator extends IteratorIterator implements RecursiveIterator, Countable
{

	function hasChildren()
	{
		$obj = $this->current();
		return ($obj instanceof IteratorAggregate && $obj->getIterator() instanceof RecursiveIterator)
			|| $obj instanceof RecursiveIterator;
	}

	function getChildren()
	{
		$obj = $this->current();
		return $obj instanceof IteratorAggregate ? $obj->getIterator() : $obj;
	}

	function count()
	{
		return iterator_count($this);
	}

}

class NLatteFilter extends NObject
{

	public $parser;

	function __construct()
	{
		$this->parser = new NParser;
		NCoreMacros::install($this->parser);
		$this->parser->addMacro('cache', new NCacheMacro($this->parser));
		NUIMacros::install($this->parser);
		NFormMacros::install($this->parser);
	}

	function __invoke($s)
	{
		$this->parser->context = array(NParser::CONTEXT_TEXT);
		$this->parser->setDelimiters('\\{(?![\\s\'"{}])', '\\}');
		return $this->parser->parse($s);
	}

}

class NHtmlNode extends NObject
{

	public $name;

	public $isEmpty = FALSE;

	public $attrs = array();

	public $closing = FALSE;

	public $offset;

	function __construct($name)
	{
		$this->name = $name;
		$this->isEmpty = isset(NHtml::$emptyElements[strtolower($this->name)]);
	}

}

class NMacroNode extends NObject
{

	public $macro;

	public $name;

	public $isEmpty = FALSE;

	public $args;

	public $modifiers;

	public $closing = FALSE;

	public $tokenizer;

	public $offset;

	public $parentNode;

	public $content;

	public $data;

	function __construct(IMacro $macro, $name, $args = NULL, $modifiers = NULL, NMacroNode $parentNode = NULL)
	{
		$this->macro = $macro;
		$this->name = (string) $name;
		$this->modifiers = (string) $modifiers;
		$this->parentNode = $parentNode;
		$this->tokenizer = new NMacroTokenizer($this->args);
		$this->data = new stdClass;
		$this->setArgs($args);
	}

	function setArgs($args)
	{
		$this->args = (string) $args;
		$this->tokenizer->tokenize($this->args);
	}

	function close($content)
	{
		$this->closing = TRUE;
		$this->content = $content;
		return $this->macro->nodeClosed($this);
	}

}

class NCacheMacro extends NObject implements IMacro
{

	private $used;

	function initialize()
	{
		$this->used = FALSE;
	}

	function finalize()
	{
		if ($this->used) {
			return array('NCacheMacro::initRuntime($template, $_g);');
		}
	}

	function nodeOpened(NMacroNode $node)
	{
		$this->used = TRUE;
		$node->isEmpty = FALSE;
		return NPhpWriter::using($node)
			->write('<?php if (NCacheMacro::createCache($netteCacheStorage, %var, $_g->caches, %node.array?)) { ?>',
				NStrings::random()
			);
	}

	function nodeClosed(NMacroNode $node)
	{
		return '<?php $_l->tmp = array_pop($_g->caches); if (!$_l->tmp instanceof stdClass) $_l->tmp->end(); } ?>';
	}

	static function initRuntime($template, $global)
	{
		if (!empty($global->caches)) {
			end($global->caches)->dependencies[NCache::FILES][] = $template->getFile();
		}
	}

	static function createCache(ICacheStorage $cacheStorage, $key, & $parents, $args = NULL)
	{
		if ($args) {
			if (array_key_exists('if', $args) && !$args['if']) {
				return $parents[] = (object) NULL;
			}
			$key = array_merge(array($key), array_intersect_key($args, range(0, count($args))));
		}
		if ($parents) {
			end($parents)->dependencies[NCache::ITEMS][] = $key;
		}

		$cache = new NCache($cacheStorage, 'Nette.Templating.Cache');
		if ($helper = $cache->start($key)) {
			$helper->dependencies = array(
				NCache::TAGS => isset($args['tags']) ? $args['tags'] : NULL,
				NCache::EXPIRATION => isset($args['expire']) ? $args['expire'] : '+ 7 days',
			);
			$parents[] = $helper;
		}
		return $helper;
	}

}

class NMacroSet extends NObject implements IMacro
{

	public $parser;

	private $macros;

	function __construct(NParser $parser)
	{
		$this->parser = $parser;
	}

	function addMacro($name, $begin, $end = NULL)
	{
		$this->macros[$name] = array($begin, $end);
		$this->parser->addMacro($name, $this);
	}

	static function install(NParser $parser)
	{
		return new self($parser);
	}

	function initialize()
	{
	}

	function finalize()
	{
	}

	function nodeOpened(NMacroNode $node)
	{
		$node->isEmpty = !isset($this->macros[$node->name][1]);
		return $this->compile($node, $this->macros[$node->name][0]);
	}

	function nodeClosed(NMacroNode $node)
	{
		return $this->compile($node, $this->macros[$node->name][1]);
	}

	private function compile(NMacroNode $node, $def)
	{
		$writer = NPhpWriter::using($node, $this->parser->context);
		if (is_string($def)&& substr($def, 0, 1) !== "\0") {
			$code = $writer->write($def);
		} else {
			$code = callback($def)->invoke($node, $writer);
			if ($code === FALSE) {
				return FALSE;
			}
		}
		return "<?php $code ?>";
	}

}

class NCoreMacros extends NMacroSet
{

	static function install(NParser $parser)
	{
		$me = new self($parser);

		$me->addMacro('if', array($me, 'macroIf'), array($me, 'macroEndIf'));
		$me->addMacro('elseif', 'elseif (%node.args):');
		$me->addMacro('else', 'else:');
		$me->addMacro('ifset', 'if (isset(%node.args)):', 'endif');
		$me->addMacro('elseifset', 'elseif (isset(%node.args)):');

		$me->addMacro('foreach', array($me, 'macroForeach'), '$iterations++; endforeach; array_pop($_l->its); $iterator = end($_l->its)');
		$me->addMacro('for', 'for (%node.args):', 'endfor');
		$me->addMacro('while', 'while (%node.args):', 'endwhile');
		$me->addMacro('continueIf', 'if (%node.args) continue');
		$me->addMacro('breakIf', 'if (%node.args) break');
		$me->addMacro('first', 'if ($iterator->isFirst(%node.args)):', 'endif');
		$me->addMacro('last', 'if ($iterator->isLast(%node.args)):', 'endif');
		$me->addMacro('sep', 'if (!$iterator->isLast(%node.args)):', 'endif');

		$me->addMacro('var', array($me, 'macroVar'));
		$me->addMacro('assign', array($me, 'macroVar'));
		$me->addMacro('default', array($me, 'macroVar'));
		$me->addMacro('dump', array($me, 'macroDump'));
		$me->addMacro('debugbreak', array($me, 'macroDebugbreak'));
		$me->addMacro('l', '?>{<?php');
		$me->addMacro('r', '?>}<?php');

		$me->addMacro('_', array($me, 'macroTranslate'), array($me, 'macroTranslate'));
		$me->addMacro('=', array($me, 'macroExpr'));
		$me->addMacro('?', array($me, 'macroExpr'));

		$me->addMacro('syntax', array($me, 'macroSyntax'), array($me, 'macroSyntax'));
		$me->addMacro('capture', array($me, 'macroCapture'), array($me, 'macroCaptureEnd'));
		$me->addMacro('include', array($me, 'macroInclude'));
		$me->addMacro('use', array($me, 'macroUse'));

		$me->addMacro('@href', NULL, NULL);
		$me->addMacro('@class', array($me, 'macroClass'));
		$me->addMacro('@attr', array($me, 'macroAttr'));
		$me->addMacro('attr', array($me, 'macroOldAttr'));
	}

	function finalize()
	{
		return array('list($_l, $_g) = NCoreMacros::initRuntime($template, '
			. var_export($this->parser->templateId, TRUE) . ')');
	}

	function macroIf(NMacroNode $node, $writer)
	{
		if ($node->data->capture = ($node->args === '')) {
			return 'ob_start()';
		}
		return $writer->write('if (%node.args):');
	}

	function macroEndIf(NMacroNode $node, $writer)
	{
		if ($node->data->capture) {
			if ($node->args === '') {
				throw new NLatteException('Missing condition in {if} macro.');
			}
			return $writer->write('if (%node.args) ob_end_flush(); else ob_end_clean()');
		}
		return 'endif';
	}

	function macroTranslate(NMacroNode $node, $writer)
	{
		if ($node->closing) {
			return $writer->write('echo %modify($template->translate(ob_get_clean()))');

		} elseif ($node->isEmpty = ($node->args !== '')) {
			return $writer->write('echo %modify($template->translate(%node.args))');

		} else {
			return 'ob_start()';
		}
	}

	function macroSyntax(NMacroNode $node)
	{
		if ($node->closing) {
			$node->args = 'latte';
		}
		switch ($node->args) {
		case '':
		case 'latte':
			$this->parser->setDelimiters('\\{(?![\\s\'"{}])', '\\}');
			break;

		case 'double':
			$this->parser->setDelimiters('\\{\\{(?![\\s\'"{}])', '\\}\\}');
			break;

		case 'asp':
			$this->parser->setDelimiters('<%\s*', '\s*%>');
			break;

		case 'python':
			$this->parser->setDelimiters('\\{[{%]\s*', '\s*[%}]\\}');
			break;

		case 'off':
			$this->parser->setDelimiters('[^\x00-\xFF]', '');
			break;

		default:
			throw new NLatteException("Unknown syntax '$node->args'");
		}
	}

	function macroInclude(NMacroNode $node, $writer)
	{
		$code = $writer->write('NCoreMacros::includeTemplate(%node.word, %node.array? + $template->getParams(), $_l->templates[%var])',
			$this->parser->templateId);

		if ($node->modifiers) {
			return $writer->write('echo %modify(%raw->__toString(TRUE))', $code);
		} else {
			return $code . '->render()';
		}
	}

	function macroUse(NMacroNode $node, $writer)
	{
		call_user_func(array($node->tokenizer->fetchWord(), 'install'), $this->parser)
			->initialize();
	}

	function macroCapture(NMacroNode $node, $writer)
	{
		$variable = $node->tokenizer->fetchWord();
		if (substr($variable, 0, 1) !== '$') {
			throw new NLatteException("Invalid capture block variable '$variable'");
		}
		$node->data->variable = $variable;
		return 'ob_start()';
	}

	function macroCaptureEnd(NMacroNode $node, $writer)
	{
		return $writer->write("{$node->data->variable} = %modify(ob_get_clean())");
	}

	function macroForeach(NMacroNode $node, $writer)
	{
		return '$iterations = 0; foreach ($iterator = $_l->its[] = new NSmartCachingIterator('
			. preg_replace('#(.*)\s+as\s+#i', '$1) as ', $writer->formatArgs(), 1) . '):';
	}

	function macroClass(NMacroNode $node, $writer)
	{
		return $writer->write('if ($_l->tmp = trim(implode(" ", array_unique(%node.array)))) echo \' class="\' . %escape($_l->tmp) . \'"\'');
	}

	function macroAttr(NMacroNode $node, $writer)
	{
		return $writer->write('echo NHtml::el(NULL, %node.array)->attributes()');
	}

	function macroOldAttr(NMacroNode $node)
	{
		return NStrings::replace($node->args . ' ', '#\)\s+#', ')->');
	}

	function macroDump(NMacroNode $node, $writer)
	{
		$args = $writer->formatArgs();
		return $writer->write('NDebugger::barDump(' . ($node->args ? "array(%var => $args)" : 'get_defined_vars()')
			. ', "Template " . str_replace(dirname(dirname($template->getFile())), "\xE2\x80\xA6", $template->getFile()))', $args);
	}

	function macroDebugbreak(NMacroNode $node, $writer)
	{
		return $writer->write(($node->args == NULL ? '' : 'if (!(%node.args)); else')
			. 'if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break()');
	}

	function macroVar(NMacroNode $node, $writer)
	{
		$out = '';
		$var = TRUE;
		$tokenizer = $writer->preprocess();
		while ($token = $tokenizer->fetchToken()) {
			if ($var && ($token['type'] === NMacroTokenizer::T_SYMBOL || $token['type'] === NMacroTokenizer::T_VARIABLE)) {
				if ($node->name === 'default') {
					$out .= "'" . ltrim($token['value'], "$") . "'";
				} else {
					$out .= '$' . ltrim($token['value'], "$");
				}
				$var = NULL;

			} elseif (($token['value'] === '=' || $token['value'] === '=>') && $token['depth'] === 0) {
				$out .= $node->name === 'default' ? '=>' : '=';
				$var = FALSE;

			} elseif ($token['value'] === ',' && $token['depth'] === 0) {
				$out .= $node->name === 'default' ? ',' : ';';
				$var = TRUE;

			} elseif ($var === NULL && $node->name === 'default' && $token['type'] !== NMacroTokenizer::T_WHITESPACE) {
				throw new NLatteException("Unexpected '$token[value]' in {default $node->args}");

			} else {
				$out .= $writer->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
			}
		}
		return $node->name === 'default' ? "extract(array($out), EXTR_SKIP)" : $out;
	}

	function macroExpr(NMacroNode $node, $writer)
	{
		return $writer->write(($node->name === '?' ? '' : 'echo ') . '%modify(%node.args)');
	}

	static function includeTemplate($destination, $params, $template)
	{
		if ($destination instanceof ITemplate) {
			$tpl = $destination;

		} elseif ($destination == NULL) {
			throw new InvalidArgumentException("Template file name was not specified.");

		} else {
			$tpl = clone $template;
			if ($template instanceof IFileTemplate) {
				if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
					$destination = dirname($template->getFile()) . '/' . $destination;
				}
				$tpl->setFile($destination);
			}
		}

		$tpl->setParams($params);
		return $tpl;
	}

	static function initRuntime($template, $templateId)
	{

		if (isset($template->_l)) {
			$local = $template->_l;
			unset($template->_l);
		} else {
			$local = (object) NULL;
		}
		$local->templates[$templateId] = $template;

		if (!isset($template->_g)) {
			$template->_g = (object) NULL;
		}

		return array($local, $template->_g);
	}

}

class NFormMacros extends NMacroSet
{

	static function install(NParser $parser)
	{
		$me = new self($parser);
		$me->addMacro('form',
			'$form = $control[%node.word]; echo $form->getElementPrototype()->addAttributes(%node.array)->startTag()',
			'?><div><?php
foreach ($form->getComponents(TRUE, \'NHiddenField\') as $_tmp) echo $_tmp->getControl();
if (iterator_count($form->getComponents(TRUE, \'NTextInput\')) < 2) echo "<!--[if IE]><input type=IEbug disabled style=\"display:none\"><![endif]-->";
?></div>
<?php echo $form->getElementPrototype()->endTag()');
		$me->addMacro('label', array($me, 'macroLabel'), '?></label><?php');
		$me->addMacro('input', 'echo $form[%node.word]->getControl()->addAttributes(%node.array)');
	}

	function macroLabel(NMacroNode $node, $writer)
	{
		$cmd = 'if ($_label = $form[%node.word]->getLabel()) echo $_label->addAttributes(%node.array)';
		if ($node->isEmpty = (substr($node->args, -1) === '/')) {
			$node->setArgs(substr($node->args, 0, -1));
			return $writer->write($cmd);
		} else {
			return $writer->write($cmd . '->startTag()');
		}
	}

}

class NUIMacros extends NMacroSet
{

	const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF]*';

	private $namedBlocks = array();

	private $extends;

	static function install(NParser $parser)
	{
		$me = new self($parser);
		$me->addMacro('include', array($me, 'macroInclude'));
		$me->addMacro('includeblock', array($me, 'macroIncludeBlock'));
		$me->addMacro('extends', array($me, 'macroExtends'));
		$me->addMacro('layout', array($me, 'macroExtends'));
		$me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
		$me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
		$me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
		$me->addMacro('ifset', array($me, 'macroIfset'), 'endif');

		$me->addMacro('widget', array($me, 'macroControl'));
		$me->addMacro('control', array($me, 'macroControl'));

		$me->addMacro('@href', create_function('NMacroNode $node, $writer', 'extract(NCFix::$vars['.NCFix::uses(array('me'=>$me)).'], EXTR_REFS);
			return \' ?> href="<?php \' . $me->macroLink($node, $writer) . \' ?>"<?php \';
		'));
		$me->addMacro('plink', array($me, 'macroLink'));
		$me->addMacro('link', array($me, 'macroLink'));
		$me->addMacro('ifCurrent', array($me, 'macroIfCurrent'), 'endif');

		$me->addMacro('contentType', array($me, 'macroContentType'));
		$me->addMacro('status', array($me, 'macroStatus'));
	}

	function initialize()
	{
		$this->namedBlocks = array();
		$this->extends = NULL;
	}

	function finalize()
	{

		try {
			$this->parser->writeMacro('/block');
		} catch (NLatteException $e) {
		}

		$epilog = $prolog = array();

		if ($this->namedBlocks) {
			foreach ($this->namedBlocks as $name => $code) {
				$func = '_lb' . substr(md5($this->parser->templateId . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
				$snippet = $name[0] === '_';
				$prolog[] = "//\n// block $name\n//\n"
					. "if (!function_exists(\$_l->blocks[" . var_export($name, TRUE) . "][] = '$func')) { "
					. "function $func(\$_l, \$_args) { "
					. (PHP_VERSION_ID > 50208 ? 'extract($_args)' : 'foreach ($_args as $__k => $__v) $$__k = $__v')
					. ($snippet ? '; $control->validateControl(' . var_export(substr($name, 1), TRUE) . ')' : '')
					. "\n?>$code<?php\n}}";
			}
			$prolog[] = "//\n// end of blocks\n//";
		}

		if ($this->namedBlocks || $this->extends) {
			$prolog[] = "// template extending and snippets support";

			if (is_bool($this->extends)) {
				$prolog[] = '$_l->extends = ' . var_export($this->extends, TRUE) . '; unset($_extends, $template->_extends);';
			} else {
				$prolog[] = '$_l->extends = empty($template->_extends) ? FALSE : $template->_extends; unset($_extends, $template->_extends);';
			}

			$prolog[] = '
if ($_l->extends) {
	ob_start();
} elseif (!empty($control->snippetMode)) {
	return NUIMacros::renderSnippets($control, $_l, get_defined_vars());
}';
			$epilog[] = '
// template extending support
if ($_l->extends) {
	ob_end_clean();
	NCoreMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render();
}';
		} else {
			$prolog[] = '
// snippets support
if (!empty($control->snippetMode)) {
	return NUIMacros::renderSnippets($control, $_l, get_defined_vars());
}';
		}

		return array(implode("\n\n", $prolog), implode("\n", $epilog));
	}

	function macroInclude(NMacroNode $node, $writer)
	{
		$destination = $node->tokenizer->fetchWord();
		if (substr($destination, 0, 1) !== '#') {
			return FALSE;
		}

		$destination = ltrim($destination, '#');
		if (!NStrings::match($destination, '#^\$?' . self::RE_IDENTIFIER . '$#')) {
			throw new NLatteException("Included block name must be alphanumeric string, '$destination' given.");
		}

		$parent = $destination === 'parent';
		if ($destination === 'parent' || $destination === 'this') {
			$item = $node->parentNode;
			while ($item && $item->name !== 'block' && !isset($item->data->name)) $item = $item->parentNode;
			if (!$item) {
				throw new NLatteException("Cannot include $destination block outside of any block.");
			}
			$destination = $item->data->name;
		}

		$name = $destination[0] === '$' ? $destination : var_export($destination, TRUE);
		if (isset($this->namedBlocks[$destination]) && !$parent) {
			$cmd = "call_user_func(reset(\$_l->blocks[$name]), \$_l, %node.array? + \$template->getParams())";
		} else {
			$cmd = 'NUIMacros::callBlock' . ($parent ? 'Parent' : '') . "(\$_l, $name, %node.array? + \$template->getParams())";
		}

		if ($node->modifiers) {
			return $writer->write("ob_start(); $cmd; echo %modify(ob_get_clean())");
		} else {
			return $writer->write($cmd);
		}
	}

	function macroIncludeBlock(NMacroNode $node, $writer)
	{
		return $writer->write('NCoreMacros::includeTemplate(%node.word, %node.array? + get_defined_vars(), $_l->templates[%var])->render()',
			$this->parser->templateId);
	}

	function macroExtends(NMacroNode $node, $writer)
	{
		if (!$node->args) {
			throw new NLatteException("Missing destination in {extends}");
		}
		if (!empty($node->parentNode)) {
			throw new NLatteException("{extends} must be placed outside any macro.");
		}
		if ($this->extends !== NULL) {
			throw new NLatteException("Multiple {extends} declarations are not allowed.");
		}
		$this->extends = $node->args !== 'none';
		return $this->extends ? '$_l->extends = ' . ($node->args === 'auto' ? '$layout' : $writer->formatArgs()) : '';
	}

	function macroBlock(NMacroNode $node, $writer)
	{
		$name = $node->tokenizer->fetchWord();

		if ($node->name === 'block' && $name === FALSE) {
			return $node->modifiers === '' ? '' : 'ob_start()';
		}

		$node->data->name = $name = ltrim($name, '#');
		$node->data->end = '';
		if ($name == NULL) {
			if ($node->name !== 'snippet') {
				throw new NLatteException("Missing block name.");
			}

		} elseif (!NStrings::match($name, '#^' . self::RE_IDENTIFIER . '$#')) {
			if ($node->name === 'snippet') {
				$parent = $node->parentNode;
				while ($parent && $parent->name !== 'snippet') $parent = $parent->parentNode;
				if (!$parent) {
					throw new NLatteException("Dynamic snippets are allowed only inside static snippet.");
				}
				$parent->data->dynamic = TRUE;

				$tag = trim($node->tokenizer->fetchWord(), '<>');
				$tag = $tag ? $tag : 'div';
				$node->data->leave = TRUE;
				$node->data->end = "\$_dynSnippets[\$_dynSnippetId] = ob_get_flush() ?>\n</$tag><?php";
				return $writer->write("?>\n<$tag id=\"<?php echo \$_dynSnippetId = \$control->getSnippetId({$writer->formatWord($name)}) ?>\"><?php ob_start()");

			} else {
				$node->data->leave = TRUE;
				$fname = $writer->formatWord($name);
				$node->data->end = "}} call_user_func(reset(\$_l->blocks[$fname]), \$_l, get_defined_vars())";
				$func = '_lb' . substr(md5($this->parser->templateId . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
				return "//\n// block $name\n//\n"
					. "if (!function_exists(\$_l->blocks[$fname][] = '$func')) { "
					. "function $func(\$_l, \$_args) { "
					. (PHP_VERSION_ID > 50208 ? 'extract($_args)' : 'foreach ($_args as $__k => $__v) $$__k = $__v');
			}
		}

		if ($node->name === 'snippet') {
			$node->data->name = $name = '_' . $name;
		}
		if (isset($this->namedBlocks[$name])) {
			throw new NLatteException("Cannot redeclare static block '$name'");
		}
		$top = empty($node->parentNode);
		$this->namedBlocks[$name] = TRUE;

		$include = 'call_user_func(reset($_l->blocks[%var]), $_l, ' . ($node->name === 'snippet' ? '$template->getParams()' : 'get_defined_vars()') . ')';
		if ($node->modifiers) {
			$include = "ob_start(); $include; echo %modify(ob_get_clean())";
		}

		if ($node->name === 'snippet') {
			$tag = trim($node->tokenizer->fetchWord(), '<>');
			$tag = $tag ? $tag : 'div';
			return $writer->write("?>\n<$tag id=\"<?php echo \$control->getSnippetId(%var) ?>\"><?php $include ?>\n</$tag><?php ",
				(string) substr($name, 1), $name
			);

		} elseif ($node->name === 'define') {
			return '';

		} elseif (!$top) {
			return $writer->write($include, $name);

		} elseif ($this->extends) {
			return '';

		} else {
			return $writer->write("if (!\$_l->extends) { $include; }", $name);
		}
	}

	function macroBlockEnd(NMacroNode $node, $writer)
	{
		if (isset($node->data->name)) {
			if (empty($node->data->leave)) {
				if (!empty($node->data->dynamic)) {
					$node->content .= '<?php if (isset($_dynSnippets)) return $_dynSnippets; ?>';
				}
				$this->namedBlocks[$node->data->name] = $node->content;
				$node->content = '';
			}
			return $node->data->end;

		} elseif ($node->modifiers) {
			return $writer->write('echo %modify(ob_get_clean())');
		}
	}

	function macroIfset(NMacroNode $node, $writer)
	{
		if (strpos($node->args, '#') === FALSE) {
			return FALSE;
		}
		$list = array();
		while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
			$list[] = $name[0] === '#' ? '$_l->blocks["' . substr($name, 1) . '"]' : $name;
		}
		return 'if (isset(' . implode(', ', $list) . ')):';
	}

	function macroControl(NMacroNode $node, $writer)
	{
		$pair = $node->tokenizer->fetchWord();
		if ($pair === FALSE) {
			throw new NLatteException("Missing control name in {control}");
		}
		$pair = explode(':', $pair, 2);
		$name = $writer->formatWord($pair[0]);
		$method = isset($pair[1]) ? ucfirst($pair[1]) : '';
		$method = NStrings::match($method, '#^(' . self::RE_IDENTIFIER . '|)$#') ? "render$method" : "{\"render$method\"}";
		$param = $writer->formatArray();
		if (strpos($node->args, '=>') === FALSE) {
			$param = substr($param, 6, -1);
		}
		return ($name[0] === '$' ? "if (is_object($name)) \$_ctrl = $name; else " : '')
			. '$_ctrl = $control->getWidget(' . $name . '); '
			. 'if ($_ctrl instanceof IPartiallyRenderable) $_ctrl->validateControl(); '
			. "\$_ctrl->$method($param)";
	}

	function macroLink(NMacroNode $node, $writer)
	{
		return $writer->write('echo %escape(' . ($node->name === 'plink' ? '$presenter' : '$control') . '->link(%node.word, %node.array?))');
	}

	function macroIfCurrent(NMacroNode $node, $writer)
	{
		return $writer->write(($node->args ? 'try { $presenter->link(%node.word, %node.array?); } catch (NInvalidLinkException $e) {}' : '')
			. '; if ($presenter->getLastCreatedRequestFlag("current")):');
	}

	function macroContentType(NMacroNode $node, $writer)
	{
		if (strpos($node->args, 'html') !== FALSE) {
			$this->parser->context = array(NParser::CONTEXT_TEXT);

		} elseif (strpos($node->args, 'xml') !== FALSE) {
			$this->parser->context = array(NParser::CONTEXT_NONE, 'xml');

		} elseif (strpos($node->args, 'javascript') !== FALSE) {
			$this->parser->context = array(NParser::CONTEXT_NONE, 'js');

		} elseif (strpos($node->args, 'css') !== FALSE) {
			$this->parser->context = array(NParser::CONTEXT_NONE, 'css');

		} elseif (strpos($node->args, 'plain') !== FALSE) {
			$this->parser->context = array(NParser::CONTEXT_NONE, 'text');

		} else {
			$this->parser->context = array(NParser::CONTEXT_NONE);
		}

		if (strpos($node->args, '/')) {
			return $writer->write('$netteHttpResponse->setHeader("Content-Type", %var)', $node->args);
		}
	}

	function macroStatus(NMacroNode $node, $writer)
	{
		return $writer->write((substr($node->args, -1) === '?' ? 'if (!$netteHttpResponse->isSent()) ' : '') .
			'$netteHttpResponse->setCode(%var)', (int) $node->args
		);
	}

	static function callBlock($context, $name, $params)
	{
		if (empty($context->blocks[$name])) {
			throw new InvalidStateException("Cannot include undefined block '$name'.");
		}
		$block = reset($context->blocks[$name]);
		$block($context, $params);
	}

	static function callBlockParent($context, $name, $params)
	{
		if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
			throw new InvalidStateException("Cannot include undefined parent block '$name'.");
		}
		$block($context, $params);
	}

	static function renderSnippets($control, $local, $params)
	{
		$control->snippetMode = FALSE;
		$payload = $control->getPresenter()->getPayload();
		if (isset($local->blocks)) {
			foreach ($local->blocks as $name => $function) {
				if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) {
					continue;
				}
				ob_start();
				$function = reset($function);
				$snippets = $function($local, $params);
				$payload->snippets[$id = $control->getSnippetId(substr($name, 1))] = ob_get_clean();
				if ($snippets) {
					$payload->snippets += $snippets;
					unset($payload->snippets[$id]);
			}
		}
		}
		if ($control instanceof NControl) {
			foreach ($control->getComponents(FALSE, 'NControl') as $child) {
				if ($child->isControlInvalid()) {
					$child->snippetMode = TRUE;
					$child->render();
					$child->snippetMode = FALSE;
				}
			}
		}
	}

}

class NTokenizer extends NObject
{

	public $tokens;

	public $position = 0;

	public $ignored = array();

	private $input;

	private $re;

	private $types;

	public $current;

	function __construct(array $patterns, $flags = '')
	{
		$this->re = '~(' . implode(')|(', $patterns) . ')~A' . $flags;
		$keys = array_keys($patterns);
		$this->types = $keys === range(0, count($patterns) - 1) ? FALSE : $keys;
	}

	function tokenize($input)
	{
		$this->input = $input;
		if ($this->types) {
			$this->tokens = NStrings::matchAll($input, $this->re);
			$len = 0;
			$count = count($this->types);
			$line = 1;
			foreach ($this->tokens as & $match) {
				$type = NULL;
				for ($i = 1; $i <= $count; $i++) {
					if (!isset($match[$i])) {
						break;
					} elseif ($match[$i] != NULL) {
						$type = $this->types[$i - 1]; break;
					}
				}
				$match = self::createToken($match[0], $type, $line);
				$len += strlen($match['value']);
				$line += substr_count($match['value'], "\n");
			}
			if ($len !== strlen($input)) {
				$errorOffset = $len;
			}

		} else {
			$this->tokens = NStrings::split($input, $this->re, PREG_SPLIT_NO_EMPTY);
			if ($this->tokens && !NStrings::match(end($this->tokens), $this->re)) {
				$tmp = NStrings::split($this->input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
				list(, $errorOffset) = end($tmp);
			}
		}

		if (isset($errorOffset)) {
			$line = $errorOffset ? substr_count($this->input, "\n", 0, $errorOffset) + 1 : 1;
			$col = $errorOffset - strrpos(substr($this->input, 0, $errorOffset), "\n") + 1;
			$token = str_replace("\n", '\n', substr($input, $errorOffset, 10));
			throw new NTokenizerException("Unexpected '$token' on line $line, column $col.");
		}
		return $this->tokens;
	}

	static function createToken($value, $type = NULL, $line = NULL)
	{
		return array('value' => $value, 'type' => $type, 'line' => $line);
	}

	function getOffset($i)
	{
		$tokens = NStrings::split($this->input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
		$offset = isset($tokens[$i]) ? $tokens[$i][1] : strlen($this->input);
		return array(
			$offset,
			($offset ? substr_count($this->input, "\n", 0, $offset) + 1 : 1),
			$offset - strrpos(substr($this->input, 0, $offset), "\n"),
		);
	}

	function fetch()
	{
		$args = func_get_args();
		return $this->scan($args, TRUE);
	}

	function fetchToken()
	{
		$args = func_get_args();
		return $this->scan($args, TRUE) === FALSE ? FALSE : $this->current;
	}

	function fetchAll()
	{
		$args = func_get_args();
		return $this->scan($args, FALSE);
	}

	function fetchUntil($arg)
	{
		$args = func_get_args();
		return $this->scan($args, FALSE, TRUE, TRUE);
	}

	function isNext($arg)
	{
		$args = func_get_args();
		return (bool) $this->scan($args, TRUE, FALSE);
	}

	function isPrev($arg)
	{
		$args = func_get_args();
		return (bool) $this->scan($args, TRUE, FALSE, FALSE, TRUE);
	}

	function hasNext()
	{
		return isset($this->tokens[$this->position]);
	}

	function hasPrev()
	{
		return $this->position > 1;
	}

	function isCurrent($arg)
	{
		$args = func_get_args();
		if (is_array($this->current)) {
			return in_array($this->current['value'], $args, TRUE)
				|| in_array($this->current['type'], $args, TRUE);
		} else {
			return in_array($this->current, $args, TRUE);
		}
	}

	private function scan($wanted, $first, $advance = TRUE, $neg = FALSE, $prev = FALSE)
	{
		$res = FALSE;
		$pos = $this->position + ($prev ? -2 : 0);
		while (isset($this->tokens[$pos])) {
			$token = $this->tokens[$pos];
			$pos += $prev ? -1 : 1;
			$value = is_array($token) ? $token['value'] : $token;
			$type = is_array($token) ? $token['type'] : $token;
			if (!$wanted || (in_array($value, $wanted, TRUE) || in_array($type, $wanted, TRUE)) ^ $neg) {
				if ($advance) {
					$this->position = $pos;
					$this->current = $token;
				}
				$res .= $value;
				if ($first) {
					break;
				}

			} elseif ($neg || !in_array($type, $this->ignored, TRUE)) {
				break;
			}
		}
		return $res;
	}

}

class NTokenizerException extends Exception
{
}

class NMacroTokenizer extends NTokenizer
{
	const T_WHITESPACE = 1,
		T_COMMENT = 2,
		T_SYMBOL = 3,
		T_NUMBER = 4,
		T_VARIABLE = 5,
		T_STRING = 6,
		T_CAST = 7,
		T_KEYWORD = 8,
		T_CHAR = 9;

	function __construct($input)
	{
		parent::__construct(array(
			self::T_WHITESPACE => '\s+',
			self::T_COMMENT => '(?s)/\*.*?\*/',
			self::T_STRING => NParser::RE_STRING,
			self::T_KEYWORD => '(?:true|false|null|and|or|xor|clone|new|instanceof|return|continue|break|[A-Z_][A-Z0-9_]{2,})(?![\w\pL_])',
			self::T_CAST => '\([a-z]+\)',
			self::T_VARIABLE => '\$[\w\pL_]+',
			self::T_NUMBER => '[+-]?[0-9]+(?:\.[0-9]+)?(?:e[0-9]+)?',
			self::T_SYMBOL => '[\w\pL_]+(?:-[\w\pL_]+)*',
			self::T_CHAR => '::|=>|[^"\']',
		), 'u');
		$this->ignored = array(self::T_COMMENT, self::T_WHITESPACE);
		$this->tokenize($input);
	}

	function fetchWord()
	{
		$word = $this->fetchUntil(self::T_WHITESPACE, ',');
		$this->fetch(',');
		$this->fetchAll(self::T_WHITESPACE, self::T_COMMENT);
		return $word;
	}

}

class NTemplateException extends InvalidStateException
{

	public $sourceFile;

	public $sourceLine;

	function __construct($message, $code = 0, $sourceLine = 0)
	{
		$this->sourceLine = (int) $sourceLine;
		parent::__construct($message, $code);
	}

	function setSourceFile($file)
	{
		$this->sourceFile = (string) $file;
		$this->message = rtrim($this->message, '.') . " in " . str_replace(dirname(dirname($file)), '...', $file)
			. ($this->sourceLine ? ":$this->sourceLine" : '');
	}

}

class NLatteException extends NTemplateException
{
}

class NParser extends NObject
{

	const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';

	const N_PREFIX = 'n:';

	private $macroRe;

	private $input;

	private $output;

	private $offset;

	private $macros;

	private $macroHandlers;

	private $htmlNodes = array();

	private $macroNodes = array();

	public $context;

	public $templateId;

	const CONTEXT_TEXT = 'text',
		CONTEXT_CDATA = 'cdata',
		CONTEXT_TAG = 'tag',
		CONTEXT_ATTRIBUTE = 'attribute',
		CONTEXT_NONE = 'none',
		CONTEXT_COMMENT = 'comment';

	function __construct()
	{
		$this->macroHandlers = new SplObjectStorage;
		$this->setDelimiters('\\{(?![\\s\'"{}])', '\\}');
		$this->context = array(self::CONTEXT_NONE, 'text');
	}

	function addMacro($name, IMacro $macro)
	{
		$this->macros[$name][] = $macro;
		$this->macroHandlers->attach($macro);
		return $this;
	}

	function parse($s)
	{
		//if (!NStrings::checkEncoding($s)) {
		//	throw new NLatteException('Template is not valid UTF-8 stream.');
		//}
		$s = str_replace("\r\n", "\n", $s);

		$this->templateId = NStrings::random();
		$this->input = & $s;
		$this->offset = 0;
		$this->output = '';
		$this->htmlNodes = $this->macroNodes = array();

		foreach ($this->macroHandlers as $handler) {
			$handler->initialize($this);
		}

		$len = strlen($s);

		try {
			while ($this->offset < $len) {
				$matches = $this->{"context".$this->context[0]}();

				if (!$matches) {
					break;

				} elseif (!empty($matches['comment'])) {

				} elseif (!empty($matches['macro'])) {
					list($macroName, $macroArgs, $macroModifiers) = $this->parseMacro($matches['macro']);
					$isRightmost = $this->offset >= $len || $this->input[$this->offset] === "\n";
					$this->writeMacro($macroName, $macroArgs, $macroModifiers, $isRightmost);

				} else {
					$this->output .= $matches[0];
				}
			}
		} catch (NLatteException $e) {
			if (!$e->sourceLine) {
				$e->sourceLine = $this->getLine();
			}
			throw $e;
		}

		$this->output .= substr($this->input, $this->offset);

		foreach ($this->htmlNodes as $node) {
			if (!empty($node->attrs)) {
				throw new NLatteException("Missing end tag </$node->name> for macro-attribute " . self::N_PREFIX
					. implode(' and ' . self::N_PREFIX, array_keys($node->attrs)) . ".", 0, $this->getLine());
			}
		}

		$prologs = $epilogs = '';
		foreach ($this->macroHandlers as $handler) {
			$res = $handler->finalize();
			$prologs .= isset($res[0]) ? "<?php $res[0]\n?>" : '';
			$epilogs .= isset($res[1]) ? "<?php $res[1]\n?>" : '';
		}
		$this->output = ($prologs ? $prologs . "<?php\n//\n// main template\n//\n?>\n" : '') . $this->output . $epilogs;

		if ($this->macroNodes) {
			throw new NLatteException("There are unclosed macros.", 0, $this->getLine());
		}

		return $this->output;
	}

	private function contextText()
	{
		$matches = $this->match('~
			(?:(?<=\n|^)[ \t]*)?<(?P<closing>/?)(?P<tag>[a-z0-9:]+)|  ##  begin of HTML tag <tag </tag - ignores <!DOCTYPE
			<(?P<htmlcomment>!--)|           ##  begin of HTML comment <!--
			'.$this->macroRe.'           ##  curly tag
		~xsi');

		if (!$matches || !empty($matches['macro']) || !empty($matches['comment'])) {

		} elseif (!empty($matches['htmlcomment'])) {
			$this->context = array(self::CONTEXT_COMMENT);

		} elseif (empty($matches['closing'])) {
			$this->htmlNodes[] = $node = new NHtmlNode($matches['tag']);
			$node->offset = strlen($this->output);
			$this->context = array(self::CONTEXT_TAG);

		} else {
			do {
				$node = array_pop($this->htmlNodes);
				if (!$node) {
					$node = new NHtmlNode($matches['tag']);
				}
			} while (strcasecmp($node->name, $matches['tag']));
			$this->htmlNodes[] = $node;
			$node->closing = TRUE;
			$node->offset = strlen($this->output);
			$this->context = array(self::CONTEXT_TAG);
		}
		return $matches;
	}

	private function contextCData()
	{
		$node = end($this->htmlNodes);
		$matches = $this->match('~
			</'.$node->name.'(?![a-z0-9:])| ##  end HTML tag </tag
			'.$this->macroRe.'           ##  curly tag
		~xsi');

		if ($matches && empty($matches['macro']) && empty($matches['comment'])) {
			$node->closing = TRUE;
			$node->offset = strlen($this->output);
			$this->context = array(self::CONTEXT_TAG);
		}
		return $matches;
	}

	private function contextTag()
	{
		$matches = $this->match('~
			(?P<end>\ ?/?>)(?P<tagnewline>[ \t]*\n)?|  ##  end of HTML tag
			'.$this->macroRe.'|          ##  curly tag
			\s*(?P<attr>[^\s/>={]+)(?:\s*=\s*(?P<value>["\']|[^\s/>{]+))? ## begin of HTML attribute
		~xsi');

		if (!$matches || !empty($matches['macro']) || !empty($matches['comment'])) {

		} elseif (!empty($matches['end'])) {
			$node = end($this->htmlNodes);
			$isEmpty = !$node->closing && (strpos($matches['end'], '/') !== FALSE || $node->isEmpty);

			if ($isEmpty) {
				$matches[0] = (NHtml::$xhtml ? ' />' : '>')
					. (isset($matches['tagnewline']) ? $matches['tagnewline'] : '');
			}

			if (!empty($node->attrs)) {
				$code = substr($this->output, $node->offset) . $matches[0];
				$this->output = substr($this->output, 0, $node->offset);
				$this->writeAttrsMacro($code, $node->attrs, $node->closing);
				if ($isEmpty) {
					$this->writeAttrsMacro('', $node->attrs, TRUE);
				}
				$matches[0] = '';
			}

			if ($isEmpty) {
				$node->closing = TRUE;
			}

			if (!$node->closing && (strcasecmp($node->name, 'script') === 0 || strcasecmp($node->name, 'style') === 0)) {
				$this->context = array(self::CONTEXT_CDATA, strcasecmp($node->name, 'style') ? 'js' : 'css');
			} else {
				$this->context = array(self::CONTEXT_TEXT);
				if ($node->closing) {
					array_pop($this->htmlNodes);
				}
			}

		} else {
			$name = $matches['attr'];
			$value = isset($matches['value']) ? $matches['value'] : '';
			$node = end($this->htmlNodes);

			if (NStrings::startsWith($name, self::N_PREFIX)) {
				$name = substr($name, strlen(self::N_PREFIX));
				if ($value === '"' || $value === "'") {
					if ($matches = $this->match('~(.*?)' . $value . '~xsi')) {
						$value = $matches[1];
					}
				}
				$node->attrs[$name] = $value;
				$matches[0] = '';

			} elseif ($value === '"' || $value === "'") {
				$this->context = array(self::CONTEXT_ATTRIBUTE, $name, $value);
			}
		}
		return $matches;
	}

	private function contextAttribute()
	{
		$matches = $this->match('~
			(' . $this->context[2] . ')|      ##  1) end of HTML attribute
			'.$this->macroRe.'                ##  curly tag
		~xsi');

		if ($matches && empty($matches['macro']) && empty($matches['comment'])) {
			$this->context = array(self::CONTEXT_TAG);
		}
		return $matches;
	}

	private function contextComment()
	{
		$matches = $this->match('~
			(--\s*>)|                    ##  1) end of HTML comment
			'.$this->macroRe.'           ##  curly tag
		~xsi');

		if ($matches && empty($matches['macro']) && empty($matches['comment'])) {
			$this->context = array(self::CONTEXT_TEXT);
		}
		return $matches;
	}

	private function contextNone()
	{
		$matches = $this->match('~
			'.$this->macroRe.'           ##  curly tag
		~xsi');
		return $matches;
	}

	private function match($re)
	{
		if ($matches = NStrings::match($this->input, $re, PREG_OFFSET_CAPTURE, $this->offset)) {
			$this->output .= substr($this->input, $this->offset, $matches[0][1] - $this->offset);
			$this->offset = $matches[0][1] + strlen($matches[0][0]);
			foreach ($matches as $k => $v) $matches[$k] = $v[0];
		}
		return $matches;
	}

	function getLine()
	{
		return $this->input && $this->offset ? substr_count($this->input, "\n", 0, $this->offset - 1) + 1 : NULL;
	}

	function setDelimiters($left, $right)
	{
		$this->macroRe = '
			(?P<comment>' . $left . '\\*.*?\\*' . $right . '\n{0,2})|
			' . $left . '
				(?P<macro>(?:' . self::RE_STRING . '|[^\'"]+?)*?)
			' . $right . '
			(?P<rmargin>[ \t]*(?=\n))?
		';
		return $this;
	}

	function writeMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE)
	{
		$isLeftmost = trim(substr($this->output, $leftOfs = strrpos("\n$this->output", "\n"))) === '';

		if ($name[0] === '/') {
			$node = end($this->macroNodes);

			if (!$node || ("/$node->name" !== $name && '/' !== $name) || $modifiers
				|| ($args && $node->args && !NStrings::startsWith("$node->args ", "$args "))
			) {
				$name .= $args ? ' ' : '';
				throw new NLatteException("Unexpected macro {{$name}{$args}{$modifiers}}"
					. ($node ? ", expecting {/$node->name}" . ($args && $node->args ? " or eventually {/$node->name $node->args}" : '') : ''),
					0, $this->getLine());
			}

			array_pop($this->macroNodes);
			if (!$node->args) {
				$node->setArgs($args);
			}
			if ($isLeftmost && $isRightmost) {
				$this->output = substr($this->output, 0, $leftOfs);
			}

			$code = $node->close(substr($this->output, $node->offset));

			if (!$isLeftmost && $isRightmost && substr($code, -2) === '?>') {
				$code .= "\n";
			}
			$this->output = substr($this->output, 0, $node->offset) . $node->content. $code;

		} else {
			list($node, $code) = $this->expandMacro($name, $args, $modifiers);
			if (!$node->isEmpty) {
				$this->macroNodes[] = $node;
			}

			if ($isRightmost) {
				if ($isLeftmost && substr($code, 0, 11) !== '<?php echo ') {
					$this->output = substr($this->output, 0, $leftOfs);
				} elseif (substr($code, -2) === '?>') {
					$code .= "\n";
				}
			}

			$this->output .= $code;
			$node->offset = strlen($this->output);
		}
	}

	function writeAttrsMacro($code, $attrs, $closing)
	{
		$left = $right = array();
		foreach ($this->macros as $name => $foo) {
			if ($name[0] === '@') {
				$name = substr($name, 1);
				if (isset($attrs[$name])) {
					if (!$closing) {
						$pos = strrpos($code, '>');
						if ($code[$pos-1] === '/') {
							$pos--;
						}
						list(, $macroCode) = $this->expandMacro("@$name", $attrs[$name]);
						$code = substr_replace($code, $macroCode, $pos, 0);
					}
					unset($attrs[$name]);
				}
			}

			$macro = $closing ? "/$name" : $name;
			if (isset($attrs[$name])) {
				if ($closing) {
					$right[] = array($macro, '');
				} else {
					array_unshift($left, array($macro, $attrs[$name]));
				}
			}

			$innerName = "inner-$name";
			if (isset($attrs[$innerName])) {
				if ($closing) {
					$left[] = array($macro, '');
				} else {
					array_unshift($right, array($macro, $attrs[$innerName]));
				}
			}

			$tagName = "tag-$name";
			if (isset($attrs[$tagName])) {
				array_unshift($left, array($name, $attrs[$tagName]));
				$right[] = array("/$name", '');
			}

			unset($attrs[$name], $attrs[$innerName], $attrs[$tagName]);
		}

		if ($attrs) {
			throw new NLatteException("Unknown macro-attribute " . self::N_PREFIX
				. implode(' and ' . self::N_PREFIX, array_keys($attrs)), 0, $this->getLine());
		}

		foreach ($left as $item) {
			$this->writeMacro($item[0], $item[1]);
			if (substr($this->output, -2) === '?>') {
				$this->output .= "\n";
			}
		}
		$this->output .= $code;

		foreach ($right as $item) {
			$this->writeMacro($item[0], $item[1]);
			if (substr($this->output, -2) === '?>') {
				$this->output .= "\n";
			}
		}
	}

	function expandMacro($name, $args, $modifiers = NULL)
	{
		if (empty($this->macros[$name])) {
			throw new NLatteException("Unknown macro {{$name}}", 0, $this->getLine());
		}
		foreach (array_reverse($this->macros[$name]) as $macro) {
			$node = new NMacroNode($macro, $name, $args, $modifiers, $this->macroNodes ? end($this->macroNodes) : NULL);
			$code = $macro->nodeOpened($node);
			if ($code !== FALSE) {
				return array($node, $code);
			}
		}
		throw new NLatteException("Unhandled macro {{$name}}", 0, $this->getLine());
	}

	function parseMacro($macro)
	{
		$match = NStrings::match($macro, '~^
			(
				(?P<name>\?|/?[a-z]\w*+(?:[.:]\w+)*+(?!::|\())|
				(?P<noescape>!?)(?P<shortname>/?[=\~#%^&_]?)
			)(?P<args>.*?)
			(?P<modifiers>\|[a-z](?:'.NParser::RE_STRING.'|[^\'"]+)*)?
		()$~isx');

		if (!$match) {
			return FALSE;
		}
		if ($match['name'] === '') {
			$match['name'] = ($tmp= $match['shortname']) ? $tmp : '=';
			if (!$match['noescape'] && substr($match['shortname'], 0, 1) !== '/') {
				$match['modifiers'] .= '|escape';
			}
		}
		return array($match['name'], trim($match['args']), $match['modifiers']);
	}

}

class NPhpWriter extends NObject
{

	private $argsTokenizer;

	private $modifiers;

	private $context;

	static function using(NMacroNode $node, $context = NULL)
	{
		return new self($node->tokenizer, $node->modifiers, $context);
	}

	function __construct(NMacroTokenizer $argsTokenizer, $modifiers = NULL, $context = NULL)
	{
		$this->argsTokenizer = $argsTokenizer;
		$this->modifiers = $modifiers;
		$this->context = $context;
	}

	function write($mask)
	{
		$args = func_get_args();
		array_shift($args);
		$word = strpos($mask, '%node.word') === FALSE ? NULL : $this->argsTokenizer->fetchWord();
		$me = $this;
		$mask = NStrings::replace($mask, '#%escape(\(([^()]*+|(?1))+\))#', callback(create_function('$m', 'extract(NCFix::$vars['.NCFix::uses(array('me'=>$me)).'], EXTR_REFS);
			return $me->escape(substr($m[1], 1, -1));
		')));
		$mask = NStrings::replace($mask, '#%modify(\(([^()]*+|(?1))+\))#', callback(create_function('$m', 'extract(NCFix::$vars['.NCFix::uses(array('me'=>$me)).'], EXTR_REFS);
			return $me->formatModifiers(substr($m[1], 1, -1));
		')));

		return NStrings::replace($mask, '#([,+]\s*)?%(node\.word|node\.array|node\.args|var|raw)(\?)?(\s*\+\s*)?()#',
			callback(create_function('$m', 'extract(NCFix::$vars['.NCFix::uses(array('me'=>$me,'word'=> $word, 'args'=>& $args)).'], EXTR_REFS);
			list(, $l, $macro, $cond, $r) = $m;

			switch ($macro) {
			case \'node.word\':
				$code = $me->formatWord($word); break;
			case \'node.args\':
				$code = $me->formatArgs(); break;
			case \'node.array\':
				$code = $me->formatArray();
				$code = $cond && $code === \'array()\' ? \'\' : $code; break;
			case \'var\':
				$code = var_export(array_shift($args), TRUE); break;
			case \'raw\':
				$code = (string) array_shift($args); break;
			}

			if ($cond && $code === \'\') {
				return $r ? $l : $r;
			} else {
				return $l . $code . $r;
			}
		')));
	}

	function formatModifiers($var)
	{
		$modifiers = ltrim($this->modifiers, '|');
		if (!$modifiers) {
			return $var;
		}

		$tokenizer = $this->preprocess(new NMacroTokenizer($modifiers));
		$inside = FALSE;
		while ($token = $tokenizer->fetchToken()) {
			if ($token['type'] === NMacroTokenizer::T_WHITESPACE) {
				$var = rtrim($var) . ' ';

			} elseif (!$inside) {
				if ($token['type'] === NMacroTokenizer::T_SYMBOL) {
					if ($this->context && $token['value'] === 'escape') {
						$var = $this->escape($var);
						$tokenizer->fetch('|');
					} else {
						$var = "\$template->" . $token['value'] . "($var";
						$inside = TRUE;
					}
				} else {
					throw new NLatteException("Modifier name must be alphanumeric string, '$token[value]' given.");
				}
			} else {
				if ($token['value'] === ':' || $token['value'] === ',') {
					$var = $var . ', ';

				} elseif ($token['value'] === '|') {
					$var = $var . ')';
					$inside = FALSE;

				} else {
					$var .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
				}
			}
		}
		return $inside ? "$var)" : $var;
	}

	function formatArgs()
	{
		$out = '';
		$tokenizer = $this->preprocess();
		while ($token = $tokenizer->fetchToken()) {
			$out .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
		}
		return $out;
	}

	function formatArray()
	{
		$out = '';
		$expand = NULL;
		$tokenizer = $this->preprocess();
		while ($token = $tokenizer->fetchToken()) {
			if ($token['value'] === '(expand)' && $token['depth'] === 0) {
				$expand = TRUE;
				$out .= '),';

			} elseif ($expand && ($token['value'] === ',') && !$token['depth']) {
				$expand = FALSE;
				$out .= ', array(';
			} else {
				$out .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
			}
		}
		if ($expand === NULL) {
			return "array($out)";
		} else {
			return "array_merge(array($out" . ($expand ? ', array(' : '') ."))";
		}
	}

	function formatWord($s)
	{
		return (is_numeric($s) || strspn($s, '\'"$') || in_array(strtolower($s), array('true', 'false', 'null')))
			? $s : '"' . $s . '"';
	}

	function canQuote($tokenizer)
	{
		return $tokenizer->isCurrent(NMacroTokenizer::T_SYMBOL)
			&& (!$tokenizer->hasPrev() || $tokenizer->isPrev(',', '(', '[', '=', '=>', ':', '?'))
			&& (!$tokenizer->hasNext() || $tokenizer->isNext(',', ')', ']', '=', '=>', ':', '|'));
	}

	function preprocess(NMacroTokenizer $tokenizer = NULL)
	{
		$tokenizer = $tokenizer === NULL ? $this->argsTokenizer : $tokenizer;
		$inTernary = $prev = NULL;
		$tokens = $arrays = array();
		while ($token = $tokenizer->fetchToken()) {
			$token['depth'] = $depth = count($arrays);

			if ($token['type'] === NMacroTokenizer::T_COMMENT) {
				continue;

			} elseif ($token['type'] === NMacroTokenizer::T_WHITESPACE) {
				$tokens[] = $token;
				continue;
			}

			if ($token['value'] === '?') {
				$inTernary = $depth;

			} elseif ($token['value'] === ':') {
				$inTernary = NULL;

			} elseif ($inTernary === $depth && ($token['value'] === ',' || $token['value'] === ')' || $token['value'] === ']')) {
				$tokens[] = NMacroTokenizer::createToken(':') + array('depth' => $depth);
				$tokens[] = NMacroTokenizer::createToken('null') + array('depth' => $depth);
				$inTernary = NULL;
			}

			if ($token['value'] === '[') {
				if ($arrays[] = $prev['value'] !== ']' && $prev['type'] !== NMacroTokenizer::T_SYMBOL && $prev['type'] !== NMacroTokenizer::T_VARIABLE) {
					$tokens[] = NMacroTokenizer::createToken('array') + array('depth' => $depth);
					$token = NMacroTokenizer::createToken('(');
				}
			} elseif ($token['value'] === ']') {
				if (array_pop($arrays) === TRUE) {
					$token = NMacroTokenizer::createToken(')');
				}
			} elseif ($token['value'] === '(') {
				$arrays[] = '(';

			} elseif ($token['value'] === ')') {
				array_pop($arrays);
			}

			$tokens[] = $prev = $token;
		}

		if ($inTernary !== NULL) {
			$tokens[] = NMacroTokenizer::createToken(':') + array('depth' => count($arrays));
			$tokens[] = NMacroTokenizer::createToken('null') + array('depth' => count($arrays));
		}

		$tokenizer = clone $tokenizer;
		$tokenizer->position = 0;
		$tokenizer->tokens = $tokens;
		return $tokenizer;
	}

	function escape($s)
	{
		switch ($this->context[0]) {
		case NParser::CONTEXT_TEXT:
			return "NTemplateHelpers::escapeHtml($s, ENT_NOQUOTES)";
		case NParser::CONTEXT_TAG:
			return "NTemplateHelpers::escapeHtml($s)";
		case NParser::CONTEXT_ATTRIBUTE:
			list(, $name, $quote) = $this->context;
			$quote = $quote === '"' ? '' : ', ENT_QUOTES';
			if (strncasecmp($name, 'on', 2) === 0) {
				return "htmlSpecialChars(NTemplateHelpers::escapeJs($s)$quote)";
			} elseif ($name === 'style') {
				return "htmlSpecialChars(NTemplateHelpers::escapeCss($s)$quote)";
			} else {
				return "htmlSpecialChars($s$quote)";
			}
		case NParser::CONTEXT_COMMENT:
			return "NTemplateHelpers::escapeHtmlComment($s)";
		case NParser::CONTEXT_CDATA;
			return 'NTemplateHelpers::escape' . ucfirst($this->context[1]) . "($s)";
		case NParser::CONTEXT_NONE:
			switch (isset($this->context[1]) ? $this->context[1] : NULL) {
			case 'xml':
			case 'js':
			case 'css':
				return 'NTemplateHelpers::escape' . ucfirst($this->context[1]) . "($s)";
			case 'text':
				return $s;
			default:
				return "\$template->escape($s)";
			}
		}
	}

}

class NRobotLoader extends NAutoLoader
{

	public $scanDirs;

	public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';

	public $acceptFiles = '*.php, *.php5';

	public $autoRebuild = TRUE;

	private $list = array();

	private $files;

	private $rebuilt = FALSE;

	private $cacheStorage;

	function __construct()
	{
		if (!extension_loaded('tokenizer')) {
			throw new NotSupportedException("PHP extension Tokenizer is not loaded.");
		}
	}

	function register()
	{
		$cache = $this->getCache();
		$key = $this->getKey();
		if (isset($cache[$key])) {
			$this->list = $cache[$key];
		} else {
			$this->rebuild();
		}

		if (isset($this->list[strtolower(__CLASS__)]) && class_exists('NNetteLoader', FALSE)) {
			NNetteLoader::getInstance()->unregister();
		}

		parent::register();
	}

	function tryLoad($type)
	{
		$type = ltrim(strtolower($type), '\\');

		if (isset($this->list[$type][0]) && !is_file($this->list[$type][0])) {
			unset($this->list[$type]);
		}

		if (!isset($this->list[$type])) {
			$trace = debug_backtrace();
			$initiator = & $trace[2]['function'];
			if ($initiator === 'class_exists' || $initiator === 'interface_exists') {
				$this->list[$type] = FALSE;
				if ($this->autoRebuild && $this->rebuilt) {
					$this->getCache()->save($this->getKey(), $this->list, array(
						NCache::CONSTS => 'NFramework::REVISION',
					));
				}
			}

			if ($this->autoRebuild && !$this->rebuilt) {
				$this->rebuild();
			}
		}

		if (isset($this->list[$type][0])) {
			NLimitedScope::load($this->list[$type][0]);
			self::$count++;
		}
	}

	function rebuild()
	{
		$this->getCache()->save($this->getKey(), callback($this, '_rebuildCallback'), array(
			NCache::CONSTS => 'NFramework::REVISION',
		));
		$this->rebuilt = TRUE;
	}

	function _rebuildCallback()
	{
		foreach ($this->list as $pair) {
			if ($pair) {
				$this->files[$pair[0]] = $pair[1];
			}
		}
		foreach (array_unique($this->scanDirs) as $dir) {
			$this->scanDirectory($dir);
		}
		$this->files = NULL;
		return $this->list;
	}

	function getIndexedClasses()
	{
		$res = array();
		foreach ($this->list as $class => $pair) {
			if ($pair) {
				$res[$pair[2]] = $pair[0];
			}
		}
		return $res;
	}

	function addDirectory($path)
	{
		foreach ((array) $path as $val) {
			$real = realpath($val);
			if ($real === FALSE) {
				throw new DirectoryNotFoundException("Directory '$val' not found.");
			}
			$this->scanDirs[] = $real;
		}
		return $this;
	}

	private function addClass($class, $file, $time)
	{
		$lClass = strtolower($class);
		if (isset($this->list[$lClass][0]) && ($file2 = $this->list[$lClass][0]) !== $file && is_file($file2)) {
			if ($this->files[$file2] !== filemtime($file2)) {
				$this->scanScript($file2);
				return $this->addClass($class, $file, $time);
			}
			$e = new InvalidStateException("Ambiguous class '$class' resolution; defined in $file and in " . $this->list[$lClass][0] . ".");
			if (PHP_VERSION_ID < 50300) {
				NDebugger::_exceptionHandler($e);
				exit;
			} else {
				throw $e;
			}
		}
		$this->list[$lClass] = array($file, $time, $class);
		$this->files[$file] = $time;
	}

	private function scanDirectory($dir)
	{
		if (is_dir($dir)) {
			$ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : NStrings::split($this->ignoreDirs, '#[,\s]+#');
			$disallow = array();
			foreach ($ignoreDirs as $item) {
				if ($item = realpath($item)) {
					$disallow[$item] = TRUE;
				}
			}
			$iterator = NFinder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : NStrings::split($this->acceptFiles, '#[,\s]+#'))
				->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS);
					return !isset($disallow[$file->getPathname()]);
				'))
				->from($dir)
				->exclude($ignoreDirs)
				->filter($filter = create_function('$dir', 'extract(NCFix::$vars['.NCFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS);
					$path = $dir->getPathname();
					if (is_file("$path/netterobots.txt")) {
						foreach (file("$path/netterobots.txt") as $s) {
							if ($matches = NStrings::match($s, \'#^disallow\\\\s*:\\\\s*(\\\\S+)#i\')) {
								$disallow[$path . str_replace(\'/\', DIRECTORY_SEPARATOR, rtrim(\'/\' . ltrim($matches[1], \'/\'), \'/\'))] = TRUE;
							}
						}
					}
					return !isset($disallow[$path]);
				'));
			$filter(new SplFileInfo($dir));
		} else {
			$iterator = new ArrayIterator(array(new SplFileInfo($dir)));
		}

		foreach ($iterator as $entry) {
			$path = $entry->getPathname();
			if (!isset($this->files[$path]) || $this->files[$path] !== $entry->getMTime()) {
				$this->scanScript($path);
			}
		}
	}

	private function scanScript($file)
	{
		$T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
		$T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;

		$expected = FALSE;
		$namespace = '';
		$level = $minLevel = 0;
		$time = filemtime($file);
		$s = file_get_contents($file);

		foreach ($this->list as $class => $pair) {
			if ($pair && $pair[0] === $file) {
				unset($this->list[$class]);
			}
		}

		if ($matches = NStrings::match($s, '#//nette'.'loader=(\S*)#')) {
			foreach (explode(',', $matches[1]) as $name) {
				$this->addClass($name, $file, $time);
			}
			return;
		}

		foreach (token_get_all($s) as $token) {
			if (is_array($token)) {
				switch ($token[0]) {
				case T_COMMENT:
				case T_DOC_COMMENT:
				case T_WHITESPACE:
					continue 2;

				case $T_NS_SEPARATOR:
				case T_STRING:
					if ($expected) {
						$name .= $token[1];
					}
					continue 2;

				case $T_NAMESPACE:
				case T_CLASS:
				case T_INTERFACE:
					$expected = $token[0];
					$name = '';
					continue 2;
				case T_CURLY_OPEN:
				case T_DOLLAR_OPEN_CURLY_BRACES:
					$level++;
				}
			}

			if ($expected) {
				switch ($expected) {
				case T_CLASS:
				case T_INTERFACE:
					if ($level === $minLevel) {
						$this->addClass($namespace . $name, $file, $time);
					}
					break;

				case $T_NAMESPACE:
					$namespace = $name ? $name . '\\' : '';
					$minLevel = $token === '{' ? 1 : 0;
				}

				$expected = NULL;
			}

			if ($token === '{') {
				$level++;
			} elseif ($token === '}') {
				$level--;
			}
		}
	}

	function setCacheStorage(ICacheStorage $storage)
	{
		$this->cacheStorage = $storage;
		return $this;
	}

	function getCacheStorage()
	{
		return $this->cacheStorage;
	}

	protected function getCache()
	{
		if (!$this->cacheStorage) {
			trigger_error('Missing cache storage.', E_USER_WARNING);
			$this->cacheStorage = new NDevNullStorage;
		}
		return new NCache($this->cacheStorage, 'Nette.RobotLoader');
	}

	protected function getKey()
	{
		return array($this->ignoreDirs, $this->acceptFiles, $this->scanDirs);
	}

}

class NMailMimePart extends NObject
{

	const ENCODING_BASE64 = 'base64',
		ENCODING_7BIT = '7bit',
		ENCODING_8BIT = '8bit',
		ENCODING_QUOTED_PRINTABLE = 'quoted-printable';

	const EOL = "\r\n";
	const LINE_LENGTH = 76;

	private $headers = array();

	private $parts = array();

	private $body;

	function setHeader($name, $value, $append = FALSE)
	{
		if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
			throw new InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
		}

		if ($value == NULL) {
			if (!$append) {
				unset($this->headers[$name]);
			}

		} elseif (is_array($value)) {
			$tmp = & $this->headers[$name];
			if (!$append || !is_array($tmp)) {
				$tmp = array();
			}

			foreach ($value as $email => $name) {
				if ($name !== NULL && !NStrings::checkEncoding($name)) {
					throw new InvalidArgumentException("Name is not valid UTF-8 string.");
				}

				if (!preg_match('#^[^@",\s]+@[^@",\s]+\.[a-z]{2,10}$#i', $email)) {
					throw new InvalidArgumentException("Email address '$email' is not valid.");
				}

				if (preg_match('#[\r\n]#', $name)) {
					throw new InvalidArgumentException("Name must not contain line separator.");
				}
				$tmp[$email] = $name;
			}

		} else {
			$value = (string) $value;
			if (!NStrings::checkEncoding($value)) {
				throw new InvalidArgumentException("Header is not valid UTF-8 string.");
			}
			$this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
		}
		return $this;
	}

	function getHeader($name)
	{
		return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
	}

	function clearHeader($name)
	{
		unset($this->headers[$name]);
		return $this;
	}

	function getEncodedHeader($name)
	{
		$offset = strlen($name) + 2;

		if (!isset($this->headers[$name])) {
			return NULL;

		} elseif (is_array($this->headers[$name])) {
			$s = '';
			foreach ($this->headers[$name] as $email => $name) {
				if ($name != NULL) {
					$s .= self::encodeHeader(
						strpbrk($name, '.,;<@>()[]"=?') ? '"' . addcslashes($name, '"\\') . '"' : $name,
						$offset
					);
					$email = " <$email>";
				}
				$email .= ',';
				if ($s !== '' && $offset + strlen($email) > self::LINE_LENGTH) {
					$s .= self::EOL . "\t";
					$offset = 1;
				}
				$s .= $email;
				$offset += strlen($email);
			}
			return substr($s, 0, -1);

		} else {
			return self::encodeHeader($this->headers[$name], $offset);
		}
	}

	function getHeaders()
	{
		return $this->headers;
	}

	function setContentType($contentType, $charset = NULL)
	{
		$this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
		return $this;
	}

	function setEncoding($encoding)
	{
		$this->setHeader('Content-Transfer-Encoding', $encoding);
		return $this;
	}

	function getEncoding()
	{
		return $this->getHeader('Content-Transfer-Encoding');
	}

	function addPart(NMailMimePart $part = NULL)
	{
		return $this->parts[] = $part === NULL ? new self : $part;
	}

	function setBody($body)
	{
		$this->body = $body;
		return $this;
	}

	function getBody()
	{
		return $this->body;
	}

	function generateMessage()
	{
		$output = '';
		$boundary = '--------' . NStrings::random();

		foreach ($this->headers as $name => $value) {
			$output .= $name . ': ' . $this->getEncodedHeader($name);
			if ($this->parts && $name === 'Content-Type') {
				$output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
			}
			$output .= self::EOL;
		}
		$output .= self::EOL;

		$body = (string) $this->body;
		if ($body !== '') {
			switch ($this->getEncoding()) {
			case self::ENCODING_QUOTED_PRINTABLE:
				$output .= function_exists('quoted_printable_encode') ? quoted_printable_encode($body) : self::encodeQuotedPrintable($body);
				break;

			case self::ENCODING_BASE64:
				$output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
				break;

			case self::ENCODING_7BIT:
				$body = preg_replace('#[\x80-\xFF]+#', '', $body);

			case self::ENCODING_8BIT:
				$body = str_replace(array("\x00", "\r"), '', $body);
				$body = str_replace("\n", self::EOL, $body);
				$output .= $body;
				break;

			default:
				throw new InvalidStateException('Unknown encoding.');
			}
		}

		if ($this->parts) {
			if (substr($output, -strlen(self::EOL)) !== self::EOL) {
				$output .= self::EOL;
			}
			foreach ($this->parts as $part) {
				$output .= '--' . $boundary . self::EOL . $part->generateMessage() . self::EOL;
			}
			$output .= '--' . $boundary.'--';
		}

		return $output;
	}

	private static function encodeHeader($s, & $offset = 0)
	{
		$o = '';
		if ($offset >= 55) {
			$o = self::EOL . "\t";
			$offset = 1;
		}

		if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}=? _\r\n\t") === strlen($s)
			&& ($offset + strlen($s) <= self::LINE_LENGTH)
		) {
			$offset += strlen($s);
			return $o . $s;
		}

		$o .= str_replace("\n ", "\n\t", substr(iconv_mime_encode(str_repeat(' ', $offset), $s, array(
			'scheme' => 'B',
			'input-charset' => 'UTF-8',
			'output-charset' => 'UTF-8',
		)), $offset + 2));

		$offset = strlen($o) - strrpos($o, "\n");
		return $o;
	}

	static function encodeQuotedPrintable($s)
	{
		$range = '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}';
		$pos = 0;
		$len = 0;
		$o = '';
		$size = strlen($s);
		while ($pos < $size) {
			if ($l = strspn($s, $range, $pos)) {
				while ($len + $l > self::LINE_LENGTH - 1) {
					$lx = self::LINE_LENGTH - $len - 1;
					$o .= substr($s, $pos, $lx) . '=' . self::EOL;
					$pos += $lx;
					$l -= $lx;
					$len = 0;
				}
				$o .= substr($s, $pos, $l);
				$len += $l;
				$pos += $l;

			} else {
				$len += 3;
				if ($len > self::LINE_LENGTH - 1) {
					$o .= '=' . self::EOL;
					$len = 3;
				}
				$o .= '=' . strtoupper(bin2hex($s[$pos]));
				$pos++;
			}
		}
		return rtrim($o, '=' . self::EOL);
	}

}

class NMail extends NMailMimePart
{

	const HIGH = 1,
		NORMAL = 3,
		LOW = 5;

	public static $defaultMailer = 'NSendmailMailer';

	public static $defaultHeaders = array(
		'MIME-Version' => '1.0',
		'X-Mailer' => 'Nette Framework',
	);

	private $mailer;

	private $attachments = array();

	private $inlines = array();

	private $html;

	private $basePath;

	function __construct()
	{
		foreach (self::$defaultHeaders as $name => $value) {
			$this->setHeader($name, $value);
		}
		$this->setHeader('Date', date('r'));
	}

	function setFrom($email, $name = NULL)
	{
		$this->setHeader('From', $this->formatEmail($email, $name));
		return $this;
	}

	function getFrom()
	{
		return $this->getHeader('From');
	}

	function addReplyTo($email, $name = NULL)
	{
		$this->setHeader('Reply-To', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	function setSubject($subject)
	{
		$this->setHeader('Subject', $subject);
		return $this;
	}

	function getSubject()
	{
		return $this->getHeader('Subject');
	}

	function addTo($email, $name = NULL)
	{
		$this->setHeader('To', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	function addCc($email, $name = NULL)
	{
		$this->setHeader('Cc', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	function addBcc($email, $name = NULL)
	{
		$this->setHeader('Bcc', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	private function formatEmail($email, $name)
	{
		if (!$name && preg_match('#^(.+) +<(.*)>$#', $email, $matches)) {
			return array($matches[2] => $matches[1]);
		} else {
			return array($email => $name);
		}
	}

	function setReturnPath($email)
	{
		$this->setHeader('Return-Path', $email);
		return $this;
	}

	function getReturnPath()
	{
		return $this->getHeader('From');
	}

	function setPriority($priority)
	{
		$this->setHeader('X-Priority', (int) $priority);
		return $this;
	}

	function getPriority()
	{
		return $this->getHeader('X-Priority');
	}

	function setHtmlBody($html, $basePath = NULL)
	{
		$this->html = $html;
		$this->basePath = $basePath;
		return $this;
	}

	function getHtmlBody()
	{
		return $this->html;
	}

	function addEmbeddedFile($file, $content = NULL, $contentType = NULL)
	{
		return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
			->setHeader('Content-ID', $this->getRandomId());
	}

	function addAttachment($file, $content = NULL, $contentType = NULL)
	{
		return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
	}

	private function createAttachment($file, $content, $contentType, $disposition)
	{
		$part = new NMailMimePart;
		if ($content === NULL) {
			$content = file_get_contents($file);
			if ($content === FALSE) {
				throw new FileNotFoundException("Unable to read file '$file'.");
			}
		} else {
			$content = (string) $content;
		}
		$part->setBody($content);
		$part->setContentType($contentType ? $contentType : NMimeTypeDetector::fromString($content));
		$part->setEncoding(preg_match('#(multipart|message)/#A', $contentType) ? self::ENCODING_8BIT : self::ENCODING_BASE64);
		$part->setHeader('Content-Disposition', $disposition . '; filename="' . NStrings::fixEncoding(basename($file)) . '"');
		return $part;
	}

	function send()
	{
		$this->getMailer()->send($this->build());
	}

	function setMailer(IMailer $mailer)
	{
		$this->mailer = $mailer;
		return $this;
	}

	function getMailer()
	{
		if ($this->mailer === NULL) {
			$this->mailer = is_object(self::$defaultMailer) ? self::$defaultMailer : new self::$defaultMailer;
		}
		return $this->mailer;
	}

	function generateMessage()
	{
		if ($this->getHeader('Message-ID')) {
			return parent::generateMessage();
		} else {
			return $this->build()->generateMessage();
		}
	}

	protected function build()
	{
		$mail = clone $this;
		$mail->setHeader('Message-ID', $this->getRandomId());

		$mail->buildHtml();
		$mail->buildText();

		$cursor = $mail;
		if ($mail->attachments) {
			$tmp = $cursor->setContentType('multipart/mixed');
			$cursor = $cursor->addPart();
			foreach ($mail->attachments as $value) {
				$tmp->addPart($value);
			}
		}

		if ($mail->html != NULL) {
			$tmp = $cursor->setContentType('multipart/alternative');
			$cursor = $cursor->addPart();
			$alt = $tmp->addPart();
			if ($mail->inlines) {
				$tmp = $alt->setContentType('multipart/related');
				$alt = $alt->addPart();
				foreach ($mail->inlines as $name => $value) {
					$tmp->addPart($value);
				}
			}
			$alt->setContentType('text/html', 'UTF-8')
				->setEncoding(preg_match('#\S{990}#', $mail->html)
					? self::ENCODING_QUOTED_PRINTABLE
					: (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
				->setBody($mail->html);
		}

		$text = $mail->getBody();
		$mail->setBody(NULL);
		$cursor->setContentType('text/plain', 'UTF-8')
			->setEncoding(preg_match('#\S{990}#', $text)
				? self::ENCODING_QUOTED_PRINTABLE
				: (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
			->setBody($text);

		return $mail;
	}

	protected function buildHtml()
	{
		if ($this->html instanceof ITemplate) {
			$this->html->mail = $this;
			if ($this->basePath === NULL && $this->html instanceof IFileTemplate) {
				$this->basePath = dirname($this->html->getFile());
			}
			$this->html = $this->html->__toString(TRUE);
		}

		if ($this->basePath !== FALSE) {
			$cids = array();
			$matches = NStrings::matchAll(
				$this->html,
				'#(src\s*=\s*|background\s*=\s*|url\()(["\'])(?![a-z]+:|[/\\#])(.+?)\\2#i',
				PREG_OFFSET_CAPTURE
			);
			foreach (array_reverse($matches) as $m) {
				$file = rtrim($this->basePath, '/\\') . '/' . $m[3][0];
				if (!isset($cids[$file])) {
					$cids[$file] = substr($this->addEmbeddedFile($file)->getHeader("Content-ID"), 1, -1);
				}
				$this->html = substr_replace($this->html,
					"{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}{$m[2][0]}",
					$m[0][1], strlen($m[0][0])
				);
			}
		}

		if (!$this->getSubject() && $matches = NStrings::match($this->html, '#<title>(.+?)</title>#is')) {
			$this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
		}
	}

	protected function buildText()
	{
		$text = $this->getBody();
		if ($text instanceof ITemplate) {
			$text->mail = $this;
			$this->setBody($text->__toString(TRUE));

		} elseif ($text == NULL && $this->html != NULL) {
			$text = NStrings::replace($this->html, array(
				'#<(style|script|head).*</\\1>#Uis' => '',
				'#<t[dh][ >]#i' => " $0",
				'#[\r\n]+#' => ' ',
				'#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
			));
			$text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
			$text = NStrings::replace($text, '#[ \t]+#', ' ');
			$this->setBody(trim($text));
		}
	}

	private function getRandomId()
	{
		return '<' . NStrings::random() . '@' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
			: (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'))
			. '>';
	}

}

class NSendmailMailer extends NObject implements IMailer
{

	public $commandArgs;

	function send(NMail $mail)
	{
		$tmp = clone $mail;
		$tmp->setHeader('Subject', NULL);
		$tmp->setHeader('To', NULL);

		$parts = explode(NMail::EOL . NMail::EOL, $tmp->generateMessage(), 2);

		NDebugger::tryError();
		$args = array(
			str_replace(NMail::EOL, PHP_EOL, $mail->getEncodedHeader('To')),
			str_replace(NMail::EOL, PHP_EOL, $mail->getEncodedHeader('Subject')),
			str_replace(NMail::EOL, PHP_EOL, $parts[1]),
			str_replace(NMail::EOL, PHP_EOL, $parts[0]),
		);
		if ($this->commandArgs) {
			$args[] = (string) $this->commandArgs;
		}
		$res = call_user_func_array('mail', $args);

		if (NDebugger::catchError($e)) {
			throw new InvalidStateException('mail(): ' . $e->getMessage(), 0, $e);

		} elseif (!$res) {
			throw new InvalidStateException('Unable to send email.');
		}
	}

}

class NSmtpMailer extends NObject implements IMailer
{

	private $connection;

	private $host;

	private $port;

	private $username;

	private $password;

	private $secure;

	private $timeout;

	function __construct(array $options = array())
	{
		if (isset($options['host'])) {
			$this->host = $options['host'];
			$this->port = isset($options['port']) ? (int) $options['port'] : NULL;
		} else {
			$this->host = ini_get('SMTP');
			$this->port = (int) ini_get('smtp_port');
		}
		$this->username = isset($options['username']) ? $options['username'] : '';
		$this->password = isset($options['password']) ? $options['password'] : '';
		$this->secure = isset($options['secure']) ? $options['secure'] : '';
		$this->timeout = isset($options['timeout']) ? (int) $options['timeout'] : 20;
		if (!$this->port) {
			$this->port = $this->secure === 'ssl' ? 465 : 25;
		}
	}

	function send(NMail $mail)
	{
		$data = $mail->generateMessage();

		$this->connect();

		$from = $mail->getHeader('From');
		if ($from) {
			$from = array_keys($from);
			$this->write("MAIL FROM:<$from[0]>", 250);
		}

		foreach (array_merge(
			(array) $mail->getHeader('To'),
			(array) $mail->getHeader('Cc'),
			(array) $mail->getHeader('Bcc')
		) as $email => $name) {
			$this->write("RCPT TO:<$email>", array(250, 251));
		}

		$this->write('DATA', 354);
		$data = preg_replace('#^\.#m', '..', $data);
		$this->write($data);
		$this->write('.', 250);

		$this->write('QUIT', 221);

		$this->disconnect();
	}

	private function connect()
	{
		$this->connection = @fsockopen(
			($this->secure === 'ssl' ? 'ssl://' : '') . $this->host,
			$this->port, $errno, $error, $this->timeout
		);
		if (!$this->connection) {
			throw new NSmtpException($error, $errno);
		}
		stream_set_timeout($this->connection, $this->timeout, 0);
		$this->read();

		$self = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';
		$this->write("EHLO $self");
		if ((int) $this->read() !== 250) {
			$this->write("HELO $self", 250);
		}

		if ($this->secure === 'tls') {
			$this->write('STARTTLS', 220);
			if (!stream_socket_enable_crypto($this->connection, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
				throw new NSmtpException('Unable to connect via TLS.');
			}
		}

		if ($this->username != NULL && $this->password != NULL) {
			$this->write('AUTH LOGIN', 334);
			$this->write(base64_encode($this->username), 334, 'username');
			$this->write(base64_encode($this->password), 235, 'password');
		}
	}

	private function disconnect()
	{
		fclose($this->connection);
		$this->connection = NULL;
	}

	private function write($line, $expectedCode = NULL, $message = NULL)
	{
		fwrite($this->connection, $line . NMail::EOL);
		if ($expectedCode && !in_array((int) $this->read(), (array) $expectedCode)) {
			throw new NSmtpException('SMTP server did not accept ' . ($message ? $message : $line));
		}
	}

	private function read()
	{
		$s = '';
		while (($line = fgets($this->connection, 1e3)) != NULL) {
			$s .= $line;
			if (substr($line, 3, 1) === ' ') {
				break;
			}
		}
		return $s;
	}

}

class NSmtpException extends Exception
{
}

class NAnnotation extends NObject implements IAnnotation
{

	function __construct(array $values)
	{
		foreach ($values as $k => $v) {
			$this->$k = $v;
		}
	}

	function __toString()
	{
		return $this->value;
	}

}

/**
 * Annotations support for PHP.
 *
 * @author     David Grudl
 * @Annotation
 */
final class NAnnotationsParser
{

	const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';

	const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-]*';

	public static $useReflection;

	public static $inherited = array('description', 'param', 'return');

	private static $cache;

	private static $timestamps;

	private static $cacheStorage;

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function getAll(Reflector $r)
	{
		if ($r instanceof ReflectionClass) {
			$type = $r->getName();
			$member = '';

		} elseif ($r instanceof ReflectionMethod) {
			$type = $r->getDeclaringClass()->getName();
			$member = $r->getName();

		} else {
			$type = $r->getDeclaringClass()->getName();
			$member = '$' . $r->getName();
		}

		if (!self::$useReflection) {
			$file = $r instanceof ReflectionClass ? $r->getFileName() : $r->getDeclaringClass()->getFileName();
			if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
				unset(self::$cache[$type]);
			}
			unset(self::$timestamps[$file]);
		}

		if (isset(self::$cache[$type][$member])) {
			return self::$cache[$type][$member];
		}

		if (self::$useReflection === NULL) {
			self::$useReflection = (bool) NClassReflection::from(__CLASS__)->getDocComment();
		}

		if (self::$useReflection) {
			$annotations = self::parseComment($r->getDocComment());

		} else {
			if (!self::$cacheStorage) {

				self::$cacheStorage = new NDevNullStorage;
			}
			$outerCache = new NCache(self::$cacheStorage, 'Nette.Reflection.Annotations');

			if (self::$cache === NULL) {
				self::$cache = (array) $outerCache->offsetGet('list');
				self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
			}

			if (!isset(self::$cache[$type]) && $file) {
				self::$cache['*'][$file] = filemtime($file);
				self::parseScript($file);
				$outerCache->save('list', self::$cache);
			}

			if (isset(self::$cache[$type][$member])) {
				$annotations = self::$cache[$type][$member];
			} else {
				$annotations = array();
			}
		}

		if ($r instanceof ReflectionMethod && !$r->isPrivate()
			&& (!$r->isConstructor() || !empty($annotations['inheritdoc'][0])))
		{
			try {
				$inherited = self::getAll(new ReflectionMethod(get_parent_class($type), $member));
			} catch (ReflectionException $e) {
				try {
					$inherited = self::getAll($r->getPrototype());
				} catch (ReflectionException $e) {
					$inherited = array();
				}
			}
			$annotations += array_intersect_key($inherited, array_flip(self::$inherited));
		}

		return self::$cache[$type][$member] = $annotations;
	}

	private static function parseComment($comment)
	{
		static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);

		$res = array();
		$comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
		$parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);

		$description = trim($parts[0]);
		if ($description !== '') {
			$res['description'] = array($description);
		}

		$matches = NStrings::matchAll(
			isset($parts[1]) ? $parts[1] : '',
			'~
				(?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]*      ##  annotation
				(
					\((?>'.self::RE_STRING.'|[^\'")@]+)+\)|  ##  (value)
					[^(@\r\n][^@\r\n]*|)                     ##  value
			~xi'
		);

		foreach ($matches as $match) {
			list(, $name, $value) = $match;

			if (substr($value, 0, 1) === '(') {
				$items = array();
				$key = '';
				$val = TRUE;
				$value[0] = ',';
				while ($m = NStrings::match(
					$value,
					'#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
				) {
					$value = substr($value, strlen($m[0]));
					list(, $key, $val) = $m;
					if ($val[0] === "'" || $val[0] === '"') {
						$val = substr($val, 1, -1);

					} elseif (is_numeric($val)) {
						$val = 1 * $val;

					} else {
						$lval = strtolower($val);
						$val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
					}

					if ($key === '') {
						$items[] = $val;

					} else {
						$items[$key] = $val;
					}
				}

				$value = count($items) < 2 && $key === '' ? $val : $items;

			} else {
				$value = trim($value);
				if (is_numeric($value)) {
					$value = 1 * $value;

				} else {
					$lval = strtolower($value);
					$value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
				}
			}

			$class = $name . 'Annotation';
			if (class_exists($class)) {
				$res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));

			} else {
				$res[$name][] = is_array($value) ? new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS) : $value;
			}
		}

		return $res;
	}

	private static function parseScript($file)
	{
		$T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
		$T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;

		$s = file_get_contents($file);

		if (NStrings::match($s, '#//nette'.'loader=(\S*)#')) {
			return;
		}

		$expected = $namespace = $class = $docComment = NULL;
		$level = $classLevel = 0;

		foreach (token_get_all($s) as $token) {

			if (is_array($token)) {
				switch ($token[0]) {
				case T_DOC_COMMENT:
					$docComment = $token[1];
				case T_WHITESPACE:
				case T_COMMENT:
					continue 2;

				case T_STRING:
				case $T_NS_SEPARATOR:
				case T_VARIABLE:
					if ($expected) {
						$name .= $token[1];
					}
					continue 2;

				case T_FUNCTION:
				case T_VAR:
				case T_PUBLIC:
				case T_PROTECTED:
				case $T_NAMESPACE:
				case T_CLASS:
				case T_INTERFACE:
					$expected = $token[0];
					$name = NULL;
					continue 2;

				case T_STATIC:
				case T_ABSTRACT:
				case T_FINAL:
					continue 2;

				case T_CURLY_OPEN:
				case T_DOLLAR_OPEN_CURLY_BRACES:
					$level++;
				}
			}

			if ($expected) {
				switch ($expected) {
				case T_CLASS:
				case T_INTERFACE:
					$class = $namespace . $name;
					$classLevel = $level;
					$name = '';

				case T_FUNCTION:
					if ($token === '&') {
						continue 2;
					}
				case T_VAR:
				case T_PUBLIC:
				case T_PROTECTED:
					if ($class && $name !== NULL && $docComment) {
						self::$cache[$class][$name] = self::parseComment($docComment);
					}
					break;

				case $T_NAMESPACE:
					$namespace = $name . '\\';
				}

				$expected = $docComment = NULL;
			}

			if ($token === ';') {
				$docComment = NULL;
			} elseif ($token === '{') {
				$docComment = NULL;
				$level++;
			} elseif ($token === '}') {
				$level--;
				if ($level === $classLevel) {
					$class = NULL;
				}
			}
		}
	}

	static function setCacheStorage(ICacheStorage $storage)
	{
		self::$cacheStorage = $storage;
	}

	static function getCacheStorage()
	{
		return self::$cacheStorage;
	}

}

class NExtensionReflection extends ReflectionExtension
{

	function __toString()
	{
		return 'Extension ' . $this->getName();
	}

	function getClasses()
	{
		$res = array();
		foreach (parent::getClassNames() as $val) {
			$res[$val] = new NClassReflection($val);
		}
		return $res;
	}

	function getFunctions()
	{
		foreach ($res = parent::getFunctions() as $key => $val) {
			$res[$key] = new NFunctionReflection($key);
		}
		return $res;
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NFunctionReflection extends ReflectionFunction
{

	private $value;

	function __construct($name)
	{
		parent::__construct($this->value = $name);
	}

	function getDefaultParameters()
	{
		return NMethodReflection::buildDefaultParameters(parent::getParameters());
	}

	function invokeNamedArgs($args)
	{
		return $this->invokeArgs(NMethodReflection::combineArgs($this->getDefaultParameters(), $args));
	}

	function toCallback()
	{
		return new NCallback($this->value);
	}

	function __toString()
	{
		return 'Function ' . $this->getName() . '()';
	}

	function getClosure()
	{
		return $this->isClosure() ? $this->value : NULL;
	}

	function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new NExtensionReflection($name) : NULL;
	}

	function getParameters()
	{
		foreach ($res = parent::getParameters() as $key => $val) {
			$res[$key] = new NParameterReflection($this->value, $val->getName());
		}
		return $res;
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NMethodReflection extends ReflectionMethod
{

	static function from($class, $method)
	{
		return new self(is_object($class) ? get_class($class) : $class, $method);
	}

	function getDefaultParameters()
	{
		return self::buildDefaultParameters(parent::getParameters());
	}

	function invokeNamedArgs($object, $args)
	{
		return $this->invokeArgs($object, self::combineArgs($this->getDefaultParameters(), $args));
	}

	function toCallback()
	{
		return new NCallback(parent::getDeclaringClass()->getName(), $this->getName());
	}

	function __toString()
	{
		return 'Method ' . parent::getDeclaringClass()->getName() . '::' . $this->getName() . '()';
	}

	function getDeclaringClass()
	{
		return new NClassReflection(parent::getDeclaringClass()->getName());
	}

	function getPrototype()
	{
		$prototype = parent::getPrototype();
		return new NMethodReflection($prototype->getDeclaringClass()->getName(), $prototype->getName());
	}

	function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new NExtensionReflection($name) : NULL;
	}

	function getParameters()
	{
		$me = array(parent::getDeclaringClass()->getName(), $this->getName());
		foreach ($res = parent::getParameters() as $key => $val) {
			$res[$key] = new NParameterReflection($me, $val->getName());
		}
		return $res;
	}

	function hasAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	function getAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	function getAnnotations()
	{
		return NAnnotationsParser::getAll($this);
	}

	function getDescription()
	{
		return $this->getAnnotation('description');
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

	static function buildDefaultParameters($params)
	{
		$res = array();
		foreach ($params as $param) {
			$res[$param->getName()] = $param->isDefaultValueAvailable()
				? $param->getDefaultValue()
				: NULL;

			if ($param->isArray()) {
				settype($res[$param->getName()], 'array');
			}
		}
		return $res;
	}

	static function combineArgs($params, $args)
	{
		$res = array();
		$i = 0;
		foreach ($params as $name => $def) {
			if (isset($args[$name])) {
				$val = $args[$name];
				if ($def !== NULL) {
					settype($val, gettype($def));
				}
				$res[$i++] = $val;
			} else {
				$res[$i++] = $def;
			}
		}
		return $res;
	}

}

class NParameterReflection extends ReflectionParameter
{

	private $function;

	function __construct($function, $parameter)
	{
		parent::__construct($this->function = $function, $parameter);
	}

	function getClass()
	{
		return ($ref = parent::getClass()) ? new NClassReflection($ref->getName()) : NULL;
	}

	function getClassName()
	{
		return ($tmp = NStrings::match($this, '#>\s+([a-z0-9_\\\\]+)#i')) ? $tmp[1] : NULL;
	}

	function getDeclaringClass()
	{
		return ($ref = parent::getDeclaringClass()) ? new NClassReflection($ref->getName()) : NULL;
	}

	function getDeclaringFunction()
	{
		return is_array($this->function)
			? new NMethodReflection($this->function[0], $this->function[1])
			: new NFunctionReflection($this->function);
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NPropertyReflection extends ReflectionProperty
{

	function __toString()
	{
		return 'Property ' . parent::getDeclaringClass()->getName() . '::$' . $this->getName();
	}

	function getDeclaringClass()
	{
		return new NClassReflection(parent::getDeclaringClass()->getName());
	}

	function hasAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	function getAnnotation($name)
	{
		$res = NAnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	function getAnnotations()
	{
		return NAnnotationsParser::getAll($this);
	}

	function getDescription()
	{
		return $this->getAnnotation('description');
	}

	function getReflection()
	{
		return new NClassReflection($this);
	}

	function __call($name, $args)
	{
		return NObjectMixin::call($this, $name, $args);
	}

	function &__get($name)
	{
		return NObjectMixin::get($this, $name);
	}

	function __set($name, $value)
	{
		return NObjectMixin::set($this, $name, $value);
	}

	function __isset($name)
	{
		return NObjectMixin::has($this, $name);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NAuthenticationException extends Exception
{
}

class NIdentity extends NFreezableObject implements IIdentity
{

	private $id;

	private $roles;

	private $data;

	function __construct($id, $roles = NULL, $data = NULL)
	{
		$this->setId($id);
		$this->setRoles((array) $roles);
		$this->data = $data instanceof Traversable ? iterator_to_array($data) : (array) $data;
	}

	function setId($id)
	{
		$this->updating();
		$this->id = is_numeric($id) ? 1 * $id : $id;
		return $this;
	}

	function getId()
	{
		return $this->id;
	}

	function setRoles(array $roles)
	{
		$this->updating();
		$this->roles = $roles;
		return $this;
	}

	function getRoles()
	{
		return $this->roles;
	}

	function getData()
	{
		return $this->data;
	}

	function __set($key, $value)
	{
		$this->updating();
		if (parent::__isset($key)) {
			parent::__set($key, $value);

		} else {
			$this->data[$key] = $value;
		}
	}

	function &__get($key)
	{
		if (parent::__isset($key)) {
			return parent::__get($key);

		} else {
			return $this->data[$key];
		}
	}

	function __isset($key)
	{
		return isset($this->data[$key]) || parent::__isset($key);
	}

	function __unset($name)
	{
		NObjectMixin::remove($this, $name);
	}

}

class NPermission extends NObject implements IAuthorizator
{

	private $roles = array();

	private $resources = array();

	private $rules = array(
		'allResources' => array(
			'allRoles' => array(
				'allPrivileges' => array(
					'type' => self::DENY,
					'assert' => NULL,
				),
				'byPrivilege' => array(),
			),
			'byRole' => array(),
		),
		'byResource' => array(),
	);

	private $queriedRole, $queriedResource;

	function addRole($role, $parents = NULL)
	{
		$this->checkRole($role, FALSE);
		if (isset($this->roles[$role])) {
			throw new InvalidStateException("Role '$role' already exists in the list.");
		}

		$roleParents = array();

		if ($parents !== NULL) {
			if (!is_array($parents)) {
				$parents = array($parents);
			}

			foreach ($parents as $parent) {
				$this->checkRole($parent);
				$roleParents[$parent] = TRUE;
				$this->roles[$parent]['children'][$role] = TRUE;
			}
		}

		$this->roles[$role] = array(
			'parents'  => $roleParents,
			'children' => array(),
		);

		return $this;
	}

	function hasRole($role)
	{
		$this->checkRole($role, FALSE);
		return isset($this->roles[$role]);
	}

	private function checkRole($role, $need = TRUE)
	{
		if (!is_string($role) || $role === '') {
			throw new InvalidArgumentException("Role must be a non-empty string.");

		} elseif ($need && !isset($this->roles[$role])) {
			throw new InvalidStateException("Role '$role' does not exist.");
		}
	}

	function getRoleParents($role)
	{
		$this->checkRole($role);
		return array_keys($this->roles[$role]['parents']);
	}

	function roleInheritsFrom($role, $inherit, $onlyParents = FALSE)
	{
		$this->checkRole($role);
		$this->checkRole($inherit);

		$inherits = isset($this->roles[$role]['parents'][$inherit]);

		if ($inherits || $onlyParents) {
			return $inherits;
		}

		foreach ($this->roles[$role]['parents'] as $parent => $foo) {
			if ($this->roleInheritsFrom($parent, $inherit)) {
				return TRUE;
			}
		}

		return FALSE;
	}

	function removeRole($role)
	{
		$this->checkRole($role);

		foreach ($this->roles[$role]['children'] as $child => $foo) {
			unset($this->roles[$child]['parents'][$role]);
		}

		foreach ($this->roles[$role]['parents'] as $parent => $foo) {
			unset($this->roles[$parent]['children'][$role]);
		}

		unset($this->roles[$role]);

		foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
			if ($role === $roleCurrent) {
				unset($this->rules['allResources']['byRole'][$roleCurrent]);
			}
		}

		foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
			if (isset($visitor['byRole'])) {
				foreach ($visitor['byRole'] as $roleCurrent => $rules) {
					if ($role === $roleCurrent) {
						unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
					}
				}
			}
		}

		return $this;
	}

	function removeAllRoles()
	{
		$this->roles = array();

		foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
			unset($this->rules['allResources']['byRole'][$roleCurrent]);
		}

		foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
			foreach ($visitor['byRole'] as $roleCurrent => $rules) {
				unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
			}
		}

		return $this;
	}

	function addResource($resource, $parent = NULL)
	{
		$this->checkResource($resource, FALSE);

		if (isset($this->resources[$resource])) {
			throw new InvalidStateException("Resource '$resource' already exists in the list.");
		}

		if ($parent !== NULL) {
			$this->checkResource($parent);
			$this->resources[$parent]['children'][$resource] = TRUE;
		}

		$this->resources[$resource] = array(
			'parent'   => $parent,
			'children' => array()
		);

		return $this;
	}

	function hasResource($resource)
	{
		$this->checkResource($resource, FALSE);
		return isset($this->resources[$resource]);
	}

	private function checkResource($resource, $need = TRUE)
	{
		if (!is_string($resource) || $resource === '') {
			throw new InvalidArgumentException("Resource must be a non-empty string.");

		} elseif ($need && !isset($this->resources[$resource])) {
			throw new InvalidStateException("Resource '$resource' does not exist.");
		}
	}

	function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE)
	{
		$this->checkResource($resource);
		$this->checkResource($inherit);

		if ($this->resources[$resource]['parent'] === NULL) {
			return FALSE;
		}

		$parent = $this->resources[$resource]['parent'];
		if ($inherit === $parent) {
			return TRUE;

		} elseif ($onlyParent) {
			return FALSE;
		}

		while ($this->resources[$parent]['parent'] !== NULL) {
			$parent = $this->resources[$parent]['parent'];
			if ($inherit === $parent) {
				return TRUE;
			}
		}

		return FALSE;
	}

	function removeResource($resource)
	{
		$this->checkResource($resource);

		$parent = $this->resources[$resource]['parent'];
		if ($parent !== NULL) {
			unset($this->resources[$parent]['children'][$resource]);
		}

		$removed = array($resource);
		foreach ($this->resources[$resource]['children'] as $child => $foo) {
			$this->removeResource($child);
			$removed[] = $child;
		}

		foreach ($removed as $resourceRemoved) {
			foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
				if ($resourceRemoved === $resourceCurrent) {
					unset($this->rules['byResource'][$resourceCurrent]);
				}
			}
		}

		unset($this->resources[$resource]);
		return $this;
	}

	function removeAllResources()
	{
		foreach ($this->resources as $resource => $foo) {
			foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
				if ($resource === $resourceCurrent) {
					unset($this->rules['byResource'][$resourceCurrent]);
				}
			}
		}

		$this->resources = array();
		return $this;
	}

	function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
	{
		$this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion);
		return $this;
	}

	function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
	{
		$this->setRule(TRUE, self::DENY, $roles, $resources, $privileges, $assertion);
		return $this;
	}

	function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
	{
		$this->setRule(FALSE, self::ALLOW, $roles, $resources, $privileges);
		return $this;
	}

	function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
	{
		$this->setRule(FALSE, self::DENY, $roles, $resources, $privileges);
		return $this;
	}

	protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = NULL)
	{

		if ($roles === self::ALL) {
			$roles = array(self::ALL);

		} else {
			if (!is_array($roles)) {
				$roles = array($roles);
			}

			foreach ($roles as $role) {
				$this->checkRole($role);
			}
		}

		if ($resources === self::ALL) {
			$resources = array(self::ALL);

		} else {
			if (!is_array($resources)) {
				$resources = array($resources);
			}

			foreach ($resources as $resource) {
				$this->checkResource($resource);
			}
		}

		if ($privileges === self::ALL) {
			$privileges = array();

		} elseif (!is_array($privileges)) {
			$privileges = array($privileges);
		}

		$assertion = $assertion ? callback($assertion) : NULL;

		if ($toAdd) {
			foreach ($resources as $resource) {
				foreach ($roles as $role) {
					$rules = & $this->getRules($resource, $role, TRUE);
					if (count($privileges) === 0) {
						$rules['allPrivileges']['type'] = $type;
						$rules['allPrivileges']['assert'] = $assertion;
						if (!isset($rules['byPrivilege'])) {
							$rules['byPrivilege'] = array();
						}
					} else {
						foreach ($privileges as $privilege) {
							$rules['byPrivilege'][$privilege]['type'] = $type;
							$rules['byPrivilege'][$privilege]['assert'] = $assertion;
						}
					}
				}
			}

		} else {
			foreach ($resources as $resource) {
				foreach ($roles as $role) {
					$rules = & $this->getRules($resource, $role);
					if ($rules === NULL) {
						continue;
					}
					if (count($privileges) === 0) {
						if ($resource === self::ALL && $role === self::ALL) {
							if ($type === $rules['allPrivileges']['type']) {
								$rules = array(
									'allPrivileges' => array(
										'type' => self::DENY,
										'assert' => NULL
										),
									'byPrivilege' => array()
									);
							}
							continue;
						}
						if ($type === $rules['allPrivileges']['type']) {
							unset($rules['allPrivileges']);
						}
					} else {
						foreach ($privileges as $privilege) {
							if (isset($rules['byPrivilege'][$privilege]) &&
								$type === $rules['byPrivilege'][$privilege]['type']) {
								unset($rules['byPrivilege'][$privilege]);
							}
						}
					}
				}
			}
		}
		return $this;
	}

	function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
	{
		$this->queriedRole = $role;
		if ($role !== self::ALL) {
			if ($role instanceof IRole) {
				$role = $role->getRoleId();
			}
			$this->checkRole($role);
		}

		$this->queriedResource = $resource;
		if ($resource !== self::ALL) {
			if ($resource instanceof IResource) {
				$resource = $resource->getResourceId();
			}
			$this->checkResource($resource);
		}

		do {

			if ($role !== NULL && NULL !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) {
				break;
			}

			if ($privilege === self::ALL) {
				if ($rules = $this->getRules($resource, self::ALL)) {
					foreach ($rules['byPrivilege'] as $privilege => $rule) {
						if (self::DENY === ($result = $this->getRuleType($resource, NULL, $privilege))) {
							break 2;
						}
					}
					if (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
						break;
					}
				}
			} else {
				if (NULL !== ($result = $this->getRuleType($resource, NULL, $privilege))) {
					break;

				} elseif (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
					break;
				}
			}

			$resource = $this->resources[$resource]['parent'];
		} while (TRUE);

		$this->queriedRole = $this->queriedResource = NULL;
		return $result;
	}

	function getQueriedRole()
	{
		return $this->queriedRole;
	}

	function getQueriedResource()
	{
		return $this->queriedResource;
	}

	private function searchRolePrivileges($all, $role, $resource, $privilege)
	{
		$dfs = array(
			'visited' => array(),
			'stack' => array($role),
		);

		while (NULL !== ($role = array_pop($dfs['stack']))) {
			if (isset($dfs['visited'][$role])) {
				continue;
			}
			if ($all) {
				if ($rules = $this->getRules($resource, $role)) {
					foreach ($rules['byPrivilege'] as $privilege2 => $rule) {
						if (self::DENY === $this->getRuleType($resource, $role, $privilege2)) {
							return self::DENY;
						}
					}
					if (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
						return $type;
					}
				}
			} else {
				if (NULL !== ($type = $this->getRuleType($resource, $role, $privilege))) {
					return $type;

				} elseif (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
					return $type;
				}
			}

			$dfs['visited'][$role] = TRUE;
			foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
				$dfs['stack'][] = $roleParent;
			}
		}
		return NULL;
	}

	private function getRuleType($resource, $role, $privilege)
	{
		if (!$rules = $this->getRules($resource, $role)) {
			return NULL;
		}

		if ($privilege === self::ALL) {
			if (isset($rules['allPrivileges'])) {
				$rule = $rules['allPrivileges'];
			} else {
				return NULL;
			}
		} elseif (!isset($rules['byPrivilege'][$privilege])) {
			return NULL;

		} else {
			$rule = $rules['byPrivilege'][$privilege];
		}

		if ($rule['assert'] === NULL || $rule['assert']->__invoke($this, $role, $resource, $privilege)) {
			return $rule['type'];

		} elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
			return NULL;

		} elseif (self::ALLOW === $rule['type']) {
			return self::DENY;

		} else {
			return self::ALLOW;
		}
	}

	private function & getRules($resource, $role, $create = FALSE)
	{
		$null = NULL;
		if ($resource === self::ALL) {
			$visitor = & $this->rules['allResources'];
		} else {
			if (!isset($this->rules['byResource'][$resource])) {
				if (!$create) {
					return $null;
				}
				$this->rules['byResource'][$resource] = array();
			}
			$visitor = & $this->rules['byResource'][$resource];
		}

		if ($role === self::ALL) {
			if (!isset($visitor['allRoles'])) {
				if (!$create) {
					return $null;
				}
				$visitor['allRoles']['byPrivilege'] = array();
			}
			return $visitor['allRoles'];
		}

		if (!isset($visitor['byRole'][$role])) {
			if (!$create) {
				return $null;
			}
			$visitor['byRole'][$role]['byPrivilege'] = array();
		}

		return $visitor['byRole'][$role];
	}

}

class NSimpleAuthenticator extends NObject implements IAuthenticator
{

	private $userlist;

	function __construct(array $userlist)
	{
		$this->userlist = $userlist;
	}

	function authenticate(array $credentials)
	{
		list($username, $password) = $credentials;
		foreach ($this->userlist as $name => $pass) {
			if (strcasecmp($name, $username) === 0) {
				if ((string) $pass === (string) $password) {
					return new NIdentity($name);
				} else {
					throw new NAuthenticationException("Invalid password.", self::INVALID_CREDENTIAL);
				}
			}
		}
		throw new NAuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND);
	}

}

final class NTemplateHelpers
{
	private static $helpers = array(
		'normalize' => 'NStrings::normalize',
		'toascii' => 'NStrings::toAscii',
		'webalize' => 'NStrings::webalize',
		'truncate' => 'NStrings::truncate',
		'lower' => 'NStrings::lower',
		'upper' => 'NStrings::upper',
		'firstupper' => 'NStrings::firstUpper',
		'capitalize' => 'NStrings::capitalize',
		'trim' => 'NStrings::trim',
		'padleft' => 'NStrings::padLeft',
		'padright' => 'NStrings::padRight',
		'replacere' => 'NStrings::replace',
		'url' => 'rawurlencode',
		'striptags' => 'strip_tags',
		'nl2br' => 'nl2br',
		'substr' => 'iconv_substr',
		'repeat' => 'str_repeat',
		'implode' => 'implode',
		'number' => 'number_format',
	);

	public static $dateFormat = '%x';

	static function loader($helper)
	{
		if (method_exists(__CLASS__, $helper)) {
			return callback(__CLASS__, $helper);
		} elseif (isset(self::$helpers[$helper])) {
			return self::$helpers[$helper];
		}
	}

	static function escapeHtml($s, $quotes = ENT_QUOTES)
	{
		if (is_object($s) && ($s instanceof ITemplate || $s instanceof NHtml || $s instanceof NForm)) {
			return $s->__toString(TRUE);
		}
		return htmlSpecialChars($s, $quotes);
	}

	static function escapeHtmlComment($s)
	{

		return str_replace('--', '--><!-- ', $s);
	}

	static function escapeXML($s)
	{

		return htmlSpecialChars(preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $s), ENT_QUOTES);
	}

	static function escapeCss($s)
	{

		return addcslashes($s, "\x00..\x1F!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~");
	}

	static function escapeJs($s)
	{
		if (is_object($s) && ($s instanceof ITemplate || $s instanceof NHtml || $s instanceof NForm)) {
			$s = $s->__toString(TRUE);
		}
		return str_replace(']]>', ']]\x3E', NJson::encode($s));
	}

	static function escapeICal($s)
	{

		return addcslashes(preg_replace('#[\x00-\x08\x0B\x0C-\x1F]+#', '', $s), "\";\\,:\n");
	}

	static function strip($s)
	{
		return NStrings::replace(
			$s,
			'#(</textarea|</pre|</script|^).*?(?=<textarea|<pre|<script|$)#si',
			callback(create_function('$m', '
				return trim(preg_replace("#[ \\t\\r\\n]+#", " ", $m[0]));
			')));
	}

	static function indent($s, $level = 1, $chars = "\t")
	{
		if ($level >= 1) {
			$s = NStrings::replace($s, '#<(textarea|pre).*?</\\1#si', callback(create_function('$m', '
				return strtr($m[0], " \\t\\r\\n", "\\x1F\\x1E\\x1D\\x1A");
			')));
			$s = NStrings::indent($s, $level, $chars);
			$s = strtr($s, "\x1F\x1E\x1D\x1A", " \t\r\n");
		}
		return $s;
	}

	static function date($time, $format = NULL)
	{
		if ($time == NULL) {
			return NULL;
		}

		if (!isset($format)) {
			$format = self::$dateFormat;
		}

		$time = NDateTime53::from($time);
		return strpos($format, '%') === FALSE
			? $time->format($format)
			: strftime($format, $time->format('U'));
	}

	static function bytes($bytes, $precision = 2)
	{
		$bytes = round($bytes);
		$units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
		foreach ($units as $unit) {
			if (abs($bytes) < 1024 || $unit === end($units)) {
				break;
			}
			$bytes = $bytes / 1024;
		}
		return round($bytes, $precision) . ' ' . $unit;
	}

	static function length($var)
	{
		return is_string($var) ? NStrings::length($var) : count($var);
	}

	static function replace($subject, $search, $replacement = '')
	{
		return str_replace($search, $replacement, $subject);
	}

	static function dataStream($data, $type = NULL)
	{
		if ($type === NULL) {
			$type = NMimeTypeDetector::fromString($data, NULL);
		}
		return 'data:' . ($type ? "$type;" : '') . 'base64,' . base64_encode($data);
	}

	static function null($value)
	{
		return '';
	}

}

class NTemplate extends NObject implements ITemplate
{

	public $warnOnUndefined = TRUE;

	public $onPrepareFilters = array();

	private $source;

	private $params = array();

	private $filters = array();

	private $helpers = array();

	private $helperLoaders = array();

	private $cacheStorage;

	function setSource($source)
	{
		$this->source = $source;
		return $this;
	}

	function getSource()
	{
		return $this->source;
	}

	function render()
	{
		$cache = new NCache($storage = $this->getCacheStorage(), 'Nette.Template');
		$cached = $compiled = $cache->load($this->source);

		if ($compiled === NULL) {
			$compiled = $this->compile();
			$cache->save($this->source, $compiled, array(NCache::CONSTS => 'NFramework::REVISION'));
			$cache->release();
			$cached = $cache->load($this->source);
		}

		if ($cached !== NULL && $storage instanceof NPhpFileStorage) {
			NLimitedScope::load($cached['file'], $this->getParams());
		} else {
			NLimitedScope::evaluate($compiled, $this->getParams());
		}
	}

	function save($file)
	{
		if (file_put_contents($file, $this->__toString(TRUE)) === FALSE) {
			throw new IOException("Unable to save file '$file'.");
		}
	}

	function __toString()
	{
		$args = func_get_args();
		ob_start();
		try {
			$this->render();
			return ob_get_clean();

		} catch (Exception $e) {
			ob_end_clean();
			if ($args && $args[0]) {
				throw $e;
			} else {
				NDebugger::toStringException($e);
			}
		}
	}

	function compile()
	{
		if (!$this->filters) {
			$this->onPrepareFilters($this);
		}

		$code = $this->getSource();
		foreach ($this->filters as $filter) {
			$code = self::extractPhp($code, $blocks);
			$code = $filter->invoke($code);
			$code = strtr($code, $blocks);
		}

		return self::optimizePhp($code);
	}

	function registerFilter($callback)
	{
		$callback = callback($callback);
		if (in_array($callback, $this->filters)) {
			throw new InvalidStateException("Filter '$callback' was registered twice.");
		}
		$this->filters[] = $callback;
	}

	final function getFilters()
	{
		return $this->filters;
	}

	function registerHelper($name, $callback)
	{
		$this->helpers[strtolower($name)] = callback($callback);
	}

	function registerHelperLoader($callback)
	{
		$this->helperLoaders[] = callback($callback);
	}

	final function getHelpers()
	{
		return $this->helpers;
	}

	function __call($name, $args)
	{
		$lname = strtolower($name);
		if (!isset($this->helpers[$lname])) {
			foreach ($this->helperLoaders as $loader) {
				$helper = $loader->invoke($lname);
				if ($helper) {
					$this->registerHelper($lname, $helper);
					return $this->helpers[$lname]->invokeArgs($args);
				}
			}
			return parent::__call($name, $args);
		}

		return $this->helpers[$lname]->invokeArgs($args);
	}

	function setTranslator(ITranslator $translator = NULL)
	{
		$this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
		return $this;
	}

	function add($name, $value)
	{
		if (array_key_exists($name, $this->params)) {
			throw new InvalidStateException("The variable '$name' already exists.");
		}

		$this->params[$name] = $value;
	}

	function setParams(array $params)
	{
		$this->params = $params + $this->params;
		return $this;
	}

	function getParams()
	{
		$this->params['template'] = $this;
		return $this->params;
	}

	function __set($name, $value)
	{
		$this->params[$name] = $value;
	}

	function &__get($name)
	{
		if ($this->warnOnUndefined && !array_key_exists($name, $this->params)) {
			trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
		}

		return $this->params[$name];
	}

	function __isset($name)
	{
		return isset($this->params[$name]);
	}

	function __unset($name)
	{
		unset($this->params[$name]);
	}

	function setCacheStorage(ICacheStorage $storage)
	{
		$this->cacheStorage = $storage;
	}

	function getCacheStorage()
	{
		if ($this->cacheStorage === NULL) {
			return new NDevNullStorage;
		}
		return $this->cacheStorage;
	}

	private static function extractPhp($source, & $blocks)
	{
		$res = '';
		$blocks = array();
		$tokens = token_get_all($source);
		foreach ($tokens as $n => $token) {
			if (is_array($token)) {
				if ($token[0] === T_INLINE_HTML || $token[0] === T_CLOSE_TAG) {
					$res .= $token[1];
					continue;

				} elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?php' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
					$php = & $res;
					$token[1] = '<<?php ?>?';

				} elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
					$res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
					$php = & $blocks[$id];
				}
				$php .= $token[1];

			} else {
				$php .= $token;
			}
		}
		return $res;
	}

	static function optimizePhp($source, $lineLength = 80, $existenceOfThisParameterSolvesDamnBugInPHP535 = NULL)
	{
		$res = $php = '';
		$lastChar = ';';
		$tokens = new ArrayIterator(token_get_all($source));
		foreach ($tokens as $key => $token) {
			if (is_array($token)) {
				if ($token[0] === T_INLINE_HTML) {
					$lastChar = '';
					$res .= $token[1];

				} elseif ($token[0] === T_CLOSE_TAG) {
					$next = isset($tokens[$key + 1]) ? $tokens[$key + 1] : NULL;
					if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*$#', $php)) {
						$php = '';

					} elseif (is_array($next) && $next[0] === T_OPEN_TAG) {
						if (!strspn($lastChar, ';{}:/')) {
							$php .= $lastChar = ';';
						}
						if (substr($next[1], -1) === "\n") {
							$php .= "\n";
						}
						$tokens->next();

					} elseif ($next) {
						$res .= preg_replace('#;?(\s)*$#', '$1', $php) . $token[1];
						if (strlen($res) - strrpos($res, "\n") > $lineLength
							&& (!is_array($next) || strpos($next[1], "\n") === FALSE)
						) {
							$res .= "\n";
						}
						$php = '';

					} else {
						if (!strspn($lastChar, '};')) {
							$php .= ';';
						}
					}

				} elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) {
					if ($tokens[$key + 1] === ':' && $lastChar === '}') {
						$php .= ';';
					}
					$lastChar = '';
					$php .= $token[1];

				} else {
					if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG))) {
						$lastChar = '';
					}
					$php .= $token[1];
				}
			} else {
				$php .= $lastChar = $token;
			}
		}
		return $res . $php;
	}

}

class NFileTemplate extends NTemplate implements IFileTemplate
{

	private $file;

	function __construct($file = NULL)
	{
		if ($file !== NULL) {
			$this->setFile($file);
		}
	}

	function setFile($file)
	{
		$this->file = realpath($file);
		if (!$this->file) {
			throw new FileNotFoundException("Missing template file '$file'.");
		}
		return $this;
	}

	function getFile()
	{
		return $this->file;
	}

	function getSource()
	{
		return file_get_contents($this->file);
	}

	function render()
	{
		if ($this->file == NULL) {
			throw new InvalidStateException("Template file name was not specified.");
		}

		$cache = new NCache($storage = $this->getCacheStorage(), 'Nette.FileTemplate');
		if ($storage instanceof NPhpFileStorage) {
			$storage->hint = str_replace(dirname(dirname($this->file)), '', $this->file);
		}
		$cached = $compiled = $cache->load($this->file);

		if ($compiled === NULL) {
			try {
				$compiled = "<?php\n\n// source file: $this->file\n\n?>" . $this->compile();

			} catch (NTemplateException $e) {
				$e->setSourceFile($this->file);
				throw $e;
			}

			$cache->save($this->file, $compiled, array(
				NCache::FILES => $this->file,
				NCache::CONSTS => 'NFramework::REVISION',
			));
			$cache->release();
			$cached = $cache->load($this->file);
		}

		if ($cached !== NULL && $storage instanceof NPhpFileStorage) {
			NLimitedScope::load($cached['file'], $this->getParams());
		} else {
			NLimitedScope::evaluate($compiled, $this->getParams());
		}
	}

}

final class NArrays
{

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function get(array $arr, $key, $default = NULL)
	{
		foreach (is_array($key) ? $key : array($key) as $k) {
			if (is_array($arr) && array_key_exists($k, $arr)) {
				$arr = $arr[$k];
			} else {
				if (func_num_args() < 3) {
					throw new InvalidArgumentException("Missing item '$k'.");
				}
				return $default;
			}
		}
		return $arr;
	}

	static function & getRef(& $arr, $key)
	{
		foreach (is_array($key) ? $key : array($key) as $k) {
			if (is_array($arr) || $arr === NULL) {
				$arr = & $arr[$k];
			} else {
				throw new InvalidArgumentException('Traversed item is not an array.');
			}
		}
		return $arr;
	}

	static function mergeTree($arr1, $arr2)
	{
		$res = $arr1 + $arr2;
		foreach (array_intersect_key($arr1, $arr2) as $k => $v) {
			if (is_array($v) && is_array($arr2[$k])) {
				$res[$k] = self::mergeTree($v, $arr2[$k]);
			}
		}
		return $res;
	}

	static function searchKey($arr, $key)
	{
		$foo = array($key => NULL);
		return array_search(key($foo), array_keys($arr), TRUE);
	}

	static function insertBefore(array &$arr, $key, array $inserted)
	{
		$offset = self::searchKey($arr, $key);
		$arr = array_slice($arr, 0, $offset, TRUE) + $inserted + array_slice($arr, $offset, count($arr), TRUE);
	}

	static function insertAfter(array &$arr, $key, array $inserted)
	{
		$offset = self::searchKey($arr, $key);
		$offset = $offset === FALSE ? count($arr) : $offset + 1;
		$arr = array_slice($arr, 0, $offset, TRUE) + $inserted + array_slice($arr, $offset, count($arr), TRUE);
	}

	static function renameKey(array &$arr, $oldKey, $newKey)
	{
		$offset = self::searchKey($arr, $oldKey);
		if ($offset !== FALSE) {
			$keys = array_keys($arr);
			$keys[$offset] = $newKey;
			$arr = array_combine($keys, $arr);
		}
	}

	static function grep(array $arr, $pattern, $flags = 0)
	{
		NDebugger::tryError();
		$res = preg_grep($pattern, $arr, $flags);
		NStrings::catchPregError($pattern);
		return $res;
	}

}

final class NCriticalSection
{

	private static $criticalSections;

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function enter()
	{
		if (self::$criticalSections) {
			throw new InvalidStateException('Critical section has already been entered.');
		}

		$handle = substr(PHP_OS, 0, 3) === 'WIN'
			? @fopen(NETTE_DIR . '/lockfile', 'w')
			: @fopen(__FILE__, 'r');

		if (!$handle) {
			throw new InvalidStateException("Unable initialize critical section.");
		}
		flock(self::$criticalSections = $handle, LOCK_EX);
	}

	static function leave()
	{
		if (!self::$criticalSections) {
			throw new InvalidStateException('Critical section has not been initialized.');
		}
		flock(self::$criticalSections, LOCK_UN);
		fclose(self::$criticalSections);
		self::$criticalSections = NULL;
	}

}

class NFinder extends NObject implements IteratorAggregate
{

	private $paths = array();

	private $groups;

	private $exclude = array();

	private $order = RecursiveIteratorIterator::SELF_FIRST;

	private $maxDepth = -1;

	private $cursor;

	static function find($mask)
	{
		if (!is_array($mask)) {
			$mask = func_get_args();
		}
		$finder = new self;
		return $finder->select(array(), 'isDir')->select($mask, 'isFile');
	}

	static function findFiles($mask)
	{
		if (!is_array($mask)) {
			$mask = func_get_args();
		}
		$finder = new self;
		return $finder->select($mask, 'isFile');
	}

	static function findDirectories($mask)
	{
		if (!is_array($mask)) {
			$mask = func_get_args();
		}
		$finder = new self;
		return $finder->select($mask, 'isDir');
	}

	private function select($masks, $type)
	{
		$this->cursor = & $this->groups[];
		$pattern = self::buildPattern($masks);
		if ($type || $pattern) {
			$this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('type'=>$type,'pattern'=> $pattern)).'], EXTR_REFS);
				return !$file->isDot()
					&& (!$type || $file->$type())
					&& (!$pattern || preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\')));
			'));
		}
		return $this;
	}

	function in($path)
	{
		if (!is_array($path)) {
			$path = func_get_args();
		}
		$this->maxDepth = 0;
		return $this->from($path);
	}

	function from($path)
	{
		if ($this->paths) {
			throw new InvalidStateException('Directory to search has already been specified.');
		}
		if (!is_array($path)) {
			$path = func_get_args();
		}
		$this->paths = $path;
		$this->cursor = & $this->exclude;
		return $this;
	}

	function childFirst()
	{
		$this->order = RecursiveIteratorIterator::CHILD_FIRST;
		return $this;
	}

	private static function buildPattern($masks)
	{
		$pattern = array();

		foreach ($masks as $mask) {
			$mask = rtrim(strtr($mask, '\\', '/'), '/');
			$prefix = '';
			if ($mask === '') {
				continue;

			} elseif ($mask === '*') {
				return NULL;

			} elseif ($mask[0] === '/') {
				$mask = ltrim($mask, '/');
				$prefix = '(?<=^/)';
			}
			$pattern[] = $prefix . strtr(preg_quote($mask, '#'),
				array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-'));
		}
		return $pattern ? '#/(' . implode('|', $pattern) . ')$#i' : NULL;
	}

	function getIterator()
	{
		if (!$this->paths) {
			throw new InvalidStateException('Call in() or from() to specify directory to search.');

		} elseif (count($this->paths) === 1) {
			return $this->buildIterator($this->paths[0]);

		} else {
			$iterator = new AppendIterator();
			foreach ($this->paths as $path) {
				$iterator->append($this->buildIterator($path));
			}
			return $iterator;
		}
	}

	private function buildIterator($path)
	{
		if (PHP_VERSION_ID < 50301) {
			$iterator = new NRecursiveDirectoryIteratorFixed($path);
		} else {
			$iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
		}

		if ($this->exclude) {
			$filters = $this->exclude;
			$iterator = new NRecursiveCallbackFilterIterator($iterator, create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('filters'=>$filters)).'], EXTR_REFS);
				if (!$file->isDot() && !$file->isFile()) {
					foreach ($filters as $filter) {
						if (!call_user_func($filter, $file)) {
							return FALSE;
						}
					}
				}
				return TRUE;
			'));
		}

		if ($this->maxDepth !== 0) {
			$iterator = new RecursiveIteratorIterator($iterator, $this->order);
			$iterator->setMaxDepth($this->maxDepth);
		}

		if ($this->groups) {
			$groups = $this->groups;
			$iterator = new NCallbackFilterIterator($iterator, create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('groups'=>$groups)).'], EXTR_REFS);
				foreach ($groups as $filters) {
					foreach ($filters as $filter) {
						if (!call_user_func($filter, $file)) {
							continue 2;
						}
					}
					return TRUE;
				}
				return FALSE;
			'));
		}

		return $iterator;
	}

	function exclude($masks)
	{
		if (!is_array($masks)) {
			$masks = func_get_args();
		}
		$pattern = self::buildPattern($masks);
		if ($pattern) {
			$this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('pattern'=>$pattern)).'], EXTR_REFS);
				return !preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\'));
			'));
		}
		return $this;
	}

	function filter($callback)
	{
		$this->cursor[] = $callback;
		return $this;
	}

	function limitDepth($depth)
	{
		$this->maxDepth = $depth;
		return $this;
	}

	function size($operator, $size = NULL)
	{
		if (func_num_args() === 1) {
			if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#i', $operator, $matches)) {
				throw new InvalidArgumentException('Invalid size predicate format.');
			}
			list(, $operator, $size, $unit) = $matches;
			static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
			$size *= $units[strtolower($unit)];
			$operator = $operator ? $operator : '=';
		}
		return $this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('operator'=>$operator,'size'=> $size)).'], EXTR_REFS);
			return NFinder::compare($file->getSize(), $operator, $size);
		'));
	}

	function date($operator, $date = NULL)
	{
		if (func_num_args() === 1) {
			if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#i', $operator, $matches)) {
				throw new InvalidArgumentException('Invalid date predicate format.');
			}
			list(, $operator, $date) = $matches;
			$operator = $operator ? $operator : '=';
		}
		$date = NDateTime53::from($date)->format('U');
		return $this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('operator'=>$operator,'date'=> $date)).'], EXTR_REFS);
			return NFinder::compare($file->getMTime(), $operator, $date);
		'));
	}

	static function compare($l, $operator, $r)
	{
		switch ($operator) {
		case '>':
			return $l > $r;
		case '>=':
			return $l >= $r;
		case '<':
			return $l < $r;
		case '<=':
			return $l <= $r;
		case '=':
		case '==':
			return $l == $r;
		case '!':
		case '!=':
		case '<>':
			return $l != $r;
		}
		throw new InvalidArgumentException("Unknown operator $operator.");
	}

}

if (PHP_VERSION_ID < 50301) {

	class NRecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
	{
		function hasChildren()
		{
			return parent::hasChildren(TRUE);
		}
	}
}

final class NJson
{
	const FORCE_ARRAY = 1;

	private static $messages = array(
		JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
		JSON_ERROR_STATE_MISMATCH => 'Syntax error, malformed JSON',
		JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
		JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
	);

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function encode($value)
	{
		NDebugger::tryError();
		if (function_exists('ini_set')) {
			$old = ini_set('display_errors', 0);
			$json = json_encode($value);
			ini_set('display_errors', $old);
		} else {
			$json = json_encode($value);
		}
		if (NDebugger::catchError($e)) {
			throw new NJsonException($e->getMessage());
		}
		return $json;
	}

	static function decode($json, $options = 0)
	{
		$json = (string) $json;
		$value = json_decode($json, (bool) ($options & self::FORCE_ARRAY));
		if ($value === NULL && $json !== '' && strcasecmp($json, 'null')) {
			$error = PHP_VERSION_ID >= 50300 ? json_last_error() : 0;
			throw new NJsonException(isset(self::$messages[$error]) ? self::$messages[$error] : 'Unknown error', $error);
		}
		return $value;
	}

}

class NJsonException extends Exception
{
}

final class NMimeTypeDetector
{

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function fromFile($file)
	{
		if (!is_file($file)) {
			throw new FileNotFoundException("File '$file' not found.");
		}

		$info = @getimagesize($file);
		if (isset($info['mime'])) {
			return $info['mime'];

		} elseif (extension_loaded('fileinfo')) {
			$type = preg_replace('#[\s;].*$#', '', finfo_file(finfo_open(FILEINFO_MIME), $file));

		} elseif (function_exists('mime_content_type')) {
			$type = mime_content_type($file);
		}

		return isset($type) && preg_match('#^\S+/\S+$#', $type) ? $type : 'application/octet-stream';
	}

	static function fromString($data)
	{
		if (extension_loaded('fileinfo') && preg_match('#^(\S+/[^\s;]+)#', finfo_buffer(finfo_open(FILEINFO_MIME), $data), $m)) {
			return $m[1];

		} elseif (strncmp($data, "\xff\xd8", 2) === 0) {
			return 'image/jpeg';

		} elseif (strncmp($data, "\x89PNG", 4) === 0) {
			return 'image/png';

		} elseif (strncmp($data, "GIF", 3) === 0) {
			return 'image/gif';

		} else {
			return 'application/octet-stream';
		}
	}

}

class NNeon extends NObject
{
	const BLOCK = 1;

	private static $patterns = array(
		'\'[^\'\n]*\'|"(?:\\\\.|[^"\\\\\n])*"',
		'@[a-zA-Z_0-9\\\\]+',
		'[:-](?=\s|$)|[,=[\]{}()]',
		'?:#.*',
		'\n[\t ]*',
		'[^#"\',:=@[\]{}()<>\x00-\x20!`](?:[^#,:=\]})>\x00-\x1F]+|:(?!\s|$)|(?<!\s)#)*(?<!\s)',
		'?:[\t ]+',
	);

	private static $tokenizer;

	private static $brackets = array(
		'[' => ']',
		'{' => '}',
		'(' => ')',
	);

	private $n = 0;

	private $indentTabs;

	static function encode($var, $options = NULL)
	{
		if ($var instanceof DateTime) {
			return $var->format('Y-m-d H:i:s O');
		}
		if (is_object($var)) {
			$obj = $var; $var = array();
			foreach ($obj as $k => $v) {
				$var[$k] = $v;
			}
		}
		if (is_array($var)) {
			$isArray = array_keys($var) === range(0, count($var) - 1);
			$s = '';
			if ($options & self::BLOCK) {
				foreach ($var as $k => $v) {
					$v = self::encode($v, self::BLOCK);
					$s .= ($isArray ? '-' : self::encode($k) . ':')
						. (strpos($v, "\n") === FALSE ? ' ' . $v : "\n\t" . str_replace("\n", "\n\t", $v))
						. "\n";
					continue;
				}
				return $s;

			} else {
				foreach ($var as $k => $v) {
					$s .= ($isArray ? '' : self::encode($k) . ': ') . self::encode($v) . ', ';
				}
				return ($isArray ? '[' : '{') . substr($s, 0, -2) . ($isArray ? ']' : '}');
			}

		} elseif (is_string($var) && !is_numeric($var)
			&& !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)$~i', $var)
			&& preg_match('~^' . self::$patterns[5] . '$~', $var)
		) {
			return $var;

		} else {
			return json_encode($var);
		}
	}

	static function decode($input)
	{
		if (!is_string($input)) {
			throw new InvalidArgumentException("Argument must be a string, " . gettype($input) . " given.");
		}
		if (!self::$tokenizer) {
			self::$tokenizer = new NTokenizer(self::$patterns, 'mi');
		}

		$input = str_replace("\r", '', $input);
		self::$tokenizer->tokenize($input);

		$parser = new self;
		$res = $parser->parse(0);

		while (isset(self::$tokenizer->tokens[$parser->n])) {
			if (self::$tokenizer->tokens[$parser->n][0] === "\n") {
				$parser->n++;
			} else {
				$parser->error();
			}
		}
		return $res;
	}

	private function parse($indent = NULL, $result = NULL)
	{
		$inlineParser = $indent === NULL;
		$value = $key = $object = NULL;
		$hasValue = $hasKey = FALSE;
		$tokens = self::$tokenizer->tokens;
		$n = & $this->n;
		$count = count($tokens);

		for (; $n < $count; $n++) {
			$t = $tokens[$n];

			if ($t === ',') {
				if (!$hasValue || !$inlineParser) {
					$this->error();
				}
				if ($hasKey) {
					$result[$key] = $value;
				} else {
					$result[] = $value;
				}
				$hasKey = $hasValue = FALSE;

			} elseif ($t === ':' || $t === '=') {
				if ($hasKey || !$hasValue) {
					$this->error();
				}
				if (is_array($value) || (is_object($value) && !method_exists($value, '__toString'))) {
					$this->error('Unacceptable key');
				} else {
					$key = (string) $value;
				}
				$hasKey = TRUE;
				$hasValue = FALSE;

			} elseif ($t === '-') {
				if ($hasKey || $hasValue || $inlineParser) {
					$this->error();
				}
				$key = NULL;
				$hasKey = TRUE;

			} elseif (isset(self::$brackets[$t])) {
				if ($hasValue) {
					if ($value[0] === '@' && $t === '(') {
						$n++;
						$value = $this->parse(NULL, array('@' => substr($value, 1)));
					} else {
						$this->error();
					}
				} else {
					$n++;
					$value = $this->parse(NULL, array());
				}
				$hasValue = TRUE;
				if (!isset($tokens[$n]) || $tokens[$n] !== self::$brackets[$t]) {
					$this->error();
				}

			} elseif ($t === ']' || $t === '}' || $t === ')') {
				if (!$inlineParser) {
					$this->error();
				}
				break;

			} elseif ($t[0] === "\n") {
				if ($inlineParser) {
					if ($hasValue) {
						if ($hasKey) {
							$result[$key] = $value;
						} else {
							$result[] = $value;
						}
						$hasKey = $hasValue = FALSE;
					}

				} else {
					while (isset($tokens[$n+1]) && $tokens[$n+1][0] === "\n") $n++;
					if (!isset($tokens[$n+1])) {
						break;
					}

					$newIndent = strlen($tokens[$n]) - 1;
					if ($indent === NULL) {
						$indent = $newIndent;
					}
					if ($newIndent) {
						if ($this->indentTabs === NULL) {
							$this->indentTabs = $tokens[$n][1] === "\t";
						}
						if (strpos($tokens[$n], $this->indentTabs ? ' ' : "\t")) {
							$n++;
							$this->error('Either tabs or spaces may be used as indenting chars, but not both.');
						}
					}

					if ($newIndent > $indent) {
						if ($hasValue || !$hasKey) {
							$n++;
							$this->error('Unexpected indentation.');
						} elseif ($key === NULL) {
							$result[] = $this->parse($newIndent);
						} else {
							$result[$key] = $this->parse($newIndent);
						}
						$newIndent = isset($tokens[$n]) ? strlen($tokens[$n]) - 1 : 0;
						$hasKey = FALSE;

					} else {
						if ($hasValue && !$hasKey) {
							break;

						} elseif ($hasKey) {
							$value = $hasValue ? $value : NULL;
							if ($key === NULL) {
								$result[] = $value;
							} else {
								$result[$key] = $value;
							}
							$hasKey = $hasValue = FALSE;
						}
					}

					if ($newIndent < $indent) {
						return $result;
					}
				}

			} else {
				if ($hasValue) {
					$this->error();
				}
				static $consts = array(
					'true' => TRUE, 'True' => TRUE, 'TRUE' => TRUE, 'yes' => TRUE, 'Yes' => TRUE, 'YES' => TRUE, 'on' => TRUE, 'On' => TRUE, 'ON' => TRUE,
					'false' => FALSE, 'False' => FALSE, 'FALSE' => FALSE, 'no' => FALSE, 'No' => FALSE, 'NO' => FALSE, 'off' => FALSE, 'Off' => FALSE, 'OFF' => FALSE,
				);
				if ($t[0] === '"') {
					$value = preg_replace_callback('#\\\\(?:u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', array($this, 'cbString'), substr($t, 1, -1));
				} elseif ($t[0] === "'") {
					$value = substr($t, 1, -1);
				} elseif (isset($consts[$t])) {
					$value = $consts[$t];
				} elseif ($t === 'null' || $t === 'Null' || $t === 'NULL') {
					$value = NULL;
				} elseif (is_numeric($t)) {
					$value = $t * 1;
				} elseif (preg_match('#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::\d\d)?)?)?$#A', $t)) {
					$value = new NDateTime53($t);
				} else {
					$value = $t;
				}
				$hasValue = TRUE;
			}
		}

		if ($inlineParser) {
			if ($hasValue) {
				if ($hasKey) {
					$result[$key] = $value;
				} else {
					$result[] = $value;
				}
			} elseif ($hasKey) {
				$this->error();
			}
		} else {
			if ($hasValue && !$hasKey) {
				if ($result === NULL) {
					$result = $value;
				} else {
					$this->error();
				}
			} elseif ($hasKey) {
				$value = $hasValue ? $value : NULL;
				if ($key === NULL) {
					$result[] = $value;
				} else {
					$result[$key] = $value;
				}
			}
		}
		return $result;
	}

	private function cbString($m)
	{
		static $mapping = array('t' => "\t", 'n' => "\n", '"' => '"', '\\' => '\\',  '/' => '/', '_' => "\xc2\xa0");
		$sq = $m[0];
		if (isset($mapping[$sq[1]])) {
			return $mapping[$sq[1]];
		} elseif ($sq[1] === 'u' && strlen($sq) === 6) {
			return NStrings::chr(hexdec(substr($sq, 2)));
		} elseif ($sq[1] === 'x' && strlen($sq) === 4) {
			return chr(hexdec(substr($sq, 2)));
		} else {
			$this->error("Invalid escaping sequence $sq");
		}
	}

	private function error($message = "Unexpected '%s'")
	{
		list(, $line, $col) = self::$tokenizer->getOffset($this->n);
		$token = isset(self::$tokenizer->tokens[$this->n])
			? str_replace("\n", '<new line>', NStrings::truncate(self::$tokenizer->tokens[$this->n], 40))
			: 'end';
		throw new NNeonException(str_replace('%s', $token, $message) . " on line $line, column $col.");
	}

}

class NNeonException extends Exception
{
}

class NPaginator extends NObject
{

	private $base = 1;

	private $itemsPerPage = 1;

	private $page;

	private $itemCount;

	function setPage($page)
	{
		$this->page = (int) $page;
		return $this;
	}

	function getPage()
	{
		return $this->base + $this->getPageIndex();
	}

	function getFirstPage()
	{
		return $this->base;
	}

	function getLastPage()
	{
		return $this->itemCount === NULL ? NULL : $this->base + max(0, $this->getPageCount() - 1);
	}

	function setBase($base)
	{
		$this->base = (int) $base;
		return $this;
	}

	function getBase()
	{
		return $this->base;
	}

	protected function getPageIndex()
	{
		$index = max(0, $this->page - $this->base);
		return $this->itemCount === NULL ? $index : min($index, max(0, $this->getPageCount() - 1));
	}

	function isFirst()
	{
		return $this->getPageIndex() === 0;
	}

	function isLast()
	{
		return $this->itemCount === NULL ? FALSE : $this->getPageIndex() >= $this->getPageCount() - 1;
	}

	function getPageCount()
	{
		return $this->itemCount === NULL ? NULL : (int) ceil($this->itemCount / $this->itemsPerPage);
	}

	function setItemsPerPage($itemsPerPage)
	{
		$this->itemsPerPage = max(1, (int) $itemsPerPage);
		return $this;
	}

	function getItemsPerPage()
	{
		return $this->itemsPerPage;
	}

	function setItemCount($itemCount)
	{
		$this->itemCount = ($itemCount === FALSE || $itemCount === NULL) ? NULL : max(0, (int) $itemCount);
		return $this;
	}

	function getItemCount()
	{
		return $this->itemCount;
	}

	function getOffset()
	{
		return $this->getPageIndex() * $this->itemsPerPage;
	}

	function getCountdownOffset()
	{
		return $this->itemCount === NULL
			? NULL
			: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
	}

	function getLength()
	{
		return $this->itemCount === NULL
			? $this->itemsPerPage
			: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
	}

}

class NStrings
{

	final function __construct()
	{
		throw new NStaticClassException;
	}

	static function checkEncoding($s, $encoding = 'UTF-8')
	{
		return $s === self::fixEncoding($s, $encoding);
	}

	static function fixEncoding($s, $encoding = 'UTF-8')
	{

		$s = @iconv('UTF-16', $encoding . '//IGNORE', iconv($encoding, 'UTF-16//IGNORE', $s));
		return str_replace("\xEF\xBB\xBF", '', $s);
	}

	static function chr($code, $encoding = 'UTF-8')
	{
		return iconv('UTF-32BE', $encoding . '//IGNORE', pack('N', $code));
	}

	static function startsWith($haystack, $needle)
	{
		return strncmp($haystack, $needle, strlen($needle)) === 0;
	}

	static function endsWith($haystack, $needle)
	{
		return strlen($needle) === 0 || substr($haystack, -strlen($needle)) === $needle;
	}

	static function normalize($s)
	{

		$s = str_replace("\r\n", "\n", $s);
		$s = strtr($s, "\r", "\n");

		$s = preg_replace('#[\x00-\x08\x0B-\x1F\x7F]+#', '', $s);

		$s = preg_replace("#[\t ]+$#m", '', $s);

		$s = trim($s, "\n");

		return $s;
	}

	static function toAscii($s)
	{
		$s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', '', $s);
		$s = strtr($s, '`\'"^~', "\x01\x02\x03\x04\x05");
		if (ICONV_IMPL === 'glibc') {
			$s = @iconv('UTF-8', 'WINDOWS-1250//TRANSLIT', $s);
			$s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e"
				. "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3"
				. "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
				. "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe",
				"ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt");
		} else {
			$s = @iconv('UTF-8', 'ASCII//TRANSLIT', $s);
		}
		$s = str_replace(array('`', "'", '"', '^', '~'), '', $s);
		return strtr($s, "\x01\x02\x03\x04\x05", '`\'"^~');
	}

	static function webalize($s, $charlist = NULL, $lower = TRUE)
	{
		$s = self::toAscii($s);
		if ($lower) {
			$s = strtolower($s);
		}
		$s = preg_replace('#[^a-z0-9' . preg_quote($charlist, '#') . ']+#i', '-', $s);
		$s = trim($s, '-');
		return $s;
	}

	static function truncate($s, $maxLen, $append = "\xE2\x80\xA6")
	{
		if (self::length($s) > $maxLen) {
			$maxLen = $maxLen - self::length($append);
			if ($maxLen < 1) {
				return $append;

			} elseif ($matches = self::match($s, '#^.{1,'.$maxLen.'}(?=[\s\x00-/:-@\[-`{-~])#us')) {
				return $matches[0] . $append;

			} else {
				return iconv_substr($s, 0, $maxLen, 'UTF-8') . $append;
			}
		}
		return $s;
	}

	static function indent($s, $level = 1, $chars = "\t")
	{
		return $level < 1 ? $s : self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
	}

	static function lower($s)
	{
		return mb_strtolower($s, 'UTF-8');
	}

	static function upper($s)
	{
		return mb_strtoupper($s, 'UTF-8');
	}

	static function firstUpper($s)
	{
		return self::upper(mb_substr($s, 0, 1, 'UTF-8')) . mb_substr($s, 1, self::length($s), 'UTF-8');
	}

	static function capitalize($s)
	{
		return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
	}

	static function compare($left, $right, $len = NULL)
	{
		if ($len < 0) {
			$left = iconv_substr($left, $len, -$len, 'UTF-8');
			$right = iconv_substr($right, $len, -$len, 'UTF-8');
		} elseif ($len !== NULL) {
			$left = iconv_substr($left, 0, $len, 'UTF-8');
			$right = iconv_substr($right, 0, $len, 'UTF-8');
		}
		return self::lower($left) === self::lower($right);
	}

	static function length($s)
	{
		return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s));
	}

	static function trim($s, $charlist = " \t\n\r\0\x0B\xC2\xA0")
	{
		$charlist = preg_quote($charlist, '#');
		return self::replace($s, '#^['.$charlist.']+|['.$charlist.']+$#u', '');
	}

	static function padLeft($s, $length, $pad = ' ')
	{
		$length = max(0, $length - self::length($s));
		$padLen = self::length($pad);
		return str_repeat($pad, $length / $padLen) . iconv_substr($pad, 0, $length % $padLen, 'UTF-8') . $s;
	}

	static function padRight($s, $length, $pad = ' ')
	{
		$length = max(0, $length - self::length($s));
		$padLen = self::length($pad);
		return $s . str_repeat($pad, $length / $padLen) . iconv_substr($pad, 0, $length % $padLen, 'UTF-8');
	}

	static function random($length = 10, $charlist = '0-9a-z')
	{
		$charlist = str_shuffle(preg_replace_callback('#.-.#', create_function('$m', '
			return implode(\'\', range($m[0][0], $m[0][2]));
		'), $charlist));
		$chLen = strlen($charlist);

		$s = '';
		for ($i = 0; $i < $length; $i++) {
			if ($i % 5 === 0) {
				$rand = lcg_value();
				$rand2 = microtime(TRUE);
			}
			$rand *= $chLen;
			$s .= $charlist[($rand + $rand2) % $chLen];
			$rand -= (int) $rand;
		}
		return $s;
	}

	static function split($subject, $pattern, $flags = 0)
	{
		NDebugger::tryError();
		$res = preg_split($pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE);
		self::catchPregError($pattern);
		return $res;
	}

	static function match($subject, $pattern, $flags = 0, $offset = 0)
	{
		if ($offset > strlen($subject)) {
			return NULL;
		}
		NDebugger::tryError();
		$res = preg_match($pattern, $subject, $m, $flags, $offset);
		self::catchPregError($pattern);
		if ($res) {
			return $m;
		}
	}

	static function matchAll($subject, $pattern, $flags = 0, $offset = 0)
	{
		if ($offset > strlen($subject)) {
			return array();
		}
		NDebugger::tryError();
		$res = preg_match_all(
			$pattern, $subject, $m,
			($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
			$offset
		);
		self::catchPregError($pattern);
		return $m;
	}

	static function replace($subject, $pattern, $replacement = NULL, $limit = -1)
	{
		NDebugger::tryError();
		if (is_object($replacement) || is_array($replacement)) {
			if ($replacement instanceof NCallback) {
				$replacement = $replacement->getNative();
			}
			if (!is_callable($replacement, FALSE, $textual)) {
				NDebugger::catchError($foo);
				throw new InvalidStateException("Callback '$textual' is not callable.");
			}
			$res = preg_replace_callback($pattern, $replacement, $subject, $limit);

			if (NDebugger::catchError($e)) {
				$trace = $e->getTrace();
				if (isset($trace[2]['class']) && $trace[2]['class'] === __CLASS__) {
					throw new NRegexpException($e->getMessage() . " in pattern: $pattern");
				}
			}

		} elseif (is_array($pattern)) {
			$res = preg_replace(array_keys($pattern), array_values($pattern), $subject, $limit);

		} else {
			$res = preg_replace($pattern, $replacement, $subject, $limit);
		}
		self::catchPregError($pattern);
		return $res;
	}

	static function catchPregError($pattern)
	{
		if (NDebugger::catchError($e)) {
			throw new NRegexpException($e->getMessage() . " in pattern: $pattern");

		} elseif (preg_last_error()) {
			static $messages = array(
				PREG_INTERNAL_ERROR => 'Internal error',
				PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
				PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
				PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
				5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
			);
			$code = preg_last_error();
			throw new NRegexpException((isset($messages[$code]) ? $messages[$code] : 'Unknown error') . " (pattern: $pattern)", $code);
		}
	}

	static function expand($s, array $params, $recursive = FALSE)
	{
		$parts = preg_split('#%([\w.-]*)%#i', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
		$res = '';
		foreach ($parts as $n => $part) {
			if ($n % 2 === 0) {
				$res .= $part;

			} elseif ($part === '') {
				$res .= '%';

			} elseif (isset($recursive[$part])) {
				throw new InvalidArgumentException('Circular reference detected for variables: ' . implode(', ', array_keys($recursive)) . '.');

			} else {
				$val = NArrays::get($params, explode('.', $part));
				if ($recursive && is_string($val)) {
					$val = self::expand($val, $params, (is_array($recursive) ? $recursive : array()) + array($part => 1));
				}
				if (strlen($part) + 2 === strlen($s)) {
					return $val;
				}
				if (!is_scalar($val)) {
					throw new InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$s'.");
				}
				$res .= $val;
			}
		}
		return $res;
	}

}

class NRegexpException extends Exception
{
}
NDebugger::_init();

NSafeStream::register();

function callback($callback, $m = NULL)
{
	return ($m === NULL && $callback instanceof NCallback) ? $callback : new NCallback($callback, $m);
}

function dump($var)
{
	foreach (func_get_args() as $arg) NDebugger::dump($arg);
	return $var;
}