code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a method's parameter.
 * @property-read ClassType $class
 * @property-read string $className
 * @property-read ClassType $declaringClass
 * @property-read Method $declaringFunction
 * @property-read string $name
 * @property-read bool $passedByReference
 * @property-read bool $array
 * @property-read int $position
 * @property-read bool $optional
 * @property-read bool $defaultValueAvailable
 * @property-read mixed $defaultValue
 */
class Parameter extends \ReflectionParameter
{
	/** @var mixed */
	private $function;

	public function __construct($function, $parameter)
	{
		parent::__construct($this->function = $function, $parameter);
	}

	/**
	 * @return ClassType
	 */
	public function getClass()
	{
		return ($ref = parent::getClass()) ? new ClassType($ref->getName()) : NULL;
	}

	/**
	 * @return string
	 */
	public function getClassName()
	{
		try {
			return ($ref = parent::getClass()) ? $ref->getName() : NULL;
		} catch (\ReflectionException $e) {
			if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
				return $m[1];
			}
			throw $e;
		}
	}

	/**
	 * @return ClassType
	 */
	public function getDeclaringClass()
	{
		return ($ref = parent::getDeclaringClass()) ? new ClassType($ref->getName()) : NULL;
	}

	/**
	 * @return Method|GlobalFunction
	 */
	public function getDeclaringFunction()
	{
		return is_array($this->function)
			? new Method($this->function[0], $this->function[1])
			: new GlobalFunction($this->function);
	}

	/**
	 * @return bool
	 */
	public function isDefaultValueAvailable()
	{
		if (PHP_VERSION_ID === 50316) { // PHP bug #62988
			try {
				$this->getDefaultValue();
				return TRUE;
			} catch (\ReflectionException $e) {
				return FALSE;
			}
		}
		return parent::isDefaultValueAvailable();
	}

	public function __toString()
	{
		return '$' . parent::getName() . ' in ' . $this->getDeclaringFunction();
	}

	/********************* Nette\Object behaviour ****************d*g**/

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

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

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

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

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

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a classes variable.
 * @property-read ClassType $declaringClass
 * @property-read IAnnotation[][] $annotations
 * @property-read string $description
 * @property-read string $name
 * @property  mixed $value
 * @property-read bool $public
 * @property-read bool $private
 * @property-read bool $protected
 * @property-read bool $static
 * @property-read bool $default
 * @property-read int $modifiers
 * @property-read string $docComment
 * @property-write bool $accessible
 */
class Property extends \ReflectionProperty
{

	public function __toString()
	{
		return parent::getDeclaringClass()->getName() . '::$' . $this->getName();
	}

	/********************* Reflection layer ****************d*g**/

	/**
	 * @return ClassType
	 */
	public function getDeclaringClass()
	{
		return new ClassType(parent::getDeclaringClass()->getName());
	}

	/********************* Nette\Annotations support ****************d*g**/

	/**
	 * Has property specified annotation?
	 * @param  string
	 * @return bool
	 */
	public function hasAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	/**
	 * Returns an annotation value.
	 * @param  string
	 * @return IAnnotation
	 */
	public function getAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	/**
	 * Returns all annotations.
	 * @return IAnnotation[][]
	 */
	public function getAnnotations()
	{
		return AnnotationsParser::getAll($this);
	}

	/**
	 * Returns value of annotation 'description'.
	 * @return string
	 */
	public function getDescription()
	{
		return $this->getAnnotation('description');
	}

	/********************* Nette\Object behaviour ****************d*g**/

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

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

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

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

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

}

repo_name: Cerx/ToDo

license: mit


nette/robot-loader

description: Nette RobotLoader: comfortable autoloading

minimum-stability: dev


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Loaders;

use Nette;
use Nette\Caching\Cache;
use SplFileInfo;

/**
 * Nette auto loader is responsible for loading classes and interfaces.
 *
 * @property-read array $indexedClasses
 * @property   Nette\Caching\IStorage $cacheStorage
 */
class RobotLoader extends Nette\Object
{
	const RETRY_LIMIT = 3;

	/** @var string|array  comma separated wildcards */
	public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';

	/** @var string|array  comma separated wildcards */
	public $acceptFiles = '*.php, *.php5';

	/** @var bool */
	public $autoRebuild = TRUE;

	/** @var array */
	private $scanPaths = array();

	/** @var array of lowered-class => [file, time, orig] or num-of-retry */
	private $classes = array();

	/** @var bool */
	private $rebuilt = FALSE;

	/** @var array of missing classes in this request */
	private $missing = array();

	/** @var Nette\Caching\IStorage */
	private $cacheStorage;

	public function __construct()
	{
		if (!extension_loaded('tokenizer')) {
			throw new Nette\NotSupportedException('PHP extension Tokenizer is not loaded.');
		}
	}

	/**
	 * Register autoloader.
	 * @param  bool  prepend autoloader?
	 * @return self
	 */
	public function register($prepend = FALSE)
	{
		$this->classes = $this->getCache()->load($this->getKey(), array($this, 'rebuildCallback'));
		spl_autoload_register(array($this, 'tryLoad'), TRUE, (bool) $prepend);
		return $this;
	}

	/**
	 * Handles autoloading of classes, interfaces or traits.
	 * @param  string
	 * @return void
	 */
	public function tryLoad($type)
	{
		$type = $orig = ltrim($type, '\\'); // PHP namespace bug #49143
		$type = strtolower($type);

		$info = & $this->classes[$type];
		if (isset($this->missing[$type]) || (is_int($info) && $info >= self::RETRY_LIMIT)) {
			return;
		}

		if ($this->autoRebuild) {
			if (!is_array($info) || !is_file($info['file'])) {
				$info = is_int($info) ? $info + 1 : 0;
				if ($this->rebuilt) {
					$this->getCache()->save($this->getKey(), $this->classes);
				} else {
					$this->rebuild();
				}
			} elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
				$this->updateFile($info['file']);
				if (!isset($this->classes[$type])) {
					$this->classes[$type] = 0;
				}
				$this->getCache()->save($this->getKey(), $this->classes);
			}
		}

		if (isset($this->classes[$type]['file'])) {
			if ($this->classes[$type]['orig'] !== $orig) {
				trigger_error("Case mismatch on class name '$orig', correct name is '{$this->classes[$type]['orig']}'.", E_USER_WARNING);
			}
			call_user_func(function ($file) { require $file; }, $this->classes[$type]['file']);
		} else {
			$this->missing[$type] = TRUE;
		}
	}

	/**
	 * Add path or paths to list.
	 * @param  string|string[]  absolute path
	 * @return self
	 */
	public function addDirectory($path)
	{
		$this->scanPaths = array_merge($this->scanPaths, (array) $path);
		return $this;
	}

	/**
	 * @return array of class => filename
	 */
	public function getIndexedClasses()
	{
		$res = array();
		foreach ($this->classes as $info) {
			if (is_array($info)) {
				$res[$info['orig']] = $info['file'];
			}
		}
		return $res;
	}

	/**
	 * Rebuilds class list cache.
	 * @return void
	 */
	public function rebuild()
	{
		$this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
		$this->getCache()->save($this->getKey(), Nette\Utils\Callback::closure($this, 'rebuildCallback'));
	}

	/**
	 * @internal
	 */
	public function rebuildCallback()
	{
		$files = $missing = array();
		foreach ($this->classes as $class => $info) {
			if (is_array($info)) {
				$files[$info['file']]['time'] = $info['time'];
				$files[$info['file']]['classes'][] = $info['orig'];
			} else {
				$missing[$class] = $info;
			}
		}

		$this->classes = array();
		foreach ($this->scanPaths as $path) {
			foreach (is_file($path) ? array(new SplFileInfo($path)) : $this->createFileIterator($path) as $file) {
				$file = $file->getPathname();
				if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
					$classes = $files[$file]['classes'];
				} else {
					$classes = $this->scanPhp(file_get_contents($file));
				}
				$files[$file] = array('classes' => array(), 'time' => filemtime($file));

				foreach ($classes as $class) {
					$info = & $this->classes[strtolower($class)];
					if (isset($info['file'])) {
						throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
					}
					$info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
				}
			}
		}
		$this->classes += $missing;
		return $this->classes;
	}

	/**
	 * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
	 * @return \Iterator
	 * @throws Nette\IOException if path is not found
	 */
	private function createFileIterator($dir)
	{
		if (!is_dir($dir)) {
			throw new Nette\IOException("File or directory '$dir' not found.");
		}

		$ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
		$disallow = array();
		foreach ($ignoreDirs as $item) {
			if ($item = realpath($item)) {
				$disallow[$item] = TRUE;
			}
		}

		$iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
			->filter(function (SplFileInfo $file) use (& $disallow) {
				return !isset($disallow[$file->getPathname()]);
			})
			->from($dir)
			->exclude($ignoreDirs)
			->filter($filter = function (SplFileInfo $dir) use (& $disallow) {
				$path = $dir->getPathname();
				if (is_file("$path/netterobots.txt")) {
					foreach (file("$path/netterobots.txt") as $s) {
						if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
							$disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = TRUE;
						}
					}
				}
				return !isset($disallow[$path]);
			});

		$filter(new SplFileInfo($dir));
		return $iterator;
	}

	/**
	 * @return void
	 */
	private function updateFile($file)
	{
		foreach ($this->classes as $class => $info) {
			if (isset($info['file']) && $info['file'] === $file) {
				unset($this->classes[$class]);
			}
		}

		if (is_file($file)) {
			foreach ($this->scanPhp(file_get_contents($file)) as $class) {
				$info = & $this->classes[strtolower($class)];
				if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // intentionally ==, file may not exists
					$this->updateFile($info['file']);
					$info = & $this->classes[strtolower($class)];
				}
				if (isset($info['file'])) {
					throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
				}
				$info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
			}
		}
	}

	/**
	 * Searches classes, interfaces and traits in PHP file.
	 * @param  string
	 * @return array
	 */
	private function scanPhp($code)
	{
		$T_TRAIT = PHP_VERSION_ID < 50400 ? -1 : T_TRAIT;

		$expected = FALSE;
		$namespace = '';
		$level = $minLevel = 0;
		$classes = array();

		if (preg_match('#//nette'.'loader=(\S*)#', $code, $matches)) {
			foreach (explode(',', $matches[1]) as $name) {
				$classes[] = $name;
			}
			return $classes;
		}

		foreach (@token_get_all($code) as $token) { // intentionally @
			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:
					case $T_TRAIT:
						$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:
					case $T_TRAIT:
						if ($name && $level === $minLevel) {
							$classes[] = $namespace . $name;
						}
						break;

					case T_NAMESPACE:
						$namespace = $name ? $name . '\\' : '';
						$minLevel = $token === '{' ? 1 : 0;
				}

				$expected = NULL;
			}

			if ($token === '{') {
				$level++;
			} elseif ($token === '}') {
				$level--;
			}
		}
		return $classes;
	}

	/********************* backend ****************d*g**/

	/**
	 * @return RobotLoader
	 */
	public function setCacheStorage(Nette\Caching\IStorage $storage)
	{
		$this->cacheStorage = $storage;
		return $this;
	}

	/**
	 * @return Nette\Caching\IStorage
	 */
	public function getCacheStorage()
	{
		return $this->cacheStorage;
	}

	/**
	 * @return Nette\Caching\Cache
	 */
	protected function getCache()
	{
		if (!$this->cacheStorage) {
			trigger_error('Missing cache storage.', E_USER_WARNING);
			$this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
		}
		return new Cache($this->cacheStorage, 'Nette.RobotLoader');
	}

	/**
	 * @return string
	 */
	protected function getKey()
	{
		return array($this->ignoreDirs, $this->acceptFiles, $this->scanPaths);
	}

}


nette/safe-stream

description: Nette SafeStream: Atomic Operations


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Utils;

/**
 * Provides atomicity and isolation for thread safe file manipulation using stream nette.safe://
 *
 * <code>
 * file_put_contents('nette.safe://myfile.txt', $content);
 *
 * $content = file_get_contents('nette.safe://myfile.txt');
 *
 * unlink('nette.safe://myfile.txt');
 * </code>
 * @internal
 */
class SafeStream
{
	/** Name of stream protocol - nette.safe:// */
	const PROTOCOL = 'nette.safe';

	/** @var resource  orignal file handle */
	private $handle;

	/** @var resource  temporary file handle */
	private $tempHandle;

	/** @var string  orignal file path */
	private $file;

	/** @var string  temporary file path */
	private $tempFile;

	/** @var bool */
	private $deleteFile;

	/** @var bool  error detected? */
	private $writeError = FALSE;

	/**
	 * Registers protocol 'nette.safe://'.
	 * @return bool
	 */
	public static function register()
	{
		foreach (array_intersect(stream_get_wrappers(), array('safe', self::PROTOCOL)) as $name) {
			stream_wrapper_unregister($name);
		}
		stream_wrapper_register('safe', __CLASS__); // old protocol
		return stream_wrapper_register(self::PROTOCOL, __CLASS__);
	}

	/**
	 * Opens file.
	 * @param  string    file name with stream protocol
	 * @param  string    mode - see fopen()
	 * @param  int       STREAM_USE_PATH, STREAM_REPORT_ERRORS
	 * @return bool      TRUE on success or FALSE on failure
	 */
	public function stream_open($path, $mode, $options)
	{
		$path = substr($path, strpos($path, ':') + 3);  // trim protocol nette.safe://

		$flag = trim($mode, 'crwax+');  // text | binary mode
		$mode = trim($mode, 'tb');     // mode
		$use_path = (bool) (STREAM_USE_PATH & $options); // use include_path?

		// open file
		if ($mode === 'r') { // provides only isolation
			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' || $mode[0] === 'c') {
			if ($this->checkAndLock($this->handle = @fopen($path, 'x'.$flag, $use_path), LOCK_EX)) { // intentionally @
				$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;
		}

		// create temporary file in the same directory to provide atomicity
		$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));

		// copy to temporary file
		if ($mode === 'r+' || $mode[0] === 'a' || $mode[0] === 'c') {
			$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') { // emulate append mode
				fseek($this->tempHandle, 0, SEEK_END);
			}
		}

		return TRUE;
	}

	/**
	 * Checks handle and locks file.
	 * @return bool
	 */
	private function checkAndLock($handle, $lock)
	{
		if (!$handle) {
			return FALSE;

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

		return TRUE;
	}

	/**
	 * Error destructor.
	 */
	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);
		}
	}

	/**
	 * Closes file.
	 * @return void
	 */
	public function stream_close()
	{
		if (!$this->tempFile) { // 'r' mode
			flock($this->tempHandle, LOCK_UN);
			fclose($this->tempHandle);
			return;
		}

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

		if ($this->writeError || !rename($this->tempFile, $this->file)) { // try to rename temp file
			unlink($this->tempFile); // otherwise delete temp file
			if ($this->deleteFile) {
				unlink($this->file);
			}
		}
	}

	/**
	 * Reads up to length bytes from the file.
	 * @param  int    length
	 * @return string
	 */
	public function stream_read($length)
	{
		return fread($this->tempHandle, $length);
	}

	/**
	 * Writes the string to the file.
	 * @param  string    data to write
	 * @return int      number of bytes that were successfully stored
	 */
	public function stream_write($data)
	{
		$len = strlen($data);
		$res = fwrite($this->tempHandle, $data, $len);

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

		return $res;
	}

	/**
	 * Truncates a file to a given length.
	 * @param  int    The size to truncate to.
	 * @return bool
	 */
	public function stream_truncate($size)
	{
		return ftruncate($this->tempHandle, $size);
	}

	/**
	 * Returns the position of the file.
	 * @return int
	 */
	public function stream_tell()
	{
		return ftell($this->tempHandle);
	}

	/**
	 * Returns TRUE if the file pointer is at end-of-file.
	 * @return bool
	 */
	public function stream_eof()
	{
		return feof($this->tempHandle);
	}

	/**
	 * Sets the file position indicator for the file.
	 * @param  int    position
	 * @param  int    see fseek()
	 * @return int   Return TRUE on success
	 */
	public function stream_seek($offset, $whence)
	{
		return fseek($this->tempHandle, $offset, $whence) === 0; // ???
	}

	/**
	 * Gets information about a file referenced by $this->tempHandle.
	 * @return array
	 */
	public function stream_stat()
	{
		return fstat($this->tempHandle);
	}

	/**
	 * Gets information about a file referenced by filename.
	 * @param  string    file name
	 * @param  int       STREAM_URL_STAT_LINK, STREAM_URL_STAT_QUIET
	 * @return array
	 */
	public function url_stat($path, $flags)
	{
		// This is not thread safe
		$path = substr($path, strpos($path, ':') + 3);
		return ($flags & STREAM_URL_STAT_LINK) ? @lstat($path) : @stat($path); // intentionally @
	}

	/**
	 * Deletes a file.
	 * On Windows unlink is not allowed till file is opened
	 * @param  string    file name with stream protocol
	 * @return bool      TRUE on success or FALSE on failure
	 */
	public function unlink($path)
	{
		$path = substr($path, strpos($path, ':') + 3);
		return unlink($path);
	}

}


code:
<?php

require __DIR__ . '/SafeStream/SafeStream.php';

Nette\Utils\SafeStream::register();


nette/security

description: Nette Security: Access Control Component


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Bridges\SecurityDI;

use Nette;

/**
 * Security extension for Nette DI.
 */
class SecurityExtension extends Nette\DI\CompilerExtension
{
	public $defaults = array(
		'debugger' => TRUE,
		'users' => array(), // of [user => password] or [user => ['password' => password, 'roles' => [role]]]
		'roles' => array(), // of [role => parents]
		'resources' => array(), // of [resource => parents]
	);

	/** @var bool */
	private $debugMode;

	public function __construct($debugMode = FALSE)
	{
		$this->debugMode = $debugMode;
	}

	public function loadConfiguration()
	{
		$config = $this->validateConfig($this->defaults);
		$container = $this->getContainerBuilder();

		$container->addDefinition($this->prefix('userStorage'))
			->setClass('Nette\Security\IUserStorage')
			->setFactory('Nette\Http\UserStorage');

		$user = $container->addDefinition($this->prefix('user'))
			->setClass('Nette\Security\User');

		if ($this->debugMode && $config['debugger']) {
			$user->addSetup('@Tracy\Bar::addPanel', array(
				new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel'),
			));
		}

		if ($config['users']) {
			$usersList = $usersRoles = array();
			foreach ($config['users'] as $username => $data) {
				$data = is_array($data) ? $data : array('password' => $data);
				$this->validateConfig(array('password' => NULL, 'roles' => NULL), $data, $this->prefix("security.users.$username"));
				$usersList[$username] = $data['password'];
				$usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL;
			}

			$container->addDefinition($this->prefix('authenticator'))
				->setClass('Nette\Security\IAuthenticator')
				->setFactory('Nette\Security\SimpleAuthenticator', array($usersList, $usersRoles));

			if ($this->name === 'security') {
				$container->addAlias('nette.authenticator', $this->prefix('authenticator'));
			}
		}

		if ($config['roles'] || $config['resources']) {
			$authorizator = $container->addDefinition($this->prefix('authorizator'))
				->setClass('Nette\Security\IAuthorizator')
				->setFactory('Nette\Security\Permission');

			foreach ($config['roles'] as $role => $parents) {
				$authorizator->addSetup('addRole', array($role, $parents));
			}
			foreach ($config['resources'] as $resource => $parents) {
				$authorizator->addSetup('addResource', array($resource, $parents));
			}

			if ($this->name === 'security') {
				$container->addAlias('nette.authorizator', $this->prefix('authorizator'));
			}
		}

		if ($this->name === 'security') {
			$container->addAlias('user', $this->prefix('user'));
			$container->addAlias('nette.userStorage', $this->prefix('userStorage'));
		}
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Bridges\SecurityTracy;

use Nette;
use Tracy;

/**
 * User panel for Debugger Bar.
 */
class UserPanel extends Nette\Object implements Tracy\IBarPanel
{
	/** @var Nette\Security\User */
	private $user;

	public function __construct(Nette\Security\User $user)
	{
		$this->user = $user;
	}

	/**
	 * Renders tab.
	 * @return string
	 */
	public function getTab()
	{
		if (headers_sent() && !session_id()) {
			return;
		}

		ob_start();
		$user = $this->user;
		require __DIR__ . '/templates/UserPanel.tab.phtml';
		return ob_get_clean();
	}

	/**
	 * Renders panel.
	 * @return string
	 */
	public function getPanel()
	{
		ob_start();
		$user = $this->user;
		require __DIR__ . '/templates/UserPanel.panel.phtml';
		return ob_get_clean();
	}

}


code:
<?php

namespace Nette\Bridges\SecurityTracy;

use Nette,
	Tracy\Dumper;

?>

<?php if ($user->isLoggedIn()): ?>Logged in<?php else: ?>Unlogged<?php endif ?>

	<?php if ($user->getIdentity()): echo Dumper::toHtml($user->getIdentity(), array(Dumper::LIVE => TRUE)); else: ?>
no identity
<?php endif ?>


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Authentication exception.
 */
class AuthenticationException extends \Exception
{
}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Performs authentication.
 */
interface IAuthenticator
{
	/** Credential key */
	const USERNAME = 0,
		PASSWORD = 1;

	/** Exception error code */
	const IDENTITY_NOT_FOUND = 1,
		INVALID_CREDENTIAL = 2,
		FAILURE = 3,
		NOT_APPROVED = 4;

	/**
	 * Performs an authentication against e.g. database.
	 * and returns IIdentity on success or throws AuthenticationException
	 * @return IIdentity
	 * @throws AuthenticationException
	 */
	function authenticate(array $credentials);

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Authorizator checks if a given role has authorization
 * to access a given resource.
 */
interface IAuthorizator
{
	/** Set type: all */
	const ALL = NULL;

	/** Permission type: allow */
	const ALLOW = TRUE;

	/** Permission type: deny */
	const DENY = FALSE;

	/**
	 * Performs a role-based authorization.
	 * @param  string  role
	 * @param  string  resource
	 * @param  string  privilege
	 * @return bool
	 */
	function isAllowed($role, $resource, $privilege);

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Represents the user of application.
 */
interface IIdentity
{

	/**
	 * Returns the ID of user.
	 * @return mixed
	 */
	function getId();

	/**
	 * Returns a list of roles that the user is a member of.
	 * @return array
	 */
	function getRoles();

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Represents resource, an object to which access is controlled.
 */
interface IResource
{

	/**
	 * Returns a string identifier of the Resource.
	 * @return string
	 */
	function getResourceId();

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Represents role, an object that may request access to an IResource.
 */
interface IRole
{

	/**
	 * Returns a string identifier of the Role.
	 * @return string
	 */
	function getRoleId();

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

/**
 * Interface for persistent storage for user object data.
 */
interface IUserStorage
{
	/** Log-out reason {@link IUserStorage::getLogoutReason()} */
	const MANUAL = 1,
		INACTIVITY = 2,
		BROWSER_CLOSED = 4;

	/** Log-out behavior */
	const CLEAR_IDENTITY = 8;

	/**
	 * Sets the authenticated status of this user.
	 * @param  bool
	 * @return void
	 */
	function setAuthenticated($state);

	/**
	 * Is this user authenticated?
	 * @return bool
	 */
	function isAuthenticated();

	/**
	 * Sets the user identity.
	 * @return void
	 */
	function setIdentity(IIdentity $identity = NULL);

	/**
	 * Returns current user identity, if any.
	 * @return IIdentity|NULL
	 */
	function getIdentity();

	/**
	 * Enables log out from the persistent storage after inactivity.
	 * @param  string|int|DateTime number of seconds or timestamp
	 * @param  int Log out when the browser is closed | Clear the identity from persistent storage?
	 * @return void
	 */
	function setExpiration($time, $flags = 0);

	/**
	 * Why was user logged out?
	 * @return int
	 */
	function getLogoutReason();

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

use Nette;

/**
 * Default implementation of IIdentity.
 *
 * @property   mixed $id
 * @property   array $roles
 * @property-read array $data
 */
class Identity extends Nette\Object implements IIdentity
{
	/** @var mixed */
	private $id;

	/** @var array */
	private $roles;

	/** @var array */
	private $data;

	/**
	 * @param  mixed   identity ID
	 * @param  mixed   roles
	 * @param  array   user data
	 */
	public 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;
	}

	/**
	 * Sets the ID of user.
	 * @param  mixed
	 * @return self
	 */
	public function setId($id)
	{
		$this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id;
		return $this;
	}

	/**
	 * Returns the ID of user.
	 * @return mixed
	 */
	public function getId()
	{
		return $this->id;
	}

	/**
	 * Sets a list of roles that the user is a member of.
	 * @param  array
	 * @return self
	 */
	public function setRoles(array $roles)
	{
		$this->roles = $roles;
		return $this;
	}

	/**
	 * Returns a list of roles that the user is a member of.
	 * @return array
	 */
	public function getRoles()
	{
		return $this->roles;
	}

	/**
	 * Returns a user data.
	 * @return array
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Sets user data value.
	 * @param  string  property name
	 * @param  mixed   property value
	 * @return void
	 */
	public function __set($key, $value)
	{
		if (parent::__isset($key)) {
			parent::__set($key, $value);

		} else {
			$this->data[$key] = $value;
		}
	}

	/**
	 * Returns user data value.
	 * @param  string  property name
	 * @return mixed
	 */
	public function &__get($key)
	{
		if (parent::__isset($key)) {
			return parent::__get($key);

		} else {
			return $this->data[$key];
		}
	}

	/**
	 * Is property defined?
	 * @param  string  property name
	 * @return bool
	 */
	public function __isset($key)
	{
		return isset($this->data[$key]) || parent::__isset($key);
	}

	/**
	 * Removes property.
	 * @param  string  property name
	 * @return void
	 * @throws Nette\MemberAccessException
	 */
	public function __unset($name)
	{
		Nette\Utils\ObjectMixin::remove($this, $name);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

use Nette;

/**
 * Passwords tools. Requires PHP >= 5.3.7.
 */
class Passwords
{
	const BCRYPT_COST = 10;

	/**
	 * Computes salted password hash.
	 * @param  string
	 * @param  array with cost (4-31), salt (22 chars)
	 * @return string  60 chars long
	 */
	public static function hash($password, array $options = NULL)
	{
		$cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST;
		$salt = isset($options['salt']) ? (string) $options['salt'] : Nette\Utils\Random::generate(22, '0-9A-Za-z./');

		if (PHP_VERSION_ID < 50307) {
			throw new Nette\NotSupportedException(__METHOD__ . ' requires PHP >= 5.3.7.');
		} elseif (($len = strlen($salt)) < 22) {
			throw new Nette\InvalidArgumentException("Salt must be 22 characters long, $len given.");
		} elseif ($cost < 4 || $cost > 31) {
			throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $cost given.");
		}

		$hash = crypt($password, '$2y$' . ($cost < 10 ? 0 : '') . $cost . '$' . $salt);
		if (strlen($hash) < 60) {
			throw new Nette\InvalidStateException('Hash returned by crypt is invalid.');
		}
		return $hash;
	}

	/**
	 * Verifies that a password matches a hash.
	 * @return bool
	 */
	public static function verify($password, $hash)
	{
		return preg_match('#^\$2y\$(?P<cost>\d\d)\$(?P<salt>.{22})#', $hash, $m)
			&& $m['cost'] >= 4 && $m['cost'] <= 31
			&& self::hash($password, $m) === $hash;
	}

	/**
	 * Checks if the given hash matches the options.
	 * @param  string
	 * @param  array with cost (4-31)
	 * @return bool
	 */
	public static function needsRehash($hash, array $options = NULL)
	{
		$cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST;
		return !preg_match('#^\$2y\$(?P<cost>\d\d)\$(?P<salt>.{22})#', $hash, $m)
			|| $m['cost'] < $cost;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

use Nette;

/**
 * Access control list (ACL) functionality and privileges management.
 *
 * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license
 *
 * @copyright  Copyright (c) 2005, 2007 Zend Technologies USA Inc.
 *
 * @property-read array $roles
 * @property-read array $resources
 * @property-read mixed $queriedRole
 * @property-read mixed $queriedResource
 */
class Permission extends Nette\Object implements IAuthorizator
{
	/** @var array  Role storage */
	private $roles = array();

	/** @var array  Resource storage */
	private $resources = array();

	/** @var array  Access Control List rules; whitelist (deny everything to all) by default */
	private $rules = array(
		'allResources' => array(
			'allRoles' => array(
				'allPrivileges' => array(
					'type' => self::DENY,
					'assert' => NULL,
				),
				'byPrivilege' => array(),
			),
			'byRole' => array(),
		),
		'byResource' => array(),
	);

	/** @var mixed */
	private $queriedRole, $queriedResource;

	/********************* roles ****************d*g**/

	/**
	 * Adds a Role to the list. The most recently added parent
	 * takes precedence over parents that were previously added.
	 * @param  string
	 * @param  string|array
	 * @throws Nette\InvalidArgumentException
	 * @throws Nette\InvalidStateException
	 * @return self
	 */
	public function addRole($role, $parents = NULL)
	{
		$this->checkRole($role, FALSE);
		if (isset($this->roles[$role])) {
			throw new Nette\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;
	}

	/**
	 * Returns TRUE if the Role exists in the list.
	 * @param  string
	 * @return bool
	 */
	public function hasRole($role)
	{
		$this->checkRole($role, FALSE);
		return isset($this->roles[$role]);
	}

	/**
	 * Checks whether Role is valid and exists in the list.
	 * @param  string
	 * @param  bool
	 * @throws Nette\InvalidStateException
	 * @return void
	 */
	private function checkRole($role, $need = TRUE)
	{
		if (!is_string($role) || $role === '') {
			throw new Nette\InvalidArgumentException('Role must be a non-empty string.');

		} elseif ($need && !isset($this->roles[$role])) {
			throw new Nette\InvalidStateException("Role '$role' does not exist.");
		}
	}

	/**
	 * Returns all Roles.
	 * @return array
	 */
	public function getRoles()
	{
		return array_keys($this->roles);
	}

	/**
	 * Returns existing Role's parents ordered by ascending priority.
	 * @param  string
	 * @return array
	 */
	public function getRoleParents($role)
	{
		$this->checkRole($role);
		return array_keys($this->roles[$role]['parents']);
	}

	/**
	 * Returns TRUE if $role inherits from $inherit. If $onlyParents is TRUE,
	 * then $role must inherit directly from $inherit.
	 * @param  string
	 * @param  string
	 * @param  bool
	 * @throws Nette\InvalidStateException
	 * @return bool
	 */
	public 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;
	}

	/**
	 * Removes the Role from the list.
	 *
	 * @param  string
	 * @throws Nette\InvalidStateException
	 * @return self
	 */
	public 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;
	}

	/**
	 * Removes all Roles from the list.
	 *
	 * @return self
	 */
	public 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;
	}

	/********************* resources ****************d*g**/

	/**
	 * Adds a Resource having an identifier unique to the list.
	 *
	 * @param  string
	 * @param  string
	 * @throws Nette\InvalidArgumentException
	 * @throws Nette\InvalidStateException
	 * @return self
	 */
	public function addResource($resource, $parent = NULL)
	{
		$this->checkResource($resource, FALSE);

		if (isset($this->resources[$resource])) {
			throw new Nette\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;
	}

	/**
	 * Returns TRUE if the Resource exists in the list.
	 * @param  string
	 * @return bool
	 */
	public function hasResource($resource)
	{
		$this->checkResource($resource, FALSE);
		return isset($this->resources[$resource]);
	}

	/**
	 * Checks whether Resource is valid and exists in the list.
	 * @param  string
	 * @param  bool
	 * @throws Nette\InvalidStateException
	 * @return void
	 */
	private function checkResource($resource, $need = TRUE)
	{
		if (!is_string($resource) || $resource === '') {
			throw new Nette\InvalidArgumentException('Resource must be a non-empty string.');

		} elseif ($need && !isset($this->resources[$resource])) {
			throw new Nette\InvalidStateException("Resource '$resource' does not exist.");
		}
	}

	/**
	 * Returns all Resources.
	 * @return array
	 */
	public function getResources()
	{
		return array_keys($this->resources);
	}

	/**
	 * Returns TRUE if $resource inherits from $inherit. If $onlyParents is TRUE,
	 * then $resource must inherit directly from $inherit.
	 *
	 * @param  string
	 * @param  string
	 * @param  bool
	 * @throws Nette\InvalidStateException
	 * @return bool
	 */
	public 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;
	}

	/**
	 * Removes a Resource and all of its children.
	 *
	 * @param  string
	 * @throws Nette\InvalidStateException
	 * @return self
	 */
	public 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;
	}

	/**
	 * Removes all Resources.
	 * @return self
	 */
	public 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;
	}

	/********************* defining rules ****************d*g**/

	/**
	 * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s).
	 * If $assertion is provided, then it must return TRUE in order for rule to apply.
	 *
	 * @param  string|array|Permission::ALL  roles
	 * @param  string|array|Permission::ALL  resources
	 * @param  string|array|Permission::ALL  privileges
	 * @param  callable    assertion
	 * @return self
	 */
	public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
	{
		$this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion);
		return $this;
	}

	/**
	 * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s).
	 * If $assertion is provided, then it must return TRUE in order for rule to apply.
	 *
	 * @param  string|array|Permission::ALL  roles
	 * @param  string|array|Permission::ALL  resources
	 * @param  string|array|Permission::ALL  privileges
	 * @param  callable    assertion
	 * @return self
	 */
	public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
	{
		$this->setRule(TRUE, self::DENY, $roles, $resources, $privileges, $assertion);
		return $this;
	}

	/**
	 * Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges.
	 *
	 * @param  string|array|Permission::ALL  roles
	 * @param  string|array|Permission::ALL  resources
	 * @param  string|array|Permission::ALL  privileges
	 * @return self
	 */
	public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
	{
		$this->setRule(FALSE, self::ALLOW, $roles, $resources, $privileges);
		return $this;
	}

	/**
	 * Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges.
	 *
	 * @param  string|array|Permission::ALL  roles
	 * @param  string|array|Permission::ALL  resources
	 * @param  string|array|Permission::ALL  privileges
	 * @return self
	 */
	public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
	{
		$this->setRule(FALSE, self::DENY, $roles, $resources, $privileges);
		return $this;
	}

	/**
	 * Performs operations on Access Control List rules.
	 * @param  bool  operation add?
	 * @param  bool  type
	 * @param  string|array|Permission::ALL  roles
	 * @param  string|array|Permission::ALL  resources
	 * @param  string|array|Permission::ALL  privileges
	 * @param  callable    assertion
	 * @throws Nette\InvalidStateException
	 * @return self
	 */
	protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = NULL)
	{
		// ensure that all specified Roles exist; normalize input to array of Roles or NULL
		if ($roles === self::ALL) {
			$roles = array(self::ALL);

		} else {
			if (!is_array($roles)) {
				$roles = array($roles);
			}

			foreach ($roles as $role) {
				$this->checkRole($role);
			}
		}

		// ensure that all specified Resources exist; normalize input to array of Resources or NULL
		if ($resources === self::ALL) {
			$resources = array(self::ALL);

		} else {
			if (!is_array($resources)) {
				$resources = array($resources);
			}

			foreach ($resources as $resource) {
				$this->checkResource($resource);
			}
		}

		// normalize privileges to array
		if ($privileges === self::ALL) {
			$privileges = array();

		} elseif (!is_array($privileges)) {
			$privileges = array($privileges);
		}

		if ($toAdd) { // add to the rules
			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 { // remove from the rules
			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;
	}

	/********************* querying the ACL ****************d*g**/

	/**
	 * Returns TRUE if and only if the Role has access to [certain $privileges upon] the Resource.
	 *
	 * This method checks Role inheritance using a depth-first traversal of the Role list.
	 * The highest priority parent (i.e., the parent most recently added) is checked first,
	 * and its respective parents are checked similarly before the lower-priority parents of
	 * the Role are checked.
	 *
	 * @param  string|Permission::ALL|IRole  role
	 * @param  string|Permission::ALL|IResource  resource
	 * @param  string|Permission::ALL  privilege
	 * @throws Nette\InvalidStateException
	 * @return bool
	 */
	public 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 {
			// depth-first search on $role if it is not 'allRoles' pseudo-parent
			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)) { // look for rule on 'allRoles' psuedo-parent
					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))) { // look for rule on 'allRoles' pseudo-parent
					break;

				} elseif (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
					break;
				}
			}

			$resource = $this->resources[$resource]['parent']; // try next Resource
		} while (TRUE);

		$this->queriedRole = $this->queriedResource = NULL;
		return $result;
	}

	/**
	 * Returns real currently queried Role. Use by assertion.
	 * @return mixed
	 */
	public function getQueriedRole()
	{
		return $this->queriedRole;
	}

	/**
	 * Returns real currently queried Resource. Use by assertion.
	 * @return mixed
	 */
	public function getQueriedResource()
	{
		return $this->queriedResource;
	}

	/********************* internals ****************d*g**/

	/**
	 * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule
	 * allowing/denying $role access to a/all $privilege upon $resource.
	 * @param  bool  all (true) or one?
	 * @param  string
	 * @param  string
	 * @param  string  only for one
	 * @return mixed  NULL if no applicable rule is found, otherwise returns ALLOW or DENY
	 */
	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;
	}

	/**
	 * Returns the rule type associated with the specified Resource, Role, and privilege.
	 * @param  string|Permission::ALL
	 * @param  string|Permission::ALL
	 * @param  string|Permission::ALL
	 * @return mixed  NULL if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY
	 */
	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 || Nette\Utils\Callback::invoke($rule['assert'], $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;
		}
	}

	/**
	 * Returns the rules associated with a Resource and a Role, or NULL if no such rules exist.
	 * If the $create parameter is TRUE, then a rule set is first created and then returned to the caller.
	 * @param  string|Permission::ALL
	 * @param  string|Permission::ALL
	 * @param  bool
	 * @return array|NULL
	 */
	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];
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

use Nette;

/**
 * Trivial implementation of IAuthenticator.
 */
class SimpleAuthenticator extends Nette\Object implements IAuthenticator
{
	/** @var array */
	private $userlist;

	/** @var array */
	private $usersRoles;

	/**
	 * @param  array  list of pairs username => password
	 * @param  array  list of pairs username => role[]
	 */
	public function __construct(array $userlist, array $usersRoles = array())
	{
		$this->userlist = $userlist;
		$this->usersRoles = $usersRoles;
	}

	/**
	 * Performs an authentication against e.g. database.
	 * and returns IIdentity on success or throws AuthenticationException
	 * @return IIdentity
	 * @throws AuthenticationException
	 */
	public 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 Identity($name, isset($this->usersRoles[$name]) ? $this->usersRoles[$name] : NULL);
				} else {
					throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL);
				}
			}
		}
		throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (http://nette.org)
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 */

namespace Nette\Security;

use Nette;

/**
 * User authentication and authorization.
 *
 * @property-read bool $loggedIn
 * @property-read IIdentity $identity
 * @property-read mixed $id
 * @property-read array $roles
 * @property-read int $logoutReason
 * @property-read IUserStorage $storage
 * @property   IAuthenticator $authenticator
 * @property   IAuthorizator $authorizator
 */
class User extends Nette\Object
{
	/** @deprecated */
	const MANUAL = IUserStorage::MANUAL,
		INACTIVITY = IUserStorage::INACTIVITY,
		BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED;

	/** @var string  default role for unauthenticated user */
	public $guestRole = 'guest';

	/** @var string  default role for authenticated user without own identity */
	public $authenticatedRole = 'authenticated';

	/** @var callable[]  function (User $sender); Occurs when the user is successfully logged in */
	public $onLoggedIn;

	/** @var callable[]  function (User $sender); Occurs when the user is logged out */
	public $onLoggedOut;

	/** @var IUserStorage Session storage for current user */
	private $storage;

	/** @var IAuthenticator */
	private $authenticator;

	/** @var IAuthorizator */
	private $authorizator;

	public function __construct(IUserStorage $storage, IAuthenticator $authenticator = NULL, IAuthorizator $authorizator = NULL)
	{
		$this->storage = $storage;
		$this->authenticator = $authenticator;
		$this->authorizator = $authorizator;
	}

	/**
	 * @return IUserStorage
	 */
	public function getStorage()
	{
		return $this->storage;
	}

	/********************* Authentication ****************d*g**/

	/**
	 * Conducts the authentication process. Parameters are optional.
	 * @param  mixed optional parameter (e.g. username or IIdentity)
	 * @param  mixed optional parameter (e.g. password)
	 * @return void
	 * @throws AuthenticationException if authentication was not successful
	 */
	public function login($id = NULL, $password = NULL)
	{
		$this->logout(TRUE);
		if (!$id instanceof IIdentity) {
			$id = $this->getAuthenticator()->authenticate(func_get_args());
		}
		$this->storage->setIdentity($id);
		$this->storage->setAuthenticated(TRUE);
		$this->onLoggedIn($this);
	}

	/**
	 * Logs out the user from the current session.
	 * @param  bool  clear the identity from persistent storage?
	 * @return void
	 */
	public function logout($clearIdentity = FALSE)
	{
		if ($this->isLoggedIn()) {
			$this->onLoggedOut($this);
			$this->storage->setAuthenticated(FALSE);
		}
		if ($clearIdentity) {
			$this->storage->setIdentity(NULL);
		}
	}

	/**
	 * Is this user authenticated?
	 * @return bool
	 */
	public function isLoggedIn()
	{
		return $this->storage->isAuthenticated();
	}

	/**
	 * Returns current user identity, if any.
	 * @return IIdentity|NULL
	 */
	public function getIdentity()
	{
		return $this->storage->getIdentity();
	}

	/**
	 * Returns current user ID, if any.
	 * @return mixed
	 */
	public function getId()
	{
		$identity = $this->getIdentity();
		return $identity ? $identity->getId() : NULL;
	}

	/**
	 * Sets authentication handler.
	 * @return self
	 */
	public function setAuthenticator(IAuthenticator $handler)
	{
		$this->authenticator = $handler;
		return $this;
	}

	/**
	 * Returns authentication handler.
	 * @return IAuthenticator
	 */
	public function getAuthenticator($need = TRUE)
	{
		if ($need && !$this->authenticator) {
			throw new Nette\InvalidStateException('Authenticator has not been set.');
		}
		return $this->authenticator;
	}

	/**
	 * Enables log out after inactivity.
	 * @param  string|int|DateTime number of seconds or timestamp
	 * @param  bool  log out when the browser is closed?
	 * @param  bool  clear the identity from persistent storage?
	 * @return self
	 */
	public function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE)
	{
		$flags = ($whenBrowserIsClosed ? IUserStorage::BROWSER_CLOSED : 0) | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0);
		$this->storage->setExpiration($time, $flags);
		return $this;
	}

	/**
	 * Why was user logged out?
	 * @return int
	 */
	public function getLogoutReason()
	{
		return $this->storage->getLogoutReason();
	}

	/********************* Authorization ****************d*g**/

	/**
	 * Returns a list of effective roles that a user has been granted.
	 * @return array
	 */
	public function getRoles()
	{
		if (!$this->isLoggedIn()) {
			return array($this->guestRole);
		}

		$identity = $this->getIdentity();
		return $identity && $identity->getRoles() ? $identity->getRoles() : array($this->authenticatedRole);
	}

	/**
	 * Is a user in the specified effective role?
	 * @param  string
	 * @return bool
	 */
	public function isInRole($role)
	{
		return in_array($role, $this->getRoles(), TRUE);
	}

	/**
	 * Has a user effective access to the Resource?
	 * If $resource is NULL, then the query applies to all resources.
	 * @param  string  resource
	 * @param  string  privilege
	 * @return bool
	 */
	public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL)
	{
		foreach ($this->getRoles() as $role) {
			if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) {
				return TRUE;
			}
		}

		return FALSE;
	}

	/**
	 * Sets authorization handler.
	 * @return self
	 */
	public function setAuthorizator(IAuthorizator $handler)
	{
		$this->authorizator = $handler;
		return $this;
	}

	/**
	 * Returns current authorization handler.
	 * @return IAuthorizator
	 */
	public function getAuthorizator($need = TRUE)
	{
		if ($need && !$this->authorizator) {
			throw new Nette\InvalidStateException('Authorizator has not been set.');
		}
		return $this->authorizator;
	}

}


nette/tester

description: An easy-to-use PHP unit testing framework.

keywords: testing unit nette


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester\CodeCoverage;

/**
 * Code coverage collector.
 */
class Collector
{
	/** @var resource */
	private static $file;

	/**
	 * Starts gathering the information for code coverage.
	 * @param  string
	 * @return void
	 */
	public static function start($file)
	{
		if (self::$file) {
			throw new \LogicException('Code coverage collector has been already started.');
		}
		self::$file = fopen($file, 'a+');

		if (defined('PHPDBG_VERSION') && PHP_VERSION_ID >= 70000) {
			phpdbg_start_oplog();
			$collector = 'collectPhpDbg';

		} elseif (extension_loaded('xdebug')) {
			xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
			$collector = 'collectXdebug';

		} else {
			$alternative = PHP_VERSION_ID >= 70000 ? ' or phpdbg SAPI' : '';
			throw new \Exception("Code coverage functionality requires Xdebug extension$alternative.");
		}

		register_shutdown_function(function () use ($collector) {
			register_shutdown_function(function () use ($collector) {
				list($positive, $negative) = call_user_func(array(__CLASS__, $collector));
				self::save($positive, $negative);
			});
		});
	}

	/**
	 * Collects information about code coverage.
	 * @return array
	 */
	private static function collectXdebug()
	{
		$positive = $negative = array();

		foreach (xdebug_get_code_coverage() as $file => $lines) {
			if (!file_exists($file)) {
				continue;
			}

			foreach ($lines as $num => $val) {
				if ($val > 0) {
					$positive[$file][$num] = $val;
				} else {
					$negative[$file][$num] = $val;
				}
			}
		}

		return array($positive, $negative);
	}

	/**
	 * Collects information about code coverage.
	 * @return array
	 */
	private static function collectPhpDbg()
	{
		$positive = phpdbg_end_oplog();
		$negative = phpdbg_get_executable();

		foreach ($positive as $file => & $lines) {
			$lines = array_fill_keys(array_keys($lines), 1);
		}

		foreach ($negative as $file => & $lines) {
			$lines = array_fill_keys(array_keys($lines), -1);
		}

		return array($positive, $negative);
	}

	/**
	 * Saves information about code coverage. Do not call directly.
	 * @return void
	 */
	private static function save(array $positive, array $negative)
	{
		flock(self::$file, LOCK_EX);
		fseek(self::$file, 0);
		$original = @unserialize(stream_get_contents(self::$file)) ?: array(); // @ file may be empty
		$coverage = array_replace_recursive($negative, $original, $positive);

		ftruncate(self::$file, 0);
		fwrite(self::$file, serialize($coverage));
		fclose(self::$file);
	}

}


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester\CodeCoverage\Generators;

/**
 * Code coverage report generator.
 */
abstract class AbstractGenerator
{
	const
		CODE_DEAD = -2,
		CODE_UNTESTED = -1,
		CODE_TESTED = 1;

	/** @var array */
	public $acceptFiles = array('php', 'phpc', 'phpt', 'phtml');

	/** @var array */
	protected $data;

	/** @var string */
	protected $source;

	/**
	 * @param  string  path to coverage.dat file
	 * @param  string  path to covered source file or directory
	 */
	public function __construct($file, $source = NULL)
	{
		if (!is_file($file)) {
			throw new \Exception("File '$file' is missing.");
		}

		$this->data = @unserialize(file_get_contents($file)); // @ is escalated to exception
		if (!is_array($this->data)) {
			throw new \Exception("Content of file '$file' is invalid.");
		}

		if (!$source) {
			$source = key($this->data);
			for ($i = 0; $i < strlen($source); $i++) {
				foreach ($this->data as $s => $foo) {
					if (!isset($s[$i]) || $source[$i] !== $s[$i]) {
						$source = substr($source, 0, $i);
						break 2;
					}
				}
			}
			$source = dirname($source . 'x');

		} elseif (!file_exists($source)) {
			throw new \Exception("File or directory '$source' is missing.");
		}

		$this->source = realpath($source);
	}

	public function render($file = NULL)
	{
		$handle = $file ? @fopen($file, 'w') : STDOUT; // @ is escalated to exception
		if (!$handle) {
			throw new \Exception("Unable to write to file '$file'.");
		}

		ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096);
		try {
			$this->renderSelf();
		} catch (\Exception $e) {
		}
		ob_end_flush();
		fclose($handle);

		if (isset($e)) {
			if ($file) {
				unlink($file);
			}
			throw $e;
		}
	}

	/**
	 * @return AcceptIterator
	 */
	protected function getSourceIterator()
	{
		$iterator = is_dir($this->source)
			? new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->source))
			: new \ArrayIterator(array(new \SplFileInfo($this->source)));

		return new AcceptIterator($iterator, $this->acceptFiles);
	}

	abstract protected function renderSelf();

}

/** @internal */
class AcceptIterator extends \FilterIterator
{
	private $acceptFiles;

	public function __construct(\Iterator $iterator, array $acceptFiles)
	{
		parent::__construct($iterator);
		$this->acceptFiles = $acceptFiles;
	}

	public function accept()
	{
		return substr($this->current()->getBasename(), 0, 1) !== '.'  // . or .. or .gitignore
			&& in_array(pathinfo($this->current(), PATHINFO_EXTENSION), $this->acceptFiles, TRUE);
	}

}


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester\CodeCoverage\Generators;

use DOMDocument;
use DOMElement;
use Tester\CodeCoverage\PhpParser;

class CloverXMLGenerator extends AbstractGenerator
{

	private static $metricAttributesMap = array(
		'packageCount' => 'packages',
		'fileCount' => 'files',
		'linesOfCode' => 'loc',
		'linesOfNonCommentedCode' => 'ncloc',
		'classCount' => 'classes',
		'methodCount' => 'methods',
		'coveredMethodCount' => 'coveredmethods',
		'statementCount' => 'statements',
		'coveredStatementCount' => 'coveredstatements',
		'elementCount' => 'elements',
		'coveredElementCount' => 'coveredelements',
		'conditionalCount' => 'conditionals',
		'coveredConditionalCount' => 'coveredconditionals',
	);

	public function __construct($file, $source = NULL)
	{
		if (!extension_loaded('dom')) {
			throw new \LogicException('CloverXML generator requires DOM extension to be loaded.');
		}
		parent::__construct($file, $source);
	}

	protected function renderSelf()
	{
		$time = time();
		$parser = new PhpParser;

		$doc = new DOMDocument;
		$doc->formatOutput = TRUE;

		$elCoverage = $doc->appendChild($doc->createElement('coverage'));
		$elCoverage->setAttribute('generated', $time);

		// TODO: @name
		$elProject = $elCoverage->appendChild($doc->createElement('project'));
		$elProject->setAttribute('timestamp', $time);
		$elProjectMetrics = $elProject->appendChild($doc->createElement('metrics'));

		$projectMetrics = (object) array(
			'packageCount' => 0,
			'fileCount' => 0,
			'linesOfCode' => 0,
			'linesOfNonCommentedCode' => 0,
			'classCount' => 0,
			'methodCount' => 0,
			'coveredMethodCount' => 0,
			'statementCount' => 0,
			'coveredStatementCount' => 0,
			'elementCount' => 0,
			'coveredElementCount' => 0,
			'conditionalCount' => 0,
			'coveredConditionalCount' => 0,
		);

		foreach ($this->getSourceIterator() as $file) {
			$file = (string) $file;

			$projectMetrics->fileCount++;

			$coverageData = isset($this->data[$file]) ? $this->data[$file] : NULL;

			// TODO: split to <package> by namespace?
			$elFile = $elProject->appendChild($doc->createElement('file'));
			$elFile->setAttribute('name', $file);
			$elFileMetrics = $elFile->appendChild($doc->createElement('metrics'));

			$code = $parser->parse(file_get_contents($file));

			$fileMetrics = (object) array(
				'linesOfCode' => $code->linesOfCode,
				'linesOfNonCommentedCode' => $code->linesOfCode - $code->linesOfComments,
				'classCount' => count($code->classes) + count($code->traits),
				'methodCount' => 0,
				'coveredMethodCount' => 0,
				'statementCount' => 0,
				'coveredStatementCount' => 0,
				'elementCount' => 0,
				'coveredElementCount' => 0,
				'conditionalCount' => 0,
				'coveredConditionalCount' => 0,
			);

			foreach (array_merge($code->classes, $code->traits) as $name => $info) { // TODO: interfaces?
				$elClass = $elFile->appendChild($doc->createElement('class'));
				if (($tmp = strrpos($name, '\\')) === FALSE) {
					$elClass->setAttribute('name', $name);
				} else {
					$elClass->setAttribute('namespace', substr($name, 0, $tmp));
					$elClass->setAttribute('name', substr($name, $tmp + 1));
				}

				$elClassMetrics = $elClass->appendChild($doc->createElement('metrics'));
				$classMetrics = $this->calculateClassMetrics($info, $coverageData);
				self::setMetricAttributes($elClassMetrics, $classMetrics);
				self::appendMetrics($fileMetrics, $classMetrics);
			}
			self::setMetricAttributes($elFileMetrics, $fileMetrics);

			foreach ((array) $coverageData as $line => $count) {
				if ($count === self::CODE_DEAD) {
					continue;
				}

				// Line type can be 'method' but Xdebug does not report such lines as executed.
				$elLine = $elFile->appendChild($doc->createElement('line'));
				$elLine->setAttribute('num', $line);
				$elLine->setAttribute('type', 'stmt');
				$elLine->setAttribute('count', max(0, $count));
			}

			self::appendMetrics($projectMetrics, $fileMetrics);
		}

		// TODO: What about reported (covered) lines outside of class/trait definition?
		self::setMetricAttributes($elProjectMetrics, $projectMetrics);

		echo $doc->saveXML();
	}

	/**
	 * @return \stdClass
	 */
	private function calculateClassMetrics(\stdClass $info, array $coverageData = NULL)
	{
		$stats = (object) array(
			'methodCount' => count($info->methods),
			'coveredMethodCount' => 0,
			'statementCount' => 0,
			'coveredStatementCount' => 0,
			'conditionalCount' => 0,
			'coveredConditionalCount' => 0,
			'elementCount' => NULL,
			'coveredElementCount' => NULL,
		);

		foreach ($info->methods as $name => $methodInfo) {
			list($lineCount, $coveredLineCount) = $this->analyzeMethod($methodInfo, $coverageData);

			$stats->statementCount += $lineCount;

			if ($coverageData !== NULL) {
				$stats->coveredMethodCount += $lineCount === $coveredLineCount ? 1 : 0;
				$stats->coveredStatementCount += $coveredLineCount;
			}
		}

		$stats->elementCount = $stats->methodCount + $stats->statementCount;
		$stats->coveredElementCount = $stats->coveredMethodCount + $stats->coveredStatementCount;

		return $stats;
	}

	/**
	 * @return array
	 */
	private static function analyzeMethod(\stdClass $info, array $coverageData = NULL)
	{
		$count = 0;
		$coveredCount = 0;

		if ($coverageData === NULL) { // Never loaded file
			$count = max(1, $info->end - $info->start - 2);
		} else {
			for ($i = $info->start; $i <= $info->end; $i++) {
				if (isset($coverageData[$i]) && $coverageData[$i] !== self::CODE_DEAD) {
					$count++;
					if ($coverageData[$i] > 0) {
						$coveredCount++;
					}
				}
			}
		}

		return array($count, $coveredCount);
	}

	private static function appendMetrics(\stdClass $summary, \stdClass $add)
	{
		foreach ($add as $name => $value) {
			$summary->{$name} += $value;
		}
	}

	private static function setMetricAttributes(DOMElement $element, \stdClass $metrics)
	{
		foreach ($metrics as $name => $value) {
			$element->setAttribute(self::$metricAttributesMap[$name], $value);
		}
	}

}


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester\CodeCoverage\Generators;

/**
 * Code coverage report generator.
 */
class HtmlGenerator extends AbstractGenerator
{
	/** @var string */
	private $title;

	/** @var array */
	private $files = array();

	/** @var int */
	private $totalSum = 0;

	/** @var int */
	private $coveredSum = 0;

	/** @var array */
	public static $classes = array(
		self::CODE_TESTED => 't', // tested
		self::CODE_UNTESTED => 'u', // untested
		self::CODE_DEAD => 'dead', // dead code
	);

	/**
	 * @param  string  path to coverage.dat file
	 * @param  string  path to source file/directory
	 * @param  string
	 */
	public function __construct($file, $source = NULL, $title = NULL)
	{
		parent::__construct($file, $source);
		$this->title = $title;
	}

	protected function renderSelf()
	{
		$this->setupHighlight();
		$this->parse();

		$title = $this->title;
		$classes = self::$classes;
		$files = $this->files;
		$totalSum = $this->totalSum;
		$coveredSum = $this->coveredSum;

		include __DIR__ . '/template.phtml';
	}

	private function setupHighlight()
	{
		ini_set('highlight.comment', '#999; 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');
	}

	private function parse()
	{
		if (count($this->files) > 0) {
			return;
		}

		$this->files = array();
		foreach ($this->getSourceIterator() as $entry) {
			$entry = (string) $entry;

			$coverage = $covered = $total = 0;
			$loaded = isset($this->data[$entry]);
			$lines = array();
			if ($loaded) {
				$lines = $this->data[$entry];
				foreach ($lines as $flag) {
					if ($flag >= self::CODE_UNTESTED) {
						$total++;
					}
					if ($flag >= self::CODE_TESTED) {
						$covered++;
					}
				}
				$coverage = round($covered * 100 / $total);
				$this->totalSum += $total;
				$this->coveredSum += $covered;
			} else {
				$this->totalSum += count(file($entry, FILE_SKIP_EMPTY_LINES));
			}

			$light = $total ? $total < 5 : count(file($entry)) < 50;
			$this->files[] = (object) array(
				'name' => str_replace((is_dir($this->source) ? $this->source : dirname($this->source)) . DIRECTORY_SEPARATOR, '', $entry),
				'file' => $entry,
				'lines' => $lines,
				'coverage' => $coverage,
				'total' => $total,
				'class' => $light ? 'light' : ($loaded ? NULL : 'not-loaded'),
			);
		}
	}

}


code:
<!DOCTYPE html>

	<meta charset="utf-8">
	<meta name="robots" content="noindex,noarchive">
	<meta name="generator" content="Nette Tester">

	<title><?php echo $title ? htmlSpecialChars("$title - ") : ''; ?>Code coverage</title>

<?php echo $title ? htmlSpecialChars("$title - ") : ''; ?>Code coverage <?php echo $totalSum ? round($coveredSum * 100 / $totalSum) : 0 ?> %

	<?php foreach ($files as $id => $info): ?>

		<tr<?php echo $info->class ? " class='$info->class'" : '' ?>>
			<?php echo $info->coverage ?> %

<div style="width: <?php echo $info->coverage ?>%">

			<a href="#fragment<?php echo $id ?>" onclick="toggle(this); return false"><?php echo $info->name ?>

		<div class="code" id="fragment<?php echo $id ?>">
		<?php
			$source = explode('
', highlight_file($info->file, TRUE));

			end($source);
			$numWidth = strlen((string) key($source));

			unset($prevColor);
			$tags = '';
			foreach ($source as $n => $line) {
				if (isset($info->lines[$n + 1]) && isset($classes[$info->lines[$n + 1]])) {
					$color = $classes[$info->lines[$n + 1]];
				} else {
					$color = '';  // not executable
				}
				if (!isset($prevColor)) {
					$prevColor = $color;
				}
				$line = sprintf("%{$numWidth}s:    ", $n + 1) . $line;
				if ($prevColor !== $color || $n === count($source) - 1) {
					echo '
', str_replace(' />', '>', $tags);
					$openTags = array();
					preg_match_all('#<([^>]+)#', $tags, $matches);
					foreach ($matches[1] as $m) {
						if ($m[0] === '/') {
							array_pop($openTags);
						} elseif (substr($m, -1) !== '/') {
							$openTags[] = $m;
						}
					}
					foreach (array_reverse($openTags) as $tag) {
						echo '</' . preg_replace('# .*#', '', $tag) . '>';
					}
					echo "
\n";
					$tags = ($openTags ? '<' . implode('><', $openTags) . '>' : '');
					$prevColor = $color;
				}
				$tags .= "$line
\n";
			}
		?>

	<?php endforeach ?>

Generated by Nette Tester at <?php echo @date('Y/m/d H:i:s') // @ timezone may not be set ?>


code:
<?php

namespace Tester\CodeCoverage;

/**
 * Parses PHP source code and returns:
 * - the start/end line information about functions, classes, interfaces, traits and their methods
 * - the count of code lines
 * - the count of commented code lines
 *
 * @internal
 */
class PhpParser
{
	/**
	 * @param  string  PHP code to analyze
	 * @return \stdClass
	 *
	 * Returned structure is:
	 *     stdClass {
	 *         linesOfCode: int,
	 *         linesOfComments: int,
	 *         functions: [functionName => $functionInfo],
	 *         classes: [className => $info],
	 *         traits: [traitName => $info],
	 *         interfaces: [interfaceName => $info],
	 *     }
	 *
	 * where $functionInfo is:
	 *     stdClass {
	 *         start: int,
	 *         end: int
	 *     }
	 *
	 * and $info is:
	 *     stdClass {
	 *         start: int,
	 *         end: int,
	 *         methods: [methodName => $methodInfo]
	 *     }
	 *
	 * where $methodInfo is:
	 *     stdClass {
	 *         start: int,
	 *         end: int,
	 *         visibility: public|protected|private
	 *     }
	 */
	public function parse($code)
	{
		$tokens = @token_get_all($code); // @ - source code can be written in newer PHP

		$level = $classLevel = $functionLevel = NULL;
		$namespace = '';
		$line = 1;

		$result = (object) array(
			'linesOfCode' => max(1, substr_count($code, "\n")),
			'linesOfComments' => 0,
			'functions' => array(),
			'classes' => array(),
			'traits' => array(),
			'interfaces' => array(),
		);

		$T_TRAIT = PHP_VERSION_ID < 50400 ? -1 : T_TRAIT;
		while (list(, $token) = each($tokens)) {
			if (is_array($token)) {
				if (PHP_VERSION_ID < 50400 && $token[0] === T_STRING && strcasecmp($token[1], 'trait') === 0) {
					$token[0] = $T_TRAIT;
				}
				$line = $token[2];
			}

			switch (is_array($token) ? $token[0] : $token) {
				case T_NAMESPACE:
					$namespace = ltrim(self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)) . '\\', '\\');
					break;

				case T_CLASS:
				case T_INTERFACE:
				case $T_TRAIT:
					if ($name = self::fetch($tokens, T_STRING)) {
						if ($token[0] === T_CLASS) {
							$class = & $result->classes[$namespace . $name];
						} elseif ($token[0] === T_INTERFACE) {
							$class = & $result->interfaces[$namespace . $name];
						} else {
							$class = & $result->traits[$namespace . $name];
						}

						$classLevel = $level + 1;
						$class = (object) array(
							'start' => $line,
							'end' => NULL,
							'methods' => array(),
						);
					}
					break;

				case T_PUBLIC:
				case T_PROTECTED:
				case T_PRIVATE:
					$visibility = $token[1];
					break;

				case T_ABSTRACT:
					$isAbstract = TRUE;
					break;

				case T_FUNCTION:
					if (($name = self::fetch($tokens, T_STRING)) && !isset($isAbstract)) {
						if (isset($class) && $level === $classLevel) {
							$function = & $class->methods[$name];
							$function = (object) array(
								'start' => $line,
								'end' => NULL,
								'visibility' => isset($visibility) ? $visibility : 'public',
							);

						} else {
							$function = & $result->functions[$namespace . $name];
							$function = (object) array(
								'start' => $line,
								'end' => NULL,
							);
						}
						$functionLevel = $level + 1;
					}
					unset($visibility, $isAbstract);
					break;

				case T_CURLY_OPEN:
				case T_DOLLAR_OPEN_CURLY_BRACES:
				case '{':
					$level++;
					break;

				case '}':
					if (isset($function) && $level === $functionLevel) {
						$function->end = $line;
						unset($function);

					} elseif (isset($class) && $level === $classLevel) {
						$class->end = $line;
						unset($class);
					}
					$level--;
					break;

				case T_COMMENT:
				case T_DOC_COMMENT:
					$result->linesOfComments += substr_count(trim($token[1]), "\n") + 1;
					// break omitted

				case T_WHITESPACE:
				case T_CONSTANT_ENCAPSED_STRING:
					$line += substr_count($token[1], "\n");
					break;
			}
		}

		return $result;
	}

	private static function fetch(& $tokens, $take)
	{
		$res = NULL;
		while ($token = current($tokens)) {
			list($token, $s) = is_array($token) ? $token : array($token, $token);
			if (in_array($token, (array) $take, TRUE)) {
				$res .= $s;
			} elseif (!in_array($token, array(T_DOC_COMMENT, T_WHITESPACE, T_COMMENT), TRUE)) {
				break;
			}
			next($tokens);
		}
		return $res;
	}

}


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester;

/**
 * Assertion test helpers.
 */
class Assert
{
	/** used by equal() for comparing floats */
	const EPSILON = 1e-10;

	/** used by match(); in values, each $ followed by number is backreference */
	public static $patterns = array(
		'%a%' => '[^\r\n]+',    // one or more of anything except the end of line characters
		'%a\?%' => '[^\r\n]*',  // zero or more of anything except the end of line characters
		'%A%' => '.+',          // one or more of anything including the end of line characters
		'%A\?%' => '.*',        // zero or more of anything including the end of line characters
		'%s%' => '[\t ]+',      // one or more white space characters except the end of line characters
		'%s\?%' => '[\t ]*',    // zero or more white space characters except the end of line characters
		'%S%' => '\S+',         // one or more of characters except the white space
		'%S\?%' => '\S*',       // zero or more of characters except the white space
		'%c%' => '[^\r\n]',     // a single character of any sort (except the end of line)
		'%d%' => '[0-9]+',      // one or more digits
		'%d\?%' => '[0-9]*',    // zero or more digits
		'%i%' => '[+-]?[0-9]+', // signed integer value
		'%f%' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', // floating point number
		'%h%' => '[0-9a-fA-F]+',// one or more HEX digits
		'%ds%' => '[\\\\/]',    // directory separator
		'%(\[.+\][+*?{},\d]*)%' => '$1', // range
	);

	/** @var callable  function (AssertException $exception) */
	public static $onFailure;

	/** @var int  the count of assertions */
	public static $counter = 0;

	/**
	 * Checks assertion. Values must be exactly the same.
	 * @return void
	 */
	public static function same($expected, $actual, $description = NULL)
	{
		self::$counter++;
		if ($actual !== $expected) {
			self::fail(self::describe('%1 should be %2', $description), $actual, $expected);
		}
	}

	/**
	 * Checks assertion. Values must not be exactly the same.
	 * @return void
	 */
	public static function notSame($expected, $actual, $description = NULL)
	{
		self::$counter++;
		if ($actual === $expected) {
			self::fail(self::describe('%1 should not be %2', $description), $actual, $expected);
		}
	}

	/**
	 * Checks assertion. The identity of objects and the order of keys in the arrays are ignored.
	 * @return void
	 */
	public static function equal($expected, $actual, $description = NULL)
	{
		self::$counter++;
		if (!self::isEqual($expected, $actual)) {
			self::fail(self::describe('%1 should be equal to %2', $description), $actual, $expected);
		}
	}

	/**
	 * Checks assertion. The identity of objects and the order of keys in the arrays are ignored.
	 * @return void
	 */
	public static function notEqual($expected, $actual, $description = NULL)
	{
		self::$counter++;
		if (self::isEqual($expected, $actual)) {
			self::fail(self::describe('%1 should not be equal to %2', $description), $actual, $expected);
		}
	}

	/**
	 * Checks assertion. Values must contains expected needle.
	 * @return void
	 */
	public static function contains($needle, $actual, $description = NULL)
	{
		self::$counter++;
		if (is_array($actual)) {
			if (!in_array($needle, $actual, TRUE)) {
				self::fail(self::describe('%1 should contain %2', $description), $actual, $needle);
			}
		} elseif (is_string($actual)) {
			if ($needle !== '' && strpos($actual, $needle) === FALSE) {
				self::fail(self::describe('%1 should contain %2', $description), $actual, $needle);
			}
		} else {
			self::fail(self::describe('%1 should be string or array', $description), $actual);
		}
	}

	/**
	 * Checks assertion. Values must not contains expected needle.
	 * @return void
	 */
	public static function notContains($needle, $actual, $description = NULL)
	{
		self::$counter++;
		if (is_array($actual)) {
			if (in_array($needle, $actual, TRUE)) {
				self::fail(self::describe('%1 should not contain %2', $description), $actual, $needle);
			}
		} elseif (is_string($actual)) {
			if ($needle === '' || strpos($actual, $needle) !== FALSE) {
				self::fail(self::describe('%1 should not contain %2', $description), $actual, $needle);
			}
		} else {
			self::fail(self::describe('%1 should be string or array', $description), $actual);
		}
	}

	/**
	 * Checks TRUE assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function true($actual, $description = NULL)
	{
		self::$counter++;
		if ($actual !== TRUE) {
			self::fail(self::describe('%1 should be TRUE', $description), $actual);
		}
	}

	/**
	 * Checks FALSE assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function false($actual, $description = NULL)
	{
		self::$counter++;
		if ($actual !== FALSE) {
			self::fail(self::describe('%1 should be FALSE', $description), $actual);
		}
	}

	/**
	 * Checks NULL assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function null($actual, $description = NULL)
	{
		self::$counter++;
		if ($actual !== NULL) {
			self::fail(self::describe('%1 should be NULL', $description), $actual);
		}
	}

	/**
	 * Checks Not a Number assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function nan($actual, $description = NULL)
	{
		self::$counter++;
		if (!is_float($actual) || !is_nan($actual)) {
			self::fail(self::describe('%1 should be NAN', $description), $actual);
		}
	}

	/**
	 * Checks truthy assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function truthy($actual, $description = NULL)
	{
		self::$counter++;
		if (!$actual) {
			self::fail(self::describe('%1 should be truthy', $description), $actual);
		}
	}

	/**
	 * Checks falsey (empty) assertion.
	 * @param  mixed  actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function falsey($actual, $description = NULL)
	{
		self::$counter++;
		if ($actual) {
			self::fail(self::describe('%1 should be falsey', $description), $actual);
		}
	}

	/**
	 * Checks if subject has expected count.
	 * @param  int    expected count
	 * @param  mixed  subject
	 * @param  string  fail message
	 * @return void
	 */
	public static function count($count, $value, $description = NULL)
	{
		self::$counter++;
		if (!$value instanceof \Countable && !is_array($value)) {
			self::fail(self::describe('%1 should be array or countable object', $description), $value);

		} elseif (count($value) !== $count) {
			self::fail(self::describe('Count %1 should be %2', $description), count($value), $count);
		}
	}

	/**
	 * Checks assertion.
	 * @return void
	 */
	public static function type($type, $value, $description = NULL)
	{
		self::$counter++;
		if (!is_object($type) && !is_string($type)) {
			throw new \Exception('Type must be a object or a string.');

		} elseif ($type === 'list') {
			if (!is_array($value) || ($value && array_keys($value) !== range(0, count($value) - 1))) {
				self::fail(self::describe("%1 should be $type", $description), $value);
			}

		} elseif (in_array($type, array('array', 'bool', 'callable', 'float',
			'int', 'integer', 'null', 'object', 'resource', 'scalar', 'string'), TRUE)
		) {
			if (!call_user_func("is_$type", $value)) {
				self::fail(self::describe(gettype($value) . " should be $type", $description));
			}

		} elseif (!$value instanceof $type) {
			$actual = is_object($value) ? get_class($value) : gettype($value);
			self::fail(self::describe("$actual should be instance of $type", $description));
		}
	}

	/**
	 * Checks if the function throws exception.
	 * @param  callable
	 * @param  string class
	 * @param  string message
	 * @param  int code
	 * @return \Exception
	 */
	public static function exception($function, $class, $message = NULL, $code = NULL)
	{
		self::$counter++;
		$e = NULL;
		try {
			call_user_func($function);
		} catch (\Throwable $e) {
		} catch (\Exception $e) {
		}
		if ($e === NULL) {
			self::fail("$class was expected, but none was thrown");

		} elseif (!$e instanceof $class) {
			self::fail("$class was expected but got " . get_class($e) . ($e->getMessage() ? " ({$e->getMessage()})" : ''));

		} elseif ($message && !self::isMatching($message, $e->getMessage())) {
			self::fail("$class with a message matching %2 was expected but got %1", $e->getMessage(), $message);

		} elseif ($code !== NULL && $e->getCode() !== $code) {
			self::fail("$class with a code %2 was expected but got %1", $e->getCode(), $code);
		}
		return $e;
	}

	/**
	 * Checks if the function throws exception, alias for exception().
	 * @return \Exception
	 */
	public static function throws($function, $class, $message = NULL, $code = NULL)
	{
		return self::exception($function, $class, $message, $code);
	}

	/**
	 * Checks if the function generates PHP error or throws exception.
	 * @param  callable
	 * @param  int|string|array
	 * @param  string message
	 * @return null|\Exception
	 */
	public static function error($function, $expectedType, $expectedMessage = NULL)
	{
		if (is_string($expectedType) && !preg_match('#^E_[A-Z_]+\z#', $expectedType)) {
			return static::exception($function, $expectedType, $expectedMessage);
		}

		self::$counter++;
		$expected = is_array($expectedType) ? $expectedType : array(array($expectedType, $expectedMessage));
		foreach ($expected as & $item) {
			list($expectedType, $expectedMessage) = $item;
			if (is_int($expectedType)) {
				$item[2] = Helpers::errorTypeToString($expectedType);
			} elseif (is_string($expectedType)) {
				$item[0] = constant($item[2] = $expectedType);
			} else {
				throw new \Exception('Error type must be E_* constant.');
			}
		}

		set_error_handler(function ($severity, $message, $file, $line) use (& $expected) {
			if (($severity & error_reporting()) !== $severity) {
				return;
			}

			$errorStr = Helpers::errorTypeToString($severity) . ($message ? " ($message)" : '');
			list($expectedType, $expectedMessage, $expectedTypeStr) = array_shift($expected);
			if ($expectedType === NULL) {
				Assert::fail("Generated more errors than expected: $errorStr was generated in file $file on line $line");

			} elseif ($severity !== $expectedType) {
				Assert::fail("$expectedTypeStr was expected, but $errorStr was generated in file $file on line $line");

			} elseif ($expectedMessage && !Assert::isMatching($expectedMessage, $message)) {
				Assert::fail("$expectedTypeStr with a message matching %2 was expected but got %1", $message, $expectedMessage);
			}
		});

		reset($expected);
		try {
			call_user_func($function);
			restore_error_handler();
		} catch (\Exception $e) {
			restore_error_handler();
			throw $e;
		}

		if ($expected) {
			self::fail('Error was expected, but was not generated');
		}
	}

	/**
	 * Checks that the function does not generate PHP error and does not throw exception.
	 * @param  callable
	 * @return void
	 */
	public static function noError($function)
	{
		self::error($function, array());
	}

	/**
	 * Compares result using regular expression or mask:
	 *   %a%    one or more of anything except the end of line characters
	 *   %a?%   zero or more of anything except the end of line characters
	 *   %A%    one or more of anything including the end of line characters
	 *   %A?%   zero or more of anything including the end of line characters
	 *   %s%    one or more white space characters except the end of line characters
	 *   %s?%   zero or more white space characters except the end of line characters
	 *   %S%    one or more of characters except the white space
	 *   %S?%   zero or more of characters except the white space
	 *   %c%    a single character of any sort (except the end of line)
	 *   %d%    one or more digits
	 *   %d?%   zero or more digits
	 *   %i%    signed integer value
	 *   %f%    floating point number
	 *   %h%    one or more HEX digits
	 * @param  string  mask|regexp; only delimiters ~ and # are supported for regexp
	 * @param  string actual
	 * @param  string  fail message
	 * @return void
	 */
	public static function match($pattern, $actual, $description = NULL)
	{
		self::$counter++;
		if (!is_string($pattern)) {
			throw new \Exception('Pattern must be a string.');

		} elseif (!is_scalar($actual) || !self::isMatching($pattern, $actual)) {
			self::fail(self::describe('%1 should match %2', $description), $actual, rtrim($pattern));
		}
	}

	/**
	 * Compares results using mask sorted in file.
	 * @return void
	 */
	public static function matchFile($file, $actual, $description = NULL)
	{
		self::$counter++;
		$pattern = @file_get_contents($file); // @ is escalated to exception
		if ($pattern === FALSE) {
			throw new \Exception("Unable to read file '$file'.");

		} elseif (!is_scalar($actual) || !self::isMatching($pattern, $actual)) {
			self::fail(self::describe('%1 should match %2', $description), $actual, rtrim($pattern));
		}
	}

	/**
	 * Failed assertion
	 * @return void
	 */
	public static function fail($message, $actual = NULL, $expected = NULL)
	{
		$e = new AssertException($message, $expected, $actual);
		if (self::$onFailure) {
			call_user_func(self::$onFailure, $e);
		} else {
			throw $e;
		}
	}

	private static function describe($reason, $description)
	{
		return ($description ? $description . ': ' : '') . $reason;
	}

	public static function with($obj, \Closure $closure)
	{
		return $closure->bindTo($obj, $obj)->__invoke();
	}

	/********************* helpers ****************d*g**/

	/**
	 * Compares using mask.
	 * @return bool
	 * @internal
	 */
	public static function isMatching($pattern, $actual)
	{
		if (!is_string($pattern) && !is_scalar($actual)) {
			throw new \Exception('Value and pattern must be strings.');
		}

		$old = ini_set('pcre.backtrack_limit', '10000000');

		if (!preg_match('/^([~#]).+(\1)[imsxUu]*\z/s', $pattern)) {
			$utf8 = preg_match('#\x80-\x{10FFFF}]#u', $pattern) ? 'u' : '';
			$patterns = static::$patterns + array(
				'[.\\\\+*?[^$(){|\x00\#]' => '\$0', // preg quoting
				'[\t ]*\r?\n' => "[\\t ]*\n", // right trim
			);
			$pattern = '#^' . preg_replace_callback('#' . implode('|', array_keys($patterns)) . '#U' . $utf8, function ($m) use ($patterns) {
				foreach ($patterns as $re => $replacement) {
					$s = preg_replace("#^$re\\z#", str_replace('\\', '\\\\', $replacement), $m[0], 1, $count);
					if ($count) {
						return $s;
					}
				}
			}, rtrim($pattern)) . '\s*$#sU' . $utf8;
			$actual = str_replace("\r\n", "\n", $actual);
		}

		$res = preg_match($pattern, $actual);
		ini_set('pcre.backtrack_limit', $old);
		if ($res === FALSE || preg_last_error()) {
			throw new \Exception('Error while executing regular expression. (PREG Error Code ' . preg_last_error() . ')');
		}
		return (bool) $res;
	}

	/**
	 * Compares two structures. Ignores the identity of objects and the order of keys in the arrays.
	 * @return bool
	 * @internal
	 */
	public static function isEqual($expected, $actual, $level = 0, $objects = NULL)
	{
		if ($level > 10) {
			throw new \Exception('Nesting level too deep or recursive dependency.');
		}

		if (is_float($expected) && is_float($actual) && is_finite($expected) && is_finite($actual)) {
			$diff = abs($expected - $actual);
			return ($diff < self::EPSILON) || ($diff / max(abs($expected), abs($actual)) < self::EPSILON);
		}

		if (is_object($expected) && is_object($actual) && get_class($expected) === get_class($actual)) {
			$objects = $objects ? clone $objects : new \SplObjectStorage;
			if (isset($objects[$expected])) {
				return $objects[$expected] === $actual;
			} elseif ($expected === $actual) {
				return TRUE;
			}
			$objects[$expected] = $actual;
			$objects[$actual] = $expected;
			$expected = (array) $expected;
			$actual = (array) $actual;
		}

		if (is_array($expected) && is_array($actual)) {
			ksort($expected, SORT_STRING);
			ksort($actual, SORT_STRING);
			if (array_keys($expected) !== array_keys($actual)) {
				return FALSE;
			}

			foreach ($expected as $value) {
				if (!self::isEqual($value, current($actual), $level + 1, $objects)) {
					return FALSE;
				}
				next($actual);
			}
			return TRUE;
		}

		return $expected === $actual;
	}

}


code:
<?php

/**
 * This file is part of the Nette Tester.
 * Copyright (c) 2009 David Grudl (https://davidgrudl.com)
 */

namespace Tester;

/**
 * Assertion exception.
 */
class AssertException extends \Exception
{
	public $origMessage;

	public $actual;

	public $expected;

	public function __construct($message, $expected, $actual)
	{
		parent::__construct();
		$this->expected = $expected;
		$this->actual = $actual;
		$this->setMessage($message);
	}

	public function setMessage($message)
	{
		$this->origMessage = $message;
		$this->message = strtr($message, array(
			'%1' => Dumper::toLine($this->actual),
			'%2' => Dumper::toLine($this->expected),
		));
		return $this;
	}

}