<?php

	/*
	*	Gekko - Open Source Web Development Framework
	*	------------------------------------------------------------------------
	*	Copyright (C) 2004-2005, J. Carlos Nieto <xiam@users.sourceforge.net>
	*	This program is Free Software.
	*
	*	@package	Gekko
	*	@license	http://www.gnu.org/copyleft/gpl.html GNU/GPL License 2.0
	*	@author		J. Carlos Nieto <xiam@users.sf.net>
	*	@version	CVS: $Id:$
	*	@link		http://www.gekkoware.org
	*/

	if (!defined("IN-GEKKO")) die("Get a life!");

	require_once GEKKO_ROOT."modules/users/main.lib.php";
	require_once GEKKO_ROOT."modules/packages/main.lib.php";

	Class GBBCodeParser {

		var $patterns = array();
		var $replaces = array();
		var $pre_patterns = array();
		var $pre_replaces = array();
		var $smiley_p = array();
		var $smiley_r = array();

		// GBBCodeParser(user groups)
		// Initializes parser for that user group
		function GBBCodeParser($usergroups = 0) {

			$abs_smileydir = GEKKO_ROOT."media/smileys/"._l("C_SITE.SMILEYSTHEME")."/";
			$rel_smileydir = "media/smileys/"._l("C_SITE.SMILEYSTHEME")."/";

			/* those tags must be evaluated first */
			$this->pre_patterns[] = "/\[code\](.*?)\[\/code\]/se";
			$this->pre_replaces[] = "\$this->gbbt_precode('\\1')";

			if ($usergroups) {
				// Undocumented cool features: "[block:1]" will load block wich id=1.
				// Obviously, a privileged level is required!
				if (gauth("blocks", $usergroups)) {
					$this->patterns[] = "/\[block\:([0-9]*?)\]/ie";
					$this->replaces[] = "\$this->gbbc_load_block('\\1')";
				}
			}

			/* those tags must be evaluated at the end */
			$this->patterns[] = "/\[code\](.*?)\[\/code\]/se";
			$this->replaces[] = "\$this->gbbt_code('\\1')";

			// smiley patterns
			if (!isset($_cache["emoticon_pattern"])) {
				// parseing patterns
				$_cache["emoticon_pattern"] = Array("pattern" => Array(), "replace" => Array());
				pkg_parse_xml("{$abs_smileydir}package.xml", $xml = "");
				$xml["pkginfo"]["template"] = trim($xml["pkginfo"]["template"]);
				foreach ($xml["emoticon-map"] as $file => $buff) {
					$pattern = $buff["pattern"];
					$_cache["emoticon_pattern"]["pattern"][] = $buff["pattern"];
					$_cache["emoticon_pattern"]["replace"][] = str_replace("%file", _l("C_SITE.URL").$rel_smileydir.$file, $xml["pkginfo"]["template"]);
				}
			}
			foreach ($_cache["emoticon_pattern"]["pattern"] as $key => $pattern) {
				$this->smiley_p[] = $pattern;
				$this->smiley_r[] = $_cache["emoticon_pattern"]["replace"][$key];
			}
			foreach ($this->smiley_p as $key => $val) {
				$this->smiley_p[$key] = "/(?<=.\W|\W.|^\W|^)".$val."(?=.\W|\W.|\W$|$)/si";
			}
		}
		// works with [block:id] GBBCode tag, inserts a block in the given position
		function gbbc_load_block($id) {
			require_once GEKKO_ROOT."modules/blocks/main.lib.php";
			return loadBlock($id);
		}
		function gbbc_stripslashes($buff) {
			return preg_replace(array("/\\\\\"/", "/\\\\\\\/"), array("\"", "\\"), $buff);
		}
		function escape($buff) {
			return urlencode($buff);
		}
		function unescape($buff) {
			return urldecode($buff);
		}
		// pre parser for [code] tag
		function gbbt_precode($buff) {
			// we must hide [code] tag since it could be affected by other tags
			return "[code]".$this->escape(trim($this->gbbc_stripslashes($buff)))."[/code]";
		}
		// gbbcode tag: [code]
		function gbbt_code($buff) {
			$buff = $this->unescape(trim($this->gbbc_stripslashes($buff)));
			return "<code>".$buff."</code>";

			/*
			$ldiv = "";
			# begin line numbering
			$maxln = substr_count($buff, "\n")+1;
			for ($i = 1; $i <= $maxln; $i++) {
				$ldiv .= "$i\n";
			}
			# todo: css class for lineno
			$ldiv = "<div class=\"lineno\" style=\"text-align:right;float:left;margin-right: 5px;\">".$ldiv."</div>";
			# end line numbers

			return "<code>\n".$ldiv."".$buff."</code>\n";
			*/
		}
		// preparser
		function preParse($data) {
			$data = preg_replace($this->pre_patterns, $this->pre_replaces, $data);
			return $data;
		}
		// parser
		function parse($data) {
			$data = preg_replace($this->patterns, $this->replaces, $data);
			return $data;
		}
		// strips gbbcode tags
		function strip($data) {
			$data = preg_replace("/\[[^\]]*?\]/", "", $data);
			return $data;
		}
		// parses smileys
		function smile($data) {
			$data = preg_replace($this->smiley_p, $this->smiley_r, $data);
			return $data;
		}
	}

	/*
		htmlFilter

		I wrote all this filter, but part of the code was based on
		the reading of kses 0.2.2 (http://kses.sourceforge.net).
		Thanks Ulf Harnhammar <metaur@users.sourceforge.net>
		kses 0.2.2 was released under the terms of the GNU/GPL.

		This filter aims to strip out all malicious HTML code for an
		unprivileged user, leaving trusted users to write whatever
		they want.

		You can specify new rules for new user levels.
	*/
	Class htmlFilter {

		var $allowed_tags = "";
		// allowed tabs for $index level
		var $allowed_tags_db = Array (
			0 => "a,br,b,u,i,s,code",
			1 => "a,b,u,i,s,br,hr,sub,sup,small,strong,pre,code",
			2 => "a,b,u,i,s,br,hr,div,span,ul,ol,li,pre,code,table,tr,td,sub,sup,small,strong,img,embed,object,h1,h2,h3,h4,h5",
			3 => "*"
		);
		var $banned_attributes = "on([a-zA-Z]*)";
		// banned attributes for $index level
		var $banned_attributes_db = Array (
			0 => "on([a-z0-9]*),style,class,src",
			1 => "on([a-z0-9]*),style,class,src",
			2 => "on([a-z0-9]*)",
			3 => ""
		);
		// default user level
		var $userlevel = 0;

		// initializing html filter for the given $usergroups
		function htmlFilter($usergroups = null) {
			$this->userlevel = $this->editLevel($usergroups);
			$allowed = explode(",", $this->allowed_tags_db[$this->getLevel()]);
			foreach ($allowed as $tag) {
				$this->allowed_tags[trim($tag)] = true;
			}
			$this->banned_attributes = explode(",", $this->banned_attributes_db[$this->getLevel()]);
		}

		function get_allowed_tags($level) { return $this->allowed_tags_db[$level]; }

		// returns edit level for the given user groups
		function editLevel($usergroups) {
			$level = 0;
			if ($usergroups) {
				// Setting level
				if (gauth("admin", $usergroups)) {
					// level 3: allows any tags and attributes
					/*
						rtbEditor is only allowed when $level = 3
					*/
					$level = 3;
				} elseif (gauth("#", $usergroups)) {
					// level 2: allows an extended set of tags and many attributes
					$level = 2;
				} elseif (gauth("$", $usergroups)) {
					// level 1: allows a few tags and no attributes
					$level = 1;
				}
			}
			return $level;
		}

		function getLevel() { return $this->userlevel; }

		function preFilter($data) {
			// removing leading spaces, cleaning code
			return preg_replace("/<\s*([^>]*)\s*>/", "<\\1>", $data);
		}
		function Filter($data) {
			// checking tags and attributes
			return preg_replace("/(<[^>]*(>|$)|>)/e", "\$this->TagFilter(str_replace(\"\\'\", \"'\", \"\\1\"))", $data);
		}
		function postFilter($data) {
			// checking for incomplete tags
			foreach ($this->allowed_tags as $tag => $null) {

				if ($tag == "*")
					continue;

				preg_match_all("/<$tag>|<$tag\s+?[^>]*>/", $data, $match);
				//print_r($match);
				$otags = count($match[0]);

				preg_match_all("/<".$tag."\s+?[^>]*\/>|<\/$tag>/", $data, $match);
				//print_r($match);
				$ctags = count($match[0]);

				if (($e = $otags - $ctags)) {
					$r = abs($e);
					//echo "possible error: $tag; opening tags: $otags; closing tags: $ctags; removing $r tags...\n";
					# removing incomplete tags
					$data = preg_replace(($e > 0)? "/<$tag>|<$tag\s+?[^>]*>/" : "/<".$tag."\s+?[^>]*\/>|<\/$tag>/", "", $data, $r);
				}

			}
			// special fixes
			if ($this->getLevel() <= 2) { $data = nl2br($data); }
			return $data;
		}
		function strip($data) {
			$data = preg_replace("/\<[^>]*?>/", "", $data);
			return $data;
		}
		function applyFilters($data) {
			$data = $this->preFilter($data);
			$data = $this->Filter($data);
			$data = $this->postFilter($data);
			return $data;
		}
		function tagSplit($tag) {
			// splitting tags
			preg_match("#^<\s*(/\s*)?([a-zA-Z0-9]*)([^>]*)>?$#", $tag, $matches);
			//gdebug($matches);
			if (count($matches) == 4) {
				$tag = Array(
					"html" => $matches[0],
					"slsh" => $matches[1],
					"name" => $matches[2],
					"attr" => $matches[3]
				);
				unset($matches);
				return $tag;
			}
		}
		// fix tag attributes
		function fixAttr($attr) {
			if (substr_count($attr, "\"")%2 || substr_count($attr, "'")%2) {
				// checking for matching quotes number
				return -1;
			} else {
				$s = Array("/</", "/>/");
				$r = Array("&lt;", "&gt;");

				// preventing <* href="javascript:someHax0rCode();">
				if ($this->getLevel() < 3) {
					preg_match_all("/(href|src)=(\".*?\"|'.*?'|[^\s]*)/", $attr, $m);

					for ($i = 0; isset($m[0][$i]); $i++) {
						// attribute quotation style
						$q = (($q = substr($m[2][$i], 0, 1)) && ($q == "\"" || $q == "'")) ? $q : "";
						// replacing attribute with a checked href
						$attr = str_replace(
							$m[0][$i],
							(($safe = safeURL(trim($m[2][$i], "'\""))) != "") ? $m[1][$i]."=$q".$safe."$q" : "",
							$attr
						);
					}
				}

				return preg_replace($s, $r, $attr);
			}
		}
		function TagFilter($tagdata) {

			$level = $this->getLevel();

			// level 3: allows any tag
			if ($level == 3)
				return $tagdata;

			// splitting tag
			$tag = $this->tagSplit($tagdata);

			// if this html element is not allowed, escape it an return
			if (!isset($this->allowed_tags[strtolower($tag["name"])])) {
				return htmlspecialchars($tag["html"]);
			}

			// no attributes allowed for closing tags
			if ($tag["slsh"])
				return "<".$tag["slsh"].$tag["name"].">";

			// level 1: no attributtes allowed at all
			if ($level == 1)
				return "<".$tag["name"].">";

			// checking attributes
			if (count($this->banned_attributes)) {
				$tag["attr"] = $this->fixAttr($tag["attr"]);
				if ($tag["attr"] > 0 || !intval($tag["attr"])) {
					foreach ($this->banned_attributes as $bpattern) {
						// if (!$bpattern) return $tag["html"];
						// this will replace banned attributes with an invalid one
						$tag["attr"] = preg_replace("/\s*($bpattern)\s*=\s*/i", " null=", $tag["attr"]);
					}
					$tag["html"] = "<".$tag["name"]." ".trim($tag["attr"]).">";
				} else {
					return htmlspecialchars($tag["html"]);
				}
			}
			// returning a clean tag and attributte :)
			return $tag["html"];
		}
	}

	/*
		Text Format
	*/
	Class Format {

		// Gekko style formatting (based in user levels)

		function Style(&$data, $userid, $nocache = false, $append_key = null) {
			if ($append_key)
				$append_key = ".$append_key";

			if ($nocache || !cache_read($cache_key = cache_assign_key($data, $userid.$append_key), $data)) {

				$data = textFormat (
					$data,
					Conf::getKey("core", "html_filter", 1),
					Conf::getKey("core", "gbbcode", 1),
					Conf::getKey("core", "gbbcode.smileys", 1),
					getUserGroups($userid)
				);

				// fixes
				$data = user_env_vars($data);

				if (!$nocache)
					cache_save($cache_key, $data);
			}
		}
	}

	/*
		textFormat(String data, [bool HTML filter], [bool GBBCode], [bool Smileys]);
		@returns: a "safe" formatted string that could contain html tags
	*/
	function textFormat($data, $html_filter = true, $gbbcode = true, $smileys = true, $usergroups = null) {

		$gbbp = new GBBCodeParser($usergroups);

		$filter = new htmlFilter($usergroups);

		if ($gbbcode)
			$data = $gbbp->preParse($data);

		if ($html_filter)
			$data = $filter->applyFilters($data);

		if ($smileys)
			$data = $gbbp->smile($data);

		if ($gbbcode)
			$data = $gbbp->parse($data);

		return $data;
	}

	/*
		mkIntro(String data, Int offset);
		@returns: a unformatted string limited to $len chars.
	*/
	function mkIntro($text, $len = 255) {
		$return = "";
		if (!cache_read($cachekey = cache_assign_key($text, $len), $return)) {
			$text = htmlFilter::strip(GBBCodeParser::strip($text));
			$return = ((strlen($text) > $len) ? substr($text, 0, $len + ((($e = strpos(substr($text, $len), " ")) < 30) ? $e : 0))."..." : $text);
			cache_save($cachekey, $return);
		}
		return $return;
	}

	// human readable date from unix timestamp
	function dateFormat($timestamp) {
		$age = (time() - $timestamp);
		if ($age < 60) {
			return sprintf(_l("L_SECONDS_AGO"), $age);
		} elseif ($age < 3600) {
			return sprintf(_l("L_MINUTES_AGO"), round($age/60));
		} else {
			$today = explode(":", date("n:d:Y"));
			// today began at...
			$today_s = mktime(0, 0, 0, $today[0], $today[1], $today[2]);
			// today ends at...
			$today_e = $today_s + 86399;
			// yesterday began at...
			$yesterday_s = $today_s - 86399;

			if (($timestamp > $today_s) && ($timestamp < $today_e)) {
				return strftime(_l("L_TODAY_AT"), $timestamp);
			} elseif (($timestamp < $today_s) && ($timestamp > $yesterday_s)) {
				return strftime(_l("L_YESTERDAY_AT"), $timestamp);
			}
		}
		return strftime(_l("L_FULL_DATE"), $timestamp);
	}


	/*
		saneHTML(data, [comma separated data index exceptions (when data is array)])

		Sanitizes an HTML string or array
	*/
	function saneHTML(&$data, $exclude_keys = false) {
		if (is_array($data)) {
			if ($exclude_keys !== false) $exclude_keys = explode(",", $exclude_keys);
			foreach ($data as $key => $null) {
				if (!$exclude_keys || !in_array($key, $exclude_keys)) {
					saneHTML($data[$key], $exclude_keys ? $exclude_keys : false);
				}
			}
		} else {
			$data = htmlspecialchars($data);
		}
	}

	/*
		rtbEditor
		Based on http://www.mozilla.org/editor/midas-spec.html

		I know you may say WYSIWYG editors are evil, but this editor
		could speed up my writing ;). The main problem is that this
		editor doesn't ouput valid XHTML 1.0 code, so I need to clean
		up the submitted data before inserting it to the database.

		Gekko is fully configurable, so, if you're feeling much more geek
		than average you can disable rtbEditor from the configuration
		module. It will leave you with a nice textarea input where you can
		play with the cold HTML code :)

		This editor is not (and won't) be compatible with IE unless IE
		folks apply the midas specification on their crappy browser.

		Yeah, I know they have a way to do this kind of editors but I
		really don't care and I won't waste my time on learning it.

		If you need this code to be fully compatible with Microsoft
		Internet Explorer you've got 3 options:

			1) Hack the code by yourself.
			2) Request MSIE team to adopt Mozilla's Midas Specification
			3) Pay me to do it :)

		Of course, If you're feeling generous I will be very happy with
		the 3rd option :)
	*/

	Class rtbEditor {

		var $id;
		var $buff;
		var $vars = Array();
		var $enable = false;
		var $level = 0;
		var $filter;
		var $buttons = Array();

		// Colors listed below are shown when you click the color fill icon
		var $color_pallete = Array (
			"#000",
			"#333",
			"#666",
			"#888",
			"#999",
			"#aaa",
			"#ddd",
			"#008",
			"#00f",
			"#080",
			"#088",
			"#08f",
			"#0f0",
			"#0f8",
			"#0ff",
			"#f00",
			"#f08",
			"#f0f",
			"#f80",
			"#f88",
			"#f8f",
			"#fff"
		);

		var $headings = Array (
			"h1",
			"h2",
			"h3",
			"h4",
			"h5"
		);

		var $fonts = Array (
			"Sans",
			"Serif",
			"Sans-Serif",
			"Trebuchet MS",
			"Verdana",
			"Monotype",
		);

		function is_compatible() {
			$agent = strtolower($_SERVER["HTTP_USER_AGENT"]);
			// Checking for Gecko Rendering Engine (Mozilla, Mozilla Firefox)
			if (strpos($agent, "gecko"))
				return true;
			return false;
		}
		function setSize($w, $h) {
			$this->vars["V_WIDTH"] = $w."px";
			$this->vars["V_HEIGHT"] = $h."px";
		}
		function mkColorPallete() {
			$colors = "";
			foreach ($this->color_pallete as $color) {
				$colors .= "<div class=\"colordot\" style=\"background:$color;\" onclick=\"gExec('{$this->id}','forecolor','$color');\"></div>";
			}
			return $colors;
		}
		function mkHeadings() {
			/* headings */
			$headings = "<option value=\"\">("._l("L_DEFAULT").")</option>";
			foreach ($this->headings as $head) {
				/* class="$tag" */
				$headings .= "<option value=\"$head\">$head</option>";
			}
			return $headings;
		}
		function mkFontSel() {
			/* fonts */
			natsort($this->fonts);
			$fonts = "<option value=\"\">("._l("L_DEFAULT").")</option>";
			foreach ($this->fonts as $font) {
				$fonts .= "<option style=\"font-family:$font\" value=\"$font\">$font</option>";
			}
			return $fonts;
		}
		function rtbEditor($content = "", $field = "content") {

			if (!isset($GLOBALS["_cache"]["mkRTBEditor"]))
				$GLOBALS["_cache"]["mkRTBEditor"] = 0;

			$this->id = intval($GLOBALS["_cache"]["mkRTBEditor"]);
			// using filter
			$this->filter = new htmlFilter();

			// editor level
			if ($this->is_compatible() && Conf::getKey("core", "rtbeditor")) {
				$this->level = $this->filter->editLevel($GLOBALS["User"]["Groups"]);
				$this->enable = ($this->level == 3);
			}

			if ($this->enable) {

				$editor = Conf::getKey("core", "rtbeditor.default");

				$this->buff = $GLOBALS["T"]->load("main/gEditor.tpl", 1);
				$this->vars = Array (
					"V_BUTTONS" => $this->mkbuttons(),
					"V_FONTS.SEL" => $this->mkFontSel(),
					"V_COLORS" => $this->mkColorPallete(),
					"V_HEADINGS" => $this->mkHeadings(),
					"V_EDITOR.DISPLAY" => ($editor ? "block" : "none"),
					"V_SOURCE.DISPLAY" => ($editor ? "none" : "block"),
					"V_SOURCE.ICON" => mkIcon("source.png", 16),
					"V_VISUAL.ICON" => mkIcon("visual.png", 16)
				);
			} else {
				$this->buff = $GLOBALS["T"]->load("main/gEditorSimple.tpl", 1);
			}

			$this->vars = array_merge($this->vars,
				array (
					"V_ID" => "".$this->id."",
					"V_FIELD" => $field,
					"V_CONTENT" => $content,
					"V_WIDTH" => "400px",
					"V_HEIGHT" => "300px",
					"V_PREVIEW.ICON" => mkIcon("preview.png", 16),
					"V_LEVEL" => $this->level,
					"V_ALLOWED_TAGS" => (($tags = $this->filter->get_allowed_tags($this->level)) != null) ? "&lt;".str_replace(",", "&gt;, &lt;", $tags)."&gt;" : false
				)
			);

			$GLOBALS["_cache"]["mkRTBEditor"]++;
		}

		// returns a set of buttons that a user level may use
		function mkbuttons() {

			if (!$this->buttons) {
				$this->buttons = Array (
					"b" => "bold",
					"u" => "underline" ,
					"i" => "italic" ,
					"s" => "strikethrough" ,
					"jl" => "justifyleft" ,
					"jc" => "justifycenter" ,
					"jr" => "justifyright" ,
					"jf" => "justifyfull" ,
					"a" => "createlink;prompt('"._l("L_URL")."','http://')" ,
					"img" => ($this->level == 3) ? ":insertimage;uploadImage(%id);": "insertimage;prompt('"._l("L_URL")."','http://')" ,
					//":insertimage;rtbAlignObject(%id, 'left');",
					"color" => ":colorfill;show('gColorPicker_%id');" ,
					"hr" => "inserthorizontalrule" ,
					"sub" => "subscript" ,
					"sup" => "superscript" ,
					"ol" => "insertorderedlist" ,
					"ul" => "insertunorderedlist" ,
					"ind" => "indent" ,
					"outd" => "outdent" ,
					"rf" => "removeformat"
				);
			}

			$allowed = array();

			// comparing allowed tags agains compiled buttons
			$tags = explode(",", $this->filter->get_allowed_tags($this->level));

			foreach ($tags as $tag) {
				if ($tag == "*") {
					foreach ($this->buttons as $key => $val) {
						$allowed[$key] = true;
					}
					break;
				} else {
					switch ($tag) {
						case "b": $allowed["b"] = true; break;
						case "u": $allowed["u"] = true; break;
						case "i": $allowed["i"] = true; break;
						case "s": $allowed["s"] = true; break;
						case "div":
							$allowed["jl"] = $allowed["jr"] = $allowed["jc"] = $allowed["jf"] = true;
						break;
						case "span": $allowed["color"] = $allowed["ind"] = $allowed["outd"] = true; break;
						case "hr": $allowed["hr"] = true; break;
						case "sub": $allowed["sub"] = true; break;
						case "sup": $allowed["sup"] = true; break;
						case "ol": $allowed["ol"] = true; break;
						case "ul": $allowed["ul"] = true; break;
						case "a": $allowed["a"] = true; break;
						case "img": $allowed["img"] = true; break;
					}
				}
			}
			$allowed["rf"] = true;

			$btn = "";
			foreach ($allowed as $key => $e) {
				if ($e) {
					$key = $this->buttons[$key];
					// command?
					$u = (substr($key, 0, 1) == ":");
					if ($u)
						$key = substr($key, 1);

					$d = strpos($key, ";");
					$b = $d ? substr($key, 0, $d) : $key;
					$a = $d ? substr($key, $d+1) : null;
					$js = $u ? str_replace("%id", $this->id, $a) : "gExec('{$this->id}', '$b'".(($a) ? ", $a": "").");";

					$btn .= "<img style=\"cursor:pointer\" alt=\"$b\" onclick=\"$js\" src=\""._l("C_ICONDIR")."toolbar/$b.png\" width=\"16\" height=\"16\" />\n";
				}
			}

			return $btn;
		}
		// returns a compiled rtbEditor
		function make() {
			return $GLOBALS["T"]->make($GLOBALS["T"]->parse($this->buff, $this->vars, true));
		}
	}

	// --------
	function rtb_codefix_attr_split($tmp) {
		$attr = Array();
		preg_match_all("/([a-z]*)\=\\\"(.*?)\\\"/", " $tmp ", $match);
		foreach ($match[1] as $k => $v) {
			$attr[$v] = $match[2][$k];
		}
		return $attr;
	}
	function rtb_codefix_attr_join($attr) {
		$buff = "";
		foreach ($attr as $a => $v) {
			$buff .= "$a=\"$v\" ";
		}
		return trim($buff);
	}
	/*
		rtb_codefix_resources(tag attributes);
		fix resources founds inside html tags
	*/
	function rtb_codefix_resources($tag) {
		$resource = array("href", "src");
		$attr = rtb_codefix_attr_split($tag);

		foreach ($resource as $res) {
			if (isset($attr[$res])) {
				$attr[$res] = rtb_codefix_source($attr[$res]);
			}
		}

		return rtb_codefix_attr_join($attr);
	}

	/*
		rtb_codefix_source(url resource);
			converts a relative link into absolute
	*/
	function rtb_codefix_source($source) {
		if (substr($source, 0, 1) != '/' && !preg_match("/([a-z]*):\/\/(.*)/i", $source)) {

			$base = dirname($_SERVER["HTTP_REFERER"]);

			/* splitting referer url */
			preg_match("/([a-z]*:\/\/[^\/]*)\/(.*)index.php/i", $base, $url);

			$host = $url[1];
			$path = explode('/', dirname($url[2]));
			if (($del = strpos($source, "?")) !== false) {
				/* source contains passed variables */
				$relative = substr($source, 0, $del);
			}

			$rel = explode('/', isset($relative) ? $relative : $source);

			for ($i = 0; $rel[$i] == ".."; $i++) {
				array_pop($path);
			}

			$source = '/'.(count($path) ? implode('/', $path).'/' : '').substr($source, $i*3);
		}
		return $source;
	}

	function rtb_codefix_img_attr($img) {
		$attr = rtb_codefix_attr_split($img);

		if (isset($attr["src"])) {
			$attr["src"] = rtb_codefix_source($attr["src"]);

			if (!(isset($attr["width"]) && isset($attr["height"])) && preg_match("/".$_SERVER["HTTP_HOST"]."/", $attr["src"])) {

				$spath = urldecode($attr["src"]);

				if (substr($spath, 0, 1) == '/') {
					if (file_exists($file = $_SERVER["DOCUMENT_ROOT"].$spath)) {
						if (($size = @getimagesize($file)) !== false)
							list($attr["width"], $attr["height"]) = $size;
					}
				} else {

					// finding similarities between script path and script url
					// to guess absolute image location
					$i = strlen($_SERVER["SCRIPT_NAME"]);
					$a = strlen($_SERVER["PATH_TRANSLATED"])-$i;
					while (--$i >= 0 && (substr($_SERVER["SCRIPT_NAME"], $i, 1) == substr($_SERVER["PATH_TRANSLATED"], $i+$a, 1)))
						;
					$i++;
					$home = substr($_SERVER["SCRIPT_NAME"], 0, $i);
					$wwwr = substr($_SERVER["PATH_TRANSLATED"], 0, $i+$a);
					$patt = str_replace("/", "\/", "://".$_SERVER["HTTP_HOST"].$home."/");
					$real = "$wwwr/".preg_replace("/.*?$patt(.*?)/", "\\1", $spath);

					// it seems that real image location in this server is $real
					if (file_exists($real) && ($size = @getimagesize($real)) !== false)
						list($attr["width"], $attr["height"]) = $size;

				}
			}
			if (!isset($attr["alt"]))
				$attr["alt"] = htmlspecialchars(basename(urldecode($attr["src"])));

			return rtb_codefix_attr_join($attr);
		}
	}

	function rtb_codefix(&$buff) {

		/*
			rtbEditor doesn't returns valid XHTML 1.0 code...
			This is an attempt to solve the most common mistakes when
			writing HTML and it's also a way to avoid resource linking
			problems.
		*/

		$buff = trim($buff);

		if ($buff == "<br>") {
			// when post was empty
			$buff = "";
		} else {

			$filter = new htmlFilter();

			$level = $filter->editLevel($GLOBALS["User"]["Groups"]);

			$buff = stripslashes($buff);
			/* patterns */
			$s = Array(
				"/<hr.*?>/",
				"/<br.*?>/",
				"/<img(.*?)>/e",
				"/<a(.*?)>/e"
			);

			/* replacements */
			$r = Array(
				"<hr />",
				"<br />",
				"'<img '.rtb_codefix_img_attr(stripslashes('\\1')).' />'",
				"'<a '.rtb_codefix_resources(stripslashes('\\1')).'>'",
			);

			$buff = preg_replace($s, $r, $buff);

			$buff = addslashes($buff);

		}
		return true;
	}
	// -----------

	function getAge($sqltime) {
		$tmp = explode("/", date("j/n/Y"));
		$nd = $tmp[0];
		$nm = $tmp[1];
		$ny = $tmp[2];
		$y = substr($sqltime, 0, 4);
		$m = intval(substr($sqltime, 4, 2));
		$d = intval(substr($sqltime, 6, 2));
		$age = ($ny - $y);
		if ($age) $age += ((($nm > $m) || ($nm == $m && $nd >= $d)) ? 0 : -1);
		return $age;
	}


	function break_words($text, $mwl = 22) {
		// TODO: use a more elegant form, with patterns
		$text = explode(" ", $text);
		while (list($i) = each($text)) {
			if (strlen($text[$i]) > $mwl) {
				$text[$i] = substr($text[$i], 0, $mwl - 5)."[...]";
			}
		}
		return join(" ", $text);
	}
?>