code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * HTML output
 */
final class TexyHtmlOutputModule extends TexyModule
{
	/** @var bool  indent HTML code? */
	public $indent = TRUE;

	/** @var array */
	public $preserveSpaces = array('textarea', 'pre', 'script', 'code', 'samp', 'kbd');

	/** @var int  base indent level */
	public $baseIndent = 0;

	/** @var int  wrap width, doesn't include indent space */
	public $lineWrap = 80;

	/** @var bool  remove optional HTML end tags? */
	public $removeOptional = FALSE;

	/** @var int  indent space counter */
	private $space;

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

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

	/** @var array  content DTD used, when context is not defined */
	private $baseDTD;

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

	public function __construct($texy)
	{
		$this->texy = $texy;
		$texy->addHandler('postProcess', array($this, 'postProcess'));
	}

	/**
	 * Converts  ...  ... .
	 * into  ...  ...
	 */
	public function postProcess($texy, & $s)
	{
		$this->space = $this->baseIndent;
		$this->tagStack = array();
		$this->tagUsed = array();
		$this->xml = $texy->getOutputMode() & Texy::XML;

		// special "base content"
		$this->baseDTD = $texy->dtd['div'][1] + $texy->dtd['html'][1] /*+ $texy->dtd['head'][1]*/ + $texy->dtd['body'][1] + array('html' => 1);

		// wellform and reformat
		$s = TexyRegexp::replace(
			$s . '</end/>',
			'#([^<]*+)<(?:(!--.*--)|(/?)([a-z][a-z0-9._:-]*)(|[ \n].*)\s*(/?))>()#Uis',
			array($this, 'cb')
		);

		// empty out stack
		foreach ($this->tagStack as $item) {
			$s .= $item['close'];
		}

		// right trim
		$s = TexyRegexp::replace($s, "#[\t ]+(\n|\r|$)#", '$1'); // right trim

		// join double \r to single \n
		$s = str_replace("\r\r", "\n", $s);
		$s = strtr($s, "\r", "\n");

		// greedy chars
		$s = TexyRegexp::replace($s, '#\\x07 *#', '');
		// back-tabs
		$s = TexyRegexp::replace($s, '#\\t? *\\x08#', '');

		// line wrap
		if ($this->lineWrap > 0) {
			$s = TexyRegexp::replace(
				$s,
				'#^(\t*)(.*)$#m',
				array($this, 'wrap')
			);
		}

		// remove HTML 4.01 optional end tags
		if (!$this->xml && $this->removeOptional) {
			$s = TexyRegexp::replace($s, '#\\s*</(colgroup|dd|dt|li|option|p|td|tfoot|th|thead|tr)>#u', '');
		}
	}

	/**
	 * Callback function: <tag> | </tag> | ....
	 * @return string
	 * @internal
	 */
	public function cb($matches)
	{
		// html tag
		list(, $mText, $mComment, $mEnd, $mTag, $mAttr, $mEmpty) = $matches;
		// [1] => text
		// [1] => !-- comment --
		// [2] => /
		// [3] => TAG
		// [4] => ... (attributes)
		// [5] => / (empty)

		$s = '';

		// phase #1 - stuff between tags
		if ($mText !== '') {
			$item = reset($this->tagStack);
			if ($item && !isset($item['dtdContent']['%DATA'])) {  // text not allowed?

			} elseif (array_intersect(array_keys($this->tagUsed, TRUE), $this->preserveSpaces)) { // inside pre & textarea preserve spaces
				$s = Texy::freezeSpaces($mText);

			} else {
				$s = TexyRegexp::replace($mText, '#[ \n]+#', ' '); // otherwise shrink multiple spaces
			}
		}

		// phase #2 - HTML comment
		if ($mComment) {
			return $s . '<' . Texy::freezeSpaces($mComment) . '>';
		}

		// phase #3 - HTML tag
		$mEmpty = $mEmpty || isset(TexyHtml::$emptyElements[$mTag]);
		if ($mEmpty && $mEnd) {
			return $s; // bad tag; /end/
		}

		if ($mEnd) { // end tag

			// has start tag?
			if (empty($this->tagUsed[$mTag])) {
				return $s;
			}

			// autoclose tags
			$tmp = array();
			$back = TRUE;
			foreach ($this->tagStack as $i => $item) {
				$tag = $item['tag'];
				$s .= $item['close'];
				$this->space -= $item['indent'];
				$this->tagUsed[$tag]--;
				$back = $back && isset(TexyHtml::$inlineElements[$tag]);
				unset($this->tagStack[$i]);
				if ($tag === $mTag) {
					break;
				}
				array_unshift($tmp, $item);
			}

			if (!$back || !$tmp) {
				return $s;
			}

			// allowed-check (nejspis neni ani potreba)
			$item = reset($this->tagStack);
			$dtdContent = $item ? $item['dtdContent'] : $this->baseDTD;
			if (!isset($dtdContent[$tmp[0]['tag']])) {
				return $s;
			}

			// autoopen tags
			foreach ($tmp as $item) {
				$s .= $item['open'];
				$this->space += $item['indent'];
				$this->tagUsed[$item['tag']]++;
				array_unshift($this->tagStack, $item);
			}

		} else { // start tag

			$dtdContent = $this->baseDTD;

			if (!isset($this->texy->dtd[$mTag])) {
				// unknown (non-html) tag
				$allowed = TRUE;
				$item = reset($this->tagStack);
				if ($item) {
					$dtdContent = $item['dtdContent'];
				}

			} else {
				// optional end tag closing
				foreach ($this->tagStack as $i => $item) {
					// is tag allowed here?
					$dtdContent = $item['dtdContent'];
					if (isset($dtdContent[$mTag])) {
						break;
					}

					$tag = $item['tag'];

					// auto-close hidden, optional and inline tags
					if ($item['close'] && (!isset(TexyHtml::$optionalEnds[$tag]) && !isset(TexyHtml::$inlineElements[$tag]))) {
						break;
					}

					// close it
					$s .= $item['close'];
					$this->space -= $item['indent'];
					$this->tagUsed[$tag]--;
					unset($this->tagStack[$i]);
					$dtdContent = $this->baseDTD;
				}

				// is tag allowed in this content?
				$allowed = isset($dtdContent[$mTag]);

				// check deep element prohibitions
				if ($allowed && isset(TexyHtml::$prohibits[$mTag])) {
					foreach (TexyHtml::$prohibits[$mTag] as $pTag) {
						if (!empty($this->tagUsed[$pTag])) {
							$allowed = FALSE;
							break;
						}
					}
				}
			}

			// empty elements se neukladaji do zasobniku
			if ($mEmpty) {
				if (!$allowed) {
					return $s;
				}

				if ($this->xml) {
					$mAttr .= ' /';
				}

				$indent = $this->indent && !array_intersect(array_keys($this->tagUsed, TRUE), $this->preserveSpaces);

				if ($indent && $mTag === 'br') { // formatting exception
					return rtrim($s) . '<' . $mTag . $mAttr . ">\n" . str_repeat("\t", max(0, $this->space - 1)) . "\x07";

				} elseif ($indent && !isset(TexyHtml::$inlineElements[$mTag])) {
					$space = "\r" . str_repeat("\t", $this->space);
					return $s . $space . '<' . $mTag . $mAttr . '>' . $space;

				} else {
					return $s . '<' . $mTag . $mAttr . '>';
				}
			}

			$open = NULL;
			$close = NULL;
			$indent = 0;

			/*
			if (!isset(TexyHtml::$inlineElements[$mTag])) {
				// block tags always decorate with \n
				$s .= "\n";
				$close = "\n";
			}
			*/

			if ($allowed) {
				$open = '<' . $mTag . $mAttr . '>';

				// receive new content (ins & del are special cases)
				if (!empty($this->texy->dtd[$mTag][1])) {
					$dtdContent = $this->texy->dtd[$mTag][1];
				}

				// format output
				if ($this->indent && !isset(TexyHtml::$inlineElements[$mTag])) {
					$close = "\x08" . '</'.$mTag.'>' . "\n" . str_repeat("\t", $this->space);
					$s .= "\n" . str_repeat("\t", $this->space++) . $open . "\x07";
					$indent = 1;
				} else {
					$close = '</'.$mTag.'>';
					$s .= $open;
				}

				// TODO: problematic formatting of select / options, object / params
			}

			// open tag, put to stack, increase counter
			$item = array(
				'tag' => $mTag,
				'open' => $open,
				'close' => $close,
				'dtdContent' => $dtdContent,
				'indent' => $indent,
			);
			array_unshift($this->tagStack, $item);
			$tmp = & $this->tagUsed[$mTag];
			$tmp++;
		}

		return $s;
	}

	/**
	 * Callback function: wrap lines.
	 * @return string
	 * @internal
	 */
	public function wrap($m)
	{
		list(, $space, $s) = $m;
		return $space . wordwrap($s, $this->lineWrap, "\n" . $space);
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Image.
 */
final class TexyImage extends TexyObject
{
	/** @var string  base image URL */
	public $URL;

	/** @var string  on-mouse-over image URL */
	public $overURL;

	/** @var string  anchored image URL */
	public $linkedURL;

	/** @var int  optional image width */
	public $width;

	/** @var int  optional image height */
	public $height;

	/** @var bool  image width and height are maximal */
	public $asMax;

	/** @var TexyModifier */
	public $modifier;

	/** @var string  reference name (if is stored as reference) */
	public $name;

	public function __construct()
	{
		$this->modifier = new TexyModifier;
	}

	public function __clone()
	{
		if ($this->modifier) {
			$this->modifier = clone $this->modifier;
		}
	}

}

repo_name: devarped/pierwszykanal

license: mit


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Images module.
 */
final class TexyImageModule extends TexyModule
{
	/** @var string  root of relative images (http) */
	public $root = 'images/';

	/** @var string  root of linked images (http) */
	public $linkedRoot = 'images/';

	/** @var string  physical location of images on server */
	public $fileRoot = NULL;

	/** @var string  left-floated images CSS class */
	public $leftClass;

	/** @var string  right-floated images CSS class */
	public $rightClass;

	/** @var string  default alternative text */
	public $defaultAlt = '';

	/** @var string  images onload handler */
	public $onLoad = "var i=new Image();i.src='%i';if(typeof preload=='undefined')preload=new Array();preload[preload.length]=i;this.onload=''";

	/** @var array image references */
	private $references = array();

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->allowed['image/definition'] = TRUE;
		$texy->allowed['image/hover'] = TRUE;
		$texy->addHandler('image', array($this, 'solve'));
		$texy->addHandler('beforeParse', array($this, 'beforeParse'));

		// [*image*]:LINK
		$texy->registerLinePattern(
			array($this, 'patternImage'),
			'#\[\* *+([^\n'.TexyPatterns::MARK.']{1,1000})'.TexyPatterns::MODIFIER.'? *+(\*|(?<!<)>|<)\]' // [* urls .(title)[class]{style} >]
			. '(?::('.TexyPatterns::LINK_URL.'|:))??()#Uu',
			'image'
		);
	}

	/**
	 * Text pre-processing.
	 * @param  Texy
	 * @param  string
	 * @return void
	 */
	public function beforeParse($texy, & $text)
	{
		if (!empty($texy->allowed['image/definition'])) {
			// [*image*]: urls .(title)[class]{style}
			$text = TexyRegexp::replace(
				$text,
				'#^\[\*([^\n]{1,100})\*\]:\ +(.{1,1000})\ *'.TexyPatterns::MODIFIER.'?\s*()$#mUu',
				array($this, 'patternReferenceDef')
			);
		}
	}

	/**
	 * Callback for: [*image*]: urls .(title)[class]{style}.
	 *
	 * @param  array      regexp matches
	 * @return string
	 * @internal
	 */
	public function patternReferenceDef($matches)
	{
		list(, $mRef, $mURLs, $mMod) = $matches;
		// [1] => [* (reference) *]
		// [2] => urls
		// [3] => .(title)[class]{style}<>

		$image = $this->factoryImage($mURLs, $mMod, FALSE);
		$this->addReference($mRef, $image);
		return '';
	}

	/**
	 * Callback for [* small.jpg 80x13 | small-over.jpg | big.jpg .(alternative text)[class]{style}>]:LINK.
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternImage($parser, $matches)
	{
		list(, $mURLs, $mMod, $mAlign, $mLink) = $matches;
		// [1] => URLs
		// [2] => .(title)[class]{style}<>
		// [3] => * < >
		// [4] => url | [ref] | [*image*]

		$tx = $this->texy;

		$image = $this->factoryImage($mURLs, $mMod.$mAlign);

		if ($mLink) {
			if ($mLink === ':') {
				$link = new TexyLink($image->linkedURL === NULL ? $image->URL : $image->linkedURL);
				$link->raw = ':';
				$link->type = TexyLink::IMAGE;
			} else {
				$link = $tx->linkModule->factoryLink($mLink, NULL, NULL);
			}
		} else {
			$link = NULL;
		}

		return $tx->invokeAroundHandlers('image', $parser, array($image, $link));
	}

	/**
	 * Adds new named reference to image.
	 *
	 * @param  string  reference name
	 * @param  TexyImage
	 * @return void
	 */
	public function addReference($name, TexyImage $image)
	{
		$image->name = TexyUtf::strtolower($name);
		$this->references[$image->name] = $image;
	}

	/**
	 * Returns named reference.
	 *
	 * @param  string  reference name
	 * @return TexyImage  reference descriptor (or FALSE)
	 */
	public function getReference($name)
	{
		$name = TexyUtf::strtolower($name);
		if (isset($this->references[$name])) {
			return clone $this->references[$name];
		}

		return FALSE;
	}

	/**
	 * Parses image's syntax.
	 * @param  string  input: small.jpg 80x13 | small-over.jpg | linked.jpg
	 * @param  string
	 * @param  bool
	 * @return TexyImage
	 */
	public function factoryImage($content, $mod, $tryRef = TRUE)
	{
		$image = $tryRef ? $this->getReference(trim($content)) : FALSE;

		if (!$image) {
			$tx = $this->texy;
			$content = explode('|', $content);
			$image = new TexyImage;

			// dimensions
			$matches = NULL;
			if ($matches = TexyRegexp::match($content[0], '#^(.*) (\d+|\?) *(X|x) *(\d+|\?) *()$#U')) {
				$image->URL = trim($matches[1]);
				$image->asMax = $matches[3] === 'X';
				$image->width = $matches[2] === '?' ? NULL : (int) $matches[2];
				$image->height = $matches[4] === '?' ? NULL : (int) $matches[4];
			} else {
				$image->URL = trim($content[0]);
			}

			if (!$tx->checkURL($image->URL, Texy::FILTER_IMAGE)) {
				$image->URL = NULL;
			}

			// onmouseover image
			if (isset($content[1])) {
				$tmp = trim($content[1]);
				if ($tmp !== '' && $tx->checkURL($tmp, Texy::FILTER_IMAGE)) {
					$image->overURL = $tmp;
				}
			}

			// linked image
			if (isset($content[2])) {
				$tmp = trim($content[2]);
				if ($tmp !== '' && $tx->checkURL($tmp, Texy::FILTER_ANCHOR)) {
					$image->linkedURL = $tmp;
				}
			}
		}

		$image->modifier->setProperties($mod);
		return $image;
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  TexyImage
	 * @param  TexyLink
	 * @return TexyHtml|FALSE
	 */
	public function solve($invocation, TexyImage $image, $link)
	{
		if ($image->URL == NULL) {
			return FALSE;
		}

		$tx = $this->texy;

		$mod = $image->modifier;
		$alt = $mod->title;
		$mod->title = NULL;
		$hAlign = $mod->hAlign;
		$mod->hAlign = NULL;

		$el = TexyHtml::el('img');
		$el->attrs['src'] = NULL; // trick - move to front
		$mod->decorate($tx, $el);
		$el->attrs['src'] = Texy::prependRoot($image->URL, $this->root);
		if (!isset($el->attrs['alt'])) {
			$el->attrs['alt'] = $alt === NULL ? $this->defaultAlt : $tx->typographyModule->postLine($alt);
		}

		if ($hAlign) {
			$var = $hAlign . 'Class'; // leftClass, rightClass
			if (!empty($this->$var)) {
				$el->attrs['class'][] = $this->$var;

			} elseif (empty($tx->alignClasses[$hAlign])) {
				$el->attrs['style']['float'] = $hAlign;

			} else {
				$el->attrs['class'][] = $tx->alignClasses[$hAlign];
			}
		}

		if (!is_int($image->width) || !is_int($image->height) || $image->asMax) {
			// autodetect fileRoot
			if ($this->fileRoot === NULL && isset($_SERVER['SCRIPT_FILENAME'])) {
				$this->fileRoot = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . $this->root;
			}

			// detect dimensions
			// absolute URL & security check for double dot
			if (Texy::isRelative($image->URL) && strpos($image->URL, '..') === FALSE) {
				$file = rtrim($this->fileRoot, '/\\') . '/' . $image->URL;
				if (@is_file($file)) { // intentionally @
					$size = @getImageSize($file); // intentionally @
					if (is_array($size)) {
						if ($image->asMax) {
							$ratio = 1;
							if (is_int($image->width)) {
								$ratio = min($ratio, $image->width / $size[0]);
							}
							if (is_int($image->height)) {
								$ratio = min($ratio, $image->height / $size[1]);
							}
							$image->width = round($ratio * $size[0]);
							$image->height = round($ratio * $size[1]);

						} elseif (is_int($image->width)) {
							$image->height = round($size[1] / $size[0] * $image->width);

						} elseif (is_int($image->height)) {
							$image->width = round($size[0] / $size[1] * $image->height);

						} else {
							$image->width = $size[0];
							$image->height = $size[1];
						}
					}
				}
			}
		}

		$el->attrs['width'] = $image->width;
		$el->attrs['height'] = $image->height;

		// onmouseover actions generate
		if (!empty($tx->allowed['image/hover']) && $image->overURL !== NULL) {
			$overSrc = Texy::prependRoot($image->overURL, $this->root);
			$el->attrs['onmouseover'] = 'this.src=\'' . addSlashes($overSrc) . '\'';
			$el->attrs['onmouseout'] = 'this.src=\'' . addSlashes($el->attrs['src']) . '\'';
			$el->attrs['onload'] = str_replace('%i', addSlashes($overSrc), $this->onLoad);
			$tx->summary['preload'][] = $overSrc;
		}

		$tx->summary['images'][] = $el->attrs['src'];

		if ($link) {
			return $tx->linkModule->solve(NULL, $link, $el);
		}

		return $el;
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Link.
 */
final class TexyLink extends TexyObject
{
	/** @see $type */
	const
		COMMON = 1,
		BRACKET = 2,
		IMAGE = 3;

	/** @var string  URL in resolved form */
	public $URL;

	/** @var string  URL as written in text */
	public $raw;

	/** @var TexyModifier */
	public $modifier;

	/** @var int  how was link created? */
	public $type = self::COMMON;

	/** @var string  optional label, used by references */
	public $label;

	/** @var string  reference name (if is stored as reference) */
	public $name;

	public function __construct($URL)
	{
		$this->URL = $URL;
		$this->raw = $URL;
		$this->modifier = new TexyModifier;
	}

	public function __clone()
	{
		if ($this->modifier) {
			$this->modifier = clone $this->modifier;
		}
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Links module.
 */
final class TexyLinkModule extends TexyModule
{
	/** @var string  root of relative links */
	public $root = '';

	/** @var string image popup class */
	public $imageClass;

	/** @var string image popup event */
	public $imageOnClick = 'return !popupImage(this.href)';  //

	/** @var string class 'popup' event */
	public $popupOnClick = 'return !popup(this.href)';

	/** @var bool  always use rel="nofollow" for absolute links? */
	public $forceNoFollow = FALSE;

	/** @var bool  shorten URLs to more readable form? */
	public $shorten = TRUE;

	/** @var array link references */
	private $references = array();

	/** @var array */
	private static $livelock;

	private static $EMAIL;

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->allowed['link/definition'] = TRUE;
		$texy->addHandler('newReference', array($this, 'solveNewReference'));
		$texy->addHandler('linkReference', array($this, 'solve'));
		$texy->addHandler('linkEmail', array($this, 'solveUrlEmail'));
		$texy->addHandler('linkURL', array($this, 'solveUrlEmail'));
		$texy->addHandler('beforeParse', array($this, 'beforeParse'));

		// [reference]
		$texy->registerLinePattern(
			array($this, 'patternReference'),
			'#(\[[^\[\]\*\n'.TexyPatterns::MARK.']++\])#U',
			'link/reference'
		);

		// direct url; charaters not allowed in URL <>[\]^`{|}
		$texy->registerLinePattern(
			array($this, 'patternUrlEmail'),
			'#(?<=^|[\s([<:\x17])(?:https?://|www\.|ftp://)[0-9.'.TexyPatterns::CHAR.'-][/\d'.TexyPatterns::CHAR.'+\.~%&?@=_:;\#$!,*()\x{ad}-]{1,1000}[/\d'.TexyPatterns::CHAR.'+~?@=_\#$*]#u',
			'link/url',
			'#(?:https?://|www\.|ftp://)#u'
		);

		// direct email
		self::$EMAIL = '['.TexyPatterns::CHAR.'][0-9.+_'.TexyPatterns::CHAR.'-]{0,63}@[0-9.+_'.TexyPatterns::CHAR.'\x{ad}-]{1,252}\.['.TexyPatterns::CHAR.'\x{ad}]{2,19}';
		$texy->registerLinePattern(
			array($this, 'patternUrlEmail'),
			'#(?<=^|[\s([<\x17])'.self::$EMAIL.'#u',
			'link/email',
			'#'.self::$EMAIL.'#u'
		);
	}

	/**
	 * Text pre-processing.
	 * @param  Texy
	 * @param  string
	 * @return void
	 */
	public function beforeParse($texy, & $text)
	{
		self::$livelock = array();

		// [la trine]: http://www.latrine.cz/ text odkazu .(title)[class]{style}
		if (!empty($texy->allowed['link/definition'])) {
			$text = TexyRegexp::replace(
				$text,
				'#^\[([^\[\]\#\?\*\n]{1,100})\]: ++(\S{1,1000})(\ .{1,1000})?'.TexyPatterns::MODIFIER.'?\s*()$#mUu',
				array($this, 'patternReferenceDef')
			);
		}
	}

	/**
	 * Callback for: [la trine]: http://www.latrine.cz/ text odkazu .(title)[class]{style}.
	 *
	 * @param  array      regexp matches
	 * @return string
	 * @internal
	 */
	public function patternReferenceDef($matches)
	{
		list(, $mRef, $mLink, $mLabel, $mMod) = $matches;
		// [1] => [ (reference) ]
		// [2] => link
		// [3] => ...
		// [4] => .(title)[class]{style}

		$link = new TexyLink($mLink);
		$link->label = trim($mLabel);
		$link->modifier->setProperties($mMod);
		$this->checkLink($link);
		$this->addReference($mRef, $link);
		return '';
	}

	/**
	 * Callback for: [ref].
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternReference($parser, $matches)
	{
		list(, $mRef) = $matches;
		// [1] => [ref]

		$tx = $this->texy;
		$name = substr($mRef, 1, -1);
		$link = $this->getReference($name);

		if (!$link) {
			return $tx->invokeAroundHandlers('newReference', $parser, array($name));
		}

		$link->type = TexyLink::BRACKET;

		if ($link->label != '') { // NULL or ''
			// prevent circular references
			if (isset(self::$livelock[$link->name])) {
				$content = $link->label;
			} else {
				self::$livelock[$link->name] = TRUE;
				$el = TexyHtml::el();
				$lineParser = new TexyLineParser($tx, $el);
				$lineParser->parse($link->label);
				$content = $el->toString($tx);
				unset(self::$livelock[$link->name]);
			}
		} else {
			$content = $this->textualUrl($link);
			$content = $this->texy->protect($content, Texy::CONTENT_TEXTUAL);
		}

		return $tx->invokeAroundHandlers('linkReference', $parser, array($link, $content));
	}

	/**
	 * Callback for: http://davidgrudl.com david@grudl.com.
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternUrlEmail($parser, $matches, $name)
	{
		list($mURL) = $matches;
		// [0] => URL

		$link = new TexyLink($mURL);
		$this->checkLink($link);

		return $this->texy->invokeAroundHandlers(
			$name === 'link/email' ? 'linkEmail' : 'linkURL',
			$parser,
			array($link)
		);
	}

	/**
	 * Adds new named reference.
	 *
	 * @param  string  reference name
	 * @param  TexyLink
	 * @return void
	 */
	public function addReference($name, TexyLink $link)
	{
		$link->name = TexyUtf::strtolower($name);
		$this->references[$link->name] = $link;
	}

	/**
	 * Returns named reference.
	 *
	 * @param  string  reference name
	 * @return TexyLink reference descriptor (or FALSE)
	 */
	public function getReference($name)
	{
		$name = TexyUtf::strtolower($name);
		if (isset($this->references[$name])) {
			return clone $this->references[$name];

		} else {
			$pos = strpos($name, '?');
			if ($pos === FALSE) {
				$pos = strpos($name, '#');
			}
			if ($pos !== FALSE) { // try to extract ?... #... part
				$name2 = substr($name, 0, $pos);
				if (isset($this->references[$name2])) {
					$link = clone $this->references[$name2];
					$link->URL .= substr($name, $pos);
					return $link;
				}
			}
		}

		return FALSE;
	}

	/**
	 * @param  string
	 * @param  string
	 * @param  string
	 * @return TexyLink
	 */
	public function factoryLink($dest, $mMod, $label)
	{
		$tx = $this->texy;
		$type = TexyLink::COMMON;

		// [ref]
		if (strlen($dest) > 1 && $dest[0] === '[' && $dest[1] !== '*') {
			$type = TexyLink::BRACKET;
			$dest = substr($dest, 1, -1);
			$link = $this->getReference($dest);

		// [* image *]
		} elseif (strlen($dest) > 1 && $dest[0] === '[' && $dest[1] === '*') {
			$type = TexyLink::IMAGE;
			$dest = trim(substr($dest, 2, -2));
			$image = $tx->imageModule->getReference($dest);
			if ($image) {
				$link = new TexyLink($image->linkedURL === NULL ? $image->URL : $image->linkedURL);
				$link->modifier = $image->modifier;
			}
		}

		if (empty($link)) {
			$link = new TexyLink(trim($dest));
			$this->checkLink($link);
		}

		if (strpos($link->URL, '%s') !== FALSE) {
			$link->URL = str_replace('%s', urlencode($tx->stringToText($label)), $link->URL);
		}
		$link->modifier->setProperties($mMod);
		$link->type = $type;
		return $link;
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  TexyLink
	 * @param  TexyHtml|string
	 * @return TexyHtml|string
	 */
	public function solve($invocation, $link, $content = NULL)
	{
		if ($link->URL == NULL) {
			return $content;
		}

		$tx = $this->texy;

		$el = TexyHtml::el('a');

		if (empty($link->modifier)) {
			$nofollow = $popup = FALSE;
		} else {
			$nofollow = isset($link->modifier->classes['nofollow']);
			$popup = isset($link->modifier->classes['popup']);
			unset($link->modifier->classes['nofollow'], $link->modifier->classes['popup']);
			$el->attrs['href'] = NULL; // trick - move to front
			$link->modifier->decorate($tx, $el);
		}

		if ($link->type === TexyLink::IMAGE) {
			// image
			$el->attrs['href'] = Texy::prependRoot($link->URL, $tx->imageModule->linkedRoot);
			if ($this->imageClass) {
				$el->attrs['class'][] = $this->imageClass;
			} else {
				$el->attrs['onclick'] = $this->imageOnClick;
			}

		} else {
			$el->attrs['href'] = Texy::prependRoot($link->URL, $this->root);

			// rel="nofollow"
			if ($nofollow || ($this->forceNoFollow && strpos($el->attrs['href'], '//') !== FALSE)) {
				$el->attrs['rel'] = 'nofollow';
			}
		}

		// popup on click
		if ($popup) {
			$el->attrs['onclick'] = $this->popupOnClick;
		}

		if ($content !== NULL) {
			$el->add($content);
		}

		$tx->summary['links'][] = $el->attrs['href'];

		return $el;
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  TexyLink
	 * @return TexyHtml|string
	 */
	public function solveUrlEmail($invocation, $link)
	{
		$content = $this->textualUrl($link);
		$content = $this->texy->protect($content, Texy::CONTENT_TEXTUAL);
		return $this->solve(NULL, $link, $content);
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  string
	 * @return FALSE
	 */
	public function solveNewReference($invocation, $name)
	{
		// no change
		return FALSE;
	}

	/**
	 * Checks and corrects $URL.
	 * @param  TexyLink
	 * @return void
	 */
	private function checkLink($link)
	{
		// remove soft hyphens; if not removed by Texy::process()
		$link->URL = str_replace("\xC2\xAD", '', $link->URL);

		if (strncasecmp($link->URL, 'www.', 4) === 0) {
			// special supported case
			$link->URL = 'http://' . $link->URL;

		} elseif (preg_match('#'.self::$EMAIL.'$#Au', $link->URL)) {
			// email
			$link->URL = 'mailto:' . $link->URL;

		} elseif (!$this->texy->checkURL($link->URL, Texy::FILTER_ANCHOR)) {
			$link->URL = NULL;

		} else {
			$link->URL = str_replace('&', '&', $link->URL); // replace unwanted &
		}
	}

	/**
	 * Returns textual representation of URL.
	 * @param  TexyLink
	 * @return string
	 */
	private function textualUrl($link)
	{
		if ($this->texy->obfuscateEmail && preg_match('#^'.self::$EMAIL.'$#u', $link->raw)) { // email
			return str_replace('@', '@<!-- -->', $link->raw);
		}

		if ($this->shorten && preg_match('#^(https?://|ftp://|www\.|/)#i', $link->raw)) {

			$raw = strncasecmp($link->raw, 'www.', 4) === 0 ? 'none://' . $link->raw : $link->raw;

			// parse_url() in PHP damages UTF-8 - use regular expression
			if (!preg_match('~^(?:(?P<scheme>[a-z]+):)?(?://(?P<host>[^/?#]+))?(?P<path>(?:/|^)(?!/)[^?#]*)?(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?()$~', $raw, $parts)) {
				return $link->raw;
			}

			$res = '';
			if ($parts['scheme'] !== '' && $parts['scheme'] !== 'none') {
				$res .= $parts['scheme'] . '://';
			}

			if ($parts['host'] !== '') {
				$res .= $parts['host'];
			}

			if ($parts['path'] !== '') {
				$res .= (iconv_strlen($parts['path'], 'UTF-8') > 16 ? ("/\xe2\x80\xa6" . iconv_substr($parts['path'], -12, 12, 'UTF-8')) : $parts['path']);
			}

			if ($parts['query'] !== '') {
				$res .= iconv_strlen($parts['query'], 'UTF-8') > 4 ? "?\xe2\x80\xa6" : ('?' . $parts['query']);
			} elseif ($parts['fragment'] !== '') {
				$res .= iconv_strlen($parts['fragment'], 'UTF-8') > 4 ? "#\xe2\x80\xa6" : ('#' . $parts['fragment']);
			}
			return $res;
		}

		return $link->raw;
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Ordered / unordered nested list module.
 */
final class TexyListModule extends TexyModule
{
	public $bullets = array(
		// first-rexexp ordered? list-style-type next-regexp
		'*' => array('\*\ ', 0, ''),
		'-' => array('[\x{2013}-](?![>-])',0, ''),
		'+' => array('\+\ ', 0, ''),
		'1.' => array('1\.\ ',/* not \d !*/ 1, '', '\d{1,3}\.\ '),
		'1)' => array('\d{1,3}\)\ ', 1, ''),
		'I.' => array('I\.\ ', 1, 'upper-roman', '[IVX]{1,4}\.\ '),
		'I)' => array('[IVX]+\)\ ', 1, 'upper-roman'), // before A) !
		'a)' => array('[a-z]\)\ ', 1, 'lower-alpha'),
		'A)' => array('[A-Z]\)\ ', 1, 'upper-alpha'),
	);

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->addHandler('beforeParse', array($this, 'beforeParse'));
		$texy->allowed['list'] = TRUE;
		$texy->allowed['list/definition'] = TRUE;
	}

	public function beforeParse()
	{
		$RE = $REul = array();
		foreach ($this->bullets as $desc) {
			$RE[] = $desc[0];
			if (!$desc[1]) {
				$REul[] = $desc[0];
			}
		}

		$this->texy->registerBlockPattern(
			array($this, 'patternList'),
			'#^(?:'.TexyPatterns::MODIFIER_H.'\n)?' // .{color: red}
			. '('.implode('|', $RE).')\ *+\S.*$#mUu', // item (unmatched)
			'list'
		);

		$this->texy->registerBlockPattern(
			array($this, 'patternDefList'),
			'#^(?:'.TexyPatterns::MODIFIER_H.'\n)?' // .{color:red}
			. '(\S.{0,2000})\:\ *'.TexyPatterns::MODIFIER_H.'?\n' // Term:
			. '(\ ++)('.implode('|', $REul).')\ *+\S.*$#mUu', // - description
			'list/definition'
		);
	}

	/**
	 * Callback for:.
	 *
	 * 1) .... .(title)[class]{style}>
	 * 2) ....
	 *   + ...
	 *   + ...
	 * 3) ....
	 *
	 * @param  TexyBlockParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|FALSE
	 */
	public function patternList($parser, $matches)
	{
		list(, $mMod, $mBullet) = $matches;
		// [1] => .(title)[class]{style}<>
		// [2] => bullet * + - 1) a) A) IV)

		$tx = $this->texy;

		$el = TexyHtml::el();

		$bullet = $min = NULL;
		foreach ($this->bullets as $type => $desc) {
			if (preg_match('#'.$desc[0].'#Au', $mBullet)) {
				$bullet = isset($desc[3]) ? $desc[3] : $desc[0];
				$min = isset($desc[3]) ? 2 : 1;
				$el->setName($desc[1] ? 'ol' : 'ul');
				$el->attrs['style']['list-style-type'] = $desc[2];
				if ($desc[1]) { // ol
					if ($type[0] === '1' && (int) $mBullet > 1) {
						$el->attrs['start'] = (int) $mBullet;
					} elseif ($type[0] === 'a' && $mBullet[0] > 'a') {
						$el->attrs['start'] = ord($mBullet[0]) - 96;
					} elseif ($type[0] === 'A' && $mBullet[0] > 'A') {
						$el->attrs['start'] = ord($mBullet[0]) - 64;
					}
				}
				break;
			}
		}

		$mod = new TexyModifier($mMod);
		$mod->decorate($tx, $el);

		$parser->moveBackward(1);

		while ($elItem = $this->patternItem($parser, $bullet, FALSE, 'li')) {
			$el->add($elItem);
		}

		if ($el->count() < $min) {
			return FALSE;
		}

		// event listener
		$tx->invokeHandlers('afterList', array($parser, $el, $mod));

		return $el;
	}

	/**
	 * Callback for:.
	 *
	 * Term: .(title)[class]{style}>
	 * - description 1
	 * - description 2
	 * - description 3
	 *
	 * @param  TexyBlockParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml
	 */
	public function patternDefList($parser, $matches)
	{
		list(, $mMod, , , , $mBullet) = $matches;
		// [1] => .(title)[class]{style}<>
		// [2] => ...
		// [3] => .(title)[class]{style}<>
		// [4] => space
		// [5] => - * +

		$tx = $this->texy;

		$bullet = NULL;
		foreach ($this->bullets as $desc) {
			if (preg_match('#'.$desc[0].'#Au', $mBullet)) {
				$bullet = isset($desc[3]) ? $desc[3] : $desc[0];
				break;
			}
		}

		$el = TexyHtml::el('dl');
		$mod = new TexyModifier($mMod);
		$mod->decorate($tx, $el);
		$parser->moveBackward(2);

		$patternTerm = '#^\n?(\S.*)\:\ *'.TexyPatterns::MODIFIER_H.'?()$#mUA';

		while (TRUE) {
			if ($elItem = $this->patternItem($parser, $bullet, TRUE, 'dd')) {
				$el->add($elItem);
				continue;
			}

			if ($parser->next($patternTerm, $matches)) {
				list(, $mContent, $mMod) = $matches;
				// [1] => ...
				// [2] => .(title)[class]{style}<>

				$elItem = TexyHtml::el('dt');
				$modItem = new TexyModifier($mMod);
				$modItem->decorate($tx, $elItem);

				$elItem->parseLine($tx, $mContent);
				$el->add($elItem);
				continue;
			}

			break;
		}

		// event listener
		$tx->invokeHandlers('afterDefinitionList', array($parser, $el, $mod));

		return $el;
	}

	/**
	 * Callback for single list item.
	 *
	 * @param  TexyBlockParser
	 * @param  string  bullet type
	 * @param  string  left space
	 * @param  string  html tag
	 * @return TexyHtml|FALSE
	 */
	public function patternItem($parser, $bullet, $indented, $tag)
	{
		$tx = $this->texy;
		$spacesBase = $indented ? ('\ {1,}') : '';
		$patternItem = "#^\n?($spacesBase)$bullet\\ *(\\S.*)?".TexyPatterns::MODIFIER_H.'?()$#mAUu';

		// first line with bullet
		$matches = NULL;
		if (!$parser->next($patternItem, $matches)) {
			return FALSE;
		}

		list(, $mIndent, $mContent, $mMod) = $matches;
			// [1] => indent
			// [2] => ...
			// [3] => .(title)[class]{style}<>

		$elItem = TexyHtml::el($tag);
		$mod = new TexyModifier($mMod);
		$mod->decorate($tx, $elItem);

		// next lines
		$spaces = '';
		$content = ' ' . $mContent; // trick
		while ($parser->next('#^(\n*)'.$mIndent.'(\ {1,'.$spaces.'})(.*)()$#Am', $matches)) {
			list(, $mBlank, $mSpaces, $mContent) = $matches;
			// [1] => blank line?
			// [2] => spaces
			// [3] => ...

			if ($spaces === '') {
				$spaces = strlen($mSpaces);
			}
			$content .= "\n" . $mBlank . $mContent;
		}

		// parse content
		$elItem->parseBlock($tx, $content, TRUE);

		if (isset($elItem[0]) && $elItem[0] instanceof TexyHtml) {
			$elItem[0]->setName(NULL);
		}

		return $elItem;
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Long words wrap module.
 */
final class TexyLongWordsModule extends TexyModule
{
	public $wordLimit = 20;

	const
		DONT = 0, // don't hyphenate
		HERE = 1, // hyphenate here
		AFTER = 2; // hyphenate after

	const SAFE_LIMIT = 1000;

	private $consonants = array(
		'b','c','d','f','g','h','j','k','l','m','n','p','q','r','s','t','v','w','x','z',
		'B','C','D','F','G','H','J','K','L','M','N','P','Q','R','S','T','V','W','X','Z',
		"\xc4\x8d","\xc4\x8f","\xc5\x88","\xc5\x99","\xc5\xa1","\xc5\xa5","\xc5\xbe", //czech utf-8
		"\xc4\x8c","\xc4\x8e","\xc5\x87","\xc5\x98","\xc5\xa0","\xc5\xa4","\xc5\xbd");

	private $vowels = array(
		'a','e','i','o','u','y',
		'A','E','I','O','U','Y',
		"\xc3\xa1","\xc3\xa9","\xc4\x9b","\xc3\xad","\xc3\xb3","\xc3\xba","\xc5\xaf","\xc3\xbd", //czech utf-8
		"\xc3\x81","\xc3\x89","\xc4\x9a","\xc3\x8d","\xc3\x93","\xc3\x9a","\xc5\xae","\xc3\x9d");

	private $before_r = array(
		'b','B','c','C','d','D','f','F','g','G','k','K','p','P','r','R','t','T','v','V',
		"\xc4\x8d","\xc4\x8c","\xc4\x8f","\xc4\x8e","\xc5\x99","\xc5\x98","\xc5\xa5","\xc5\xa4"); //czech utf-8

	private $before_l = array(
		'b','B','c','C','d','D','f','F','g','G','k','K','l','L','p','P','t','T','v','V',
		"\xc4\x8d","\xc4\x8c","\xc4\x8f","\xc4\x8e","\xc5\xa5","\xc5\xa4"); //czech utf-8

	private $before_h = array('c','C','s','S');

	private $doubleVowels = array('a','A','o','O');

	public function __construct($texy)
	{
		$this->texy = $texy;

		$this->consonants = array_flip($this->consonants);
		$this->vowels = array_flip($this->vowels);
		$this->before_r = array_flip($this->before_r);
		$this->before_l = array_flip($this->before_l);
		$this->before_h = array_flip($this->before_h);
		$this->doubleVowels = array_flip($this->doubleVowels);

		$texy->registerPostLine(array($this, 'postLine'), 'longwords');
	}

	public function postLine($text)
	{
		return TexyRegexp::replace(
			$text,
			'#[^\ \n\t\x14\x15\x16\x{2013}\x{2014}\x{ad}-]{'.$this->wordLimit.',}#u',
			array($this, 'pattern')
		);
	}

	/**
	 * Callback for long words.
	 * (c) David Grudl
	 * @param  array
	 * @return string
	 * @internal
	 */
	public function pattern($matches)
	{
		list($mWord) = $matches;
		// [0] => lllloooonnnnggggwwwoorrdddd

		if (iconv_strlen($mWord, 'UTF-8') > self::SAFE_LIMIT) {
			return $mWord;
		}

		$chars = array();
		preg_match_all(
			'#['.TexyPatterns::MARK.']+|.#u',
			$mWord,
			$chars
		);

		$chars = $chars[0];
		if (count($chars) < $this->wordLimit) {
			return $mWord;
		}

		$consonants = $this->consonants;
		$vowels = $this->vowels;
		$before_r = $this->before_r;
		$before_l = $this->before_l;
		$before_h = $this->before_h;
		$doubleVowels = $this->doubleVowels;

		$s = array();
		$trans = array();

		$s[] = '';
		$trans[] = -1;
		foreach ($chars as $key => $char) {
			if (ord($char[0]) < 32) {
				continue;
			}
			$s[] = $char;
			$trans[] = $key;
		}
		$s[] = '';
		$len = count($s) - 2;

		$positions = array();
		$a = 0;
		$last = 1;

		while (++$a < $len) {
			$hyphen = self::DONT; // Do not hyphenate
			do {
				if ($s[$a] === "\xC2\xA0") {
					$a++;
					continue 2;  // here and after never
				}

				if ($s[$a] === '.') {
					$hyphen = self::HERE;
					break;
				}

				if (isset($consonants[$s[$a]])) { // consonants

					if (isset($vowels[$s[$a + 1]])) {
						if (isset($vowels[$s[$a - 1]])) {
							$hyphen = self::HERE;
						}
						break;
					}

					if (($s[$a] === 's') && ($s[$a - 1] === 'n') && isset($consonants[$s[$a + 1]])) {
						$hyphen = self::AFTER;
						break;
					}

					if (isset($consonants[$s[$a + 1]]) && isset($vowels[$s[$a - 1]])) {
						if ($s[$a + 1] === 'r') {
							$hyphen = isset($before_r[$s[$a]]) ? self::HERE : self::AFTER;
							break;
						}

						if ($s[$a + 1] === 'l') {
							$hyphen = isset($before_l[$s[$a]]) ? self::HERE : self::AFTER;
							break;
						}

						if ($s[$a + 1] === 'h') { // CH
							$hyphen = isset($before_h[$s[$a]]) ? self::DONT : self::AFTER;
							break;
						}

						$hyphen = self::AFTER;
						break;
					}

					break;
				} // end of consonants

				if (($s[$a] === 'u') && isset($doubleVowels[$s[$a - 1]])) {
					$hyphen = self::AFTER;
					break;
				}
				if (isset($vowels[$s[$a]]) && isset($vowels[$s[$a - 1]])) {
					$hyphen = self::HERE;
					break;
				}
			} while (0);

			if ($hyphen === self::DONT && ($a - $last > $this->wordLimit * 0.6)) {
				$positions[] = $last = $a - 1; // Hyphenate here
			}
			if ($hyphen === self::HERE) {
				$positions[] = $last = $a - 1; // Hyphenate here
			}
			if ($hyphen === self::AFTER) {
				$positions[] = $last = $a;
				$a++; // Hyphenate after
			}
		} // while

		$a = end($positions);
		if (($a === $len - 1) && isset($consonants[$s[$len]])) {
			array_pop($positions);
		}

		$syllables = array();
		$last = 0;
		foreach ($positions as $pos) {
			if ($pos - $last > $this->wordLimit * 0.6) {
				$syllables[] = implode('', array_splice($chars, 0, $trans[$pos] - $trans[$last]));
				$last = $pos;
			}
		}
		$syllables[] = implode('', $chars);

		//$s = implode("\xC2\xAD", $syllables); // insert shy
		//$s = str_replace(array("\xC2\xAD\xC2\xA0", "\xC2\xA0\xC2\xAD"), array(' ', ' '), $s); // shy+nbsp = normal space

		return implode("\xC2\xAD", $syllables);
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Paragraph module.
 */
final class TexyParagraphModule extends TexyModule
{

	public function __construct($texy)
	{
		$this->texy = $texy;
		$texy->addHandler('paragraph', array($this, 'solve'));
	}

	/**
	 * @param  TexyBlockParser
	 * @param  string     text
	 * @param  array
	 * @param  TexyHtml
	 * @return vois
	 */
	public function process($parser, $content, $el)
	{
		$tx = $this->texy;

		if ($parser->isIndented()) {
			$parts = preg_split('#(\n(?! )|\n{2,})#', $content, -1, PREG_SPLIT_NO_EMPTY);
		} else {
			$parts = preg_split('#(\n{2,})#', $content, -1, PREG_SPLIT_NO_EMPTY);
		}

		foreach ($parts as $s) {
			$s = trim($s);
			if ($s === '') {
				continue;
			}

			// try to find modifier
			$mod = NULL;
			if ($mx = TexyRegexp::match($s, '#'.TexyPatterns::MODIFIER_H.'(?=\n|\z)#sUm', TexyRegexp::OFFSET_CAPTURE)) {
				list($mMod) = $mx[1];
				$s = trim(substr_replace($s, '', $mx[0][1], strlen($mx[0][0])));
				if ($s === '') {
					continue;
				}
				$mod = new TexyModifier;
				$mod->setProperties($mMod);
			}

			$res = $tx->invokeAroundHandlers('paragraph', $parser, array($s, $mod));
			if ($res) {
				$el->insert(NULL, $res);
			}
		}
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  string
	 * @param  TexyModifier|NULL
	 * @return TexyHtml|FALSE
	 */
	public function solve($invocation, $content, $mod)
	{
		$tx = $this->texy;

		// find hard linebreaks
		if ($tx->mergeLines) {
			// ....
			// ... => \r means break line
			$content = TexyRegexp::replace($content, '#\n +(?=\S)#', "\r");
		} else {
			$content = TexyRegexp::replace($content, '#\n#', "\r");
		}

		$el = TexyHtml::el('p');
		$el->parseLine($tx, $content);
		$content = $el->getText(); // string

		// check content type
		// block contains block tag
		if (strpos($content, Texy::CONTENT_BLOCK) !== FALSE) {
			$el->setName(NULL); // ignores modifier!

		// block contains text (protected)
		} elseif (strpos($content, Texy::CONTENT_TEXTUAL) !== FALSE) {
			// leave element p

		// block contains text
		} elseif (preg_match('#[^\s'.TexyPatterns::MARK.']#u', $content)) {
			// leave element p

		// block contains only replaced element
		} elseif (strpos($content, Texy::CONTENT_REPLACED) !== FALSE) {
			$el->setName($tx->nontextParagraph);

		// block contains only markup tags or spaces or nothing
		} else {
			// if {ignoreEmptyStuff} return FALSE;
			if (!$mod) {
				$el->setName(NULL);
			}
		}

		if ($el->getName()) {
			// apply modifier
			if ($mod) {
				$mod->decorate($tx, $el);
			}

			// add

			if (strpos($content, "\r") !== FALSE) {
				$key = $tx->protect('
', Texy::CONTENT_REPLACED);
				$content = str_replace("\r", $key, $content);
			};
		}

		$content = strtr($content, "\r\n", '  ');
		$el->setText($content);

		return $el;
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Phrases module.
 */
final class TexyPhraseModule extends TexyModule
{

	public $tags = array(
		'phrase/strong' => 'strong', // or 'b'
		'phrase/em' => 'em', // or 'i'
		'phrase/em-alt' => 'em',
		'phrase/em-alt2' => 'em',
		'phrase/ins' => 'ins',
		'phrase/del' => 'del',
		'phrase/sup' => 'sup',
		'phrase/sup-alt' => 'sup',
		'phrase/sub' => 'sub',
		'phrase/sub-alt' => 'sub',
		'phrase/span' => 'a',
		'phrase/span-alt' => 'a',
		'phrase/cite' => 'cite',
		'phrase/acronym' => 'acronym',
		'phrase/acronym-alt' => 'acronym',
		'phrase/code' => 'code',
		'phrase/quote' => 'q',
		'phrase/quicklink' => 'a',
	);

	/** @var bool  are links allowed? */
	public $linksAllowed = TRUE;

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->addHandler('phrase', array($this, 'solve'));

/*
		// UNIVERSAL
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#((?>([*+/^_"~`-])+?))(?!\s)(.*(?!\\2).)'.TexyPatterns::MODIFIER.'?(?<!\s)\\1(?!\\2)(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/strong'
		);
*/

		// ***strong+emphasis***
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\*)\*\*\*(?![\s*])((?:[^ *]++|[ *])+)'.TexyPatterns::MODIFIER.'?(?<![\s*])\*\*\*(?!\*)(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/strong+em'
		);

		// **strong**
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\*)\*\*(?![\s*])((?:[^ *]++|[ *])+)'.TexyPatterns::MODIFIER.'?(?<![\s*])\*\*(?!\*)(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/strong'
		);

		// //emphasis//
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<![/:])\/\/(?![\s/])((?:[^ /]++|[ /])+)'.TexyPatterns::MODIFIER.'?(?<![\s/:])\/\/(?!\/)(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/em'
		);

		// *emphasisAlt*
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\*)\*(?![\s*])((?:[^\s*]++|[*])+)'.TexyPatterns::MODIFIER.'?(?<![\s*])\*(?!\*)(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/em-alt'
		);

		// *emphasisAlt2*
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<![^\s.,;:<>()"\''.TexyPatterns::MARK.'-])\*(?![\s*])((?:[^ *]++|[ *])+)'.TexyPatterns::MODIFIER.'?(?<![\s*])\*(?![^\s.,;:<>()"?!\'-])(?::('.TexyPatterns::LINK_URL.'))??()#Uus',
			'phrase/em-alt2'
		);

		// ++inserted++
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\+)\+\+(?![\s+])((?:[^\r\n +]++|[ +])+)'.TexyPatterns::MODIFIER.'?(?<![\s+])\+\+(?!\+)()#Uu',
			'phrase/ins'
		);

		// --deleted--
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<![<-])\-\-(?![\s>-])((?:[^\r\n -]++|[ -])+)'.TexyPatterns::MODIFIER.'?(?<![\s<-])\-\-(?![>-])()#Uu',
			'phrase/del'
		);

		// ^^superscript^^
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\^)\^\^(?![\s^])((?:[^\r\n ^]++|[ ^])+)'.TexyPatterns::MODIFIER.'?(?<![\s^])\^\^(?!\^)()#Uu',
			'phrase/sup'
		);

		// m^2 alternative superscript
		$texy->registerLinePattern(
			array($this, 'patternSupSub'),
			'#(?<=[a-z0-9])\^([n0-9+-]{1,4}?)(?![a-z0-9])#Uui',
			'phrase/sup-alt'
		);

		// __subscript__
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\_)\_\_(?![\s_])((?:[^\r\n _]++|[ _])+)'.TexyPatterns::MODIFIER.'?(?<![\s_])\_\_(?!\_)()#Uu',
			'phrase/sub'
		);

		// m_2 alternative subscript
		$texy->registerLinePattern(
			array($this, 'patternSupSub'),
			'#(?<=[a-z])\_([n0-9]{1,3})(?![a-z0-9])#Uui',
			'phrase/sub-alt'
		);

		// "span"
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\")\"(?!\s)((?:[^\r "]++|[ ])+)'.TexyPatterns::MODIFIER.'?(?<!\s)\"(?!\")(?::('.TexyPatterns::LINK_URL.'))??()#Uu',
			'phrase/span'
		);

		// ~alternative span~
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\~)\~(?!\s)((?:[^\r ~]++|[ ])+)'.TexyPatterns::MODIFIER.'?(?<!\s)\~(?!\~)(?::('.TexyPatterns::LINK_URL.'))??()#Uu',
			'phrase/span-alt'
		);

		// ~~cite~~
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\~)\~\~(?![\s~])((?:[^\r\n ~]++|[ ~])+)'.TexyPatterns::MODIFIER.'?(?<![\s~])\~\~(?!\~)(?::('.TexyPatterns::LINK_URL.'))??()#Uu',
			'phrase/cite'
		);

		// >>quote<<
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\>)\>\>(?![\s>])((?:[^\r\n <]++|[ <])+)'.TexyPatterns::MODIFIER.'?(?<![\s<])\<\<(?!\<)(?::('.TexyPatterns::LINK_URL.'))??()#Uu',
			'phrase/quote'
		);

		// acronym/abbr "et al."((and others))
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\")\"(?!\s)((?:[^\r\n "]++|[ ])+)'.TexyPatterns::MODIFIER.'?(?<!\s)\"(?!\")\(\((.+)\)\)()#Uu',
			'phrase/acronym'
		);

		// acronym/abbr NATO((North Atlantic Treaty Organisation))
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!['.TexyPatterns::CHAR.'])(['.TexyPatterns::CHAR.']{2,})()\(\(((?:[^\n )]++|[ )])+)\)\)#Uu',
			'phrase/acronym-alt'
		);

		// ''notexy''
		$texy->registerLinePattern(
			array($this, 'patternNoTexy'),
			'#(?<!\')\'\'(?![\s\'])((?:[^'.TexyPatterns::MARK.'\r\n\']++|[\'])+)(?<![\s\'])\'\'(?!\')()#Uu',
			'phrase/notexy'
		);

		// `code`
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#\`(\S(?:[^'.TexyPatterns::MARK.'\r\n `]++|[ `])*)'.TexyPatterns::MODIFIER.'?(?<!\s)\`(?::('.TexyPatterns::LINK_URL.'))??()#Uu',
			'phrase/code'
		);

		// ....:LINK
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(['.TexyPatterns::CHAR.'0-9@\#$%&.,_-]++)()(?=:\[)(?::('.TexyPatterns::LINK_URL.'))()#Uu',
			'phrase/quicklink'
		);

		// [text |link]
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<!\[)\[(?![\s*])([^|\r\n\]]++)\|((?:[^'.TexyPatterns::MARK.'|\r\n \]]++|[ ])+)'.TexyPatterns::MODIFIER.'?(?<!\s)\](?!\])()#Uu',
			'phrase/wikilink'
		);

		// [text](link)
		$texy->registerLinePattern(
			array($this, 'patternPhrase'),
			'#(?<![[.])\[(?![\s*])((?:[^|\r\n \]]++|[ ])+)'.TexyPatterns::MODIFIER.'?(?<!\s)\]\(((?:[^'.TexyPatterns::MARK.'\r )]++|[ ])+)\)()#Uu',
			'phrase/markdown'
		);

		$texy->allowed['phrase/ins'] = FALSE;
		$texy->allowed['phrase/del'] = FALSE;
		$texy->allowed['phrase/sup'] = FALSE;
		$texy->allowed['phrase/sub'] = FALSE;
		$texy->allowed['phrase/cite'] = FALSE;
	}

	/**
	 * Callback for: **.... .(title)[class]{style}**:LINK.
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternPhrase($parser, $matches, $phrase)
	{
		list(, $mContent, $mMod, $mLink) = $matches;
		// [1] => **
		// [2] => ...
		// [3] => .(title)[class]{style}
		// [4] => LINK

		if ($phrase === 'phrase/wikilink') {
			list($mLink, $mMod) = array($mMod, $mLink);
			$mContent = trim($mContent);
		}

		$tx = $this->texy;
		$mod = new TexyModifier($mMod);
		$link = NULL;

		$parser->again = $phrase !== 'phrase/code' && $phrase !== 'phrase/quicklink';

		if ($phrase === 'phrase/span' || $phrase === 'phrase/span-alt') {
			if ($mLink == NULL) {
				if (!$mMod) {
					return FALSE; // means "..."
				}
			} else {
				$link = $tx->linkModule->factoryLink($mLink, $mMod, $mContent);
			}

		} elseif ($phrase === 'phrase/acronym' || $phrase === 'phrase/acronym-alt') {
			$mod->title = trim(Texy::unescapeHtml($mLink));

		} elseif ($phrase === 'phrase/quote') {
			$mod->cite = $tx->blockQuoteModule->citeLink($mLink);

		} elseif ($mLink != NULL) {
			$link = $tx->linkModule->factoryLink($mLink, NULL, $mContent);
		}

		return $tx->invokeAroundHandlers('phrase', $parser, array($phrase, $mContent, $mod, $link));
	}

	/**
	 * Callback for: any^2 any_2.
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternSupSub($parser, $matches, $phrase)
	{
		list(, $mContent) = $matches;
		$mod = new TexyModifier();
		$link = NULL;
		$mContent = str_replace('-', "\xE2\x88\x92", $mContent); // −
		return $this->texy->invokeAroundHandlers('phrase', $parser, array($phrase, $mContent, $mod, $link));
	}

	/**
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return string
	 */
	public function patternNoTexy($parser, $matches)
	{
		list(, $mContent) = $matches;
		return $this->texy->protect(Texy::escapeHtml($mContent), Texy::CONTENT_TEXTUAL);
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  string
	 * @param  string
	 * @param  TexyModifier
	 * @param  TexyLink
	 * @return TexyHtml
	 */
	public function solve($invocation, $phrase, $content, $mod, $link)
	{
		$tx = $this->texy;

		$tag = isset($this->tags[$phrase]) ? $this->tags[$phrase] : NULL;

		if ($tag === 'a') {
			$tag = $link && $this->linksAllowed ? NULL : 'span';
		}

		if ($phrase === 'phrase/code') {
			$content = $tx->protect(Texy::escapeHtml($content), Texy::CONTENT_TEXTUAL);
		}

		if ($phrase === 'phrase/strong+em') {
			$el = TexyHtml::el($this->tags['phrase/strong']);
			$el->create($this->tags['phrase/em'], $content);
			$mod->decorate($tx, $el);

		} elseif ($tag) {
			$el = TexyHtml::el($tag)->setText($content);
			$mod->decorate($tx, $el);

			if ($tag === 'q') {
				$el->attrs['cite'] = $mod->cite;
			}
		} else {
			$el = $content; // trick
		}

		if ($link && $this->linksAllowed) {
			return $tx->linkModule->solve(NULL, $link, $el);
		}

		return $el;
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Scripts module.
 */
final class TexyScriptModule extends TexyModule
{
	/**
	 * @var callback|object  script elements handler
	 * function myFunc($parser, $cmd, $args, $raw)
	 */
	public $handler;

	/** @var string  arguments separator */
	public $separator = ',';

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->addHandler('script', array($this, 'solve'));

		$texy->registerLinePattern(
			array($this, 'pattern'),
			'#\{\{((?:[^'.TexyPatterns::MARK.'}]++|[}])+)\}\}()#U',
			'script'
		);
	}

	/**
	 * Callback for: {{...}}.
	 *
	 * @param  TexyLineParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function pattern($parser, $matches)
	{
		list(, $mContent) = $matches;
		// [1] => ...

		$cmd = trim($mContent);
		if ($cmd === '') {
			return FALSE;
		}

		$args = $raw = NULL;
		// function (arg, arg, ...) or function: arg, arg
		if ($matches = TexyRegexp::match($cmd, '#^([a-z_][a-z0-9_-]*)\s*(?:\(([^()]*)\)|:(.*))$#iu')) {
			$cmd = $matches[1];
			$raw = isset($matches[3]) ? trim($matches[3]) : trim($matches[2]);
			if ($raw === '') {
				$args = array();
			} else {
				$args = preg_split('#\s*' . preg_quote($this->separator, '#') . '\s*#u', $raw);
			}
		}

		// Texy 1.x way
		if ($this->handler) {
			if (is_callable(array($this->handler, $cmd))) {
				array_unshift($args, $parser);
				return call_user_func_array(array($this->handler, $cmd), $args);
			}

			if (is_callable($this->handler)) {
				return call_user_func_array($this->handler, array($parser, $cmd, $args, $raw));
			}
		}

		// Texy 2 way
		return $this->texy->invokeAroundHandlers('script', $parser, array($cmd, $args, $raw));
	}

	/**
	 * Finish invocation.
	 *
	 * @param  TexyHandlerInvocation  handler invocation
	 * @param  string  command
	 * @param  array   arguments
	 * @param  string  arguments in raw format
	 * @return TexyHtml|string|FALSE
	 */
	public function solve($invocation, $cmd, $args, $raw)
	{
		if ($cmd === 'texy') {
			if (!$args) {
				return FALSE;
			}

			switch ($args[0]) {
				case 'nofollow':
					$this->texy->linkModule->forceNoFollow = TRUE;
					break;
			}
			return '';

		} else {
			return FALSE;
		}
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Table cell TD / TH.
 */
class TexyTableCellElement extends TexyHtml
{
	/** @var int */
	public $colSpan = 1;

	/** @var int */
	public $rowSpan = 1;

	/** @var string */
	public $text;

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Table module.
 */
final class TexyTableModule extends TexyModule
{
	/** @var string  CSS class for odd rows */
	public $oddClass;

	/** @var string  CSS class for even rows */
	public $evenClass;

	private $disableTables;

	public function __construct($texy)
	{
		$this->texy = $texy;

		$texy->registerBlockPattern(
			array($this, 'patternTable'),
			'#^(?:'.TexyPatterns::MODIFIER_HV.'\n)?' // .{color: red}
			. '\|.*()$#mU', // | ....
			'table'
		);
	}

	/**
	 * Callback for:.
	 *
	 * .(title)[class]{style}>
	 * |------------------
	 * | xxx | xxx | xxx | .(..){..}[..]
	 * |------------------
	 * | aa | bb | cc |
	 *
	 * @param  TexyBlockParser
	 * @param  array      regexp matches
	 * @param  string     pattern name
	 * @return TexyHtml|string|FALSE
	 */
	public function patternTable($parser, $matches)
	{
		if ($this->disableTables) {
			return FALSE;
		}
		list(, $mMod) = $matches;
		// [1] => .(title)[class]{style}<>_

		$tx = $this->texy;

		$el = TexyHtml::el('table');
		$mod = new TexyModifier($mMod);
		$mod->decorate($tx, $el);

		$parser->moveBackward();

		if ($parser->next('#^\|(\#|\=){2,}(?![|\#=+])(.+)\\1*\|? *'.TexyPatterns::MODIFIER_H.'?()$#Um', $matches)) {
			list(, , $mContent, $mMod) = $matches;
			// [1] => # / =
			// [2] => ....
			// [3] => .(title)[class]{style}<>

			$caption = $el->create('caption');
			$mod = new TexyModifier($mMod);
			$mod->decorate($tx, $caption);
			$caption->parseLine($tx, $mContent);
		}

		$isHead = FALSE;
		$colModifier = array();
		$prevRow = array(); // rowSpan building helper
		$rowCounter = 0;
		$colCounter = 0;
		$elPart = NULL;
		$lineMode = FALSE; // rows must be separated by lines

		while (TRUE) {
			if ($parser->next('#^\|([=-])[+|=-]{2,}$#Um', $matches)) { // line
				if ($lineMode) {
					if ($matches[1] === '=') {
						$isHead = !$isHead;
					}
				} else {
					$isHead = !$isHead;
					$lineMode = $matches[1] === '=';
				}
				$prevRow = array();
				continue;
			}

			if ($parser->next('#^\|(.*)(?:|\|\ *'.TexyPatterns::MODIFIER_HV.'?)()$#U', $matches)) {
				// smarter head detection
				if ($rowCounter === 0 && !$isHead && $parser->next('#^\|[=-][+|=-]{2,}$#Um', $foo)) {
					$isHead = TRUE;
					$parser->moveBackward();
				}

				if ($elPart === NULL) {
					$elPart = $el->create($isHead ? 'thead' : 'tbody');

				} elseif (!$isHead && $elPart->getName() === 'thead') {
					$this->finishPart($elPart);
					$elPart = $el->create('tbody');
				}

				// PARSE ROW
				list(, $mContent, $mMod) = $matches;
				// [1] => ....
				// [2] => .(title)[class]{style}<>_

				$elRow = TexyHtml::el('tr');
				$mod = new TexyModifier($mMod);
				$mod->decorate($tx, $elRow);

				$rowClass = $rowCounter % 2 === 0 ? $this->oddClass : $this->evenClass;
				if ($rowClass && !isset($mod->classes[$this->oddClass]) && !isset($mod->classes[$this->evenClass])) {
					$elRow->attrs['class'][] = $rowClass;
				}

				$col = 0;
				$elCell = NULL;

				// special escape sequence \|
				$mContent = str_replace('\\|', "\x13", $mContent);
				$mContent = TexyRegexp::replace($mContent, '#(\[[^\]]*)\|#', "$1\x13"); // HACK: support for [..|..]

				foreach (explode('|', $mContent) as $cell) {
					$cell = strtr($cell, "\x13", '|');
					// rowSpan
					if (isset($prevRow[$col]) && ($lineMode || ($matches = TexyRegexp::match($cell, '#\^\ *$|\*??(.*)\ +\^$#AU')))) {
						$prevRow[$col]->rowSpan++;
						if (!$lineMode) {
							$cell = isset($matches[1]) ? $matches[1] : '';
						}
						$prevRow[$col]->text .= "\n" . $cell;
						$col += $prevRow[$col]->colSpan;
						$elCell = NULL;
						continue;
					}

					// colSpan
					if ($cell === '' && $elCell) {
						$elCell->colSpan++;
						unset($prevRow[$col]);
						$col++;
						continue;
					}

					// common cell
					$matches = TexyRegexp::match($cell, '#(\*??)\ *'.TexyPatterns::MODIFIER_HV.'??(.*)'.TexyPatterns::MODIFIER_HV.'?\ *()$#AU');
					if (!$matches) {
						continue;
					}
					list(, $mHead, $mModCol, $mContent, $mMod) = $matches;
					// [1] => * ^
					// [2] => .(title)[class]{style}<>_
					// [3] => ....
					// [4] => .(title)[class]{style}<>_

					if ($mModCol) {
						$colModifier[$col] = new TexyModifier($mModCol);
					}

					if (isset($colModifier[$col])) {
						$mod = clone $colModifier[$col];
					} else {
						$mod = new TexyModifier;
					}

					$mod->setProperties($mMod);

					$elCell = new TexyTableCellElement;
					$elCell->setName($isHead || ($mHead === '*') ? 'th' : 'td');
					$mod->decorate($tx, $elCell);
					$elCell->text = $mContent;

					$elRow->add($elCell);
					$prevRow[$col] = $elCell;
					$col++;
				}

				// even up with empty cells
				while ($col < $colCounter) {
					if (isset($prevRow[$col]) && $lineMode) {
						$prevRow[$col]->rowSpan++;
						$prevRow[$col]->text .= "\n";

					} else {
						$elCell = new TexyTableCellElement;
						$elCell->setName($isHead ? 'th' : 'td');
						if (isset($colModifier[$col])) {
							$colModifier[$col]->decorate($tx, $elCell);
						}
						$elRow->add($elCell);
						$prevRow[$col] = $elCell;
					}
					$col++;
				}
				$colCounter = $col;

				if ($elRow->count()) {
					$elPart->add($elRow);
					$rowCounter++;
				} else {
					// redundant row
					foreach ($prevRow as $elCell) {
						$elCell->rowSpan--;
					}
				}

				continue;
			}

			break;
		}

		if ($elPart === NULL) {
			// invalid table
			return FALSE;
		}

		if ($elPart->getName() === 'thead') {
			// thead is optional, tbody is required
			$elPart->setName('tbody');
		}

		$this->finishPart($elPart);

		// event listener
		$tx->invokeHandlers('afterTable', array($parser, $el, $mod));

		return $el;
	}

	/**
	 * Parse text in all cells.
	 * @param  TexyHtml
	 * @return void
	 */
	private function finishPart($elPart)
	{
		$tx = $this->texy;

		foreach ($elPart->getChildren() as $elRow) {
			foreach ($elRow->getChildren() as $elCell) {
				if ($elCell->colSpan > 1) {
					$elCell->attrs['colspan'] = $elCell->colSpan;
				}

				if ($elCell->rowSpan > 1) {
					$elCell->attrs['rowspan'] = $elCell->rowSpan;
				}

				$text = rtrim($elCell->text);
				if (strpos($text, "\n") !== FALSE) {
					// multiline parse as block
					// HACK: disable tables
					$this->disableTables = TRUE;
					$elCell->parseBlock($tx, Texy::outdent($text));
					$this->disableTables = FALSE;
				} else {
					$elCell->parseLine($tx, ltrim($text));
				}

				if ($elCell->getText() === '') {
					$elCell->setText("\xC2\xA0"); //
				}
			}
		}
	}

}


code:
<?php

/**
 * This file is part of the Texy! (http://texy.info)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

/**
 * Typography replacements module.
 */
final class TexyTypographyModule extends TexyModule
{
	// @see http://www.unicode.org/cldr/data/charts/by_type/misc.delimiters.html

	public static $locales = array(
		'cs' => array(
			'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x98"), // U+201A, U+2018
			'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9c"), // U+201E, U+201C
		),

		'en' => array(
			'singleQuotes' => array("\xe2\x80\x98", "\xe2\x80\x99"), // U+2018, U+2019
			'doubleQuotes' => array("\xe2\x80\x9c", "\xe2\x80\x9d"), // U+201C, U+201D
		),

		'fr' => array(
			'singleQuotes' => array("\xe2\x80\xb9", "\xe2\x80\xba"), // U+2039, U+203A
			'doubleQuotes' => array("\xc2\xab", "\xc2\xbb"),         // U+00AB, U+00BB
		),

		'de' => array(
			'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x98"), // U+201A, U+2018
			'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9c"), // U+201E, U+201C
		),

		'pl' => array(
			'singleQuotes' => array("\xe2\x80\x9a", "\xe2\x80\x99"), // U+201A, U+2019
			'doubleQuotes' => array("\xe2\x80\x9e", "\xe2\x80\x9d"), // U+201E, U+201D
		),
	);

	/** @var string */
	public $locale = 'cs';

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

	public function __construct($texy)
	{
		$this->texy = $texy;
		$texy->registerPostLine(array($this, 'postLine'), 'typography');
		$texy->addHandler('beforeParse', array($this, 'beforeParse'));
	}

	/**
	 * Text pre-processing.
	 * @param  Texy
	 * @param  string
	 * @return void
	 */
	public function beforeParse($texy, & $text)
	{
		// CONTENT_MARKUP mark: \x17-\x1F
		// CONTENT_REPLACED mark: \x16
		// CONTENT_TEXTUAL mark: \x17
		// CONTENT_BLOCK: not used in postLine

		if (isset(self::$locales[$this->locale])) {
			$locale = self::$locales[$this->locale];
		} else { // fall back
			$locale = self::$locales['en'];
		}

		$this->pattern = array(
			'#(?<![.\x{2026}])\.{3,4}(?![.\x{2026}])#mu' => "\xe2\x80\xa6",                // ellipsis  ...
			'#(?<=[\d ]|^)-(?=[\d ]|$)#'              => "\xe2\x80\x93",                   // en dash 123-123
			'#(?<=[^!*+,/:;<=>@\\\\_|-])--(?=[^!*+,/:;<=>@\\\\_|-])#' => "\xe2\x80\x93",   // en dash alphanum--alphanum
			'#,-#'                                    => ",\xe2\x80\x93",                  // en dash ,-
			'#(?<!\d)(\d{1,2}\.) (\d{1,2}\.) (\d\d)#' => "\$1\xc2\xa0\$2\xc2\xa0\$3",      // date 23. 1. 1978
			'#(?<!\d)(\d{1,2}\.) (\d{1,2}\.)#'        => "\$1\xc2\xa0\$2",                 // date 23. 1.
			'# --- #'                                 => "\xc2\xa0\xe2\x80\x94 ",          // em dash ---
			'# ([\x{2013}\x{2014}])#u'                => "\xc2\xa0\$1",                    //   behind dash (dash stays at line end)
			'# <-{1,2}> #'                            => " \xe2\x86\x94 ",                 // left right arrow <-->
			'#-{1,}> #'                               => "\xe2\x86\x92 ",                  // right arrow -->
			'# <-{1,}#'                               => " \xe2\x86\x90 ",                 // left arrow <--
			'#={1,}> #'                               => "\xe2\x87\x92 ",                  // right arrow ==>
			'#\\+-#'                                  => "\xc2\xb1",                       // +-
			'#(\d++) x (?=\d)#'                       => "\$1\xc2\xa0\xc3\x97\xc2\xa0",    // dimension sign 123 x 123...
			'#(\d++)x(?=\d)#'                         => "\$1\xc3\x97",                    // dimension sign 123x123...
			'#(?<=\d)x(?= |,|.|$)#m'                  => "\xc3\x97",                       // dimension sign 123x
			'#(\S ?)\(TM\)#i'                         => "\$1\xe2\x84\xa2",                // trademark (TM)
			'#(\S ?)\(R\)#i'                          => "\$1\xc2\xae",                    // registered (R)
			'#\(C\)( ?\S)#i'                          => "\xc2\xa9\$1",                    // copyright (C)
			'#\(EUR\)#'                               => "\xe2\x82\xac",                   // Euro (EUR)
			'#(\d) (?=\d{3})#'                        => "\$1\xc2\xa0",                    // (phone) number 1 123 123 123...

			'#(?<=[^\s\x17])\s++([\x17-\x1F]++)(?=\s)#u'=> "\$1",                            // remove intermarkup space phase 1
			'#(?<=\s)([\x17-\x1F]++)\s++#u'             => "\$1",                            // remove intermarkup space phase 2

			'#(?<=.{50})\s++(?=[\x17-\x1F]*\S{1,6}[\x17-\x1F]*$)#us' => "\xc2\xa0",         // space before last short word

			// nbsp space between number (optionally followed by dot) and word, symbol, punctation, currency symbol
			'#(?<=^| |\.|,|-|\+|\x16|\(|\d\x{A0})([\x17-\x1F]*\d++\.?[\x17-\x1F]*)\s++(?=[\x17-\x1F]*[%'.TexyPatterns::CHAR.'\x{b0}-\x{be}\x{2020}-\x{214f}])#mu'
													=> "\$1\xc2\xa0",

			// space between preposition and word
			'#(?<=^|[^0-9'.TexyPatterns::CHAR.'])([\x17-\x1F]*[ksvzouiKSVZOUIA][\x17-\x1F]*)\s++(?=[\x17-\x1F]*[0-9'.TexyPatterns::CHAR.'])#mus'
													=> "\$1\xc2\xa0",

			'#(?<!"|\w)"(?!\ |")((?:[^"]++|")+)(?<!\ |")"(?!["'.TexyPatterns::CHAR.'])()#Uu' => $locale['doubleQuotes'][0].'$1'.$locale['doubleQuotes'][1], // double ""
			'#(?<!\'|\w)\'(?!\ |\')((?:[^\']++|\')+)(?<!\ |\')\'(?![\''.TexyPatterns::CHAR.'])()#Uu' => $locale['singleQuotes'][0].'$1'.$locale['singleQuotes'][1], // single ''
		);
	}

	public function postLine($text, $preserveSpaces = FALSE)
	{
		if (!$preserveSpaces) {
			$text = TexyRegexp::replace($text, '# {2,}#', ' ');
		}
		return TexyRegexp::replace($text, $this->pattern);
	}

}


code:
<?php
/**
 * Uploader is a file transfer class. it can upload files and images.
 * It also can resize and crop images.
 * Works on PHP 5
 * @author Alaa Al-Hussein
 * @link https://www.freelancer-id.com/projects
 * @version 1.0
 *
 */
class uploader{
	/**
	 * Array, The file object as $_FILES['element'].
	 * String, file location.
	 */
	public $source;
	/**
	 * Destination file location as folder.
	 */
	public $destDir;
	/**
	 * Directory for Resized images.
	 */
	public $resizeDir;
	/**
	 * Directory for Cropped images.
	 */
	public $cropDir;
	/**
	 * stores information for uploading file
	 */
	private $info = '';
	/**
	 * Handles the error when it occurs.
	 */
	private $errorMsg = '';
	/**
	 * new width for resizing and cropping.
	 */
	public $newWidth;
	/**
	 * new height for resizing and cropping.
	 */
	public $newHeight;
	/**
	 * TOP postion to cropping image.
	 */
	public $top = 0;
	/**
	 * LEFT position for cropping image.
	 */
	public $left = 0;
	/**
	 * JPG quality (0 - 100). used for image resizing or cropping.
	 */
	public $quality = 90;
	/**
	 * Boolean: Auto name, to prevent file name duplicate.
	 */
	public $autoName = true;
	/**
	 * Handle new generated file name for Auto File name.
	 */
	public $fileName;

	public function __construct(){
		//nothing
	}
	/**
	 * Uploads the file to the server.
	 * @param Array $_FILES[]
	 */
	public function upload($source){
		if($source != ""){
			$this->source = $source;
		}
		if(is_array($this->source)){
			if($this->fileExists()){
				return false;
			}
			return $this->copyFile();
		} else {
			if(preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $this->source)){
				$this->copyExternalFile();
			} else {
				return $this->source;
			}
		}
	}
	/**
	 * return the error messages.
	 * @return String messages.
	 */
	public function getError(){
		return $this->errorMsg;
	}
	/**
	 * Get uploading information.
	 */
	public function getInfo(){
		return $this->info;
	}
	/**
	 * Copy the uploaded file to destination.
	 */
	private function copyFile(){
		if(!$this->isWritable()){
			$this->errorMsg .= '
Error, the directory: ('.$this->destDir.') is not writable. Please fix the permission to be able to upload.
';
			return false;
		}
		if($this->autoName==true){
			$ext = explode('.',$this->source['name']);
			$this->fileName = date("U").'.'. $ext[(sizeof($ext)-1)];
		} else {
			$this->fileName = $this->source['name'];
		}
		if(copy($this->source['tmp_name'],$this->destDir . $this->fileName)){
			// Done.
			$this->info .= '
file was uploaded successfully.
';
		} else {
			$this->errorMsg .= '
Error, the file was not uploaded correctly because of an internal error. Please try again, if you see this message again, please contact web admin.
';
		}
	}

	/**
	 * Copy external file.
	 * @return boolean
	 */
	private function copyExternalFile(){
		$file_name = date("U").basename($this->source);
		if(file_put_contents($this->destDir . $file_name, file_get_contents($this->source, false, null, -1, 4194304))){
			// Done.
			$this->info .= '
file was uploaded successfully.
';
			return $file_name;
		} else {
			$this->errorMsg .= '
Error, the file was not uploaded correctly because of an internal error. Please try again, if you see this message again, please contact web admin.
';
			return false;
		}
	}
	/**
	 * Checks if the file was uploaded.
	 * @return boolean
	 */
	private function uploaded(){
		if($this->source['tmp_name']=="" || $this->source['error'] !=0){
			$this->errorMsg .= '
Error, file was not uploaded to the server. Please try again.
';
			return false;
		} else
			return true;
	}
	/**
	 * Prepares the directory.
	 */
	private function preDir(){
		if($this->destDir!="" && substr($this->destDir, -1,1) != "/"){
			$this->destDir = $this->destDir . '/';
		}
		if($this->resizeDir!="" && substr($this->resizeDir, -1,1) != "/"){
			$this->destDir = $this->resizeDir . '/';
		}
		if($this->cropDir!="" && substr($this->cropDir, -1,1) != "/"){
			$this->destDir = $this->cropDir . '/';
		}
	}
	/**
	 * Check if the folder is writable or not.
	 * @return boolean
	 */
	private function isWritable(){
		$err = false;
		if(!is_writeable($this->destDir) && $this->destDir!=""){
			$this->errorMsg .= '
Error, the directory ('.$this->destDir.') is not writeable. File could not be uploaded.
';
			$err = true;
		}
		if(!is_writeable($this->resizeDir) && $this->resizeDir!=""){
			$this->errorMsg .= '
Error, the directory ('.$this->resizeDir.') is not writeable. File could not be resized.
';
			$err = true;
		}
		if(!is_writeable($this->cropDir) && $this->cropDir!=""){
			$this->errorMsg .= '
Error, the directory ('.$this->cropDir.') is not writeable. File could not be cropped.
';
			$err = true;
		}
		if($err == true){
			return false;
		} else {
			return true;
		}
	}
	/**
	 * Checks if the file exists on the server
	 * @return boolean
	 */
	private function fileExists(){
		$this->preDir();
		if(file_exists($this->destDir.$this->source)){
			$this->errorMsg .= '
Upload error because file already exists.
';
			return true;
		} else {
			return false;
		}
	}
	/**
	 /586742130./8532 Crops image.
	 * @return String fileName or False on error
	 */
	public function crop($file='',$width='',$height='',$top='',$left=''){
		if($file!=""){ $this->source = $file;}
		if ($width != '') $this->newWidth = $width;
		if ($height != '') $this->newHeight = $height;
		if ($top != '') $this->top = $top;
		if ($left != '') $this->left = $left;
		return $this->_resize_crop(true);
	}
	/**
	 * Resizes an image.
	 * @return String fileName or False on error
	 */
	public function resize($file='',$width='',$height='',$fixed='width'){
		if($file!=""){ $this->source = $file; }
		if($width != '') $this->newWidth = $width;
		if($height != '') $this->newHeight = $height;
		return $this->_resize_crop(false,$fixed);
	}
	/**
	 * Get the Temp file location for the file.
	 * If the Source was a file location, it returns the same file location.
	 * @return String Temp File Location
	 */
	private function getTemp(){
		if(is_array($this->source)){
			return $this->source['tmp_name'];
		} else {
			return $this->source;
		}
	}
	/**
	 * Get the File location.
	 * If the source was a file location, it returns the same file location.
	 * @return String File Location
	 */
	private function getFile(){
		if(is_array($this->source)){
			return $this->source['name'];
		} else {
			return $this->source;
		}
	}
	/**
	 * Resize or crop- the image.
	 * @param boolean $crop
	 * @return String fileName False on error
	 */
	private function _resize_crop ($crop,$fixed) {
		$finfo = finfo_open(FILEINFO_MIME_TYPE);
		switch(finfo_file($finfo, $this->getTemp())) {
			case 'image/jpeg':
			case 'image/jpg':
				$ext = 'jpg';
				break;
			case 'image/png':
				$ext = 'png';
				break;
			case 'image/gif':
				$ext = 'gif';
				break;
			default:
				return false;
		}
		finfo_close($finfo);

		list($width, $height) = getimagesize($this->getTemp());
		if(!$crop){
			$ratio = $width/$height;
			if($fixed=="width"){
				if ($this->newWidth/$this->newHeight > $ratio) {
					$this->newWidth = $this->newHeight*$ratio;
				} else {
					$this->newHeight = $this->newWidth/$ratio;
				}
			} else {
				if ($this->newWidth/$this->newHeight > $ratio) {
					$this->newHeight = $this->newWidth*$ratio;
				} else {
					$this->newWidth = $this->newHeight/$ratio;
				}
			}
		}
		$normal  = imagecreatetruecolor($this->newWidth, $this->newHeight);
		imagealphablending($normal, false);
		imagesavealpha($normal, true);
		$transparent = imagecolorallocatealpha($normal, 255, 255, 255, 127);
		imagefilledrectangle($normal, 0, 0, $this->newWidth, $this->newHeight, $transparent);

		if($ext == "jpg") {
			$src = imagecreatefromjpeg($this->getTemp());
		} else if($ext == "gif") {
			$src = imagecreatefromgif ($this->getTemp());
		} else if($ext == "png") {
            		$src = imagecreatefrompng ($this->getTemp());
		}

		if($crop){
			$pre = $this->newWidth.'x'.$this->newHeight.'_crop_';
 			if(imagecopy($normal, $src, 0, 0, $this->top, $this->left, $this->newWidth, $this->newHeight)){
 				$this->info .= '
image was cropped and saved.
';
 			}
 			$dir = $this->cropDir;
		} else {
			$pre = $this->newWidth.'x'.$this->newHeight.'_';
			if(imagecopyresampled($normal, $src, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $width, $height)){
				$this->info .= '
image was resized and saved.
';
			}
			$dir = $this->resizeDir;
		}

		imagealphablending($normal, false);
		imagesavealpha($normal, true);
		imagepng ($normal, $dir . $pre . $this->fileName .'.png', 9);
		imagedestroy($src);

		return $pre . $this->fileName .'.png';
	}
}
?>


code:
<?php
/**
 * Класс подключения к базе данных (redis):
 */
class Cache
{
	/**
	 * Соединение с базой данных:
	 */
	private $connection = null;

	/**
	 * Вызов экземпляра класса:
	 */
	static function getInstance()
	{
		static $instance;

		if (!is_object($instance))
			$instance = new Cache();

		return $instance;
	}

	/**
	 * Конструктор:
	 */
	private function __construct()
	{
		if (is_null($this -> connection))
			$this -> connection = new Redis();

		return $this -> connection;
	}

	/**
	 * Получение пути к свойству класса:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	private function getNS($class, $id, $name)
	{
		if (is_null($id) && is_null($name))
			return $class;

		elseif (is_null($name))
			return $class .':'. $id;

		else
			return $class .':'. $id .':'. $name;
	}

	/**
	 * Получение значения из базы данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function get($class, $id = null, $name = null)
	{
		return $this -> connection -> get(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение ключей из базы данных по паттерну:
	 * $class   - имя класса.
	 * $id      - идентификатор.
	 * $pattern - имя свойства.
	 */
	public function getKeys($class, $id = null, $pattern = null)
	{
		return $this -> connection -> keys(
			$this -> getNS($class, $id, $pattern)
		);
	}

	/**
	 * Запись значения в базу данных.
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $value - значение свойства.
	 */
	public function set($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> set(
			$this -> getNS($class, $id, $name), $value
		);
	}

	/**
	 * Проверка существования значения в базе данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function exists($class, $id = null, $name = null)
	{
		return $this -> connection -> exists(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Удаление значения из базы данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function remove($class, $id = null, $name = null)
	{
		return $this -> connection -> delete(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Удаление значения из базы данных по паттерну:
	 * $class   - имя класса.
	 * $id      - идентификатор.
	 * $pattern - имя свойства.
	 */
	public function removeByPattern($class, $id = null, $pattern = null)
	{
		$keys  = $this -> connection -> keys(
			$this -> getNS($class, $id, $pattern)
		);

		if (!empty($keys[0]))
			foreach ($keys as $key)
			{
				$this -> connection -> delete($key);
			}

		if ($pattern == '*')
			$this -> connection -> delete(
				$this -> getNS($class, $id, $pattern)
			);

		return true;
	}

	/**
	 * Установка времени жизни ключа:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $expire - время жизни ключа.
	 */
	public function expire($class, $id = null, $name = null, $expire = null, $till = false)
	{
		if ($till)
			return $this -> connection -> expireat(
				$this -> getNS($class, $id, $name), $expire
			);
		else
			return $this -> connection -> expire(
				$this -> getNS($class, $id, $name), $expire
			);
	}

	/**
	 * Получение времени жизни ключа:
	 */
	public function lifetime($class, $id = null, $name = null)
	{
		return $this -> connection -> ttl(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Увеличение значения ключа в базе данных:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $amount - количество приращения.
	 */
	public function incr($class, $id = null, $name = null, $amount = 1)
	{
		return $this -> connection -> incr(
			$this -> getNS($class, $id, $name), $amount
		);
	}

	/**
	 * Уменьшение значения ключа в базе данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $amount - количество приращения.
	 */
	public function decr($class, $id = null, $name = null, $amount = 1)
	{
		return $this -> connection -> decr(
			$this -> getNS($class, $id, $name), $amount
		);
	}

	/**
	 * Получение длины списка:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function listLength($class, $id = null, $name = null)
	{
		return $this -> connection -> llen(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение значение из списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $offset - начальная позиция результатов.
	 * $limit  - количество результатов.
	 */
	public function listGet($class, $id = null, $name = null, $offset = 0, $limit = -1)
	{
		return $this -> connection -> lrange(
			$this -> getNS($class, $id, $name), $offset, $limit
		);
	}

	/**
	 * Добавление значения в список:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listAdd($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> push(
			$this -> getNS($class, $id, $name), $value, false
		);
	}

	/**
	 * Удаление значения из списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listRemove($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> ldelete(
			$this -> getNS($class, $id, $name), 1, $value
		);
	}

	/**
	 * Сокращение списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listTrim($class, $id = null, $name = null, $from = 0, $to = 0)
	{
		return $this -> connection -> ltrim(
			$this -> getNS($class, $id, $name), $from, $to
		);
	}

	/**
	 * Получение длины сортировочного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function sortedListLength($class, $id = null, $name = null)
	{
		return $this -> connection -> scard(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение длины сортированного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function sortedListGet($class, $id = null, $name = null, $offset = 0, $limit = -1)
	{
		return $this -> connection -> zrevrange(
			$this -> getNS($class, $id, $name), $offset, $limit
		);
	}

	/**
	 * Добавление значения в сотрированный список:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 * $score  - значение индекса сортировки.
	 */
	public function sortedListAdd($class, $id = null, $name = null, $value = null, $score = null)
	{
		return $this -> connection -> zadd(
			$this -> getNS($class, $id, $name), $score, $value
		);
	}

	/**
	 * Удаление значения из сортированного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function sortedListRemove($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> zrem(
			$this -> getNS($class, $id, $name), $value
		);
	}
}


code:
<?php
/**
 * Класс управления конфигурацией:
 */
class Config implements ArrayAccess, Iterator, Countable
{
	/**
	 * Контейнер настроек:
	 */
	private $configArray = array();

	/**
	 * Статус изменения:
	 */
	private $isChanged = false;

	/**
	 * Вызов экземпляра класса:
	 */
	public static function getInstance()
	{
		static $instance;

		if (!is_object($instance))
			$instance = new Config();

		return $instance;
	}

	/**
	 * Конструктор:
	 */
	private function __construct()
	{
		if (is_readable(CONFIG_PATH))
		{
			$config = @include_once(CONFIG_PATH);
			$this -> import($config);
		}
	}

	/**
	 * Метод установки параметра конфигурации (ArrayAccess):
	 */
	public function offsetSet($offset, $value) {
        $this -> isChanged = true;
        $this -> configArray[$offset] = $value;
    }

	/**
	 * Метод проверки существования параметра конфигурации (ArrayAccess):
	 */
    public function offsetExists($offset) {
        return isset($this -> configArray[$offset]);
    }

	/**
	 * Метод удаления параметра конфигурации (ArrayAccess):
	 */
    public function offsetUnset($offset) {
        $this -> isChanged = true;
        unset($this -> configArray[$offset]);
    }

	/**
	 * Метод получения параметра конфигурации (ArrayAccess):
	 */
    public function offsetGet($offset) {
        return isset($this -> configArray[$offset]) ? $this -> configArray[$offset] : null;
    }

	/**
	 * Метод сброса каретки в массиве (Iterator):
	 */
    public function rewind()
	{
		reset($this -> configArray);
    }

	/**
	 * Метод получения текущего значения (Iterator):
	 */
    public function current()
	{
		return current($this -> configArray);
    }

	/**
	 * Метод получения текущего ключа (Iterator):
	 */
    public function key()
	{
		return key($this -> configArray);
    }

	/**
	 * Метод перехода на следующее значение (Iterator):
	 */
    public function next()
	{
		return next($this -> configArray);
    }

	/**
	 * Метод проверки текущего значения (Iterator):
	 */
    public function valid()
    {
		return $this -> current() !== false;
    }

	/**
	 * Метод подсчета количества значений (Count):
	 */
    public function count()
	{
		return count($this -> configArray);
    }

	/**
	 * Метод установки параметров конфигурации из массива:
	 * $options - массив параметров конфигурации.
	 */
	public function import($options)
	{
		if (is_array($options))
		{
			$this -> configArray =
				array_merge_recursive($this -> configArray, $options);
			return true;
		}
		return false;
	}

	/**
	 * Деструктор:
	 */
	public function __destruct()
	{
		if ($this -> isChanged)
		{
			file_put_contents(
				CONFIG_PATH,
				'<?php return('.
					var_export($this -> configArray, true).
				'); ?>'
			);
		}
	}
}


code:
<?php
/**
 * Базовый контроллер:
 */
class Controller implements ArrayAccess
{
	/**
	 * Параметры для передачи в вид:
	 */
	protected $viewParams = array();

	/**
	 * Установка параметра вида (ArrayAccess):
	 */
	public function offsetSet($offset, $value) {
        $this -> viewParams[$offset] = $value;
    }

	/**
	 * Проверка на существование параметра вида (ArrayAccess):
	 */
    public function offsetExists($offset) {
        return isset($this -> viewParams[$offset]);
    }

	/**
	 * Удаление параметра вида (ArrayAccess):
	 */
    public function offsetUnset($offset) {
        unset($this -> viewParams[$offset]);
    }

	/**
	 * Получение параметра вида (ArrayAccess):
	 */
    public function offsetGet($offset) {
        return isset($this -> viewParams[$offset]) ? $this -> viewParams[$offset] : null;
    }

    /**
     * Установка параметров из массива:
     */
    public function setValues($values = array())
    {
    	$this -> viewParams =
    		array_merge_recursive($this -> viewParams, $values);

    	return true;
    }

    /**
     * Получение массива параметров:
     */
    public function getValues()
    {
    	return $this -> viewParams;
    }

    /**
     * Метод обработки контроллера, передает данные в вид:
     */
    public function process($template) {
		return $template-> render($this -> getValues());
    }
}


code:
<?php
/**
 * Класс подключения к базе данных (redis):
 */
class KVS
{
	/**
	 * Соединение с базой данных:
	 */
	private $connection = null;

	/**
	 * Вызов экземпляра класса:
	 */
	static function getInstance()
	{
		static $instance;

		if (!is_object($instance))
			$instance = new KVS();

		return $instance;
	}

	/**
	 * Конструктор:
	 */
	private function __construct()
	{
		if (is_null($this -> connection)) {
			$this -> connection = new Redis();
			$this -> connection -> connect(REDIS_HOST, REDIS_PORT);
		}

		return $this -> connection;
	}

	/**
	 * Получение пути к свойству класса:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	private function getNS($class, $id, $name)
	{
		if (is_null($id) && is_null($name))
			return $class;

		elseif (is_null($name))
			return $class .':'. $id;

		else
			return $class .':'. $id .':'. $name;
	}

	/**
	 * Получение значения из базы данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function get($class, $id = null, $name = null)
	{
		return $this -> connection -> get(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение ключей из базы данных по паттерну:
	 * $class   - имя класса.
	 * $id      - идентификатор.
	 * $pattern - имя свойства.
	 */
	public function getKeys($class, $id = null, $pattern = null)
	{
		return $this -> connection -> keys(
			$this -> getNS($class, $id, $pattern)
		);
	}

	/**
	 * Запись значения в базу данных.
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $value - значение свойства.
	 */
	public function set($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> set(
			$this -> getNS($class, $id, $name), $value
		);
	}

	/**
	 * Проверка существования значения в базе данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function exists($class, $id = null, $name = null)
	{
		return $this -> connection -> exists(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Удаление значения из базы данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function remove($class, $id = null, $name = null)
	{
		return $this -> connection -> unlink(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Удаление значения из базы данных по паттерну:
	 * $class   - имя класса.
	 * $id      - идентификатор.
	 * $pattern - имя свойства.
	 */
	public function removeByPattern($class, $id = null, $pattern = null)
	{
		$keys  = $this -> connection -> keys(
			$this -> getNS($class, $id, $pattern)
		);

		if (!empty($keys[0]))
			foreach ($keys as $key)
			{
				$this -> connection -> unlink($key);
			}

		if ($pattern == '*')
			$this -> connection -> unlink(
				$this -> getNS($class, $id, $pattern)
			);

		return true;
	}

	/**
	 * Установка времени жизни ключа:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $expire - время жизни ключа.
	 */
	public function expire($class, $id = null, $name = null, $expire = null, $till = false)
	{
		if ($till)
			return $this -> connection -> expireat(
				$this -> getNS($class, $id, $name), $expire
			);
		else
			return $this -> connection -> expire(
				$this -> getNS($class, $id, $name), $expire
			);
	}

	/**
	 * Получение времени жизни ключа:
	 */
	public function lifetime($class, $id = null, $name = null)
	{
		return $this -> connection -> ttl(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Увеличение значения ключа в базе данных:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $amount - количество приращения.
	 */
	public function incr($class, $id = null, $name = null, $amount = 1)
	{
		return $this -> connection -> incr(
			$this -> getNS($class, $id, $name), $amount
		);
	}

	/**
	 * Уменьшение значения ключа в базе данных:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $amount - количество приращения.
	 */
	public function decr($class, $id = null, $name = null, $amount = 1)
	{
		return $this -> connection -> decr(
			$this -> getNS($class, $id, $name), $amount
		);
	}
	/**
	 * Получение значения хеша:
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $key   - ключ хеша.
	 */
	public function hashGet($class, $id = null, $name = null, $hname)
	{
	    return $this -> connection -> hget(
			$this -> getNS($class, $id, $name), $hname
		);
	}

	/**
	 * Получение значения хеша:
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $key   - ключ хеша.
	 */
	public function hashMGet($class, $id = null, $name = null, $hnames = array())
	{
	    return $this -> connection -> hmget(
			$this -> getNS($class, $id, $name), $hnames
		);
	}

	/**
	 * Установка значения элемента хеша:
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 * $key   - ключ хеша.
	 * $value - значение.
	 */
	public function hashSet($class, $id = null, $name = null, $hname, $value = null)
	{
	    return $this -> connection -> hset(
			$this -> getNS($class, $id, $name), $hname, $value
		);
	}

	/**
	 * Увеличение значения элеметна хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $key    - ключ хеша.
	 * $amount - значение.
	 */
	public function hashIncrement($class, $id = null, $name = null, $hname, $amount = 1)
	{
	    return $this -> connection -> hincrby(
			$this -> getNS($class, $id, $name), $hname, $amount
		);
	}

	/**
	 * Существование значения элемента хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $key    - ключ хеша.
	 */
	public function hashExists($class, $id = null, $name = null, $hname)
	{
	    return $this -> connection -> hexists(
			$this -> getNS($class, $id, $name), $hname
		);
	}

	/**
	 * Удаление значения элемента хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $key    - ключ хеша.
	 */
	public function hashRemove($class, $id = null, $name = null, $hname)
	{
	    return $this -> connection -> hdel(
			$this -> getNS($class, $id, $name), $hname
		);
	}

	/**
	 * Получение ключей хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function hashGetKeys($class, $id = null, $name = null)
	{
	    return $this -> connection -> hkeys(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение значений хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function hashGetVals($class, $id = null, $name = null)
	{
	    return $this -> connection -> hvals(
			$this -> getNS($class, $id, $name)
		);
    }

	/**
	 * Получение ключей и значений хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function hashGetAll($class, $id = null, $name = null)
	{
	    return $this -> connection -> hgetall(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение длинны элементов хеша:
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function hashLength($class, $id = null, $name = null)
	{
	    return $this -> connection -> hlen(
			$this -> getNS($class, $id, $name)
		);
    }

	/**
	 * Получение длины списка:
	 * $class - имя класса.
	 * $id    - идентификатор.
	 * $name  - имя свойства.
	 */
	public function listLength($class, $id = null, $name = null)
	{
		return $this -> connection -> llen(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение значение из списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $offset - начальная позиция результатов.
	 * $limit  - количество результатов.
	 */
	public function listGet($class, $id = null, $name = null, $offset = 0, $limit = -1)
	{
		return $this -> connection -> lrange(
			$this -> getNS($class, $id, $name), $offset, $limit
		);
	}

	/**
	 * Добавление значения в список:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listAdd($class, $id = null, $name = null, $value = null, $left = false)
	{
		if ($left)
			return $this -> connection -> rpush(
				$this -> getNS($class, $id, $name), $value
			);
		else
			return $this -> connection -> lpush(
				$this -> getNS($class, $id, $name), $value
			);
	}

	/**
	 * Удаление значения из списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listRemove($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> lrem(
			$this -> getNS($class, $id, $name), $value, 1
		);
	}

	/**
	 * Сокращение списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function listTrim($class, $id = null, $name = null, $from = 0, $to = 0)
	{
		return $this -> connection -> ltrim(
			$this -> getNS($class, $id, $name), $from, $to
		);
	}

	/**
	 * Получение длины сортировочного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function sortedListLength($class, $id = null, $name = null)
	{
		return $this -> connection -> zcard(
			$this -> getNS($class, $id, $name)
		);
	}

	/**
	 * Получение длины сортированного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 */
	public function sortedListGet($class, $id = null, $name = null, $offset = 0, $limit = -1)
	{
		return $this -> connection -> zrevrange(
			$this -> getNS($class, $id, $name), $offset, $limit
		);
	}

	/**
	 * Добавление значения в сотрированный список:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 * $score  - значение индекса сортировки.
	 */
	public function sortedListAdd($class, $id = null, $name = null, $value = null, $score = null)
	{
		return $this -> connection -> zadd(
			$this -> getNS($class, $id, $name), $score, $value
		);
	}

	/**
	 * Удаление значения из сортированного списка:
	 * $class  - имя класса.
	 * $id     - идентификатор.
	 * $name   - имя свойства.
	 * $value  - значение.
	 */
	public function sortedListRemove($class, $id = null, $name = null, $value = null)
	{
		return $this -> connection -> zrem(
			$this -> getNS($class, $id, $name), $value
		);
	}
}